23

说明: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.phpenv的值取出来存入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

3. 日志配置

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']来获取服务
    }

4. 异常处理

异常处理是十分重要的,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来做异常报告以后详聊,我司每天都在用这样的效率神器,很好用,值得推荐下。

5. 注册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,

    ],

从以上外观别名数组中知道RouteIlluminateSupportFacadesRoute::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-alias.php
            return class_alias($this->aliases[$alias], $alias);
        }
    }

就是通过class_alias()给外观类设置一个别名。所以Route::get()的调用过程就是,首先发现没有Route类,就去自动加载函数堆栈中通过AliasLoader::load()函数查找到RouteIlluminateSupportFacadesRoute的别名,那就调用IlluminateSupportFacadesRoute::get(),当然这里IlluminateSupportFacadesRoute没有get()静态方法,那就调用父类Facade__callStatic()来找到名为router的服务,名为'router'的服务那就是早就注册到容器中的IlluminateRoutingRouter对象,所以最终就是调用IlluminateRoutingRouter::get()方法。这个过程主要使用了两个技术:一个是外观类的别名;一个是PHP的重载,可看这篇:Laravel学习笔记之PHP重载(overloading)

6. 注册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等到需要里面绑定的服务时再执行绑定。

7. 启动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

RightCapital招聘Laravel DevOps


lx1036
3.1k 声望923 粉丝

为五斗米折腰