此时就会有一个问题,这个init方法明显是被调用了俩次的,那么另一次调用的地方是在哪里呢!
如果在不知道新的技巧之前,就会进行一系列的断点打印,看在哪里进行了执行,比如在这个init的上层去打印。
也就是在initialize那个方法里边去打印做断点,但是这样很是麻烦的,而且很有可能浪费了大量的时间还是找不到正确的地方。
小技巧之debug_backtrace()
这个方法会产生一条回溯追踪,会显示出一个方法所有的调用位置。
使用方式就是如下图,只需要把debug_backtrace这个方法打印出来即可。
根据得到的数据信息,就可以非常快的进行定位。
第一次就是在app类的215行。
第二次是在thinkphp/library/think/route/dispatch/Module.php类的60行
可以在这里做一个打印,看一下这个module是否为index
所以说有了这个方法就可以非常快速地定位调用位置。
三、框架执行流程之初始化应用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方法会在俩个地方进行执行。
第一次的模块为空,这块代码执行是没有任何意义的。
下面在对容器的对象实例进行配置更新时进行了一次判断,判断模块的这个参数是否为空,如果不为空才会执行。
那么同样的道理,咔咔感觉在设置模块路径这块也应该在这个判断里边。
虽说第二次执行会把第一次的结果覆盖掉,但是咔咔感觉下图这样使用才会更好。