tp3.2源码解析——入口文件
Posted 我也很惆怅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tp3.2源码解析——入口文件相关的知识,希望对你有一定的参考价值。
如果有人读这篇文章并跟着做的话,希望你能使用支持函数跳转的编辑器,还要善用var_dump和exit,对着源码去调试着看。跟着入口文件读,执行到哪里你看到哪里,对于那些不能一眼看出来的配置,则要记录下来,可能一个比较简单的功能会写出很长的代码,这个时候难免会看到后面忘了前面。
那么进入正题,从index.php文件可以看到入口文件只定义了几项常量作为配置,紧接着就引入了require \'./ThinkPHP/ThinkPHP.php\';
1 // 检测PHP环境 2 if(version_compare(PHP_VERSION,\'5.3.0\',\'<\')) die(\'require PHP > 5.3.0 !\'); 3 4 // 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false 5 define(\'APP_DEBUG\',True); 6 7 // 定义应用目录 8 define(\'APP_PATH\',\'./Application/\'); 9 10 // 引入ThinkPHP入口文件 11 require \'./ThinkPHP/ThinkPHP.php\';
在ThinkpPHP文件依然比较简单,tp定义了一些常量配置项(defined函数的写法让之前在index入口文件里定义的配置项不会被重置)记录了运行时间和内存使用信息,进行了php版本的判断,以及cli命令行模式的判断。并在文件末尾再次引入了Think核心类,并进行了初始化。
require CORE_PATH.\'Think\'.EXT;路径为ThinkPHP\\Library\\Think\\Think.class.php
这个think类就比较长了,一开始就定义了$_map , $_instance两个数组,其中$_map作为映射数组使用,think类在会把核心模块的路劲存在这个数组里。$_instance则存储了系统运行时所实例化的对象
1 // 类映射 2 private static $_map = array(); 3 4 // 实例化对象 5 private static $_instance = array();
刚刚的入口文件执行了start方法来运行系统。这个start方法则一开始就通过spl_autoload_register方法注册了自动加载函数。(php本身有一些魔术方法,在执行某些方法,或变量时,如果它找不到这个方法或变量,就会执行相应的魔术方法,tp便是用自己的自动引入方法替换了该方法,达到不需要引入,直接new对象,系统便会自动引入该类文件的目的)
1 static public function start() { 2 // 注册AUTOLOAD方法 3 spl_autoload_register(\'Think\\Think::autoload\'); 4 // 设定错误和异常处理 5 register_shutdown_function(\'Think\\Think::fatalError\'); 6 set_error_handler(\'Think\\Think::appError\'); 7 set_exception_handler(\'Think\\Think::appException\'); 8 9 // 初始化文件存储方式 10 Storage::connect(STORAGE_TYPE); 11 ················
我们往下翻看,autoload方法传入了一个$class类名,然后便在类的$_map里检测是否存在该类的映射,如果有,则表明是系统核心模块直接通过存储的地址include引入;反之则会判断$class是否为带有命名空间的路径字符串,然后通过strstr函数分割字符串获得命名空间前缀,判断是否为tp系统定义的命名空间,然后便通过之前定义的常量来确定文件路径。同时判断是否为win环境,进行大小写的匹配,然后include引入;
1 public static function autoload($class) { 2 // 检查是否存在映射 3 if(isset(self::$_map[$class])) { 4 include self::$_map[$class]; 5 }elseif(false !== strpos($class,\'\\\\\')){ 6 $name = strstr($class, \'\\\\\', true); 7 if(in_array($name,array(\'Think\',\'Org\',\'Behavior\',\'Com\',\'Vendor\')) || is_dir(LIB_PATH.$name)){ 8 // Library目录下面的命名空间自动定位 9 $path = LIB_PATH; 10 }else{ 11 // 检测自定义命名空间 否则就以模块为命名空间 12 $namespace = C(\'AUTOLOAD_NAMESPACE\'); 13 $path = isset($namespace[$name])? dirname($namespace[$name]).\'/\' : APP_PATH; 14 } 15 $filename = $path . str_replace(\'\\\\\', \'/\', $class) . EXT; 16 if(is_file($filename)) { 17 // Win环境下面严格区分大小写 18 if (IS_WIN && false === strpos(str_replace(\'/\', \'\\\\\', realpath($filename)), $class . EXT)){ 19 return ; 20 } 21 include $filename; 22 } 23 }elseif (!C(\'APP_USE_NAMESPACE\')) { 24 // 自动加载的类库层 25 foreach(explode(\',\',C(\'APP_AUTOLOAD_LAYER\')) as $layer){ 26 if(substr($class,-strlen($layer))==$layer){ 27 if(require_cache(MODULE_PATH.$layer.\'/\'.$class.EXT)) { 28 return ; 29 } 30 } 31 } 32 // 根据自动加载路径设置进行尝试搜索 33 foreach (explode(\',\',C(\'APP_AUTOLOAD_PATH\')) as $path){ 34 if(import($path.\'.\'.$class)) 35 // 如果加载类成功则返回 36 return ; 37 } 38 } 39 }
通过APP_USE_NAMESPACE判断如果配置项里未使用命名空间,那么通过APP_AUTOLOAD_LAYER配置项循环判断为控制器或模型,调用公共方法判断路径并require。若引入失败则再按照配置文件中的APP_AUTOLOAD_PATH路径寻找引入;
注册了autoload方法后,tp系统又注册了错误及异常处理方法,接管了报错时的信息提示功能(这些方法与autoload差不多,有兴趣的朋友自己研究,在这里就不一一赘述了)。
然后它通过Storage::connect(STORAGE_TYPE);类初始化了自己的文件存储类。ThinkPHP\\Library\\Think\\Storage.class.php该类简单的定义了一个操作句柄,一个初始化方法,将各种不同方式的操作对象赋予本类,(通过传入不同参数,可以确定为SAE环境不同类型的存储操作,默认为file文件操作类)
1 namespace Think; 2 // 分布式文件存储类 3 class Storage { 4 5 /** 6 * 操作句柄 7 * @var string 8 * @access protected 9 */ 10 static protected $handler ; 11 12 /** 13 * 连接分布式文件系统 14 * @access public 15 * @param string $type 文件类型 16 * @param array $options 配置数组 17 * @return void 18 */ 19 static public function connect($type=\'File\',$options=array()) { 20 $class = \'Think\\\\Storage\\\\Driver\\\\\'.ucwords($type); 21 self::$handler = new $class($options); 22 } 23 24 static public function __callstatic($method,$args){ 25 //调用缓存驱动的方法 26 if(method_exists(self::$handler, $method)){ 27 return call_user_func_array(array(self::$handler,$method), $args); 28 } 29 } 30 }
file文件位于ThinkPHP\\Library\\Think\\Storage\\Driver\\File.class.php(tp通过不同的driver驱动层,来适应不同环境,不同类型的动态操作)这个文件也是写的相当简单,类方法里定义了写入,删除,加载,读取,等基本操作。
回到start方法,Tp通过APP_DEBUG配置来判断是否有runtime缓存文件,通过Storage::has方法来读取,或unlink方法删除该缓存文件。
1 $runtimefile = RUNTIME_PATH.APP_MODE.\'~runtime.php\'; 2 if(!APP_DEBUG && Storage::has($runtimefile)){ 3 Storage::load($runtimefile); 4 }else{ 5 if(Storage::has($runtimefile)) 6 Storage::unlink($runtimefile);
然后它读取了应用模式:
1 $content = \'\'; 2 // 读取应用模式 3 $mode = include is_file(CONF_PATH.\'core.php\')?CONF_PATH.\'core.php\':MODE_PATH.APP_MODE.\'.php\'; 4 // 加载核心文件 5 foreach ($mode[\'core\'] as $file){ 6 if(is_file($file)) { 7 include $file; 8 if(!APP_DEBUG) $content .= compile($file); 9 } 10 }
1 is_file(CONF_PATH.\'core.php\')? //判断是否有隐含应用模式文件 2 CONF_PATH.\'core.php\':MODE_PATH.APP_MODE.\'.php\'; 3 //yes,读取Application/Common/Conf/core.php ; 否,读取/ThinkPHP/Mode/common.php
大家打开ThinkPHP/Common/functions.php文件,这个文件里通过数组定义了配置文件和行文扩展,其中core里面存储了tp的核心文件。(大家在查看源码的时候,可以多用var_dump和exit这两个函数来查看一些变量常量的值)这里放一下这个配置文件的路径
1 //载入配置列表 2 Array 3 ( 4 [config] => Array 5 ( 6 [0] => D:\\wamp\\www\\tp\\ThinkPHP/Conf/convention.php 7 [1] => ./Application/Common/Conf/config.php 8 ) 9 //增加为映射 10 [alias] => Array 11 ( 12 [Think\\Log] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Log.class.php 13 [Think\\Log\\Driver\\File] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Log/Driver/File.class.php 14 [Think\\Exception] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Exception.class.php 15 [Think\\Model] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Model.class.php 16 [Think\\Db] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Db.class.php 17 [Think\\Template] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Template.class.php 18 [Think\\Cache] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Cache.class.php 19 [Think\\Cache\\Driver\\File] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Cache/Driver/File.class.php 20 [Think\\Storage] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Storage.class.php 21 ) 22 23 //加载核心类 24 [core] => Array 25 ( 26 [0] => D:\\wamp\\www\\tp\\ThinkPHP/Common/functions.php 27 [1] => ./Application/Common/Common/function.php 28 [2] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Hook.class.php 29 [3] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/App.class.php 30 [4] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Dispatcher.class.php 31 [5] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Route.class.php 32 [6] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/Controller.class.php 33 [7] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Think/View.class.php 34 [8] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Behavior/BuildLiteBehavior.class.php 35 [9] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Behavior/ParseTemplateBehavior.class.php 36 [10] => D:\\wamp\\www\\tp\\ThinkPHP\\Library/Behavior/ContentReplaceBehavior.class.php 37 ) 38 //加载到Think/Hook->tags里 39 [tags] => Array 40 ( 41 [app_init] => Array 42 ( 43 [0] => Behavior\\BuildLiteBehavior 44 ) 45 46 [app_begin] => Array 47 ( 48 [0] => Behavior\\ReadhtmlCacheBehavior 49 ) 50 51 [app_end] => Array 52 ( 53 [0] => Behavior\\ShowPageTraceBehavior 54 ) 55 56 [view_parse] => Array 57 ( 58 [0] => Behavior\\ParseTemplateBehavior 59 ) 60 61 [template_filter] => Array 62 ( 63 [0] => Behavior\\ContentReplaceBehavior 64 ) 65 66 [view_filter] => Array 67 ( 68 [0] => Behavior\\WriteHtmlCacheBehavior 69 ) 70 71 ) 72 73 )
在include核心文件后,通过C方法加载了应用模式的配置。(C方法位于ThinkPHP\\Common\\functions.php,tp的单字母函数都是在这里定义的,也是比较简单,通过静态变量来存储配置)
function C($name=null, $value=null,$default=null)这里解释下auto自动变量会随着函数被调用和退出而存在和消失,而static类局部变量不会,它不管其所在的函数是否被调用,都将一直存在;不过,尽管该变量还继续存在,但不能使用它。倘若再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。
加载了配置项,又通过map定义了模式的别名。
然后加载了应用行为定义,这里的行为比较关键,解释一下这个概念。
行为就是钩子函数,有些了解钩子函数的同学可能已经知道这是干嘛的了,这里解释一下,钩子函数就是系统在运行过程中,挂在某一段代码中的方法,在代码执行到钩子方法那里的时候就会执行这个钩子上所绑的函数了,不了解的同学可以理解为方法间的调用,比如我有一个a方法,一个b方法,a方法里显示的写了b();这样来调用b方法,这样虽然能起到调用的目的,但是一旦程序需要改动,要把调用b方法换成调用c方法则需要改动所有写了b();的地方,十分繁琐,还可能出错,于是,如果我们在需要调用b方法的地方,不显示的调用b方法,而是读取一个配置变量,或配置文件,调用配置里定义的方法,那现在这样写就把a方法和b方法的耦合给解开了,以后要改变b方法为c方法d方法的时候我都可以只改动配置文件,是不是很方便呢?
1 Function a(){ 2 ```` 3 B(); 4 ````` 5 } 6 7 Function b(){ 8 Echo ‘我是钩子函数b’; 9 }
如果你已经理解了钩子函数,那么ThinkPHP\\Library\\Think\\Hook.class.php这就是tp里的钩子类,用来挂载行为(tp中把钩子函数叫做行为)。打开这个文件来看一下(这里我就不整篇贴代码了):
- 一开始定义了一个$tags变量,用来存储需要执行的方法。
- Add方法添插件行为(就是钩子函数),import方法批量导入,
- get获取插件数组,
- exec方法执行插件,
- listen方法则是线判断了是否为debug模式,如果是debug模式,则通过G方法记录了插件的执行,再调用exec方法,最后通过trace记录了日志。
看完了hook类,再回到think类来,加载应用行为就很好理解了,通过Hook::import将tags.php里配置的钩子数组导入了hook类里(tp里定义钩子函数通过config里新建tags.php,不清楚的朋友自行翻阅手册)。Tp的行为在上面放出的tags配置数组里可以看到。
加载完行为,又加载了语言包、引入debug文件、读取应用状态的配置文件、创建基本目录结构、记录加载文件时间。
1 // 读取当前应用模式对应的配置文件 2 if(\'common\' != APP_MODE && is_file(CONF_PATH.\'config_\'.APP_MODE.CONF_EXT)) 3 C(load_config(CONF_PATH.\'config_\'.APP_MODE.CONF_EXT)); 4 5 // 加载模式别名定义 6 if(isset($mode[\'alias\'])){ 7 self::addMap(is_array($mode[\'alias\'])?$mode[\'alias\']:include $mode[\'alias\']); 8 } 9 10 // 加载应用别名定义文件 11 if(is_file(CONF_PATH.\'alias.php\')) 12 self::addMap(include CONF_PATH.\'alias.php\'); 13 14 // 加载模式行为定义 15 if(isset($mode[\'tags\'])) { 16 Hook::import(is_array($mode[\'tags\'])?$mode[\'tags\']:include $mode[\'tags\']); 17 } 18 19 // 加载应用行为定义 20 if(is_file(CONF_PATH.\'tags.php\')) 21 // 允许应用增加开发模式配置定义 22 Hook::import(include CONF_PATH.\'tags.php\'); 23 24 // 加载框架底层语言包 25 L(include THINK_PATH.\'Lang/\'.strtolower(C(\'DEFAULT_LANG\')).\'.php\'); 26 27 if(!APP_DEBUG){ 28 $content .= "\\nnamespace { Think\\\\Think::addMap(".var_export(self::$_map,true).");"; 29 $content .= "\\nL(".var_export(L(),true).");\\nC(".var_export(C(),true).\');Think\\Hook::import(\'.var_export(Hook::get(),true).\');}\'; 30 Storage::put($runtimefile,strip_whitespace(\'<?php \'.$content)); 31 }else{ 32 // 调试模式加载系统默认的配置文件 33 C(include THINK_PATH.\'Conf/debug.php\'); 34 // 读取应用调试配置文件 35 if(is_file(CONF_PATH.\'debug\'.CONF_EXT)) 36 C(include CONF_PATH.\'debug\'.CONF_EXT); 37 } 38 } 39 40 // 读取当前应用状态对应的配置文件 41 if(APP_STATUS && is_file(CONF_PATH.APP_STATUS.CONF_EXT)) 42 C(include CONF_PATH.APP_STATUS.CONF_EXT); 43 44 // 设置系统时区 45 date_default_timezone_set(C(\'DEFAULT_TIMEZONE\')); 46 47 // 检查应用目录结构 如果不存在则自动创建 48 if(C(\'CHECK_APP_DIR\')) { 49 $module = defined(\'BIND_MODULE\') ? BIND_MODULE : C(\'DEFAULT_MODULE\'); 50 if(!is_dir(APP_PATH.$module) || !is_dir(LOG_PATH)){ 51 // 检测应用目录结构 52 Build::checkDir($module); 53 } 54 } 55 56 // 记录加载文件时间 57 G(\'loadTime\'); 58 // 运行应用 59 App::run(); 60 }
最后app:run()又调用了另一个类,整个配置完成,开始运行了。ThinkPHP\\Library\\Think\\App.class.php
Run方法一开始就通过刚刚说的Hook::listen方法调用了一个行为,然后执行了init方法来初始化系统。
1 static public function run() { 2 // 应用初始化标签 3 Hook::listen(\'app_init\'); 4 App::init(); 5 // 应用开始标签 6 Hook::listen(\'app_begin\'); 7 // Session初始化 8 if(!IS_CLI){ 9 session(C(\'SESSION_OPTIONS\')); 10 } 11 // 记录应用初始化时间 12 G(\'initTime\'); 13 App::exec(); 14 // 应用结束标签 15 Hook::listen(\'app_end\'); 16 return ; 17 }
Init方法也是一开始就加载了公共配置,配置了日志路径,以及定义了不少常量,都是与服务器接到的请求有关。
1 static public function init() { 2 // 加载动态应用公共文件和配置 3 load_ext_file(COMMON_PATH); 4 5 // 日志目录转换为绝对路径 默认情况下存储到公共模块下面 6 C(\'LOG_PATH\', realpath(LOG_PATH).\'/Common/\'); 7 8 // 定义当前请求的系统常量 9 define(\'NOW_TIME\', $_SERVER[\'REQUEST_TIME\']); 10 define(\'REQUEST_METHOD\',$_SERVER[\'REQUEST_METHOD\']); 11 define(\'IS_GET\', REQUEST_METHOD ==\'GET\' ? true : false); 12以上是关于tp3.2源码解析——入口文件的主要内容,如果未能解决你的问题,请参考以下文章