摘要:調用了的可以看出,所有服務提供器都在配置文件文件的數組中。啟動的啟動由類負責引導應用的屬性中記錄的所有服務提供器,就是依次調用這些服務提供器的方法,引導完成后就代表應用正式啟動了,可以開始處理請求了。
服務提供器是所有 Laravel 應用程序引導中心。你的應用程序自定義的服務、第三方資源包提供的服務以及 Laravel 的所有核心服務都是通過服務提供器進行注冊(register)和引導(boot)的。
拿一個Laravel框架自帶的服務提供器來舉例子
class BroadcastServiceProvider extends ServiceProvider { protected $defer = true; public function register() { $this->app->singleton(BroadcastManager::class, function ($app) { return new BroadcastManager($app); }); $this->app->singleton(BroadcasterContract::class, function ($app) { return $app->make(BroadcastManager::class)->connection(); }); //將BroadcastingFactory::class設置為BroadcastManager::class的別名 $this->app->alias( BroadcastManager::class, BroadcastingFactory::class ); } public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }
在服務提供器BroadcastServiceProvider的register中, 為BroadcastingFactory的類名綁定了類實現BroadcastManager,這樣就能通過服務容器來make出通過BroadcastingFactory::class綁定的服務BroadcastManger對象供應用程序使用了。
本文主要時來梳理一下laravel是如何注冊、和初始化這些服務的,關于如何編寫自己的服務提供器,可以參考官方文檔
BootStrap首先laravel注冊和引導應用需要的服務是發生在尋找路由處理客戶端請求之前的Bootstrap階段的,在框架的入口文件里我們可以看到,框架在實例化了Application對象后從服務容器中解析出了HTTP Kernel對象
$kernel = $app->make(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::capture() );
在Kernel處理請求時會先讓請求通過中間件然后在發送請求給路由對應的控制器方法, 在這之前有一個BootStrap階段來引導啟動Laravel應用程序,如下面代碼所示。
public function handle($request) { ...... $response = $this->sendRequestThroughRouter($request); ...... return $response; }
protected function sendRequestThroughRouter($request) { $this->app->instance("request", $request); Facade::clearResolvedInstance("request"); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } //引導啟動Laravel應用程序 public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { /**依次執行$bootstrappers中每一個bootstrapper的bootstrap()函數 $bootstrappers = [ "IlluminateFoundationBootstrapDetectEnvironment", "IlluminateFoundationBootstrapLoadConfiguration", "IlluminateFoundationBootstrapConfigureLogging", "IlluminateFoundationBootstrapHandleExceptions", "IlluminateFoundationBootstrapRegisterFacades", "IlluminateFoundationBootstrapRegisterProviders", "IlluminateFoundationBootstrapBootProviders", ];*/ $this->app->bootstrapWith($this->bootstrappers()); } }
上面bootstrap中會分別執行每一個bootstrapper的bootstrap方法來引導啟動應用程序的各個部分
1. DetectEnvironment 檢查環境 2. LoadConfiguration 加載應用配置 3. ConfigureLogging 配置日至 4. HandleException 注冊異常處理的Handler 5. RegisterFacades 注冊Facades 6. RegisterProviders 注冊Providers 7. BootProviders 啟動Providers
啟動應用程序的最后兩部就是注冊服務提供這和啟動提供者,如果對前面幾個階段具體時怎么實現的可以參考這篇文章。在這里我們主要關注服務提供器的注冊和啟動。
先來看注冊服務提供器,服務提供器的注冊由類 IlluminateFoundationBootstrapRegisterProviders::class 負責,該類用于加載所有服務提供器的 register 函數,并保存延遲加載的服務的信息,以便實現延遲加載。
class RegisterProviders { public function bootstrap(Application $app) { //調用了Application的registerConfiguredProviders() $app->registerConfiguredProviders(); } } class Application extends Container implements ApplicationContract, HttpKernelInterface { public function registerConfiguredProviders() { (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($this->config["app.providers"]); } public function getCachedServicesPath() { return $this->bootstrapPath()."/cache/services.php"; } }
可以看出,所有服務提供器都在配置文件 app.php 文件的 providers 數組中。類 ProviderRepository 負責所有的服務加載功能:
class ProviderRepository { public function load(array $providers) { $manifest = $this->loadManifest(); if ($this->shouldRecompile($manifest, $providers)) { $manifest = $this->compileManifest($providers); } foreach ($manifest["when"] as $provider => $events) { $this->registerLoadEvents($provider, $events); } foreach ($manifest["eager"] as $provider) { $this->app->register($provider); } $this->app->addDeferredServices($manifest["deferred"]); } }
loadManifest()會加載服務提供器緩存文件services.php,如果框架是第一次啟動時沒有這個文件的,或者是緩存文件中的providers數組項與config/app.php里的providers數組項不一致都會編譯生成services.php。
//判斷是否需要編譯生成services文件 public function shouldRecompile($manifest, $providers) { return is_null($manifest) || $manifest["providers"] != $providers; } //編譯生成文件的具體過程 protected function compileManifest($providers) { $manifest = $this->freshManifest($providers); foreach ($providers as $provider) { $instance = $this->createProvider($provider); if ($instance->isDeferred()) { foreach ($instance->provides() as $service) { $manifest["deferred"][$service] = $provider; } $manifest["when"][$provider] = $instance->when(); } else { $manifest["eager"][] = $provider; } } return $this->writeManifest($manifest); } protected function freshManifest(array $providers) { return ["providers" => $providers, "eager" => [], "deferred" => []]; }
緩存文件中 providers 放入了所有自定義和框架核心的服務。
如果服務提供器是需要立即注冊的,那么將會放入緩存文件中 eager 數組中。
如果服務提供器是延遲加載的,那么其函數 provides() 通常會提供服務別名,這個服務別名通常是向服務容器中注冊的別名,別名將會放入緩存文件的 deferred 數組中,與真正要注冊的服務提供器組成一個鍵值對。
延遲加載若由 event 事件激活,那么可以在 when 函數中寫入事件類,并寫入緩存文件的 when 數組中。
生成的緩存文件內容如下:
array ( "providers" => array ( 0 => "IlluminateAuthAuthServiceProvider", 1 => "IlluminateBroadcastingBroadcastServiceProvider", ... ), "eager" => array ( 0 => "IlluminateAuthAuthServiceProvider", 1 => "IlluminateCookieCookieServiceProvider", ... ), "deferred" => array ( "IlluminateBroadcastingBroadcastManager" => "IlluminateBroadcastingBroadcastServiceProvider", "IlluminateContractsBroadcastingFactory" => "IlluminateBroadcastingBroadcastServiceProvider", ... ), "when" => array ( "IlluminateBroadcastingBroadcastServiceProvider" => array ( ), ... )事件觸發時注冊延遲服務提供器
延遲服務提供器除了利用 IOC 容器解析服務方式激活,還可以利用 Event 事件來激活:
protected function registerLoadEvents($provider, array $events) { if (count($events) < 1) { return; } $this->app->make("events")->listen($events, function () use ($provider) { $this->app->register($provider); }); }即時注冊服務提供器
需要即時注冊的服務提供器的register方法由Application的register方法里來調用:
class Application extends Container implements ApplicationContract, HttpKernelInterface { public function register($provider, $options = [], $force = false) { if (($registered = $this->getProvider($provider)) && ! $force) { return $registered; } if (is_string($provider)) { $provider = $this->resolveProvider($provider); } if (method_exists($provider, "register")) { $provider->register(); } $this->markAsRegistered($provider); if ($this->booted) { $this->bootProvider($provider); } return $provider; } public function getProvider($provider) { $name = is_string($provider) ? $provider : get_class($provider); return Arr::first($this->serviceProviders, function ($value) use ($name) { return $value instanceof $name; }); } public function resolveProvider($provider) { return new $provider($this); } protected function markAsRegistered($provider) { //這個屬性在稍后booting服務時會用到 $this->serviceProviders[] = $provider; $this->loadedProviders[get_class($provider)] = true; } protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, "boot")) { return $this->call([$provider, "boot"]); } } }
可以看出,服務提供器的注冊過程:
判斷當前服務提供器是否被注冊過,如注冊過直接返回對象
解析服務提供器
調用服務提供器的 register 函數
標記當前服務提供器已經注冊完畢
若框架已經加載注冊完畢所有的服務容器,那么就啟動服務提供器的 boot 函數,該函數由于是 call 調用,所以支持依賴注入。
服務解析時注冊延遲服務提供器延遲服務提供器首先需要添加到 Application 中
public function addDeferredServices(array $services) { $this->deferredServices = array_merge($this->deferredServices, $services); }
我們之前說過,延遲服務提供器的激活注冊有兩種方法:事件與服務解析。
當特定的事件被激發后,就會調用 Application 的 register 函數,進而調用服務提供器的 register 函數,實現服務的注冊。
當利用 Ioc 容器解析服務名時,例如解析服務名 BroadcastingFactory:
class BroadcastServiceProvider extends ServiceProvider { protected $defer = true; public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }
在Application的make方法里會通過別名BroadcastingFactory查找是否有對應的延遲注冊的服務提供器,如果有的話那么
就先通過registerDeferredProvider方法注冊服務提供器。
class Application extends Container implements ApplicationContract, HttpKernelInterface { public function make($abstract) { $abstract = $this->getAlias($abstract); if (isset($this->deferredServices[$abstract])) { $this->loadDeferredProvider($abstract); } return parent::make($abstract); } public function loadDeferredProvider($service) { if (! isset($this->deferredServices[$service])) { return; } $provider = $this->deferredServices[$service]; if (! isset($this->loadedProviders[$provider])) { $this->registerDeferredProvider($provider, $service); } } }
由 deferredServices 數組可以得知,BroadcastingFactory 為延遲服務,接著程序會利用函數 loadDeferredProvider 來加載延遲服務提供器,調用服務提供器的 register 函數,若當前的框架還未注冊完全部服務。那么將會放入服務啟動的回調函數中,以待服務啟動時調用:
public function registerDeferredProvider($provider, $service = null) { if ($service) { unset($this->deferredServices[$service]); } $this->register($instance = new $provider($this)); if (! $this->booted) { $this->booting(function () use ($instance) { $this->bootProvider($instance); }); } }
還是拿服務提供器BroadcastServiceProvider來舉例:
class BroadcastServiceProvider extends ServiceProvider { protected $defer = true; public function register() { $this->app->singleton(BroadcastManager::class, function ($app) { return new BroadcastManager($app); }); $this->app->singleton(BroadcasterContract::class, function ($app) { return $app->make(BroadcastManager::class)->connection(); }); //將BroadcastingFactory::class設置為BroadcastManager::class的別名 $this->app->alias( BroadcastManager::class, BroadcastingFactory::class ); } public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }
函數 register 為類 BroadcastingFactory 向 服務容器綁定了特定的實現類 BroadcastManager,Application中的 make 函數里執行parent::make($abstract) 通過服務容器的make就會正確的解析出服務 BroadcastingFactory。
因此函數 provides() 返回的元素一定都是 register() 向 服務容器中綁定的類名或者別名。這樣當我們利用App::make() 解析這些類名的時候,服務容器才會根據服務提供器的 register() 函數中綁定的實現類,正確解析出服務功能。
啟動ApplicationApplication的啟動由類 IlluminateFoundationBootstrapBootProviders 負責:
class BootProviders { public function bootstrap(Application $app) { $app->boot(); } } class Application extends Container implements ApplicationContract, HttpKernelInterface { public function boot() { if ($this->booted) { return; } $this->fireAppCallbacks($this->bootingCallbacks); array_walk($this->serviceProviders, function ($p) { $this->bootProvider($p); }); $this->booted = true; $this->fireAppCallbacks($this->bootedCallbacks); } protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, "boot")) { return $this->call([$provider, "boot"]); } } }
引導應用Application的serviceProviders屬性中記錄的所有服務提供器,就是依次調用這些服務提供器的boot方法,引導完成后$this->booted = true 就代表應用Application正式啟動了,可以開始處理請求了。這里額外說一句,之所以等到所有服務提供器都注冊完后再來進行引導是因為有可能在一個服務提供器的boot方法里調用了其他服務提供器注冊的服務,所以需要等到所有即時注冊的服務提供器都register完成后再來boot。
本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。
本文參考鏈接:
[1] [2],這兩篇文章讓我在學習服務提供器時提供了不少幫助
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/28138.html
摘要:擴展用戶認證系統上一節我們介紹了系統實現的一些細節知道了是如何應用看守器和用戶提供器來進行用戶認證的,但是針對我們自己開發的項目或多或少地我們都會需要在自帶的看守器和用戶提供器基礎之上做一些定制化來適應項目,本節我會列舉一個在做項目時遇到的 擴展用戶認證系統 上一節我們介紹了Laravel Auth系統實現的一些細節知道了Laravel是如何應用看守器和用戶提供器來進行用戶認證的,但是...
摘要:一個服務提供器必須包含至少一種方法。服務提供器一旦被注冊,就可被用于程序的各個地方。注意服務提供器的變量來自類中。啟動服務當所有的服務提供器注冊之后,他們就變成了已啟動狀態。再次提示,把服務提供器作為一種組織工具來使用。 聲明:本文并非博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當然也不是原汁原味的翻譯,能保證90%...
摘要:官網源碼解讀號外號外歡迎大家我們開發組定了一個就線下聚一次的小目標里面的框架算是非常重的了這里的重先不具體到性能層面主要是框架的設計思想和框架集成的服務讓框架可以既可以快速解決很多問題又可以輕松擴展中的框架有在應該無出其右了這次解讀的源碼 官網: https://www.swoft.org/源碼解讀: http://naotu.baidu.com/file/8... 號外號外, 歡迎大...
摘要:可以為服務提供者的方法設置類型提示。方法將在所有其他服務提供者均已注冊之后調用。所有服務提供者都在配置文件中注冊。可以選擇推遲服務提供者的注冊,直到真正需要注冊綁定時,這樣可以提供應用程序的性能。 本文最早發布于 Rootrl的Blog 導言 Laravel是一款先進的現代化框架,里面有一些概念非常重要。在上手Laravel之前,我認為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP...
摘要:系統的核心是由的認證組件的看守器和提供器組成。使用的認證系統,幾乎所有東西都已經為你配置好了。其配置文件位于,其中包含了用于調整認證服務行為的注釋清晰的選項配置。 用戶認證系統(基礎介紹) 使用過Laravel的開發者都知道,Laravel自帶了一個認證系統來提供基本的用戶注冊、登錄、認證、找回密碼,如果Auth系統里提供的基礎功能不滿足需求還可以很方便的在這些基礎功能上進行擴展。這篇...
閱讀 2996·2021-11-23 09:51
閱讀 2817·2021-11-11 16:55
閱讀 2926·2021-10-14 09:43
閱讀 1402·2021-09-23 11:22
閱讀 1044·2019-08-30 11:04
閱讀 1673·2019-08-29 11:10
閱讀 965·2019-08-27 10:56
閱讀 3115·2019-08-26 12:01