Laravel 学习笔记之 Bootstrap 源码解析

说明:Laravel在把Request通过管道Pipeline送入中间件Middleware和路由Router之前,还做了程序的启动Bootstrap工作,本文主要学习相关源码,看看Laravel启动程序做了哪些具体工作,并将个人的研究心得分享出来,希望对别人有所帮助。Laravel在入口index.php时先加载Composer加载器:Laravel学习笔记之Composer自动加载,然后进行Application的实例化:Laravel学习笔记之IoC Container实例化源码解析,得到实例化后的Application对象再从容器中解析出Kernel服务,然后进行Request实例化(Request实例化下次再聊),然后进行Bootstrap操作启动程序,再通过Pipeline送到Middleware:Laravel学习笔记之Middleware源码解析,然后经过路由映射找到对该请求的操作action(以后再聊),生成Response对象经过Kernel的send()发送给Client。本文主要聊下程序的启动操作,主要做了哪些准备工作。

开发环境:Laravel5.3 + PHP7 + OS X 10.11

在Laravel学习笔记之Middleware源码解析聊过,Kernel中的sendRequestThroughRouter()处理Request,并把Request交给Pipeline送到Middleware和Router中,看源码:

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    /* 依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数,做了几件准备事情:
    1. 环境检测 DetectEnvironment
    2. 配置加载 LoadConfiguration
    3. 日志配置 ConfigureLogging
    4. 异常处理 HandleException
    5. 注册Facades RegisterFacades
    6. 注册Providers RegisterProviders
    7. 启动Providers BootProviders
     protected $bootstrappers = [
        'Illuminate\Foundation\Bootstrap\DetectEnvironment',
        'Illuminate\Foundation\Bootstrap\LoadConfiguration',
        'Illuminate\Foundation\Bootstrap\ConfigureLogging',
        'Illuminate\Foundation\Bootstrap\HandleExceptions',
        'Illuminate\Foundation\Bootstrap\RegisterFacades',
        'Illuminate\Foundation\Bootstrap\RegisterProviders',
        'Illuminate\Foundation\Bootstrap\BootProviders',
    ];*/
    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

在Request被Pipeline送到Middleware前还有一步操作bootstrap()操作,这步操作就是启动程序,看下\Illuminate\Foundation\Http\Kernel中的bootstrap()源码:

protected $hasBeenBootstrapped = false;

...

/**
 * Bootstrap the application for HTTP requests.
 *
 * @return void
 */
public function bootstrap()
{
    // 检查程序是否已经启动
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

public function hasBeenBootstrapped()
{
    return $this->hasBeenBootstrapped;
}

protected function bootstrappers()
{
    return $this->bootstrappers;
}

protected $bootstrappers = [
    'Illuminate\Foundation\Bootstrap\DetectEnvironment',
    'Illuminate\Foundation\Bootstrap\LoadConfiguration',
    'Illuminate\Foundation\Bootstrap\ConfigureLogging',
    'Illuminate\Foundation\Bootstrap\HandleExceptions',
    'Illuminate\Foundation\Bootstrap\RegisterFacades',
    'Illuminate\Foundation\Bootstrap\RegisterProviders',
    'Illuminate\Foundation\Bootstrap\BootProviders',
];

从以上源码可知道,程序将会依次bootstrapWith()数组$bootstrappers中各个bootstrapper,看下容器中的bootstrapWith()源码:

public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

        $this->make($bootstrapper)->bootstrap($this);

        $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
    }
}

首先触发'bootstrapping: '.$bootstrapper事件,告知将要启动该bootstrapper,然后从容器中make($bootstrapper)出该$bootstrapper,并执行该$bootstrapper中的bootstrap()方法,最后在触发事件:'bootstrapped: '.$bootstrapper,告知该$bootstrapper已经启动OK了。启动的bootstrappers就是数组$bootstrappers中的7个bootstrapper,看下程序做了哪些启动工作。

  1. 环境检测
    查看Illuminate\Foundation\Bootstrap\DetectEnvironment中的bootstrap()源码:

    public function bootstrap(Application $app)
    {
    // 查看bootstrap/cache/config.php缓存文件是否存在
    // php artisan config:cache来生成配置缓存文件,就是把config/下的所有文件放在一个缓存文件内,提高性能
    // 这里假设没有缓存配置文件
    if (! $app->configurationIsCached()) {
    $this->checkForSpecificEnvironmentFile($app);

        try {
            $env = $_ENV; // 调试添加的,此时为空
            // 这里把.env文件值取出存入$_ENV内
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
            // 这里$_ENV数组有值了
            $env = $_ENV;
        } catch (InvalidPathException $e) {
            //
        }
    }

    }

    protected function checkForSpecificEnvironmentFile($app)
    {
    // 读取$_ENV全局变量中'APP_ENV'值,此时是空
    if (! env('APP_ENV')) {
    return;
    }

    $file = $app->environmentFile().'.'.env('APP_ENV'); // .env.local
    
    if (file_exists($app->environmentPath().'/'.$file)) {
        $app->loadEnvironmentFrom($file);
    }

    }
    环境监测核心就是把.env文件内值存入到$_ENV全局变量中\Dotenv\Dotenv::load()函数实现了这个功能,具体不详述了。可以通过Xdebug调试查看:
    图片描述

  2. 配置加载
    配置加载就是读取config/文件夹下的所有配置值,然后存入\Illuminate\Config\Repository对象中,而环境检测是读取.env文件存入$_ENV全局变量中,加载环境配置主要是使用\Symfony\Component\Finder\Finder这个组件进行文件查找,看下LoadConfiguration::bootstrap()的源码:

    public function bootstrap(Application $app)
    {
    $items = [];
    // 查看config有没有缓存文件,缓存文件是在bootstrap/cache/config.php
    // 通过php artisan config:cache命令来生成缓存文件,把config/下的所有配置文件打包成一个文件,提高程序执行速度
    // 这里假设没有缓存文件
    if (file_exists($cached = $app->getCachedConfigPath())) {
    $items = require $cached;

        $loadedFromCache = true;
    }
    // 绑定服务'config',服务是\Illuminate\Config\Repository对象
    $app->instance('config', $config = new Repository($items));
    
    if (! isset($loadedFromCache)) {
        // 加载config/*.php所有配置文件,把所有配置存入Repository对象中
        $this->loadConfigurationFiles($app, $config);
    }
    // 检查'APP_ENV'环境设置,一般也就是'dev','stg','prd'三个环境,即'development', 'staging', 'production'
    $app->detectEnvironment(function () use ($config) {
        return $config->get('app.env', 'production');
    });
    
    // 设置时区,$config['app.timezone']就是调用Repository::get('app.timezone'),因为Repository实现了ArrayAccess Interface,
    // '.'语法读取是Arr::get()实现的,很好用的一个方法
    date_default_timezone_set($config['app.timezone']);
    
    mb_internal_encoding('UTF-8');

    }
    加载配置文件,就是读取/config/*.php文件,看下源码:

    protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
    {
    foreach ($this->getConfigurationFiles($app) as $key => $path) {
    // 存入到Repository对象中,以'key => value'存入到$items[]属性中
    $repository->set($key, require $path);
    }
    }

    protected function getConfigurationFiles(Application $app)
    {
    $files = [];
    // 就是'config/'这个路径
    $configPath = realpath($app->configPath());
    // Finder链式接口读取config/.php所有文件,获取所有文件名称,然后依次遍历
    foreach (Finder::create()->files()->name('
    .php')->in($configPath) as $file) {
    $nesting = $this->getConfigurationNesting($file, $configPath);

        $files[$nesting.basename($file->getRealPath(), '.php')] = $file->getRealPath();
    }
    
    return $files;

    }
    可以通过Xdebug调试知道$files的返回值是这样的数组:

    $files = [
    'app' => '/vagrant/config/app.php', //文件的绝对路径
    'auth' => 'vagrant/config/auth.php',
    'broadcasting' => '/vagrant/config/broadcasting.php',
    'cache' => '/vagrant/config/cache.php',
    'compile' => 'vagrant/config/compile.php',
    'database' => '/vagrant/config/databse.php',
    'filesystems' => '/vagrant/config/filesystems.php',
    'mail' => '/vagrant/config/mail.php',
    'queue' => '/vagrant/config/queue.php',
    'services' => '/vagrant/config/services.php',
    'session' => '/vagrant/config/session.php',
    'view' => '/vagrant/config/view.php',
    ];
    然后通过Application的detectEnvironment()方法把app.env的值即app.php中env的值取出来存入Application对象的$env属性中:

    public function detectEnvironment(Closure $callback)
    {
    $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : null;

    return $this['env'] = (new EnvironmentDetector())->detect($callback, $args);

    }

    public function detect(Closure $callback, $consoleArgs = null)
    {
    if ($consoleArgs) {
    return $this->detectConsoleEnvironment($callback, $consoleArgs);
    }

    return $this->detectWebEnvironment($callback);

    }

    protected function detectWebEnvironment(Closure $callback)
    {
    return call_user_func($callback);
    }
    所以属性检查的时候就存到了$env属性的值了,开发代码中就可以App::environment()得到这个$env属性然后进行一些操作,可以看下environment()的源码,该方法有两个feature:如果不传入值则读取$env值;如果传入值则判断该值是否与$env一样。这里如果对Application没有$env成员属性定义有疑惑,是因为PHP可以后期添加属性,如:

class ClassField
{

}

$class_field = new ClassField();
$class_field->name = 'Laravel';
echo $class_field->name . PHP_EOL;

/* output:
Laravel

  1. 日志配置
    Laravel主要利用Monolog日志库来做日志处理,\Illuminate\Log\Writer相当于Monolog Bridge,把Monolog库接入到Laravel中。看下ConfigureLogging::bootstrap()源码:

    public function bootstrap(Application $app)
    {
    // 注册'log'服务
    $log = $this->registerLogger($app);

    // 检查是否已经注册了Monolog
    // 这里假设开始没有注册
    if ($app->hasMonologConfigurator()) {
        call_user_func(
            $app->getMonologConfigurator(), $log->getMonolog()
        );
    } else {
        // 
        $this->configureHandlers($app, $log);
    }

    }

    protected function registerLogger(Application $app)
    {
    // 向容器中绑定'log'服务,即Writer对象
    $app->instance('log', $log = new Writer(
    new Monolog($app->environment()), $app['events'])
    );

    return $log;

    }

Laravel的Log模块中已经内置了几个类型的LogHandler:Single,Daily,Syslog,Errorlog.根据config/app.php文件中'log'的配置选择其中一个handler,看下configureHandlers()源码:

protected function configureHandlers(Application $app, Writer $log)
{
    $method = 'configure'.ucfirst($app['config']['app.log']).'Handler';

    $this->{$method}($app, $log);
}

configureHandlers()这方法也是一个技巧,找到方法名然后调用,这在Laravel中经常这么用,如Filesystem那一模块中有'create'.ucfirst(xxx).'Driver'这样的源码,是个不错的设计。这里看下configureDailyHandler()的源码,其余三个也类似:

protected function configureDailyHandler(Application $app, Writer $log)
{
    // 解析'config'服务
    $config = $app->make('config');
    // 默认没有设置,就为null
    $maxFiles = $config->get('app.log_max_files');

    $log->useDailyFiles(
        $app->storagePath().'/logs/laravel.log', // storage/log/laravel.log
        is_null($maxFiles) ? 5 : $maxFiles, // 5
        $config->get('app.log_level', 'debug') 
    );
}

// Writer.php
public function useDailyFiles($path, $days = 0, $level = 'debug')
{
    $this->monolog->pushHandler(
        $handler = new RotatingFileHandler($path, $days, $this->parseLevel($level))
    );

    $handler->setFormatter($this->getDefaultFormatter());
}

利用Mnolog的RotatingFileHandler()来往laravel.log里打印log值,当然在应用程序中经常\Log::info(),\Log::warning(),\Log::debug()来打印变量值,即Writer类中定义的的方法。Log的facade是\Illuminate\Support\Facades\Log:

class Log extends Facade
{
/**

  • Get the registered name of the component.
  • @return string
    */
    protected static function getFacadeAccessor()
    {
    return 'log';
    }
    }
    而'log'服务在上文中bootstrap()源码第一步registerLogger()就注册了。当然,至于使用Facade来从容器中获取服务也聊过,也不复杂,看下\Illuminate\Support\Facades\Facade的resolveFacadeInstance()源码就知道了:

    protected static function resolveFacadeInstance($name)
    {
    if (is_object($name)) {
    return $name;
    }

    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }
    
    return static::$resolvedInstance[$name] = static::$app[$name]; // 实际上就是使用$app['log']来获取服务

    }

    1. 异常处理
      异常处理是十分重要的,Laravel中异常处理类\App\Exception\Handler中有一个方法report(),该方法可以用来向第三方服务(如Sentry)发送程序异常堆栈(以后在一起聊聊这个Sentry,效率神器),如Production Code线上环境报出个异常,可以很清楚整个堆栈,出错在哪一行:

图片描述

OK,看下异常设置的启动源代码,HandleExceptions::bootstrap()的源码:

public function bootstrap(Application $app)
{
    $this->app = $app;

    error_reporting(-1);
    // 出现错误,抛出throw new ErrorException
    set_error_handler([$this, 'handleError']);
    // 处理异常,使用report()方法来报告,可集成第三方服务Sentry来作为异常报告处理器ExceptionReportHandler
    set_exception_handler([$this, 'handleException']);

    register_shutdown_function([$this, 'handleShutdown']);

    if (! $app->environment('testing')) {
        ini_set('display_errors', 'Off');
    }
}

这里重点看下handleException()的源码:

public function handleException($e)
{
    if (! $e instanceof Exception) {
        $e = new FatalThrowableError($e);
    }
    // (new App\Exceptions\Handler($container))->report($e)
    $this->getExceptionHandler()->report($e);

    if ($this->app->runningInConsole()) {
        $this->renderForConsole($e);
    } else {
        $this->renderHttpResponse($e);
    }
}

protected function getExceptionHandler()
{
    // 解析出App\Exceptions\Handler对象
    // 在boostrap/app.php中做过singleton()绑定
    return $this->app->make('Illuminate\Contracts\Debug\ExceptionHandler');
}

protected function renderHttpResponse(Exception $e)
{
    // 使用(new App\Exceptions\Handler($container))->render(Request $request, $e)
    $this->getExceptionHandler()->render($this->app['request'], $e)->send();
}

从源码中知道,重点是使用App\Exceptions\Handler的report()方法报告异常情况,如向Sentry报告异常堆栈和其他有用信息;App\Exceptions\Handler的render()方法通过Request发送到浏览器。关于使用第三方服务Sentry来做异常报告以后详聊,我司每天都在用这样的效率神器,很好用,值得推荐下。

  1. 注册Facades
    在路由文件中经常会出现Route::get()这样的写法,但实际上并没有Route类,Route只是\Illuminate\Support\Facades\Route::class外观类的别名,这样取个别名只是为了简化作用,使用的是PHP内置函数class_alias(string $class, string $alias)来给类设置别名。看下RegisterFacades::bootstrap()的源码:

    public function bootstrap(Application $app)
    {
    Facade::clearResolvedInstances();

    Facade::setFacadeApplication($app);
    
    AliasLoader::getInstance($app->make('config')->get('app.aliases', []))->register();

    }

    // \Illuminate\Support\Facades\Facade
    public static function clearResolvedInstances()
    {
    static::$resolvedInstance = [];
    }

    // \Illuminate\Support\Facades\Facade
    public static function setFacadeApplication($app)
    {
    static::$app = $app;
    }
    $app->make('config')->get('app.aliases', [])是从config/app.php中读取'aliases'的值,然后注册外观类的别名,注册的外观类有:

    'aliases' => [

    'App' => Illuminate\Support\Facades\App::class,
    'Artisan' => Illuminate\Support\Facades\Artisan::class,
    'Auth' => Illuminate\Support\Facades\Auth::class,
    'Blade' => Illuminate\Support\Facades\Blade::class,
    'Cache' => Illuminate\Support\Facades\Cache::class,
    'Config' => Illuminate\Support\Facades\Config::class,
    'Cookie' => Illuminate\Support\Facades\Cookie::class,
    'Crypt' => Illuminate\Support\Facades\Crypt::class,
    'DB' => Illuminate\Support\Facades\DB::class,
    'Eloquent' => Illuminate\Database\Eloquent\Model::class,
    'Event' => Illuminate\Support\Facades\Event::class,
    'File' => Illuminate\Support\Facades\File::class,
    'Gate' => Illuminate\Support\Facades\Gate::class,
    'Hash' => Illuminate\Support\Facades\Hash::class,
    'Lang' => Illuminate\Support\Facades\Lang::class,
    'Log' => Illuminate\Support\Facades\Log::class,
    'Mail' => Illuminate\Support\Facades\Mail::class,
    'Notification' => Illuminate\Support\Facades\Notification::class,
    'Password' => Illuminate\Support\Facades\Password::class,
    'Queue' => Illuminate\Support\Facades\Queue::class,
    'Redirect' => Illuminate\Support\Facades\Redirect::class,
    'Redis' => Illuminate\Support\Facades\Redis::class,
    'Request' => Illuminate\Support\Facades\Request::class,
    'Response' => Illuminate\Support\Facades\Response::class,
    'Route' => Illuminate\Support\Facades\Route::class,
    'Schema' => Illuminate\Support\Facades\Schema::class,
    'Session' => Illuminate\Support\Facades\Session::class,
    'Storage' => Illuminate\Support\Facades\Storage::class,
    'URL' => Illuminate\Support\Facades\URL::class,
    'Validator' => Illuminate\Support\Facades\Validator::class,
    'View' => Illuminate\Support\Facades\View::class,

    ],
    从以上外观别名数组中知道Route是IlluminateSupportFacadesRoute::class的别名,所以Route::get()实际上就是IlluminateSupportFacadesRoute::get(),看下AliasLoader类的getInstance()和register()方法源码:

    public static function getInstance(array $aliases = [])
    {
    if (is_null(static::$instance)) {
    // 这里$aliases就是上面传进来的$aliases[],即config/app.php中'aliases'值
    return static::$instance = new static($aliases);
    }

    $aliases = array_merge(static::$instance->getAliases(), $aliases);
    
    static::$instance->setAliases($aliases);
    
    return static::$instance;

    }

    public function register()
    {
    if (! $this->registered) {
    $this->prependToLoaderStack();

        $this->registered = true;
    }

    }

    protected function prependToLoaderStack()
    {
    // 把AliasLoader::load()放入自动加载函数堆栈中,堆栈首的位置
    spl_autoload_register([$this, 'load'], true, true);
    }
    而loader()函数的源码:

    public function load($alias)
    {
    if (isset($this->aliases[$alias])) {
    // @link http://php.net/manual/en/function.class-al...
    return class_alias($this->aliases[$alias], $alias);
    }
    }
    就是通过class_alias()给外观类设置一个别名。所以Route::get()的调用过程就是,首先发现没有Route类,就去自动加载函数堆栈中通过AliasLoader::load()函数查找到Route是IlluminateSupportFacadesRoute的别名,那就调用IlluminateSupportFacadesRoute::get(),当然这里IlluminateSupportFacadesRoute没有get()静态方法,那就调用父类Facade的__callStatic()来找到名为router的服务,名为'router'的服务那就是早就注册到容器中的IlluminateRoutingRouter对象,所以最终就是调用IlluminateRoutingRouter::get()方法。这个过程主要使用了两个技术:一个是外观类的别名;一个是PHP的重载,可看这篇:Laravel学习笔记之PHP重载(overloading)。

  2. 注册Providers
    外观注册是注册config/app.php中的$aliases[ ]得值,Providers注册就是注册$providers[ ]的值。看下RegisterProviders::bootstrap()的源码:

    public function bootstrap(Application $app)
    {
    $app->registerConfiguredProviders();
    }

    // Application.php
    public function registerConfiguredProviders()
    {
    // 查找bootstrap/cache/services.php有没有这个缓存文件
    // services.php这个缓存文件存储的是service providers的数组值:
    // return [
    // 'providers' => [],
    // 'eager' => [],
    // 'deferred' => [],
    // 'when' => []
    // ];
    $manifestPath = $this->getCachedServicesPath();

    // 通过load()方法加载config/app.php中'$providers[ ]'数组值
    (new ProviderRepository($this, new Filesystem, $manifestPath))
                ->load($this->config['app.providers']);

    }
    看下load()的源码:

    public function load(array $providers)
    {
    // 查看bootstrap/cache/services.php有没有这个缓存文件
    // 第一次启动时是没有的
    $manifest = $this->loadManifest();
    // 开始没有这个缓存文件,那就把$providers[ ]里的值
    if ($this->shouldRecompile($manifest, $providers)) {
    // 然后根据$providers[ ]编译出services.php这个缓存文件
    $manifest = $this->compileManifest($providers);
    }

    foreach ($manifest['when'] as $provider => $events) {
        // 注册包含有事件监听的service provider
        // 包含有事件监听的service provider都要有when()函数返回
        $this->registerLoadEvents($provider, $events);
    }
    
    foreach ($manifest['eager'] as $provider) {
        // 把'eager'字段中service provider注册进容器中,
        // 即遍历每一个service provider,调用其中的register()方法
        // 向容器中注册具体的服务
        $this->app->register($this->createProvider($provider));
    }
    
    // 注册延迟的service provider,
    // deferred的service provider, 一是要设置$defer = true,二是要提供provides()方法返回绑定到容器中服务的名称
    $this->app->addDeferredServices($manifest['deferred']);

    }
    看下编译缓存文件compileManifest()方法的源码:

    protected function compileManifest($providers)
    {
    $manifest = $this->freshManifest($providers);

    foreach ($providers as $provider) {
        $instance = $this->createProvider($provider);
        // 根据每一个service provider的defer属性看是否是延迟加载的service provider
        if ($instance->isDeferred()) {
            // 延迟加载的,根据provides()方法提供的服务名称,写入到'deferred'字段里
            // 所以延迟加载的service provider都要提供provides()方法
            foreach ($instance->provides() as $service) {
                $manifest['deferred'][$service] = $provider;
            }
            // 使用when()函数提供的值注册下含有事件的service provider,
            $manifest['when'][$provider] = $instance->when();
        } else {
            // 不是延迟加载的,就放在'eager'字段里,用$this->app->register()来注册延迟加载的service provider
            $manifest['eager'][] = $provider;
        }
    }
    // 最后写入到services.php缓存文件中
    return $this->writeManifest($manifest);

    }

    protected function freshManifest(array $providers)
    {
    return ['providers' => $providers, 'eager' => [], 'deferred' => []];
    }
    总之,注册providers就是把config/app.php中$providers[ ]定义的所有service provider中,把不是defer的service provider中绑定的服务启动起来,是defer的service provider等到需要里面绑定的服务时再执行绑定。

  3. 启动Providers
    最后一步,就是启动程序了,看下BootProviders::bootstrap()源码:

    public function bootstrap(Application $app)
    {
    $app->boot();
    }

    public function boot()
    {
    // 如果程序已启动则返回,显然还没启动,还在booting状态中
    if ($this->booted) {
    return;
    }
    // 执行之前Application实例化的时候在$bootingCallbacks[]注册的回调
    $this->fireAppCallbacks($this->bootingCallbacks);
    // 之前凡是用Application::register()方法的service provider都写入到了$serviceProviders[]中
    // 这里依次执行每一个service provider里的boot()方法,如果存在的话
    array_walk($this->serviceProviders, function ($p) {
    $this->bootProvider($p);
    });

    $this->booted = true;
    // 执行之前Application实例化的时候在$bootedCallbacks[]注册的回调
    $this->fireAppCallbacks($this->bootedCallbacks);

    }

    protected function bootProvider(ServiceProvider $provider)
    {
    if (method_exists($provider, 'boot')) {
    return $this->call([$provider, 'boot']);
    }
    }
    从以上源码中知道,第(7)步和第(6)步类似:第(6)是依次执行每一个不是defer的service provider的register()方法;第(7)步是依次执行每一个不是defer的service provider的boot()方法,如果存在的话。所以官网上service provider章节说了这么一句The Boot Method:

This method is called after all other service providers have been registered, meaning you have access to all other services that have been registered by the framework
这里就明白了为啥这句话的含义了。

之前聊过Application::register()方法时里面有个检测程序是否已经启动的代码:

public function register($provider, $options = [], $force = false)
{
...

    if ($this->booted) {
        $this->bootProvider($provider);
    }

    return $provider;
}

刚刚开始实例化Application的时候还没有启动,在执行所有非defer的service provider boot()方法后程序就启动了:$this->booted = true;。

OK, 程序启动所做的准备工作就聊完了,过程不复杂,只需一步步拆解就能基本清楚Laravel启动时做了哪些具体工作。

总结:本文主要学习了Laravel启动时做的七步准备工作:1. 环境检测 DetectEnvironment; 2. 配置加载 LoadConfiguratio; 3. 日志配置 ConfigureLogging; 4. 异常处理 HandleException;5. 注册Facades RegisterFacades;6. 注册Providers RegisterProviders;7. 启动Providers BootProviders。下次有好的技术再分享,到时见。

欢迎关注Laravel-China。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1
sushengbuhuo

好长,格式乱了

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!