本文主要介绍框架的执行流程php
前言
若是不清楚框架是怎么执行的,那么看在多的代码都是只是认识代码而已,阅读源码是为了学习其框架的设计思想和代码模式。thinkphp
而执行流程则是将咱们学习的东西串联在一块儿,从而更好地理解。咔咔也会给你们把执行流程用思惟导图的方式画出来。编程
只要你们在本文学习到一点点的知识点,咔咔也是心满意足的。设计模式
这个流程图只是针对initialize的执行过程,其他的执行过程后期会进行补充,都是以脑图的形式呈现给你们的。缓存
1、框架执行流程之初始化应用的数据设置
这里的内容跟容器的内容有点重复,由于执行流程是从入口文件开始的,而且最后也是经过容器执行的。安全
而后就会进入到文件thinkphp/library/think/App.php
的run方法,在这个方法中主要就是下图框出来的地方,执行的initialize方法。app
来到initialize这个方法,先看上半部分。框架
microtime(true);
返回的是unix的微秒数memory_get_usage
返回的是分配给PHP的内存量,单位为字节- 在接下来就是对框架的几个路径进行设置
static::setInstance($this);
这里是将app这个实例设置为容器实例$this->instance('app', $this);
这个在以前容器章节就提到了,就是为了把app这个类绑定到容器里边去,也就是注册树模式。
这里有一个小的问题点给你们提出来,在初始化应用的这个方法里边存在这样一行代码。ide
有没有小伙伴对这个$this->env
和下边的$this->config
这俩个调用有疑惑。函数
若是你有疑惑那就跟着咔咔一块儿来看,没疑惑的就能够继续往下看了。
App这个类是继承的容器类,那么这个env和config不管是在app仍是container类中都是没有这俩个属性的。
那么怎么就能够直接调用呢!并且代码追踪都会追踪到env类和container类中。
须要知道这个源头就须要咱们去在大体的看一遍container类的代码。
通过一番苦读以后,能够看到下图的几行代码。这几行代码所有使用的是魔术方法。
当访问env类不存在的时候就会去执行make方法。
make这个方法在容器那一章节进行的细的不能再细的解读了。
这个make方法最终会返回一个类的实例,而且还会存到容器里边。
这里只放一个make方法的代码,若是有不会的能够去看以前的文章。
最后就是加载一系列的数据,加载详情请看前言的思惟导图。
2、如何查看一个方法都在哪里执行了
在阅读源码的过程当中,有一个很难把控的问题就是一个方法在不一样的地方进行了调用,可是我们确一时半会根本不知道都在哪里调用了。
这里用init方法来作一个演示。
init方法是初始化应用或者模块的一个方法,可是这里的module参数确实一个空值。
先作一个断点查看一下相关的数据信息。
打印的结果就是空,这就是一些新学习的伙伴会犯的一个错误,由于这个方法不可能只调用一次的。
若是初始化模块都是空那么这个方法就没有存在的必要了。
那么正确的断点方式应该是这个样子的。
此时就会有一个问题,这个init方法明显是被调用了俩次的,那么另外一次调用的地方是在哪里呢!
若是在不知道新的技巧以前,就会进行一系列的断点打印,看在哪里进行了执行,好比在这个init的上层去打印。
也就是在initialize那个方法里边去打印作断点,可是这样非常麻烦的,并且颇有可能浪费了大量的时间仍是找不到正确的地方。
小技巧之debug_backtrace()
这个方法会产生一条回溯追踪,会显示出一个方法全部的调用位置。
使用方式就是以下图,只须要把debug_backtrace这个方法打印出来便可。
根据获得的数据信息,就能够很是快的进行定位。
第一次就是在app类的215行。
第二次是在thinkphp/library/think/route/dispatch/Module.php
类的60行
能够在这里作一个打印,看一下这个module是否为index
因此说有了这个方法就能够很是快速地定位调用位置。
3、框架执行流程之初始化应用init分析
上文给你们提供了一个小技巧debug_backtrace
实战演示了如何查看一个方法都在哪里执行的。
而且案例也是使用的init这个方法来演示的,由于接下来就是要对init这个方法进行深刻的了解。
在init方法里边主要作的事情在上边的脑图已经描述的很清楚了。
- 从一开始就对模块的定位,就是在第二节中的对init方法的调用,会传入对应的模块
- 加载app目录下的tags文件,在tags文件里边就是对行为扩展定义的文件。在以前门面的文章中定义钩子执行就在这个文件中设置的。
- 加载common文件,也就是公共文件,因此说公共文件就是在这里进行加载的。
- 加载助手函数文件helper,在助手函数里边有一个你们特别熟悉的一个方法,那就是dump。这就是为何在有的地方使用dump会报错的缘由。
- 加载中间件文件,这里的直接给出的是直接加载app目录下的中间件文件,可是在框架中咱们须要在定义一个目录为http,在这个目录下定义中间件文件。
- 注册服务的容器对象实例,这里注册就使用的是容器类中的
bindTo
方法进行绑定注册的。 - 读取配置文件,这段在配置文件加载那一节中已经进行深刻的说明了, 这里就不提了。配置文件会读取俩个地方一个是第一步模块下的config文件,另外一个就是config目录下的配置文件。
- 设置模块路径,会把第一步获取到的模块进行
env
环境变量配置里边 - 最后一步就是对容器中的对象实例进行配置更新,具体更新了什么在后文中给你们详细说来。
/** * 初始化应用或模块 * @access public * @param string $module 模块名 * @return void */ public function init($module = '') { // 定位模块目录 $module = $module ? $module . DIRECTORY_SEPARATOR : ''; /** * 第一次:D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\ * 第二次:D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\index\ */ $path = $this->appPath . $module; // 加载初始化文件 if (is_file($path . 'init.php')) { include $path . 'init.php'; } elseif (is_file($this->runtimePath . $module . 'init.php')) { include $this->runtimePath . $module . 'init.php'; } else { // 加载行为扩展文件 if (is_file($path . 'tags.php')) { $tags = include $path . 'tags.php'; if (is_array($tags)) { $this->hook->import($tags); } } // 加载公共文件 if (is_file($path . 'common.php')) { include_once $path . 'common.php'; } if ('' == $module) { // 加载系统助手函数 include $this->thinkPath . 'helper.php'; } // 加载中间件 if (is_file($path . 'middleware.php')) { $middleware = include $path . 'middleware.php'; if (is_array($middleware)) { $this->middleware->import($middleware); } } // 注册服务的容器对象实例 if (is_file($path . 'provider.php')) { $provider = include $path . 'provider.php'; if (is_array($provider)) { $this->bindTo($provider); } } /** * $path : "D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\" * "D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\index\" */ // 自动读取配置文件 if (is_dir($path . 'config')) { $dir = $path . 'config' . DIRECTORY_SEPARATOR; } elseif (is_dir($this->configPath . $module)) { // D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\config\ $dir = $this->configPath . $module; } // scandir:以升序的方式读取目录中的文件 // 返回就是config目录中的全部文件 $files = isset($dir) ? scandir($dir) : []; foreach ($files as $file) { /** * $this->configExt:配置文件的后缀 * pathinfo返回的是文件后缀,关于pathinfo共有三个可选的参数PATHINFO_DIRNAME、PATHINFO_BASENAME、PATHINFO_EXTENSION,分别为只返回文件名,文件目录名,文件扩展 */ if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { /** * 俩个参数分别为 * 1.目录+config目录下的文件 * 2.config目录下文件名 */ $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); } } } $this->setModulePath($path); if ($module) { // 对容器中的对象实例进行配置更新 $this->containerConfigUpdate($module); } }
这里附带上一份代码,能够对着代码看上边的执行流程,对每一步都作了简单的说明。
咔咔我的看法对源码进行优化
在设置模块的这步代码咔咔感受不是非常严谨,由于init方法会在俩个地方进行执行。
第一次的模块为空,这块代码执行是没有任何意义的。
下面在对容器的对象实例进行配置更新时进行了一次判断,判断模块的这个参数是否为空,若是不为空才会执行。
那么一样的道理,咔咔感受在设置模块路径这块也应该在这个判断里边。
虽然说第二次执行会把第一次的结果覆盖掉,可是咔咔感受下图这样使用才会更好。
4、对容器中的对象实例进行更新配置
在上一节中这里就是最后的内容,那这个对实例进行更新配置,到底更新了什么,怎么更新没有说明。
在这一小节中就会作出说明,一样能够配合着前言的思惟导图看。
- 先会把config目录下的全部配置信息所有获取出来
- 从app配置文件中将注册异常处理类
- 第三大块是把第一步获取出来的全部配置信息给对应的类进行注册配置。
- 第四步就是在把模块肯定下来以后加载对应的语言包,语言包功能就能够实现多语言功能,以前咔咔写过一篇文章实现多语言功能,若是感兴趣的能够去查看。
- 最后一步就是根据app配置文件中的三个属性进行缓存的处理
在这一节中咔咔感受最重要的就是下图的内容了。
咱们能够随意追踪一到俩个方法查看一下那边到底执行了什么方法。
追踪方法Db::init()
追踪方法过来后能够看到就是对Db类中的config属性进行赋值,把database中的值赋值给Db类中的config属性。
追踪方法$this->middleware->setConfig()
来到中间件这个类里边,能够看到就是把本类的配置和传递过来的参数类进行合并,一样也是进行config属性的赋值。
跟上边案例的Db类的init方法实现的效果是一致的。
这里在提一嘴就是在对容器中的对象实例进行更新配置
这一幅图中能够看到紫色部分是在本类中没有引用的。
那么这是怎么能够进行执行的呢!是由于App类继承了容器类,容器类中有四个魔术方法,其中有一个__get方法,就是在获取不存在的属性时会执行那个方法。
在魔术方法__get方法中执行了一个make方法,这个make方法说了好屡次了,这个方法最终会返回一个应用的实例,而后用这个实例调用对应实例类的方法。
这一块必定要理解好,阅读源码就是这个样子,咱们须要对一切未知的进行的解决,只有这样才能提升咱们的编程能力和思想。
5、浅谈调试模式以及代码冗余
本节会对调试模式作出简单的说明,而且会对框架代码冗余状况进行简单的提出。
没有人写的代码是没有漏洞的,若是有那就是你尚未达到必定的造诣。
调试模式
在第一节中只提到了initialize方法的上半部分,由于在这一节以前聊的都是关于应用初始化init的内容。
接下来会对这一块的内容进行简单的说明。
- 从app配置文件中获取到app_debug的配置项
- 给环境变量设置debug级别
- 当框架中的debug是关闭状态时会执行ini_set这个方法,这个方法是为一个配置选项进行赋值。
接下来的内容估计不是很好理解,都是平时在工做中根本使用不到的。
- ob_get_level:返回输出缓冲机制的嵌套级别,那么怎么去理解呢!其实就是当缓存区不起做用时会返回0。
- ob_get_clean:这个函数将会返回输出缓冲的内容并终止输出缓冲。若是缓冲区没有有效内容则返回false。本质上至关于同时执行了ob_getcontens()和ob_end_clean()。
- ob_start:打开输出控制缓冲
上边这三个先暂时认识就行,后期若是有机会会专门出一篇文章作解释的。
关于框架代码冗余
这里也仅仅表明咔咔我的的观点。
能够先看看这部分的代码,这俩处代码是否是非常熟悉,没错就是在上文的init方法中容器对象实例配置更新见到过。
如图
这块也就是咔咔我的提出的看法,因为咔咔式针对5.1作的源码解读,不太了解新版版是否作出了改动。
6、总结
本节主要是针对框架执行流程中的初始化应用作了简单的探讨。
至于在app类的run方法下面还有不少的执行过程在这一节中没有作过多的解释。
在阅读源码的过程当中给你们提了一个很好得小技巧,那就是如何去查看一个方法都在哪里进行了执行。
这个方法为debug_backtrace
,这个方法须要你们多使用几回就知道怎么使用了,由于在打印出来的结果中也存在不少无用的信息。
这个方法在调试源码的过程当中是很是有效的,必定要好好利用这个方法。
在就是对初始化应用init方法进行了特别详细的介绍。
其中咔咔感受这块设计最好的就是在容器中的对象实例进行更新配置那一块,先读取全部的配置,而后在经过各个类的方法进行配置的设置。
这种代码规划和设计思路值得咱们去学习。
最后聊到了调试模式和框架的代码冗余问题,关于调试模式这里咔咔给你们提个醒项目在线上的调试模式必定要关闭。
不然你的项目就相似于裸奔的存在,没有一点点的安全可言。
这块有点很差理解的就是对于缓冲区,关于这块的内容咔咔认为暂时没有必要去钻牛角尖,先认识认识而后在进行深刻的研究。
缓冲区的这块内容估计工做了三四年的也不多有人使用,因此先认识,知道怎么一回事,咔咔后期学习了以后在给你们进行补充。
直到这里关于框架的执行流程之初始化应用就结束了,这一节没有过深须要学习的,主要是其中的代码设计模式和实现思路。
最后这个图你们必定要跟着源码看一看哈!
坚持学习、坚持写博、坚持分享是咔咔从业以来一直所秉持的信念。但愿在偌大互联网中咔咔的文章能带给你一丝丝帮助。我是咔咔,下期见。