Bootstrap

tp5.1框架二 项目启动

tp5.1框架一

1、启动顺序

执行: Container::get('app')->run()->send();

文件位置:项目名/public/index.php

解释:

根据上文调用关系为index.php->base.php->Error::register()->register_shutdown_function()。

register_shutdown_function()为注册一个会在php中止时执行的函数,代码为 register_shutdown_function([__CLASS__, 'appShutdown'])。

appShutdown()内容:

 public static function appShutdown()
    {
        if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
            // 将错误信息托管至think\ErrorException
            $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);

            self::appException($exception);
        }

        // 写入日志
        Container::get('log')->save();
    }

error_get_last()获取最后一次的错误信息,程序结束自动调用。

这里是有错误信息则用自定义的异常类处理之后log保存。

总体意思就为,在php程序执行结束时日志保存最后一次报错。

若是在base.php中Error::register()后执行exit(),则此时没有报错,文件加载(autoload()打印)顺序为think\Error 、think\Container 、think\Log 、think\LoggerInterface。

这个LoggerInterface没有对应文件,但是被log继承,所以会走下think\Loader::autoload()方法,但是think\Loader::findFile()会返回false,有对应文件的返回文件位置再加载。

根据index.php,base.php执行完执行Container::get('app')->run()->send()正式开始启动app类,此时Error(错误处理)、Container(容器)、Log(日志),都是加载好的。

Container类get()调用顺序get()->make()。make函数根据get函数的参数判断$bind数组中是否有值,有值则取对应值再调用一次make,没有则调用invokeClass()用反代理创建类实例。

所以Container::get('log')执行两次,Container::get('think/Log')执行一次,Container::get('app')同理,最后返回对应类的实例。

之后base.php实现LoggerInterface类,并设置类库别名,其中大多数类为think\Facade子类。

think\Facade为门面设计模式。

php 23种设计模式 - 走看看

think\Container为容器设计模式。

从匿名函数(闭包特性)到 PHP 设计模式之容器模式 - Sefa - 博客园

Container调用invokeClass()里会调用其他类的__make()函数。加载log类的过程中,其__make()的参数为think\App和think\Config,这俩类的构造会被调用。

#thinkphp/log
 public static function __make(App $app, Config $config)
    {
        return (new static($app))->init($config->pull('log'));
    }
 public function init($config = [])
    {
        $type = isset($config['type']) ? $config['type'] : 'File';

        $this->config = $config;

        unset($config['type']);

        if (!empty($config['close'])) {
            $this->allowWrite = false;
        }

        $this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config);

        return $this;
    }
#thinkphp/Config
public function __construct($path = '', $ext = '.php')
    {
        $this->path = $path;
        $this->ext = $ext;
        $this->yaconf = class_exists('Yaconf');
    }
#thinkphp/App
public function __construct($appPath = '')
    {
        $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
        $this->path($appPath);
    }

Config构造中使用一个Yaconf类,Yaconf为php扩展,用于管理配置文件,可以与项目文件分离,可用于管理不用项目,比如前后端分离的两个项目可以使用相同配置文件。

think\Log用init()函数初始化的时候获取$config->pull('log')为参数,作为设置日志引擎用的变量,但是此时为空,因为在没按Yaconf的情况下,并且仅初始化并没用设置$this->config。tp5.1中config文件夹下有多个文件,不同用处的配置都在不同文件中设置。根据代码,日志的默认引擎就是file,所以最后加载think\Log\dirver\File。

#think/Log
public function init($config = [])
    {
        $type = isset($config['type']) ? $config['type'] : 'File';

        $this->config = $config;

        unset($config['type']);

        if (!empty($config['close'])) {
            $this->allowWrite = false;
        }

        $this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config);

        return $this;
    }
#think/Cofig
public function pull($name)
    {
        $name = strtolower($name);

        if ($this->yaconf) {
            $yaconfName = $this->getYaconfName($name);

            if (Yaconf::has($yaconfName)) {
                $config = Yaconf::get($yaconfName);
                return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config;
            }
        }

        return isset($this->config[$name]) ? $this->config[$name] : [];
    }
总结上文,Container::get('log')->save()是通过error_get_last()调用所以think\Log、think\Congid、Yaconf、think\Log\driver\File会在每次程序执行完后调用,base.php执行完之后调用think\App::run()->send()。

2、think\App

该类设置版本号,定义为常量const VERSION。继承think\Container类,think\Container类中重新定义魔术方法,其中__get()调用think\Container::make()方法,即think\App中调用属性会获取 think\Container::bind或think\Container::name中对应的类的实例对象。

#think\App
public function run()
    {
        try {
            // 初始化应用
            $this->initialize();

            // 监听app_init
            $this->hook->listen('app_init');

            if ($this->bindModule) {
                // 模块/控制器绑定
                $this->route->bind($this->bindModule);
            } elseif ($this->config('app.auto_bind_module')) {
                // 入口自动绑定
                $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
                if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
                    $this->route->bind($name);
                }
            }

            // 监听app_dispatch
            $this->hook->listen('app_dispatch');

            $dispatch = $this->dispatch;

            if (empty($dispatch)) {
                // 路由检测
                $dispatch = $this->routeCheck()->init();
            }

            // 记录当前调度信息
            $this->request->dispatch($dispatch);

            // 记录路由和请求信息
            if ($this->appDebug) {
                $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
                $this->log('[ HEADER ] ' . var_export($this->request->header(), true));
                $this->log('[ PARAM ] ' . var_export($this->request->param(), true));
            }

            // 监听app_begin
            $this->hook->listen('app_begin');

            // 请求缓存检查
            $this->checkRequestCache(
                $this->config('request_cache'),
                $this->config('request_cache_expire'),
                $this->config('request_cache_except')
            );

            $data = null;
        } catch (HttpResponseException $exception) {
            $dispatch = null;
            $data = $exception->getResponse();
        }

        $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
            return is_null($data) ? $dispatch->run() : $data;
        });

        $response = $this->middleware->dispatch($this->request);

        // 监听app_end
        $this->hook->listen('app_end', $response);

        return $response;
    }
 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;
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
        $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
        $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;

        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);

        // 注册应用命名空间
        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;
            }
        }

        // 注册异常处理类
        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'));

        // 数据库配置初始化
        Db::init($this->config->pull('database'));

        // 设置系统时区
        date_default_timezone_set($this->config('app.default_timezone'));

        // 读取语言包
        $this->loadLangPack();

        // 路由初始化
        $this->routeInit();
    }

think\App::initialize()先加载thinkphp/convention.php文件。

之后调用$this->init()函数,加载application的init.php、tags.php、common.php、helper.php、middleware.php、provider.php、config文件夹。

init.php文件在application文件夹下不存在会加载runtime文件下的该同名文件。

config文件夹在application文件夹下没有的情况会加载think\App::configPath值对应路径下的config文件夹,即根目录下的config文件夹。

加载config文件夹下的文件时会判断文件后缀为php还是yaml,yaml需要yaml_parse_file()可用,即yaml扩展已安装并可用。

加载其余配置文件后,会把对应的文件名(没有后缀名)作为think\Config的属性名,文件内容作为属性值。

3、think\facade\App

其中的注释内容如@see、@metho等为phpdocumentor内容,用于文档生成。

phpdocumento官网:phpDocumentor

phpdoc tag 解释:phpDocumentor

该类继承think\Facade。

;