摘要:總結(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)試查看:
配置加載就是讀取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.php中env的值取出來(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: Laravel3. 日志配置
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ù)組中知道Route是IlluminateSupportFacadesRoute::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ù)查找到Route是IlluminateSupportFacadesRoute的別名,那就調(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
摘要:學(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...
摘要:說(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 ...
摘要:中異常處理類(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 ...
摘要:實(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的綁...
摘要:說(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 ...
閱讀 3228·2021-11-08 13:21
閱讀 1209·2021-08-12 13:28
閱讀 1419·2019-08-30 14:23
閱讀 1938·2019-08-30 11:09
閱讀 852·2019-08-29 13:22
閱讀 2699·2019-08-29 13:12
閱讀 2560·2019-08-26 17:04
閱讀 2270·2019-08-26 13:22