Laravel5.3之bootstrap源碼解析

說明:Laravel在把Request經過管道Pipeline送入中間件Middleware和路由Router以前,還作了程序的啓動Bootstrap工做,本文主要學習相關源碼,看看Laravel啓動程序作了哪些具體工做,並將我的的研究心得分享出來,但願對別人有所幫助。Laravel在入口index.php時先加載Composer加載器:Laravel5.2之Composer自動加載,而後進行Application的實例化:Laravel5.3之IoC Container實例化源碼解析,獲得實例化後的Application對象再從容器中解析出Kernel服務,而後進行Request實例化(Request實例化下次再聊),而後進行Bootstrap操做啓動程序,再經過Pipeline送到Middleware:Laravel5.3之Middleware源碼解析,而後通過路由映射找到對該請求的操做action(之後再聊),生成Response對象通過Kernel的send()發送給Client。本文主要聊下程序的啓動操做,主要作了哪些準備工做。php

開發環境:Laravel5.3 + PHP7 + OS X 10.11html

在Laravel5.3之Middleware源碼解析聊過,Kernel中的sendRequestThroughRouter()處理Request,並把Request交給Pipeline送到Middleware和Router中,看源碼:laravel

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()源碼: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調試查看:
session

2. 配置加載

配置加載就是讀取config/文件夾下的全部配置值,而後存入\Illuminate\Config\Repository對象中,而環境檢測是讀取.env文件存入$_ENV全局變量中,加載環境配置主要是使用\Symfony\Component\Finder\Finder這個組件進行文件查找,看下LoadConfiguration::bootstrap()的源碼:app

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文件,看下源碼:ide

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的重載,可看這篇:Laravel5.2之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。下次有好的技術再分享,到時見。

相關文章
相關標籤/搜索