200 likes | 323 Views
ThinkPHP 执行流程分析. yhustc@ThinkPHP http://www.yhustc.com. 前言. 要分析什么? 分析一下从访问 index.php 到加载 Action 文件、调用用户指定操作的过程。 达到什么样的效果? 分析一下在进入自己指定的模块与操作之前发生过一些什么事情,以及如何通过 index.php 进入指定的模块与操作,让你能搞明白这些事情,要的就这效果。. 入口文件的定义. <?php define('THINK_PATH', '../ThinkPHP'); // 定义项目名称,如果不定义,默认为入口文件名称
E N D
ThinkPHP执行流程分析 yhustc@ThinkPHP http://www.yhustc.com
前言 • 要分析什么? 分析一下从访问index.php到加载Action文件、调用用户指定操作的过程。 • 达到什么样的效果? 分析一下在进入自己指定的模块与操作之前发生过一些什么事情,以及如何通过index.php进入指定的模块与操作,让你能搞明白这些事情,要的就这效果。
入口文件的定义 <?php define('THINK_PATH', '../ThinkPHP'); //定义项目名称,如果不定义,默认为入口文件名称 define('APP_NAME', 'web'); define('APP_PATH', './web'); //加载ThinkPHP框架公共入口文件 require(THINK_PATH.'/ThinkPHP.php'); //实例化一个网站应用实例 $App = new App(); //执行应用程序 $App->run(); ?>
ThinkPHP.php里面的干了些啥 记录开始执行时间 $GLOBALS[‘_beginTime’](20行) 检测 THINK_PATH、 APP_NAME、 APP_PATH、 RUNTIME_PATH 定义,如果没有则创建(23-26行) //不知道你发现没有,以index.php这基准,通过APP_PATH,可以组装出任意路径 检查是否有核心缓存~runtime.php(28行) if(file_exists(RUNTIME_PATH.'~runtime.php')) { // 加载框架核心缓存文件 // 如果有修改核心文件请删除该缓存 require RUNTIME_PATH.'~runtime.php'; }else{ 进入else } 记录加载文件时间 $GLOBALS['_loadTime']; require thinkphp.php完成
ThinkPHP.php 32行的else 加载系统定义文件 ThinkPHP\Common\defines.php 和公共函数文件 ThinkPHP\Common\functions.php(34-36行) 如果项目编译缓存目录不存在,则自动创建项目目录结构(41行buildAppDir()是在functions.php中定义的函数) 加载系统核心类库(包括ThinkPHP\LIB\THINK下级各子目录下的 Base、App、Action、Model、View、ThinkException、Log)(45-54行) 如果 PHP 版本低于 5.2.0 则加载兼容函数库 ThinkPHP\Common\compat.php(76行) 生成核心编译缓存~runtime.php(生成在APP_PATH\ Temp\目录下) 退出else
进入ThinkPHP\Lib\Think\Core\App 入口文件中调用的$App->run() public function run() { $this->init(); $this->exec(); return ; } $App->run()返回的时候,对一个请求的处理就算完成了
App.class.php的init函数(1) 设定错误和异常处理机制(set_error_handler和set_exception_handler)(92-93行) 项目预编译并载入(97-103行) if(file_exists(RUNTIME_PATH.'~app.php') && filemtime(RUNTIME_PATH.'~app.php')>filemtime(CONFIG_PATH.'config.php')) { // 直接读取编译后的项目文件 C(include RUNTIME_PATH.'~app.php'); }else{ // 预编译项目 $this->build(); } 设置时区支持(106-107行) Session过滤器检查、 session初始化(109-115行)
App.class.php的init函数(2) 检查并加载插件(118-120行) if(C('THINK_PLUGIN_ON')) { $this->loadPlugIn(); } URL分析和调度(126-135行) if(C('DISPATCH_ON')) { if( 'Think'== C('DISPATCH_NAME') ) { // 使用内置的ThinkDispatcher调度器 import('Think.Util.Dispatcher'); Dispatcher::dispatch(); }else{ // 加载第三方调度器 apply_filter('app_dispatch'); } }
App.class.php的init函数(3) 取得模块和操作名称 如果有伪装 则返回真实的名称(145-146行) if(!defined('MODULE_NAME')) define('MODULE_NAME', $this->getModule()); // Module名称 if(!defined('ACTION_NAME')) define('ACTION_NAME', $this->getAction()); // Action操作 加载模块配置文件(149-151行) 页面防刷新机制检查(154-167行) 语言检查并读取对应的语言文件(170行) $this->checkLanguage(); //通过COOKIE保存当前语言,可以dump($_COOKIE)看看
App.class.php的init函数(4) 模板检查并定义相关的模板变量(171行) $this->checkTemplate(); //通过COOKIE保存当前模板主题,可以dump($_COOKIE)看看 RBAC权限检测(173-184行) RBAC最好单独了解,这个PPT里写不了那么全面 如果开启静态写入则读取静态缓存文件(186-189行) 应用初始化过滤插件app_init (191行) apply_filter(‘app_init’); //ThinkPHP\Common\functions.php中有定义 记录应用初始化时间$GLOBALS['_initTime'] 初始化完成,init返回
Build了一些什么东西? 加载系统惯例配置文件ThinkPHP\Common\convention.php(213行) 加载项目配置文件APP_PATH\Conf\config.php(217行) 加载项目公共文件APP_PATH\Common\common.php(224行) 如果是调试模式加载系统调试配置文件ThinkPHP\Common\debug.php(233行) 如果定义了项目的调试配置文件则载入APP_PATH\Common\debug.php(236行) 生成项目编译缓存文件APP_PATH\Temp\~app.php(242行) 现在知道~app.php里面放了些啥吧?留一个问题,如何让一段代码每次都自动执行,而且不受RBAC权限控制的限制? Build完毕,继续执行init
App.class.php的loadplugin 加载有效插件文件(loadplugin函数在App.class.php 478行) if(file_exists(RUNTIME_PATH.'~plugins.php')) { include RUNTIME_PATH.‘~plugins.php’; //注意了,插件也会缓存,改了插件记得清缓存 }else{ // 检查插件数据 $common_plugins = get_plugins(THINK_PATH.'/PlugIns','Think');// 公共插件 $app_plugins = get_plugins();// 项目插件 ThinkPHP\Common\functions.php中有定义 // 合并插件数据 $plugins = array_merge($common_plugins,$app_plugins); // 缓存插件数据 $content = ''; foreach($plugins as $key=>$val) { include $val['file']; $content .= php_strip_whitespace($val['file']); } file_put_contents(RUNTIME_PATH.‘~plugins.php’,$content); //RUNTIME_PATH默认就是APP_PATH\Temp } 插件加载完毕,继续执行init
Dispatcher.class.php的dispatch (Dispatcher.class.php的第一个函数就是dispatch) 检查当前URL模式URL_MODEL 如果存在$_GET变量,则根据当前的URL模式和设置进行重定向 进行路由定义检测 分析PATH_INFO的URL信息到数组 这一部分不难理解,就不详细讲代码了 URL解析完成,继续执行init
App.class.php的getModule App.class.php 256行 检查 VAR_MODULE 变量(包括 GET 和 POST), 如果未定义,则获取默认模块名(258-262行) 检查组件模块 if(strpos($module,C(‘COMPONENT_DEPR’))) { // COMPONENT_DEPR 组件模式分隔符,默认是@ define(‘C_MODULE_NAME’,$module); // 带@的完整的模块名定义为C_MODULE_NAME $array = explode(C('COMPONENT_DEPR'),$module); $module = array_pop($array); // @后面部分作为实际的模块名称,返回后被定义为MODULE_NAME } 检查模块URL伪装 if(C('MODULE_REDIRECT')) { $res = preg_replace('@(\w+):([^,\/]+)@e', '$modules[\'\\1\']="\\2";', C('MODULE_REDIRECT')); if(array_key_exists($module,$modules)) { define(‘P_MODULE_NAME’,$module); // 伪装的模块名定义为P_MODULE_NAME $module = $modules[$module]; } } return $module; 模块名称解析完毕,也就是http://server/project/index.php/Model/Action/的Model部分,回到init
App.class.php的getAction App.class.php 292行 检查 VAR_ACTION 变量(包括 GET 和 POST), 如果未定义,则获取默认操作名 检查操作链(以下部分可以参考getModule自行分析,别骂我,学会自己分析是有必要的) if(strpos($action,C('COMPONENT_DEPR'))) { // 记录完整的操作名 define('C_ACTION_NAME',$action); $array = explode(C('COMPONENT_DEPR'),$action); // 实际的模块名称 $action = array_pop($array); } 检查操作URL伪装 if(C('ACTION_REDIRECT')) { $res = preg_replace('@(\w+):([^,\/]+)@e', '$actions[\'\\1\']="\\2";', C('ACTION_REDIRECT')); if(array_key_exists($action,$actions)) { // 记录伪装的操作名称 define('P_ACTION_NAME',$action); $action = $actions[$action]; } } 操作名称解析完毕,也就是http://server/project/index.php/Model/Action/的Action部分,回到init
App.class.php的exec函数(1) AUTO_LOAD_CLASS检查如果有则导入公共类(514-520行) 实例化当前模块的Action控制器类(522-526行) if(defined(‘C_MODULE_NAME’)) { //组件化模块 $module = A(C_MODULE_NAME); //A函数在ThinkPHP\Common\functions.php中定义,可以根据@连接的模块名自动处理找到正确的Action文件 }else{ $module = A(MODULE_NAME); } 如果Action控制器不存在则检查空模块EmptyAction (527-534行)
App.class.php的exec函数(2) App.class.php 537-555行 $action = ACTION_NAME.C(‘ACTION_SUFFIX’); if(defined(‘C_ACTION_NAME’)) { // 定义了操作链,执行操作链,最多只能有一个输出(这个得由用户自己控制,注意写代码的时候不要多处使用display来输出,用echo没问题) $actionList = explode(C('COMPONENT_DEPR'),C_ACTION_NAME); foreach ($actionList as $action){ $module->$action(); //这里就是调用指定Action的指定function了 } }else{ //如果存在前置操作,首先执行 if (method_exists($module,'_before_'.$action)) { $module->{'_before_'.$action}(); } //执行操作 $module->{$action}(); //执行指定Action的指定function,这里面的流程我就没法给你分析了,看自己代码怎么写的 //如果存在后置操作,继续执行 if (method_exists($module,'_after_'.$action)) { $module->{'_after_'.$action}(); } }
App.class.php的exec函数(3) 执行应用结束过滤器(557行) apply_filter('app_end'); 写入错误日志(560-561行) if(C('WEB_LOG_RECORD')) Log::save(); exec执行完毕,一个请求处理完成了
回到入口文件 <?php define('THINK_PATH', '../ThinkPHP'); //定义项目名称,如果不定义,默认为入口文件名称 define('APP_NAME', 'web'); define('APP_PATH', './web'); //加载ThinkPHP框架公共入口文件 require(THINK_PATH.'/ThinkPHP.php'); //实例化一个网站应用实例 $App = new App(); //执行应用程序 $App->run(); //分为init和exec两步 ?>
小结 $App->run()分为init和exec两部分,现在都已经走完,但是这中间还省略了一部分分析,比如RBAC,比如用户代码中的那些操作中,Model相关的代码。建议用户自行分析一下,这样有助于对TP的理解。有什么问题欢迎到ThinkPHP论坛(http://bbs.thinkphp.cn)或者我的Blog(http://www.yhustc.com)来交流。