成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Laravel學(xué)習(xí)筆記之bootstrap源碼解析

xiaoxiaozi / 1238人閱讀

摘要:總結(jié)本文主要學(xué)習(xí)了啟動(dòng)時(shí)做的七步準(zhǔn)備工作環(huán)境檢測(cè)配置加載日志配置異常處理注冊(cè)注冊(cè)啟動(dòng)。

說(shuō)明:Laravel在把Request通過(guò)管道Pipeline送入中間件Middleware和路由Router之前,還做了程序的啟動(dòng)Bootstrap工作,本文主要學(xué)習(xí)相關(guān)源碼,看看Laravel啟動(dòng)程序做了哪些具體工作,并將個(gè)人的研究心得分享出來(lái),希望對(duì)別人有所幫助。Laravel在入口index.php時(shí)先加載Composer加載器:Laravel學(xué)習(xí)筆記之Composer自動(dòng)加載,然后進(jìn)行Application的實(shí)例化:Laravel學(xué)習(xí)筆記之IoC Container實(shí)例化源碼解析,得到實(shí)例化后的Application對(duì)象再?gòu)娜萜髦薪馕龀鯧ernel服務(wù),然后進(jìn)行Request實(shí)例化(Request實(shí)例化下次再聊),然后進(jìn)行Bootstrap操作啟動(dòng)程序,再通過(guò)Pipeline送到Middleware:Laravel學(xué)習(xí)筆記之Middleware源碼解析,然后經(jīng)過(guò)路由映射找到對(duì)該請(qǐng)求的操作action(以后再聊),生成Response對(duì)象經(jīng)過(guò)Kernel的send()發(fā)送給Client。本文主要聊下程序的啟動(dòng)操作,主要做了哪些準(zhǔn)備工作。

開(kāi)發(fā)環(huán)境:Laravel5.3 + PHP7 + OS X 10.11

在Laravel學(xué)習(xí)筆記之Middleware源碼解析聊過(guò),Kernel中的sendRequestThroughRouter()處理Request,并把Request交給Pipeline送到Middleware和Router中,看源碼:

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

        Facade::clearResolvedInstance("request");

        /* 依次執(zhí)行$bootstrappers中每一個(gè)bootstrapper的bootstrap()函數(shù),做了幾件準(zhǔn)備事情:
        1. 環(huán)境檢測(cè) DetectEnvironment
        2. 配置加載 LoadConfiguration
        3. 日志配置 ConfigureLogging
        4. 異常處理 HandleException
        5. 注冊(cè)Facades RegisterFacades
        6. 注冊(cè)Providers RegisterProviders
        7. 啟動(dòng)Providers BootProviders
         protected $bootstrappers = [
            "IlluminateFoundationBootstrapDetectEnvironment",
            "IlluminateFoundationBootstrapLoadConfiguration",
            "IlluminateFoundationBootstrapConfigureLogging",
            "IlluminateFoundationBootstrapHandleExceptions",
            "IlluminateFoundationBootstrapRegisterFacades",
            "IlluminateFoundationBootstrapRegisterProviders",
            "IlluminateFoundationBootstrapBootProviders",
        ];*/
        $this->bootstrap();

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

在Request被Pipeline送到Middleware前還有一步操作bootstrap()操作,這步操作就是啟動(dòng)程序,看下IlluminateFoundationHttpKernel中的bootstrap()源碼:

    protected $hasBeenBootstrapped = false;
    
    ...
    
    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap()
    {
        // 檢查程序是否已經(jīng)啟動(dòng)
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
    
    public function hasBeenBootstrapped()
    {
        return $this->hasBeenBootstrapped;
    }
    
    protected function bootstrappers()
    {
        return $this->bootstrappers;
    }
    
    protected $bootstrappers = [
        "IlluminateFoundationBootstrapDetectEnvironment",
        "IlluminateFoundationBootstrapLoadConfiguration",
        "IlluminateFoundationBootstrapConfigureLogging",
        "IlluminateFoundationBootstrapHandleExceptions",
        "IlluminateFoundationBootstrapRegisterFacades",
        "IlluminateFoundationBootstrapRegisterProviders",
        "IlluminateFoundationBootstrapBootProviders",
    ];

從以上源碼可知道,程序?qū)?huì)依次bootstrapWith()數(shù)組$bootstrappers中各個(gè)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]);
        }
    }

首先觸發(fā)"bootstrapping: ".$bootstrapper事件,告知將要啟動(dòng)該bootstrapper,然后從容器中make($bootstrapper)出該$bootstrapper,并執(zhí)行該$bootstrapper中的bootstrap()方法,最后在觸發(fā)事件:"bootstrapped: ".$bootstrapper,告知該$bootstrapper已經(jīng)啟動(dòng)OK了。啟動(dòng)的bootstrappers就是數(shù)組$bootstrappers中的7個(gè)bootstrapper,看下程序做了哪些啟動(dòng)工作。

1. 環(huán)境檢測(cè)

查看IlluminateFoundationBootstrapDetectEnvironment中的bootstrap()源碼:

    public function bootstrap(Application $app)
    {
        // 查看bootstrap/cache/config.php緩存文件是否存在
        // php artisan config:cache來(lái)生成配置緩存文件,就是把config/下的所有文件放在一個(gè)緩存文件內(nèi),提高性能
        // 這里假設(shè)沒(méi)有緩存配置文件
        if (! $app->configurationIsCached()) {
            $this->checkForSpecificEnvironmentFile($app);

            try {
                $env = $_ENV; // 調(diào)試添加的,此時(shí)為空
                // 這里把.env文件值取出存入$_ENV內(nèi)
                (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
                // 這里$_ENV數(shù)組有值了
                $env = $_ENV;
            } catch (InvalidPathException $e) {
                //
            }
        }
    }
    
    protected function checkForSpecificEnvironmentFile($app)
    {
        // 讀取$_ENV全局變量中"APP_ENV"值,此時(shí)是空
        if (! env("APP_ENV")) {
            return;
        }

        $file = $app->environmentFile().".".env("APP_ENV"); // .env.local

        if (file_exists($app->environmentPath()."/".$file)) {
            $app->loadEnvironmentFrom($file);
        }
    }

環(huán)境監(jiān)測(cè)核心就是把.env文件內(nèi)值存入到$_ENV全局變量中DotenvDotenv::load()函數(shù)實(shí)現(xiàn)了這個(gè)功能,具體不詳述了。可以通過(guò)Xdebug調(diào)試查看:

2. 配置加載

配置加載就是讀取config/文件夾下的所有配置值,然后存入IlluminateConfigRepository對(duì)象中,而環(huán)境檢測(cè)是讀取.env文件存入$_ENV全局變量中,加載環(huán)境配置主要是使用SymfonyComponentFinderFinder這個(gè)組件進(jìn)行文件查找,看下LoadConfiguration::bootstrap()的源碼:

    public function bootstrap(Application $app)
    {        
        $items = [];
        // 查看config有沒(méi)有緩存文件,緩存文件是在bootstrap/cache/config.php
        // 通過(guò)php artisan config:cache命令來(lái)生成緩存文件,把config/下的所有配置文件打包成一個(gè)文件,提高程序執(zhí)行速度
        // 這里假設(shè)沒(méi)有緩存文件
        if (file_exists($cached = $app->getCachedConfigPath())) {
            $items = require $cached;

            $loadedFromCache = true;
        }
        // 綁定服務(wù)"config",服務(wù)是IlluminateConfigRepository對(duì)象
        $app->instance("config", $config = new Repository($items));

        if (! isset($loadedFromCache)) {
            // 加載config/*.php所有配置文件,把所有配置存入Repository對(duì)象中
            $this->loadConfigurationFiles($app, $config);
        }
        // 檢查"APP_ENV"環(huán)境設(shè)置,一般也就是"dev","stg","prd"三個(gè)環(huán)境,即"development", "staging", "production"
        $app->detectEnvironment(function () use ($config) {
            return $config->get("app.env", "production");
        });

        // 設(shè)置時(shí)區(qū),$config["app.timezone"]就是調(diào)用Repository::get("app.timezone"),因?yàn)镽epository實(shí)現(xiàn)了ArrayAccess Interface,
        // "."語(yǔ)法讀取是Arr::get()實(shí)現(xiàn)的,很好用的一個(gè)方法
        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對(duì)象中,以"key => value"存入到$items[]屬性中
            $repository->set($key, require $path);
        }
    }
    
    protected function getConfigurationFiles(Application $app)
    {
        $files = [];
        // 就是"config/"這個(gè)路徑
        $configPath = realpath($app->configPath());
        // Finder鏈?zhǔn)浇涌谧x取config/*.php所有文件,獲取所有文件名稱(chēng),然后依次遍歷
        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;
    }

可以通過(guò)Xdebug調(diào)試知道$files的返回值是這樣的數(shù)組:

    $files = [
        "app"          => "/vagrant/config/app.php", //文件的絕對(duì)路徑
        "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",
    ];

然后通過(guò)Application的detectEnvironment()方法把app.env的值即app.phpenv的值取出來(lái)存入Application對(duì)象的$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);
    }

所以屬性檢查的時(shí)候就存到了$env屬性的值了,開(kāi)發(fā)代碼中就可以App::environment()得到這個(gè)$env屬性然后進(jìn)行一些操作,可以看下environment()的源碼,該方法有兩個(gè)feature:如果不傳入值則讀取$env值;如果傳入值則判斷該值是否與$env一樣。這里如果對(duì)Application沒(méi)有$env成員屬性定義有疑惑,是因?yàn)镻HP可以后期添加屬性,如:

class ClassField
{
    
}

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

/* output:
Laravel
3. 日志配置

Laravel主要利用Monolog日志庫(kù)來(lái)做日志處理,IlluminateLogWriter相當(dāng)于Monolog Bridge,把Monolog庫(kù)接入到Laravel中??聪翪onfigureLogging::bootstrap()源碼:

    public function bootstrap(Application $app)
    {
        // 注冊(cè)"log"服務(wù)
        $log = $this->registerLogger($app);

        // 檢查是否已經(jīng)注冊(cè)了Monolog
        // 這里假設(shè)開(kāi)始沒(méi)有注冊(cè)
        if ($app->hasMonologConfigurator()) {
            call_user_func(
                $app->getMonologConfigurator(), $log->getMonolog()
            );
        } else {
            // 
            $this->configureHandlers($app, $log);
        }
    }
    
    protected function registerLogger(Application $app)
    {
        // 向容器中綁定"log"服務(wù),即Writer對(duì)象
        $app->instance("log", $log = new Writer(
            new Monolog($app->environment()), $app["events"])
        );

        return $log;
    }
    

Laravel的Log模塊中已經(jīng)內(nèi)置了幾個(gè)類(lèi)型的LogHandler:Single,Daily,Syslog,Errorlog.根據(jù)config/app.php文件中"log"的配置選擇其中一個(gè)handler,看下configureHandlers()源碼:

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

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

configureHandlers()這方法也是一個(gè)技巧,找到方法名然后調(diào)用,這在Laravel中經(jīng)常這么用,如Filesystem那一模塊中有"create".ucfirst(xxx)."Driver"這樣的源碼,是個(gè)不錯(cuò)的設(shè)計(jì)。這里看下configureDailyHandler()的源碼,其余三個(gè)也類(lèi)似:

    protected function configureDailyHandler(Application $app, Writer $log)
    {
        // 解析"config"服務(wù)
        $config = $app->make("config");
        // 默認(rèn)沒(méi)有設(shè)置,就為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()來(lái)往laravel.log里打印log值,當(dāng)然在應(yīng)用程序中經(jīng)常Log::info(),Log::warning(),Log::debug()來(lái)打印變量值,即Writer類(lèi)中定義的的方法。Log的facade是IlluminateSupportFacadesLog:

class Log extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return "log";
    }
}

而"log"服務(wù)在上文中bootstrap()源碼第一步registerLogger()就注冊(cè)了。當(dāng)然,至于使用Facade來(lái)從容器中獲取服務(wù)也聊過(guò),也不復(fù)雜,看下IlluminateSupportFacadesFacade的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]; // 實(shí)際上就是使用$app["log"]來(lái)獲取服務(wù)
    }
4. 異常處理

異常處理是十分重要的,Laravel中異常處理類(lèi)AppExceptionHandler中有一個(gè)方法report(),該方法可以用來(lái)向第三方服務(wù)(如Sentry)發(fā)送程序異常堆棧(以后在一起聊聊這個(gè)Sentry,效率神器),如Production Code線(xiàn)上環(huán)境報(bào)出個(gè)異常,可以很清楚整個(gè)堆棧,出錯(cuò)在哪一行:

OK,看下異常設(shè)置的啟動(dòng)源代碼,HandleExceptions::bootstrap()的源碼:

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

        error_reporting(-1);
        // 出現(xiàn)錯(cuò)誤,拋出throw new ErrorException
        set_error_handler([$this, "handleError"]);
        // 處理異常,使用report()方法來(lái)報(bào)告,可集成第三方服務(wù)Sentry來(lái)作為異常報(bào)告處理器ExceptionReportHandler
        set_exception_handler([$this, "handleException"]);

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

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

這里重點(diǎn)看下handleException()的源碼:

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

        if ($this->app->runningInConsole()) {
            $this->renderForConsole($e);
        } else {
            $this->renderHttpResponse($e);
        }
    }
    
    protected function getExceptionHandler()
    {
        // 解析出AppExceptionsHandler對(duì)象
        // 在boostrap/app.php中做過(guò)singleton()綁定
        return $this->app->make("IlluminateContractsDebugExceptionHandler");
    }
    
    protected function renderHttpResponse(Exception $e)
    {
        // 使用(new AppExceptionsHandler($container))->render(Request $request, $e)
        $this->getExceptionHandler()->render($this->app["request"], $e)->send();
    }

從源碼中知道,重點(diǎn)是使用AppExceptionsHandler的report()方法報(bào)告異常情況,如向Sentry報(bào)告異常堆棧和其他有用信息;AppExceptionsHandler的render()方法通過(guò)Request發(fā)送到瀏覽器。關(guān)于使用第三方服務(wù)Sentry來(lái)做異常報(bào)告以后詳聊,我司每天都在用這樣的效率神器,很好用,值得推薦下。

5. 注冊(cè)Facades

在路由文件中經(jīng)常會(huì)出現(xiàn)Route::get()這樣的寫(xiě)法,但實(shí)際上并沒(méi)有Route類(lèi),Route只是IlluminateSupportFacadesRoute::class外觀類(lèi)的別名,這樣取個(gè)別名只是為了簡(jiǎn)化作用,使用的是PHP內(nèi)置函數(shù)class_alias(string $class, string $alias)來(lái)給類(lèi)設(shè)置別名。看下RegisterFacades::bootstrap()的源碼:

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

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance($app->make("config")->get("app.aliases", []))->register();
    }
    
    // IlluminateSupportFacadesFacade
    public static function clearResolvedInstances()
    {
        static::$resolvedInstance = [];
    }
    
    // IlluminateSupportFacadesFacade
    public static function setFacadeApplication($app)
    {
        static::$app = $app;
    }

$app->make("config")->get("app.aliases", [])是從config/app.php中讀取"aliases"的值,然后注冊(cè)外觀類(lèi)的別名,注冊(cè)的外觀類(lèi)有:

    "aliases" => [

        "App" => IlluminateSupportFacadesApp::class,
        "Artisan" => IlluminateSupportFacadesArtisan::class,
        "Auth" => IlluminateSupportFacadesAuth::class,
        "Blade" => IlluminateSupportFacadesBlade::class,
        "Cache" => IlluminateSupportFacadesCache::class,
        "Config" => IlluminateSupportFacadesConfig::class,
        "Cookie" => IlluminateSupportFacadesCookie::class,
        "Crypt" => IlluminateSupportFacadesCrypt::class,
        "DB" => IlluminateSupportFacadesDB::class,
        "Eloquent" => IlluminateDatabaseEloquentModel::class,
        "Event" => IlluminateSupportFacadesEvent::class,
        "File" => IlluminateSupportFacadesFile::class,
        "Gate" => IlluminateSupportFacadesGate::class,
        "Hash" => IlluminateSupportFacadesHash::class,
        "Lang" => IlluminateSupportFacadesLang::class,
        "Log" => IlluminateSupportFacadesLog::class,
        "Mail" => IlluminateSupportFacadesMail::class,
        "Notification" => IlluminateSupportFacadesNotification::class,
        "Password" => IlluminateSupportFacadesPassword::class,
        "Queue" => IlluminateSupportFacadesQueue::class,
        "Redirect" => IlluminateSupportFacadesRedirect::class,
        "Redis" => IlluminateSupportFacadesRedis::class,
        "Request" => IlluminateSupportFacadesRequest::class,
        "Response" => IlluminateSupportFacadesResponse::class,
        "Route" => IlluminateSupportFacadesRoute::class,
        "Schema" => IlluminateSupportFacadesSchema::class,
        "Session" => IlluminateSupportFacadesSession::class,
        "Storage" => IlluminateSupportFacadesStorage::class,
        "URL" => IlluminateSupportFacadesURL::class,
        "Validator" => IlluminateSupportFacadesValidator::class,
        "View" => IlluminateSupportFacadesView::class,

    ],

從以上外觀別名數(shù)組中知道RouteIlluminateSupportFacadesRoute::class的別名,所以Route::get()實(shí)際上就是IlluminateSupportFacadesRoute::get(),看下AliasLoader類(lèi)的getInstance()和register()方法源碼:

    public static function getInstance(array $aliases = [])
    {
        if (is_null(static::$instance)) {
            // 這里$aliases就是上面?zhèn)鬟M(jìn)來(lái)的$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()放入自動(dòng)加載函數(shù)堆棧中,堆棧首的位置
        spl_autoload_register([$this, "load"], true, true);
    }

而loader()函數(shù)的源碼:

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

就是通過(guò)class_alias()給外觀類(lèi)設(shè)置一個(gè)別名。所以Route::get()的調(diào)用過(guò)程就是,首先發(fā)現(xiàn)沒(méi)有Route類(lèi),就去自動(dòng)加載函數(shù)堆棧中通過(guò)AliasLoader::load()函數(shù)查找到RouteIlluminateSupportFacadesRoute的別名,那就調(diào)用IlluminateSupportFacadesRoute::get(),當(dāng)然這里IlluminateSupportFacadesRoute沒(méi)有get()靜態(tài)方法,那就調(diào)用父類(lèi)Facade__callStatic()來(lái)找到名為router的服務(wù),名為"router"的服務(wù)那就是早就注冊(cè)到容器中的IlluminateRoutingRouter對(duì)象,所以最終就是調(diào)用IlluminateRoutingRouter::get()方法。這個(gè)過(guò)程主要使用了兩個(gè)技術(shù):一個(gè)是外觀類(lèi)的別名;一個(gè)是PHP的重載,可看這篇:Laravel學(xué)習(xí)筆記之PHP重載(overloading)。

6. 注冊(cè)Providers

外觀注冊(cè)是注冊(cè)config/app.php中的$aliases[ ]得值,Providers注冊(cè)就是注冊(cè)$providers[ ]的值??聪翿egisterProviders::bootstrap()的源碼:

    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
    
    // Application.php
    public function registerConfiguredProviders()
    {
        // 查找bootstrap/cache/services.php有沒(méi)有這個(gè)緩存文件
        // services.php這個(gè)緩存文件存儲(chǔ)的是service providers的數(shù)組值:
        // return [
        //    "providers" => [],
        //    "eager"     => [],
        //    "deferred"  => [],
        //    "when"      => []
        // ];
        $manifestPath = $this->getCachedServicesPath();
        
        // 通過(guò)load()方法加載config/app.php中"$providers[ ]"數(shù)組值
        (new ProviderRepository($this, new Filesystem, $manifestPath))
                    ->load($this->config["app.providers"]);
    }

看下load()的源碼:

    public function load(array $providers)
    {
        // 查看bootstrap/cache/services.php有沒(méi)有這個(gè)緩存文件
        // 第一次啟動(dòng)時(shí)是沒(méi)有的
        $manifest = $this->loadManifest();
        // 開(kāi)始沒(méi)有這個(gè)緩存文件,那就把$providers[ ]里的值
        if ($this->shouldRecompile($manifest, $providers)) {
            // 然后根據(jù)$providers[ ]編譯出services.php這個(gè)緩存文件
            $manifest = $this->compileManifest($providers);
        }

        foreach ($manifest["when"] as $provider => $events) {
            // 注冊(cè)包含有事件監(jiān)聽(tīng)的service provider
            // 包含有事件監(jiān)聽(tīng)的service provider都要有when()函數(shù)返回
            $this->registerLoadEvents($provider, $events);
        }

        foreach ($manifest["eager"] as $provider) {
            // 把"eager"字段中service provider注冊(cè)進(jìn)容器中,
            // 即遍歷每一個(gè)service provider,調(diào)用其中的register()方法
            // 向容器中注冊(cè)具體的服務(wù)
            $this->app->register($this->createProvider($provider));
        }

        // 注冊(cè)延遲的service provider,
        // deferred的service provider, 一是要設(shè)置$defer = true,二是要提供provides()方法返回綁定到容器中服務(wù)的名稱(chēng)
        $this->app->addDeferredServices($manifest["deferred"]);
    }

看下編譯緩存文件compileManifest()方法的源碼:

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

        foreach ($providers as $provider) {
            $instance = $this->createProvider($provider);
            // 根據(jù)每一個(gè)service provider的defer屬性看是否是延遲加載的service provider
            if ($instance->isDeferred()) {
                // 延遲加載的,根據(jù)provides()方法提供的服務(wù)名稱(chēng),寫(xiě)入到"deferred"字段里
                // 所以延遲加載的service provider都要提供provides()方法
                foreach ($instance->provides() as $service) {
                    $manifest["deferred"][$service] = $provider;
                }
                // 使用when()函數(shù)提供的值注冊(cè)下含有事件的service provider,
                $manifest["when"][$provider] = $instance->when();
            } else {
                // 不是延遲加載的,就放在"eager"字段里,用$this->app->register()來(lái)注冊(cè)延遲加載的service provider
                $manifest["eager"][] = $provider;
            }
        }
        // 最后寫(xiě)入到services.php緩存文件中
        return $this->writeManifest($manifest);
    }
    
    protected function freshManifest(array $providers)
    {
        return ["providers" => $providers, "eager" => [], "deferred" => []];
    }

總之,注冊(cè)providers就是把config/app.php中$providers[ ]定義的所有service provider中,把不是defer的service provider中綁定的服務(wù)啟動(dòng)起來(lái),是defer的service provider等到需要里面綁定的服務(wù)時(shí)再執(zhí)行綁定。

7. 啟動(dòng)Providers

最后一步,就是啟動(dòng)程序了,看下BootProviders::bootstrap()源碼:

    public function bootstrap(Application $app)
    {
        $app->boot();
    }
    
    public function boot()
    {
        // 如果程序已啟動(dòng)則返回,顯然還沒(méi)啟動(dòng),還在booting狀態(tài)中
        if ($this->booted) {
            return;
        }
        // 執(zhí)行之前Application實(shí)例化的時(shí)候在$bootingCallbacks[]注冊(cè)的回調(diào)
        $this->fireAppCallbacks($this->bootingCallbacks);
        // 之前凡是用Application::register()方法的service provider都寫(xiě)入到了$serviceProviders[]中
        // 這里依次執(zhí)行每一個(gè)service provider里的boot()方法,如果存在的話(huà)
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });

        $this->booted = true;
        // 執(zhí)行之前Application實(shí)例化的時(shí)候在$bootedCallbacks[]注冊(cè)的回調(diào)
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
    
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, "boot")) {
            return $this->call([$provider, "boot"]);
        }
    }

從以上源碼中知道,第(7)步和第(6)步類(lèi)似:第(6)是依次執(zhí)行每一個(gè)不是defer的service provider的register()方法;第(7)步是依次執(zhí)行每一個(gè)不是defer的service provider的boot()方法,如果存在的話(huà)。所以官網(wǎng)上service provider章節(jié)說(shuō)了這么一句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

這里就明白了為啥這句話(huà)的含義了。

之前聊過(guò)Application::register()方法時(shí)里面有個(gè)檢測(cè)程序是否已經(jīng)啟動(dòng)的代碼:

public function register($provider, $options = [], $force = false)
    {
        ...
        
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

剛剛開(kāi)始實(shí)例化Application的時(shí)候還沒(méi)有啟動(dòng),在執(zhí)行所有非defer的service provider boot()方法后程序就啟動(dòng)了:$this->booted = true;。

OK, 程序啟動(dòng)所做的準(zhǔn)備工作就聊完了,過(guò)程不復(fù)雜,只需一步步拆解就能基本清楚Laravel啟動(dòng)時(shí)做了哪些具體工作。

總結(jié):本文主要學(xué)習(xí)了Laravel啟動(dòng)時(shí)做的七步準(zhǔn)備工作:1. 環(huán)境檢測(cè) DetectEnvironment; 2. 配置加載 LoadConfiguratio; 3. 日志配置 ConfigureLogging; 4. 異常處理 HandleException;5. 注冊(cè)Facades RegisterFacades;6. 注冊(cè)Providers RegisterProviders;7. 啟動(dòng)Providers BootProviders。下次有好的技術(shù)再分享,到時(shí)見(jiàn)。

歡迎關(guān)注Laravel-China。

RightCapital招聘Laravel DevOps

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/30480.html

相關(guān)文章

  • Laravel學(xué)習(xí)筆記Middleware源碼解析

    摘要:學(xué)習(xí)筆記之已經(jīng)聊過(guò)使用了來(lái)設(shè)計(jì),看源碼發(fā)現(xiàn)其巧妙用了和的一些數(shù)組函數(shù)來(lái)設(shè)計(jì)。開(kāi)發(fā)環(huán)境內(nèi)置函數(shù)和看源碼之前,先看下這幾個(gè)內(nèi)置函數(shù)的使用。學(xué)習(xí)筆記之實(shí)例化源碼解析已經(jīng)聊過(guò)的實(shí)例化,得到中的變量,即的實(shí)例化對(duì)象。后面再學(xué)習(xí)下的源碼,到時(shí)見(jiàn)。 說(shuō)明:本文主要學(xué)習(xí)Laravel的Middleware的源碼設(shè)計(jì)思想,并將學(xué)習(xí)心得分享出來(lái),希望對(duì)別人有所幫助。Laravel學(xué)習(xí)筆記之Decorato...

    _Dreams 評(píng)論0 收藏0
  • Laravel學(xué)習(xí)筆記IoC Container實(shí)例化源碼解析

    摘要:說(shuō)明本文主要學(xué)習(xí)容器的實(shí)例化過(guò)程,主要包括等四個(gè)過(guò)程??聪碌脑创a如果是數(shù)組,抽取別名并且注冊(cè)到中,上文已經(jīng)討論實(shí)際上就是的。 說(shuō)明:本文主要學(xué)習(xí)Laravel容器的實(shí)例化過(guò)程,主要包括Register Base Bindings, Register Base Service Providers , Register Core Container Aliases and Set the ...

    ningwang 評(píng)論0 收藏0
  • Laravel學(xué)習(xí)筆記Errors Tracking神器——Sentry

    摘要:中異常處理類(lèi)主要包含兩個(gè)方法和,其中就是主要用來(lái)向第三方發(fā)送異常報(bào)告,這里選擇向這個(gè)神器發(fā)送異常報(bào)告,并使用通知開(kāi)發(fā)人員。通過(guò)也能發(fā)現(xiàn)的執(zhí)行流程。 說(shuō)明:Laravel學(xué)習(xí)筆記之bootstrap源碼解析中聊異常處理時(shí)提到過(guò)Sentry這個(gè)神器,并打算以后聊聊這款神器,本文主要就介紹這款Errors Tracking神器Sentry,Sentry官網(wǎng)有一句話(huà)個(gè)人覺(jué)得帥呆了: Stop ...

    xiguadada 評(píng)論0 收藏0
  • Laravel學(xué)習(xí)筆記Container源碼解析

    摘要:實(shí)際上的綁定主要有三種方式且只是一種的,這些已經(jīng)在學(xué)習(xí)筆記之實(shí)例化源碼解析聊過(guò),其實(shí)現(xiàn)方法并不復(fù)雜。從以上源碼發(fā)現(xiàn)的反射是個(gè)很好用的技術(shù),這里給出個(gè),看下能干些啥打印結(jié)果太長(zhǎng)了,就不粘貼了。 說(shuō)明:本文主要學(xué)習(xí)Laravel中Container的源碼,主要學(xué)習(xí)Container的綁定和解析過(guò)程,和解析過(guò)程中的依賴(lài)解決。分享自己的研究心得,希望對(duì)別人有所幫助。實(shí)際上Container的綁...

    huayeluoliuhen 評(píng)論0 收藏0
  • Laravel學(xué)習(xí)筆記Session源碼解析(中)

    摘要:說(shuō)明在上篇中學(xué)習(xí)了的啟動(dòng)過(guò)程,主要分為兩步,一是的實(shí)例化,即的實(shí)例化二是從存儲(chǔ)介質(zhì)中讀取的數(shù)據(jù)。第二步就是操作,包括對(duì)數(shù)據(jù)的增刪改查操作,本文也主要聊下相關(guān)操作源碼。下篇再學(xué)習(xí)下關(guān)閉,到時(shí)見(jiàn)。 說(shuō)明:在上篇中學(xué)習(xí)了session的啟動(dòng)過(guò)程,主要分為兩步,一是session的實(shí)例化,即IlluminateSessionStore的實(shí)例化;二是從session存儲(chǔ)介質(zhì)redis中讀取id ...

    longshengwang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<