4:ThinkPHP框架执行流程深入分析(从TP框架源码的角度分析TP框架的执行流程)
疑问:
我们已经知道,通过在URL中输入http://localhost/tp/02.php/Index/index和http://localhost/tp/02.php/User/test就能分别去调用IndexAction类中的index方法和UserAction类中的test方法,为什么这样输入URL的方式就可以调用相应的Action类中的方法呢?
我们在02.php仅仅只是引入了ThinkPHP目录中的ThinkPHP.php文件,输入http://localhost/02.php后就能看到IndexAction类中的index方法输出的内容,原因到底是因为什么呢?
1)我们从最开始编写的02.php(位于C:\AppServ\www\TP目录下)文件开始,一步一步地进行分析
<?php
// 引入框架
include('./ThinkPHP/ThinkPHP.php');
?>
2)02.php文件中引入了/ThinkPHP目录下的ThinkPHP.php文件:ThinkPHP.php,位于C:\AppServ\www\TP\ThinkPHP目录中
<?php
// ThinkPHP 入口文件
//记录开始运行时间
$GLOBALS['_beginTime'] = microtime(TRUE);
// 记录内存初始使用
define('MEMORY_LIMIT_ON',function_exists('memory_get_usage'));
if(MEMORY_LIMIT_ON) $GLOBALS['_startUseMems'] = memory_get_usage();
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
defined('RUNTIME_PATH') or define('RUNTIME_PATH',APP_PATH.'Runtime/');
defined('APP_DEBUG') or define('APP_DEBUG',false); // 是否调试模式
$runtime = defined('MODE_NAME')?'~'.strtolower(MODE_NAME).'_runtime.php':'~runtime.php';
defined('RUNTIME_FILE') or define('RUNTIME_FILE',RUNTIME_PATH.$runtime);
if(!APP_DEBUG && is_file(RUNTIME_FILE)) {
// 部署模式直接载入运行缓存
require RUNTIME_FILE;
}else{
// 系统目录定义
defined('THINK_PATH') or define('THINK_PATH', dirname(__FILE__).'/');
// 加载运行时文件
require THINK_PATH.'Common/runtime.php';
}
ThinkPHP.php文件中定义了一些变量,...
,引入了Common目录中的runtime.php文件。
3)runtime.php,位于C:\AppServ\www\TP\ThinkPHP\Common目录中
runtime.php中包含了一些方法:
build_app_dir(),我们在运行02.php后再次进入到包含02.php的文件目录下,可以看到自动生成了一些目录,就是runtime.php 中的build_app_dir()方法创建生成的。
load_runtime_file(),加载运行时所需的文件,并负责自动目录生成。“require THINK_PATH.’Common/common.php’”,加载系统基础函数库。
load_runtime_file()具体代码:
// 加载运行时所需要的文件 并负责自动目录生成
function load_runtime_file() {
// 加载系统基础函数库
require THINK_PATH.'Common/common.php';
// 读取核心编译文件列表
$list = array(
CORE_PATH.'Core/Think.class.php',
CORE_PATH.'Core/ThinkException.class.php', // 异常处理类
CORE_PATH.'Core/Behavior.class.php',
);
// 加载模式文件列表
foreach ($list as $key=>$file){
if(is_file($file)) require_cache($file);
}
// 加载系统类库别名定义
alias_import(include THINK_PATH.'Conf/alias.php');
// 检查项目目录结构 如果不存在则自动创建
if(!is_dir(LIB_PATH)) {
// 创建项目目录结构
build_app_dir();
}elseif(!is_dir(CACHE_PATH)){
// 检查缓存目录
check_runtime();
}elseif(APP_DEBUG){
// 调试模式切换删除编译缓存
if(is_file(RUNTIME_FILE)) unlink(RUNTIME_FILE);
}
}
在load_runtime_file()方法中,引入了common.php、Think.class.php、ThinkException.class.php、Behavior.class.php、alias.php多个文件。其中,common.php是ThinkPHP框架的基础函数库,ThinkException.class.php是TP框架自定义的异常处理类,Think.class.php是ThinkPHP框架的核心类文件。
4)Think.class.php,位于C:\AppServ\www\TP\ThinkPHP\Lib\Core目录下
class Think {
private static $_instance = array();
/**
+----------------------------------------------------------
* 应用程序初始化
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @return void
+----------------------------------------------------------
*/
static public function Start() {
// 设定错误和异常处理
set_error_handler(array('Think','appError'));
set_exception_handler(array('Think','appException'));
// 注册AUTOLOAD方法
spl_autoload_register(array('Think', 'autoload'));
//[RUNTIME]
Think::buildApp(); // 预编译项目
//[/RUNTIME]
// 运行应用
App::run();
return ;
}
说明:
Ⅰ.Think类中的静态方法Start()中”set_error_handler(array('Think','appError'));set_exception_handler(array('Think','appException'));“设定了TP框架处理错误和异常的方式,不使用PHP程序默认的错误和异常处理方式。TP框架中对于错误和异常的处理,分别使用appError()方法和appException()。
Ⅱ. ”spl_autoload_register(array('Think', 'autoload'));“,注册autoload方法。public static function autoload($class) ,系统自动加载ThinkPHP类库,并且支持配置自动加载路径。
Ⅲ. “Think::buildApp();”,调用Think类(Think.class.php)的静态方法buildapp。static private function buildApp() ,读取配置信息 编译项目。
Ⅳ. ”App::run();“,调用App类(App.class.php)的静态方法run。
5)App.class.php ,位于C:\AppServ\www\TP\ThinkPHP\Lib\Core目录下
static public function run() {
// 项目初始化标签
tag('app_init');
App::init();
// 项目开始标签
tag('app_begin');
// Session初始化
session(C('SESSION_OPTIONS'));
// 记录应用初始化时间
G('initTime');
App::exec();
// 项目结束标签
tag('app_end');
// 保存日志记录
if(C('LOG_RECORD')) Log::save();
return ;
}
说明:
Ⅰ. ”App::init();”,调用App类中的init方法;
部分代码如下:
class App {
/**
* 应用程序初始化
*/
static public function init() {
// 设置系统时区
date_default_timezone_set(C('DEFAULT_TIMEZONE'));
// 加载动态项目公共文件和配置
load_ext_file();
// URL调度
Dispatcher::dispatch();
//...
return ;
}
init方法,对应用程序进行初始化操作,如设置系统时区,加载公共文件等。与此同时,更为重要的一个操作是调用了Dispatcher类(Dispatcher.class.php)中的dispatch方法。
dispatch方法就是用于分析地址栏中输入的参数,根据地址栏中的参数分析出类名和方法名。如,http://localhost/tp/02.php/User/test,dispatch方法就会分析得到User类名和test方法。
Ⅱ."App::exec();",调用App类中的exec方法,exec,即executive,执行。
部分代码:
/**
* 执行应用程序
*/
static public function exec() {
// 安全检测
if(!preg_match('/^[A-Za-z_0-9]+$/',MODULE_NAME)){
$module = false;
}else{
//创建Action控制器实例
$group = defined('GROUP_NAME') ? GROUP_NAME.'/' : '';
$module = A($group.MODULE_NAME);
}
if(!$module) {
<span style="white-space:pre"> </span>//...
}
//获取当前操作名
$action = ACTION_NAME;
// 获取操作方法名标签
tag('action_name',$action);
if (method_exists($module,'_before_'.$action)) {
// 执行前置操作
call_user_func(array(&$module,'_before_'.$action));
}
//执行当前操作
call_user_func(array(&$module,$action));
if (method_exists($module,'_after_'.$action)) {
// 执行后缀操作
call_user_func(array(&$module,'_after_'.$action));
}
return ;
}
说明:
Ⅰ.“A($group.MODULE_NAME);”,dispatch方法执行后得到的MODULE_NAME中部包含非法字符时,便根据MODULE_NAME去创建Action控制器实例。A()方法是common.php中的基础函数,用于根据传入地参数实例化Action类。此时的$module已经是根据类名返回的对象。
Ⅱ."if(!$module)",传入的$module出现错误时的处理。
Ⅲ.假如当前的需要引入的类名为UserAction,需要调用的方法为test。$module就是UserAction类的实例,$action就是test。
在调用UserAction类中的test方法前,先判断在UserAction类中是否存在_before_test方法,有的话便调用;前置操作;
调用UserAction类中的test方法,“call_user_func(array(&$module,$action));”根据变量$module和$action的不同值来动态地调用不同的实例中的不同方法,也可以写成“$module->$action;”。$module和$action都是由Dispatcher.class.php中的dispatch方法分析URL得到的。
在调用UserAction类中的test方法后,会去判断在UserAction类中是否存在_after_test方法,存在即调用;后置操作。
关于“前置操作”和“后置操作”我们可以通过案例进行简单地验证:
①修改UserAction.class.php(位于C:\AppServ\www\TP\Lib\Action目录中)如下,
<?php
class UserAction extends Action {
public function test(){
echo "测试2!";
}
public function _before_test(){
echo "我会在test方法执行之前,被自动地调用执行<br />";
}
public function _after_test(){
echo "我会在test方法执行之后,被自动地调用执行<br />";
}
protected function _initialize(){
echo "我是控制器,我被调用了!<br>";
}
}
②测试:输入http://localhost/tp/02.php/User/test
从运行结果可以看出,执行顺序确实是分析的那样:_before_test()、test()、_after_test()。
我们在深入地分析ThinkPHP框架的执行流程时,可以看到在很多框架的源文件中都有定义的常量的操作。这种操作的目的是为了不同方法(函数)间使用同一个数据的方便。不同方法间需要使用到同一个数据,要么是传参数,要么是定义成全局变量,还可以是定义成常量。定义成常量的方式使用起来更加的方便。
5:图解简易说明TP框架执行流程
说明:
在ThinkPHP框架(简称TP框架)中,http://localhost/tp/02.php/User/test传入参数“User”和“test”的方式,与“http://localhost/tp/02.php?m=User&a=test”的方式是等价的,TP框架都有相应的处理。其中,m就表示$module,a就表示$action。第二种方式在此之前我们使用得较多。
简单验证:浏览器地址栏输入http://localhost/tp/02.php?m=User&a=test
6: ~runtime.php文件说明
在运行02.php的时候(浏览器地址栏中输入:http://localhost/tp/02.php),会自动生成多个目录和文件,其中在Runtime目录中有一个~runtime.php文件,它是一个编译缓存文件。分析TP框架程序的执行流程时,我们发现程序在运行的过程(各种初始化操作)中不断地引入了多个PHP文件,引入文件是一个从硬盘读取文件内容的过程,每一次的读取必然都会消耗一定的资源,如果程序重复运行多次时耗费的资源会更大!
ThinkPHP框架为了节省资源的采取的措施是:将所有需要引入的文件全部包含到一个文件(~runtime)中,当程序重复运行时就只需去读取编译好的缓存文件~runtime.php就可以了,就不再去读取多个PHP文件,只读取~runtime.php文件就可以了。从而节省开销,减少I/O操作。
TP框架采取的这种措施节省了I/O操作的开销,但也带来了另一个问题:测试/调试时,修改程序代码后,运行程序时发现运行结果并没有受到影响。这是因为缓存文件~runtime.php文件所造成的,运行程序生成了~runtime.php文件后,再次运行程序时便不会再去重新读取其他需要引入的配置文件了,不会重新地初始化应用程序,这就给调试带来了麻烦。
既然运行程序就会自动地生成~runtime.php文件,我们在调试的过程中可以手动删除~runtime.php文件;也可以“启动调试模式”——启动调试就是定义APP_DEBUG常量为true,APP_DEBUG常量默认为false。定义APP_DEBUG常量为true,启动“调试模式”事实上是通过程序来自动地删除~runtime.php文件。两种方式都可以使用,目的都是删除缓存文件~runtime.php,让程序在执行的时候能够重新初始化。程序能够初始化,重新去加载各个文件,也就是说,修改的代码能够生效。
1)定义APP_DEBUG常量为true,应用/项目的~runtime.php文件会自动删除的原因:
程序运行时会加载runtime.php(位于C:\AppServ\www\TP\ThinkPHP\Common目录下),执行runtime.php中的load_runtime_file()方法时会对常量APP_DEBUG的值进行判断,值为真的时候会删除编译缓存文件。
load_runtime_file()部分代码:
// 加载运行时所需要的文件 并负责自动目录生成
function load_runtime_file() {
//...省略一部分代码
// 检查项目目录结构 如果不存在则自动创建
if(!is_dir(LIB_PATH)) {
// 创建项目目录结构
build_app_dir();
}elseif(!is_dir(CACHE_PATH)){
// 检查缓存目录
check_runtime();
}elseif(APP_DEBUG){
// 调试模式切换删除编译缓存
if(is_file(RUNTIME_FILE)) unlink(RUNTIME_FILE);
}
}
其中,“unlink(RUNTIME_FILE);”就是去删除缓存文件。
2)验证:定义APP_DEBUG常量为true时会自动删除~runtime.php文件,是因为执行了TP框架runtime.php源码中的“unlink(RUNTIME_FILE);”相关部分代码。
①修改02.php,启动调试模式
?php
//开启调试模式
define('APP_DEBUG', true);
// 引入框架
include('./ThinkPHP/ThinkPHP.php');
?>
注意:定义常量的操作一定要写在include文件操作的前面,不然会没有效果。原因?
②在TP框架的runtime.php中的load_runtime_file()方法中添加测试输出代码
// 加载运行时所需要的文件 并负责自动目录生成
function load_runtime_file() {
// 加载系统基础函数库
require THINK_PATH.'Common/common.php';
// 读取核心编译文件列表
$list = array(
CORE_PATH.'Core/Think.class.php',
CORE_PATH.'Core/ThinkException.class.php', // 异常处理类
CORE_PATH.'Core/Behavior.class.php',
);
// 加载模式文件列表
foreach ($list as $key=>$file){
if(is_file($file)) require_cache($file);
}
// 加载系统类库别名定义
alias_import(include THINK_PATH.'Conf/alias.php');
// 检查项目目录结构 如果不存在则自动创建
if(!is_dir(LIB_PATH)) {
// 创建项目目录结构
build_app_dir();
}elseif(!is_dir(CACHE_PATH)){
// 检查缓存目录
check_runtime();
}elseif(APP_DEBUG){
// 调试模式切换删除编译缓存
echo "这部分代码被执行了!<br/>";
if(is_file(RUNTIME_FILE)) unlink(RUNTIME_FILE);
}
}
③运行02.php程序测试。运行02.php文件生成的~runtime.php文件被删除,页面输出“这部分代码被执行了!”。
3)不删除~runtime.php文件(位于C:\AppServ\www\TP\Runtime目录中),修改02.php文件,测试是否修改能够生效
<?php
//开启调试模式
// define('APP_DEBUG', true);
// 引入框架
include('./ThinkPHP/ThinkPHP.php');
echo "<br />huangSir";
?>
运行结果:
测试发现,即使不启用“调试模式”,修改02.php后运行,结果也发生变化了。
疑问:启用调试模式,禁止生成缓存文件,使修改的代码能够生效,方便调试。可是修改02.php文件后,即使没有启用调试模式,运行结果也发生改变了?是修改什么文件才必须得启动调试模式才能让修改能够生效?核心文件?对ThinkPHP框架源码的修改?配置的修改?
4)调试模式的启用
在我们开发项目的过程中通常都是定义APP_DEBUG常量为true,启用调试模式,能够更为简便地调试代码(不需要每次调试都是手动去删除相应的~runtime.php文件)。当项目完成,发布到服务器下时,就需要关闭调试模式(直接注释掉即可),来生成缓存文件,减少I/O操作,提高程序运行效率。
7:Model层(Model类)的写法
新建03.php文件,位于C:\AppServ\www\TP\demo3目录中
<?php
// 开启调试模式
define('APP_DEBUG', true);
include('../ThinkPHP/ThinkPHP.php');
?>
在生成的Lib目录中的Model目录下创建Model层文件GoodsModel.class.php,位于C:\AppServ\www\TP\demo3\Lib\Model目录下
<?php
/**
* 商品Model,用于对商品的各种操作,如,添加商品、删除商品等。
*/
class GoodsModel extends Model
{
public function __construct()
{
echo "GoodsModel被调用<br />";
}
// 模拟通过数据库查询得到商品信息
public function getGoodsInfo(){
// $goodsInfo的数据应该通过查询数据库得到
$goodsInfo = array('ID' =>'a001' ,'name' => 'Iphone6','price' =>'5300' );
return $goodsInfo;
}
}
注意:
本例中,我们在GoodsModel类中,重新编写了构造函数,目的是希望每当GoodsModel被调用时都能够很清楚地观察到,但这样做是有风险的,因为GoodsModel类并不是单独的一个类,它继承于另外一个类——Model。在我们开发过程中,当一个类继承与另外一个类时要特别小心,重写构造函数就意外着覆盖掉了父类中构造函数的作用,这会使程序很容易出错。这里的Model类中的构造函数就包含了多个初始化操作,会直接影响Model类中相关方法的调用。如果特殊需求,需要重新编写构造函数时,可以在子类的构造函数中先调用一次父类的构造函数,在调用父类的构造函数需要注意父类构造函数需要传入的参数。
<?php
/**
* 商品Model,用于对商品的各种操作,如,添加商品、删除商品等。
*/
class GoodsModel extends Model
{
public function __construct()
{
//先调用执行父类的构造函数,避免覆盖了父类构造函数的作用
parent::_construct();
echo "GoodsModel被调用<br />";
}
}
在生成的Lib目录中的Action目录下创建Controller层文件ShopAction.class.php,位于C:\AppServ\www\TP\demo3\Lib\Action目录下。控制器层(controller)调用模型层(model)来完成相应操作。
<?php
/**
* 购物车控制器
*/
class ShopAction extends Action
{
public function goodsList(){
// 调用GoodsModel来得到准备展示的商品信息
$goodsModel = new GoodsModel();
$goodsInfo = $goodsModel -> getGoodsInfo();
// 得到的商品信息应该交给视图来显示,这里是为了简单说明调用关系
print_r($goodsInfo);
}
}
测试:浏览器地址栏输入http://localhost/tp/demo3/03.php/Shop/goodsList。
注意,“Shop”中首个字母为大写,TP框架中是存在大小写约束的,这里小写的话便会提示相应错误。大小写的约束看似对程序的编写更为严格了,但基于TP框架的PHP程序往Linux服务器下移植时会显得更加地容易,不用额外考虑大小写敏感的问题。
输入:http://localhost/tp/demo3/03.php/shop/goodsList,shop小写时:
5.2 使用TP框架完成留言本
1:完成留言本“发布留言功能”
1)编写留言本项目入口文件index.php,位于C:\AppServ\www\TP\demo4目录下
<?php
// 开启调试模式
define('APP_DEBUG', true);
// 项目入口文件,引入ThinkPHP框架
include('../ThinkPHP/ThinkPHP.PHP');
?>
2)运行项目入口文件,生成基于TP框架开发所需的各个目录及文件
浏览器地址栏输入:http://localhost/tp/demo4/index.php
简单修改IndexAction.class.php(位于C:\AppServ\www\TP\demo4\Lib\Action目录下),显示访问留言本首页的欢迎信息
<?php
// 本类由系统自动生成,仅供测试用途
class IndexAction extends Action {
public function index(){
header("Content-Type:text/html; charset=utf-8");
// echo '<div style="font-weight:normal;color:blue;float:left;width:345px;text-align:center;border:1px solid silver;background:#E8EFFF;padding:8px;font-size:14px;font-family:Tahoma">^_^ Hello,欢迎使用<span style="font-weight:bold;color:red">ThinkPHP</span></div>';
echo "这是一个基于TP框架的简单留言本程序!";
}
}
运行结果:
3)留言本页面msg.html(位于C:\AppServ\www\TP\demo4目录下)
<html>
<head>
<title>TP框架实现简单留言功能</title>
<meta http-equiv="Cotent-Type" content="text/html;charset=utf-8"/>
</head>
<body>
<h1>简单留言本(增、删、改、查)</h1>
<form action="index.php/Msg/add" method="post">
标题:<input type="text" name="title"/><br>
内容:<textarea name="content"></textarea>
<input type="submit" value="发布留言"/>
</form>
</body>
</html>
运行效果:
msg.html中的form表单(发布留言时)提交给了“index.php/Msg/add”。也就是通过单一的入口文件index.php将请求交给Msg控制器(MsgAction.class.php),控制器调用add方法来处理。
4)controller层和Model层的编写:MsgAction.class.php,位于C:\AppServ\www\TP\demo4\Lib\Action目录下;MsgModel.class.php,位于C:\AppServ\www\TP\demo4\Lib\Model目录下。
MsgModel.class.php,model模型层,给控controller制器层提供业务处理方法
<?php
class MsgModel extends Model
{
// 根据控制器提供的留言信息,将留言信息插入到数据库中
public function releaseMsg($data)
{
return $this->add($data);
/*
说明:
Ⅰ.这里使用的add()方法是Model.class.php(C:\AppServ\www\TP\ThinkPHP\Lib\Core)中的方法,add方法中封装了向数据库中的插入操作。
由于MsgModel继承与Model类,所以可以直接使用。
Ⅱ.add插入数据失败返回false。
*/
}
}
?>
MsgAction.class.php,controller控制器层,调用model层的方法完成业务功能
<?php
class MsgAction extends Action
{
// 接收表单提交的留言信息,调用MsgModel将留言信息插入到数据库中
public function add(){
// 1.接收表单提交的留言信息
$message = array();
$message['title'] = $_POST['title'];
$message['content'] = $_POST['content'];
$msgModel = new MsgModel();
// $msgModel ->releaseMsg($message);//“发布留言”,将留言信息插入到数据库中
if ($msgModel ->releaseMsg($message)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
}
?>
运行测试:
①输入http://localhost/tp/demo4/msg.html
②点击“发布留言”
说明:
出现这种错误的原因是我们没有对数据库的连接进行配置,使用空密码登录时发生错误。
默认情况下TP框架使用的是MySQL数据库,使用root用户连接时密码为空。我这儿的mysql数据库的root用户是有密码的。所以连接失败,需要重新对数据库进行配置。
ThinkPHP框架中对数据库的相关配置:
TP框架的配置文件convention.php位于C:\AppServ\www\TP\ThinkPHP\Conf目录中,对TP框架的配置可以直接在该文件中进行修改,但是不推荐这样做。实际开发中,会有多个项目都会使用到ThinkPHP目录(TP框架),直接在convention.php中修改的话,就只能单单地适用于某一个项目了。convention.php的文档说明中也提示在各自的项目配置文件中进行配置相关的选项。
修改留言本项目的配置文件,使TP框架的配置能够更符合留言本这个项目的要求:
config.php,位于C:\AppServ\www\TP\demo4\Conf目录下
<?php
return array(
<span style="white-space:pre"> </span>//'配置项'=>'配置值'
/* URL设置 */
'URL_CASE_INSENSITIVE' => false, // 默认false 表示URL区分大小写 true则表示不区分大小写
'URL_MODEL' => 1, // URL访问模式,可选参数0、1、2、3,代表以下四种模式:
// 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式,提供最好的用户体验和SEO支持
'URL_PATHINFO_DEPR' => '/', // PATHINFO模式下,各参数之间的分割符号
/* 数据库设置 */
'DB_TYPE' => 'mysql', // 数据库类型
'DB_HOST' => 'localhost', // 服务器地址
'DB_NAME' => 'tp', // 数据库名
'DB_USER' => 'root', // 用户名
'DB_PWD' => 'root', // 密码
'DB_PORT' => '3306', // 端口
'DB_PREFIX' => '', // 数据库表前缀
'DB_FIELDTYPE_CHECK' => false, // 是否进行字段类型检查
'DB_FIELDS_CACHE' => true, // 启用字段缓存
'DB_CHARSET' => 'utf8', // 数据库编码默认采用utf8
);
?>
说明:
在各自的项目配置文件(config.php)中修改配置时可以参考ThinkPHP框架的convention.php中的配置选项。实际上也就是将convention.php中的配置选项复制粘贴到项目的配置文件config.php中的array数组中,修改使配置更加符合当前的项目。
再次测试时,依旧提示错误
说明:很显然错误的原因是因为mysql数据库中还没有创建名称为tp的数据库。
③创建留言本项目需要使用到的tp数据库和msg表
a.启动msql客户端,并登录
b. 创建tp数据库、msg表:
create database tp charset utf8;
use tp;
create table msg(
id int auto_increment primary key,
title varchar(30) not null default '',
content varchar(200) not null default ''
)engine myisam charset utf8;
④测试,提示“发布留言成功”
数据库中查询验证,是否真的插入成功:
疑问 :我们在MsgModel.class.php中的releaseMsg($data)方法调用父类Model的add方法很轻松就完成了插入数据的操作。那么问题来了,我们在config.php中只是配置了留言本项目使用到的tp数据库,并没有指定插入的数据添加到哪一个具体的表中,那又是怎样将数据添加到tp数据库下的msg表中的呢?
5)代码简化
对于上面的Model层代码MsgModel.class.php完成添加记录到数据库中的操作,就一行代码就完成了。开发中的很多时候,Model层完成增删改查也就是几行代码就能搞定,这时我们可以直接在controller层“直接使用Model层的方法”,让Model层的代码更加简洁,同时保留Model层方便理解。
①代码简化——修改MsgModel.class.php和MsgAction.class.php
MsgModel.class.php代码修改简化:
<?php
/*class MsgModel extends Model
{
// 根据控制器提供的留言信息,将留言信息插入到数据库中
public function releaseMsg($data)
{
return $this->add($data);
//说明:
//Ⅰ.这里使用的add()方法是Model.class.php(C:\AppServ\www\TP\ThinkPHP\Lib\Core)中的方法,add方法中封装了向数据库中的插入操作。
//由于MsgModel继承与Model类,所以可以直接使用。
//Ⅱ.add插入数据失败返回false。
}
}
*/
/**
* 代码简化
*/
class MsgModel extends Model
{
}
MsgAction.class.php代码修改简化:
<?php
/* class MsgAction extends Action
{
// 接收表单提交的留言信息,调用MsgModel将留言信息插入到数据库中
public function add(){
// 1.接收表单提交的留言信息
$message = array();
$message['title'] = $_POST['title'];
$message['content'] = $_POST['content'];
$msgModel = new MsgModel();
// $msgModel ->releaseMsg($message);//“发布留言”,将留言信息插入到数据库中
if ($msgModel ->releaseMsg($message)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
}*/
/**
* 代码简化:
*/
class MsgAction extends Action
{
public function add()
{
$msgModel = new MsgModel();
//$msgModel ->add($_POST);//这里是直接调用Model类中的add方法
//print_r($_POST);
if ($msgModel ->add($_POST)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
}
说明:
Ⅰ.容易混淆的点:
表单以POST方式提交的数据会被“包含到”$_POST数组中,我们在开发中通过"$_POST['data']"提到的“接收”表单页面的数据,更准确的说是从$_POST数组中“取出”表单发送过来的数据。因为,尽管没有“$title = $_POST['title'];”的“接收”操作,$_POST数组中也是包含有title的数据的,可以用“print_r($_POST)”方式查看。
通常将包含在$_POST或$_GET数组中提交过来的数据“取出”来的目的只是为了更容易理解。
Ⅱ.例子中MsgAction.class.php中的add方法名改成addMsg更为合适,因为调用了Model类中的add方法,容易弄混。
②测试
a.浏览器地址栏输入http://localhost/tp/demo4/msg.html
b.点击“发布留言“
2:完成留言本“删除留言”功能
1)MsgAction.class.php中添加lisMsg()方法,调用该方法可以列出留言板中的所有留言信息
<?php
class MsgAction extends Action
{
// 添加留言
public function add()
{
$msgModel = new MsgModel();
//$msgModel ->add($_POST);//这里是直接调用Model类中的add方法
if ($msgModel ->add($_POST)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
// 列出留言信息
public function listMsg()
{
$msgModel = new MsgModel();
// $msgModel -> select();
// print_r($msgModel -> select());
// 通过$msgModel调用select()方法查询得到留言信息,交给相应视图显示
$msgInfo = $msgModel -> select();
$this->assign('messageInfo',$msgInfo);
$this->display('msgInfo.html');
}
}
说明:
Ⅰ.“$msgModel -> select();”,select是Model类中的方法,用于查询数据库。这里也是简化了controller层调用model层方法的步骤,应该由controller层调用Model层去操作数据库得到处理结果,再由controller层将处理结果决定交给哪一个view显示。
Ⅱ.controller层(Action类)中不应该有直接操作数据库的代码,这里是进行了“简化”。
“$this->assign('messageInfo',$msgInfo);”和“$this->display('msgInfo.html'); ”
Ⅲ.ThinkPHP框架中也有自己的“模板”,类似于smarty的模板引擎。与smarty模板不同的是,ThinkPHP框架将assign、display等方法进行了封装,不再是通过$smarty来调用,而是通过“具体的”每一个controller(Action类)对象来调用。
通过controller对象来调用assgin、display来决定用哪一个视图(View)来显示,更符合MVC的思想(由controller层调用Model层去操作数据库得到处理结果,再由controller层将处理结果决定交给哪一个view显示)。
Ⅳ.这里的$this就是具体的controller(Action)对象。注意,这里的$this不是$msgModel对象。
2)View层的简单使用
TP框架中的模版就指的是View层,用于显示。TP框架中的模版文件存放在运行项目入口文件(本例中是入口文件是index,php)产生的Tpl目录中。Tpl,Template,模板。
编写MsgAction控制器用于显示留言信息的模版(View):
在Tpl目录下创建msgInfo.html,msgInfo.html位于C:\AppServ\www\TP\demo4\Tpl目录下
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8;">
<title>留言板信息</title>
</head>
<body>
<h1>留言板信息</h1>
<table border="1">
<tr>
<td>编号</td>
<td>标题</td>
<td>留言内容</td>
</tr>
<!-- 使用TP框架模板引擎(view层)的foreach方法遍历MsgAction(controller层)分配的结果messageInfo -->
<foreach name="messageInfo" item="msg">
<tr>
<td>{$msg.id}</td>
<td>{$msg.title}</td>
<td>{$msg.content}</td>
</tr>
</foreach>
</table>
</body>
</html>
3)测试
浏览器地址栏输入:http://localhost/tp/demo4/index.php/Msg/listMsg,“手动”调用MsgAction中的listMsg方法查看留言信息
说明:
Ⅰ.这是在开发当中很容易犯的错误,模版文件的存放位置不对,模版文件的名称出错等导致控制器在指定模版显示找不到模版文件;
Ⅱ.模板文件的存放位置:
通过具体的controller(Action)对象来调用assign和display方法,由控制器(controller)指定模版(view)显示信息。
TP框架中,某个控制器(Action类)使用到的模板文件存放到Tpl目录下以控制器名称命名(Action类名)的文件夹中。如,MsgAction中的listMsg方法中用来显示留言信息的模板文件就应该存放在/Tpl/Msg目录中,即C:\AppServ\www\TP\demo4\Tpl\Msg。
模板文件的名称:
模板文件的名称与调用该模板的控制器中的方法名(函数名)保持一致。如,MsgAction中的listMsg方法指定显示的模板文件名称应该与方法名listMsg保持一致,即模版文件名称为listMsg.html。
4)修改错误
①将listMsg方法用到的模板文件名称由mesInfo.html改成listMsg.html;
②在Tpl目录中创建名称为Msg(与MsgAction的类名相同)的文件夹,将模版文件listMsg.html移动到Msg目录中
③修改MsgAction.class.php部分代码
<?php
class MsgAction extends Action
{
// 添加留言
public function add()
{
$msgModel = new MsgModel();
//$msgModel ->add($_POST);//这里是直接调用Model类中的add方法
if ($msgModel ->add($_POST)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
// 列出留言信息
public function listMsg()
{
$msgModel = new MsgModel();
// $msgModel -> select();
// print_r($msgModel -> select());
// 通过$msgModel调用select()方法查询得到留言信息,交给相应视图显示
$msgInfo = $msgModel -> select();
$this->assign('messageInfo',$msgInfo);
// $this->display('msgInfo.html');
$this->display();
}
}
④测试:
a.浏览器地址栏输入http://localhost/tp/demo4/index.php/Msg/listMsg
b.运行结果
5)添加删除留言功能
①.显示页面(View)添加删除链接,修改listMsg.html代码
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8;">
<title>留言板信息</title>
</head>
<body>
<h1>留言板信息</h1>
<table border="1">
<tr>
<td>编号</td>
<td>标题</td>
<td>留言内容</td>
<td>删除</td>
</tr>
<!-- 使用TP框架模板引擎(view层)的foreach方法遍历MsgAction(controller层)分配的结果messageInfo -->
<foreach name="messageInfo" item="msg">
<tr>
<td>{$msg.id}</td>
<td>{$msg.title}</td>
<td>{$msg.content}</td>
<td><a href="/tp/demo4/index.php/Msg/del/id/{$msg.id}">删除</a></td>
</tr>
</foreach>
</table>
</body>
</html>
说明:
删除链接:<a href="/tp/demo4/index.php/Msg/del/id/{$msg.id}">删除</a>
"/tp/demo4/index.php/Msg/del/id/{$msg.id}"等价于"/tp/demo4/index.php?m=Msg&a=del&id={$msg.id}"。在浏览器地址栏输入http://localhost/tp/demo4/index.php/Msg/del/id/1的效果与http://localhost/tp/demo4/index.ph?m=Msg&a=del&id=1的效果一样,都是删除id为1的留言信息。可以自行测试。
②处理页面(controller+model)添加删除留言方法
<?php
class MsgAction extends Action
{
// 添加留言
public function add()
{
$msgModel = new MsgModel();
//$msgModel ->add($_POST);//这里是直接调用Model类中的add方法
if ($msgModel ->add($_POST)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
// 列出留言信息
public function listMsg()
{
$msgModel = new MsgModel();
// $msgModel -> select();
// print_r($msgModel -> select());
// 通过$msgModel调用select()方法查询得到留言信息,交给相应视图显示
$msgInfo = $msgModel -> select();
$this->assign('messageInfo',$msgInfo);
// $this->display('msgInfo.html');
$this->display();
}
// 删除留言
public function del()
{
// 使用D函数来实例化模型类
$msgModel = D('Msg');//D方法是TP框架基础函数库(common.php)中的方法
// $msgModel->delete($_GET['id']);//delete()方法是Model类(Model.class.php)中的方法
// 根据显示页面传递过来的id删除相应的留言
if ($msgModel->delete($_GET['id'])) {
echo "删除留言成功!";
} else {
echo "删除留言失败!";
}
}
}
说明:
Ⅰ.“$msgModel = D('Msg');”作用类似与“$msgModel = new MsgModel();”,都是实例化MsgModel类,但使用D函数实例化模型类会更有效率。
Ⅱ.D方法可以自动检测模型类,如果存在自定义的模型类,则实例化自定义模型类,如果不存在,则会实例化Model基类,同时对于已实例化过的模型,不会重复去实例化。——截取自ThinkPHP3.0手册
Ⅲ.D方法是ThinkPHP框架的基础函数库(common.php文件,位于C:\AppServ\www\TP\ThinkPHP\Common目录下)中的一个方法。
从源码的角度我们可以得知,D方法中使用了一个静态的变量$_model来存储了实例化的model对象,也就意味着当model类(模型类)实例化时会将对象实例存储到静态变量$_model中,如果后续代码包含实例化该对象的操作时便可以省去实例化模型类的操作,可以直接使用静态变量$_model。也就是手册中提到的,使用D方法不会去重复实例化模型类,防止重复实例化模型类,达到“单例”的效果。“单例”,单一的对象实例。
Ⅳ.delete()方法是Model类(Model.class.php)中的方法,根据表的主键删除记录。这里的msg表中的主键是留言信息的id,所以传递的参数是$_GET['id']。
Ⅴ.对于MsgAction.class.php中,add、del、listMsg方法中都有实例化MsgModel类的操作,可以考虑一下如何使用D函数简化MsgAction.class.php的代码?
③测试:
浏览器地址栏输入:http://localhost/tp/demo4/index.php/Msg/listMsg,这里也可以输入http://localhost/tp/demo4/index.php?m=Msg&a=listMsg,两种方式是等价的
A.浏览器地址栏输入:http://localhost/tp/demo4/index.php/Msg/listMsg
浏览器地址栏输入:http://localhost/tp/demo4/index.php?m=Msg&a=listMsg
B.点击“删除”
C.数据库中查询验证:
6)添加修改留言功能
①显示页面(View)添加修改留言链接
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8;">
<title>留言板信息</title>
</head>
<body>
<h1>留言板信息</h1>
<table border="1">
<tr>
<td>编号</td>
<td>标题</td>
<td>留言内容</td>
<td>删除|更新</td>
</tr>
<!-- 使用TP框架模板引擎(view层)的foreach方法遍历MsgAction(controller层)分配的结果messageInfo -->
<foreach name="messageInfo" item="msg">
<tr>
<td>{$msg.id}</td>
<td>{$msg.title}</td>
<td>{$msg.content}</td>
<td>
<a href="/tp/demo4/index.php/Msg/del/id/{$msg.id}">删除</a>|
<!-- 点击“更新”超链接跳转到更新留言的显示页面 -->
<a href="/tp/demo4/index.php/Msg/updateTpl/id/{$msg.id}">更新</a>
</td>
</tr>
</foreach>
</table>
</body>
</html>
②处理页面(controller+model)添加跳转到修改留言显示页面的方法updateTpl
<?php
class MsgAction extends Action
{
// 添加留言
public function add()
{
$msgModel = new MsgModel();
//$msgModel ->add($_POST);//这里是直接调用Model类中的add方法
if ($msgModel ->add($_POST)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
// 列出留言信息
public function listMsg()
{
$msgModel = new MsgModel();
// $msgModel -> select();
// print_r($msgModel -> select());
// 通过$msgModel调用select()方法查询得到留言信息,交给相应视图显示
$msgInfo = $msgModel -> select();
$this->assign('messageInfo',$msgInfo);
// $this->display('msgInfo.html');
$this->display();
}
// 删除留言
public function del()
{
// 使用D函数来实例化模型类
$msgModel = D('Msg');//D方法是TP框架基础函数库(common.php)中的方法
// $msgModel->delete($_GET['id']);//delete()方法是Model类(Model.class.php)中的方法
// 根据显示页面传递过来的id删除相应的留言
if ($msgModel->delete($_GET['id'])) {
echo "删除留言成功!";
} else {
echo "删除留言失败!";
}
}
// 跳转到更新留言的显示页面,显示页面内容会根据传入的留言id不同而改变
public function updateTpl()
{
$msgModel = D('Msg');
// $msgModel ->select();//select方法返回的是一个二维数组
// find方法根据指定条件查询,是Model.class.php中的方法
$aMsgInfo = $msgModel ->find($_GET['id']);//得到一条留言信息,a message info
// 指定使用C:\AppServ\www\TP\demo4\Tpl\Msg目录下的updateTpl.html模板文件显示
$this ->assign('aMessageInfo',$aMsgInfo);
$this ->display();
}
}
③创建“更新留言”的模板文件(updateTpl.html,位于C:\AppServ\www\TP\demo4\Tpl\Msg目录下)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8;">
<title>更新留言</title>
</head>
<body>
<h1>更新留言</h1>
<form action="/tp/demo4/index.php/Msg/updateMsg" method="post">
留言标题:<input type="text" name="title" value="{$aMessageInfo.title}"><br />
留言内容:<textarea name="content">{$aMessageInfo.content}</textarea>
<input type="hidden" name="id" value="{$aMessageInfo.id}">
<input type="submit" value="更新留言">
</form>
</body>
</html>
④测试
a. 为了方便测试,我们在数据库中手动添加多条留言信息。
b.浏览器地址栏输入:http://localhost/tp/demo4/index.php/Msg/listMsg
c. 点击“更新”
d.点击鼠标右键,选择“查看源文件”
e.源文件(updateTpl.html)内容如下
说明:
在查看源文件的时候我们发现在“更新留言”的提交按钮下多出了一个名称为“_hash_”的隐藏控件,这是TP框架为了防止“站外提交”和“重复提交”所采取的解决措施。
⑤处理页面(controller+model)添加修改留言内容的方法updateMsg
<?php
class MsgAction extends Action
{
// 添加留言
public function add()
{
$msgModel = new MsgModel();
//$msgModel ->add($_POST);//这里是直接调用Model类中的add方法
if ($msgModel ->add($_POST)) {
echo "发布留言成功!";
} else {
echo "发布留言失败!";
}
}
// 列出留言信息
public function listMsg()
{
$msgModel = new MsgModel();
// $msgModel -> select();
// print_r($msgModel -> select());
// 通过$msgModel调用select()方法查询得到留言信息,交给相应视图显示
$msgInfo = $msgModel -> select();
$this->assign('messageInfo',$msgInfo);
// $this->display('msgInfo.html');
$this->display();
}
// 删除留言
public function del()
{
// 使用D函数来实例化模型类
$msgModel = D('Msg');//D方法是TP框架基础函数库(common.php)中的方法
// $msgModel->delete($_GET['id']);//delete()方法是Model类(Model.class.php)中的方法
// 根据显示页面传递过来的id删除相应的留言
if ($msgModel->delete($_GET['id'])) {
echo "删除留言成功!";
} else {
echo "删除留言失败!";
}
}
// 跳转到更新留言的显示页面,显示页面内容会根据传入的留言id不同而改变
public function updateTpl()
{
$msgModel = D('Msg');
// $msgModel ->select();//select方法返回的是一个二维数组
// find方法根据指定条件查询,是Model.class.php中的方法
$aMsgInfo = $msgModel ->find($_GET['id']);//得到一条留言信息,a message info
// 指定使用C:\AppServ\www\TP\demo4\Tpl\Msg目录下的updateTpl.html模板文件显示
$this ->assign('aMessageInfo',$aMsgInfo);
$this ->display();
}
// 更新留言信息
public function updateMsg()
{
$msgModel = D('Msg');
// 使用Model.class.php中的save方法来进行更新
if ($msgModel ->save($_POST)) {
echo "更新留言信息成功!";
} else {
echo "更新留言信息失败!";
}
}
}
说明:
Model.class.php中的save方法,根据记录的主键来更新记录在数据库中的信息。在本例中,调用updateMsg方法时传入的$_POST中包含了记录的主键(msg表的主键为id),所以很轻松的就完成了更新操作。
⑥第二次测试
a.浏览器地址栏输入:http://localhost/tp/demo4/index.php/Msg/listMsg
b.选择更新编号为8的留言
c.点击"更新留言”
d.验证:可以返回到之前显示留言信息页面验证,也可以到数据库中查询验证。
到此,简易的留言本已经完成了!
=====================上午的达标检测=======================
1:能否把ThinkPHP系统被几个项目所共用?
2:在项目中,配置项目的URL路由规则,改变原本的/分割URL的方式.
3:手写一个controller,并写一个方法,及前置,后置操作.
如果能追溯源码,分析前置后置操作的工作原理更好。