慕容的工作随笔

慕容的工作随笔

记录学习和工作

ThinkPHP5.1 源码分析(五)- 框架执行流程 转

转自:https://zhuanlan.zhihu.com/p/100640786

从代码说起:

// 入口文件
// 执行应用并响应
Container::get('app')->run()->send();
获取容器中的'app'实例,并执行App.php中的run()方法,首先我们来看看run()方法中第一步初始化应用:
public function run()
{
     try {
        // 初始化应用
        $this->initialize();
        ...
}

  1. bool initialized,定义这个属性的目的是让该方法只能执行一次,即初始化只用执行一次,第二次进来直接就返回了。
2. static::setInstance($this),设置当前容器的实例,其实在入口文件中就已经实例化了app,即Container::get('app')时,这里再一次调用是为了把上面的属性存入app实例中。
  1. 加载环境变量配置文件时,为什么可以通过$this->env->load 调用到Env类的load方法?
    因为当前类App是继承于Container类的,$this->env 那么App类中没有Env属性,就会到父类Container类中去寻找Env属性,父类中没有Env属性就会触发父类的__get()方法 而__get()方法传递的参数就是Env,在__get()方法中调用make()方法创建Env类的实例。
public function initialize()
{
    if ($this->initialized) {
        return;
    }
    $this->initialized = true;
    $this->beginTime   = microtime(true);
    $this->beginMem    = memory_get_usage();
$this->rootPath    = dirname($this->appPath) . DIRECTORY_SEPARATOR; // /Users/liujingxiong/web/tp51/
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
$this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
$this->configPath  = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;

// 容器对象实例,其实在入口文件中就已经实例化了app,这里再一次调用是为了把上面的属性存入app实例中
static::setInstance($this);
// 绑定一个类实例到容器
$this->instance('app', $this);
// 加载环境变量配置文件
if (is_file($this->rootPath . '.env')) {
    $this->env->load($this->rootPath . '.env');
}
// 获取配置文件后缀名
$this->configExt = $this->env->get('config_ext', '.php');
// 加载惯例配置文件
$this->config->set(include $this->thinkPath . 'convention.php');
// 设置路径环境变量
$this->env->set([
     'think_path'   => $this->thinkPath,
     'root_path'    => $this->rootPath,
     'app_path'     => $this->appPath,
     'config_path'  => $this->configPath,
     'route_path'   => $this->routePath,
     'runtime_path' => $this->runtimePath,
     'extend_path'  => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
     'vendor_path'  => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
 ]);
 $this->namespace = $this->env->get('app_namespace', $this->namespace);
 $this->env->set('app_namespace', $this->namespace);
 // 注册应用命名空间 这里就不再赘述,详见类的自动加载
 // $this->appPath 就是应用的目录application
 Loader::addNamespace($this->namespace, $this->appPath);
 // 初始化应用
 $this->init();
 // 开启类名后缀
 $this->suffix = $this->config('app.class_suffix');
 // 应用调试模式
 $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug'));
 $this->env->set('app_debug', $this->appDebug);
 if (!$this->appDebug) {
     ini_set('display_errors', 'Off');
 } elseif (PHP_SAPI != 'cli') {
     //重新申请一块比较大的buffer
     if (ob_get_level() > 0) {
         $output = ob_get_clean();
     }
     ob_start();
     if (!empty($output)) {
         echo $output;
     }
 }
 // 注册异常处理类 冗余了 在containerConfigUpdate()方法中
 /** @see  App::containerConfigUpdate() */
 if ($this->config('app.exception_handle')) {
     Error::setExceptionHandler($this->config('app.exception_handle'));
 }
 // 注册根命名空间
 if (!empty($this->config('app.root_namespace'))) {
     Loader::addNamespace($this->config('app.root_namespace'));
 }
 // 加载composer autofile文件
 Loader::loadComposerAutoloadFiles();
 // 注册类库别名
 Loader::addClassAlias($this->config->pull('alias'));
 // 数据库配置初始化 这里也冗余了
 /** @see  App::containerConfigUpdate() */
 Db::init($this->config->pull('database'));
 // 设置系统时区
 date_default_timezone_set($this->config('app.default_timezone'));
 // 读取语言包
 $this->loadLangPack();
 // 路由初始化
 $this->routeInit();

}

4. 执行初始化应用$this->init()讲解:该方法传递一个$model参数,但是我们为什么默认会走到index模块呢?原理该方法在多个地方被调用,可以采用debug_backtrace()进行回溯追踪函数调用信息,会明确显示调用地址。init()方法会加载各种配置文件。
第一次模块为空,第二次模块为index
debug_backtrace()打印结果
public function init($module = '')
{
    // var_dump(debug_backtrace()); // 回溯追踪函数调用信息
    // 定位模块目录
    $module = $module ? $module . DIRECTORY_SEPARATOR : '';
    $path   = $this->appPath . $module;
    //echo '模块:'. $module . PHP_EOL;
    //echo $path . PHP_EOL;
// 加载初始化文件
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);
        }
    }
    // 自动读取配置文件
    if (is_dir($path . 'config')) {
        $dir = $path . 'config' . DIRECTORY_SEPARATOR;
    } elseif (is_dir($this->configPath . $module)) {
        $dir = $this->configPath . $module;
    }

    $files = isset($dir) ? scandir($dir) : [];
    foreach ($files as $file) {
        if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) {
            $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); // 传递的两个参数 一个完整的文件路径  一个文件名
        }
    }
}

$this->setModulePath($path);

if ($module) {
   // 对容器中的对象实例进行配置更新
   $this->containerConfigUpdate($module);
}

}

4.(1) common.php说明:当模块目录下和application目录都有公共文件,是不允许出现相同的公共方法的,不像配置文件那样会覆盖,所有在定义公共方法时需要使用function_exists() 判断一下该公共函数名是否已经被定义。

4.(2) provider.php说明:注册服务的容器对象实例,返回的是一个数组,定义一个类的别名,最终会和Container类的bind属性进行merge,前面在讲自定义容器的时候就已经使用到了,这里再加深一下理解。

4.(3) $this->setModulePath($path)说明:作用是将模块路径配置最终写到环境变量配置中,但是第一次模块是空的,第二次才是定位到真正的模块(index),再次调用$this->setModulePath($path)会进行覆盖,但是这样写不太严谨,放到下面的if中比较合适

if ($module) {
   $this->setModulePath($path);
   // 对容器中的对象实例进行配置更新
   $this->containerConfigUpdate($module);
}
最后,走到$this->routeInit();下一节介绍路由解读

评论 (0)

还没有评论,来做第一个吧。

发表评论

提交后需管理员审核通过后显示