摘要:調(diào)用了的可以看出,所有服務(wù)提供器都在配置文件文件的數(shù)組中。啟動的啟動由類負(fù)責(zé)引導(dǎo)應(yīng)用的屬性中記錄的所有服務(wù)提供器,就是依次調(diào)用這些服務(wù)提供器的方法,引導(dǎo)完成后就代表應(yīng)用正式啟動了,可以開始處理請求了。
服務(wù)提供器是所有 Laravel 應(yīng)用程序引導(dǎo)中心。你的應(yīng)用程序自定義的服務(wù)、第三方資源包提供的服務(wù)以及 Laravel 的所有核心服務(wù)都是通過服務(wù)提供器進(jìn)行注冊(register)和引導(dǎo)(boot)的。
拿一個Laravel框架自帶的服務(wù)提供器來舉例子
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設(shè)置為BroadcastManager::class的別名 $this->app->alias( BroadcastManager::class, BroadcastingFactory::class ); } public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }
在服務(wù)提供器BroadcastServiceProvider的register中, 為BroadcastingFactory的類名綁定了類實(shí)現(xiàn)BroadcastManager,這樣就能通過服務(wù)容器來make出通過BroadcastingFactory::class綁定的服務(wù)BroadcastManger對象供應(yīng)用程序使用了。
本文主要時來梳理一下laravel是如何注冊、和初始化這些服務(wù)的,關(guān)于如何編寫自己的服務(wù)提供器,可以參考官方文檔
BootStrap首先laravel注冊和引導(dǎo)應(yīng)用需要的服務(wù)是發(fā)生在尋找路由處理客戶端請求之前的Bootstrap階段的,在框架的入口文件里我們可以看到,框架在實(shí)例化了Application對象后從服務(wù)容器中解析出了HTTP Kernel對象
$kernel = $app->make(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::capture() );
在Kernel處理請求時會先讓請求通過中間件然后在發(fā)送請求給路由對應(yīng)的控制器方法, 在這之前有一個BootStrap階段來引導(dǎo)啟動Laravel應(yīng)用程序,如下面代碼所示。
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()); } //引導(dǎo)啟動Laravel應(yīng)用程序 public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { /**依次執(zhí)行$bootstrappers中每一個bootstrapper的bootstrap()函數(shù) $bootstrappers = [ "IlluminateFoundationBootstrapDetectEnvironment", "IlluminateFoundationBootstrapLoadConfiguration", "IlluminateFoundationBootstrapConfigureLogging", "IlluminateFoundationBootstrapHandleExceptions", "IlluminateFoundationBootstrapRegisterFacades", "IlluminateFoundationBootstrapRegisterProviders", "IlluminateFoundationBootstrapBootProviders", ];*/ $this->app->bootstrapWith($this->bootstrappers()); } }
上面bootstrap中會分別執(zhí)行每一個bootstrapper的bootstrap方法來引導(dǎo)啟動應(yīng)用程序的各個部分
1. DetectEnvironment 檢查環(huán)境 2. LoadConfiguration 加載應(yīng)用配置 3. ConfigureLogging 配置日至 4. HandleException 注冊異常處理的Handler 5. RegisterFacades 注冊Facades 6. RegisterProviders 注冊Providers 7. BootProviders 啟動Providers
啟動應(yīng)用程序的最后兩部就是注冊服務(wù)提供這和啟動提供者,如果對前面幾個階段具體時怎么實(shí)現(xiàn)的可以參考這篇文章。在這里我們主要關(guān)注服務(wù)提供器的注冊和啟動。
先來看注冊服務(wù)提供器,服務(wù)提供器的注冊由類 IlluminateFoundationBootstrapRegisterProviders::class 負(fù)責(zé),該類用于加載所有服務(wù)提供器的 register 函數(shù),并保存延遲加載的服務(wù)的信息,以便實(shí)現(xiàn)延遲加載。
class RegisterProviders { public function bootstrap(Application $app) { //調(diào)用了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"; } }
可以看出,所有服務(wù)提供器都在配置文件 app.php 文件的 providers 數(shù)組中。類 ProviderRepository 負(fù)責(zé)所有的服務(wù)加載功能:
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()會加載服務(wù)提供器緩存文件services.php,如果框架是第一次啟動時沒有這個文件的,或者是緩存文件中的providers數(shù)組項(xiàng)與config/app.php里的providers數(shù)組項(xiàng)不一致都會編譯生成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 放入了所有自定義和框架核心的服務(wù)。
如果服務(wù)提供器是需要立即注冊的,那么將會放入緩存文件中 eager 數(shù)組中。
如果服務(wù)提供器是延遲加載的,那么其函數(shù) provides() 通常會提供服務(wù)別名,這個服務(wù)別名通常是向服務(wù)容器中注冊的別名,別名將會放入緩存文件的 deferred 數(shù)組中,與真正要注冊的服務(wù)提供器組成一個鍵值對。
延遲加載若由 event 事件激活,那么可以在 when 函數(shù)中寫入事件類,并寫入緩存文件的 when 數(shù)組中。
生成的緩存文件內(nèi)容如下:
array ( "providers" => array ( 0 => "IlluminateAuthAuthServiceProvider", 1 => "IlluminateBroadcastingBroadcastServiceProvider", ... ), "eager" => array ( 0 => "IlluminateAuthAuthServiceProvider", 1 => "IlluminateCookieCookieServiceProvider", ... ), "deferred" => array ( "IlluminateBroadcastingBroadcastManager" => "IlluminateBroadcastingBroadcastServiceProvider", "IlluminateContractsBroadcastingFactory" => "IlluminateBroadcastingBroadcastServiceProvider", ... ), "when" => array ( "IlluminateBroadcastingBroadcastServiceProvider" => array ( ), ... )事件觸發(fā)時注冊延遲服務(wù)提供器
延遲服務(wù)提供器除了利用 IOC 容器解析服務(wù)方式激活,還可以利用 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); }); }即時注冊服務(wù)提供器
需要即時注冊的服務(wù)提供器的register方法由Application的register方法里來調(diào)用:
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服務(wù)時會用到 $this->serviceProviders[] = $provider; $this->loadedProviders[get_class($provider)] = true; } protected function bootProvider(ServiceProvider $provider) { if (method_exists($provider, "boot")) { return $this->call([$provider, "boot"]); } } }
可以看出,服務(wù)提供器的注冊過程:
判斷當(dāng)前服務(wù)提供器是否被注冊過,如注冊過直接返回對象
解析服務(wù)提供器
調(diào)用服務(wù)提供器的 register 函數(shù)
標(biāo)記當(dāng)前服務(wù)提供器已經(jīng)注冊完畢
若框架已經(jīng)加載注冊完畢所有的服務(wù)容器,那么就啟動服務(wù)提供器的 boot 函數(shù),該函數(shù)由于是 call 調(diào)用,所以支持依賴注入。
服務(wù)解析時注冊延遲服務(wù)提供器延遲服務(wù)提供器首先需要添加到 Application 中
public function addDeferredServices(array $services) { $this->deferredServices = array_merge($this->deferredServices, $services); }
我們之前說過,延遲服務(wù)提供器的激活注冊有兩種方法:事件與服務(wù)解析。
當(dāng)特定的事件被激發(fā)后,就會調(diào)用 Application 的 register 函數(shù),進(jìn)而調(diào)用服務(wù)提供器的 register 函數(shù),實(shí)現(xiàn)服務(wù)的注冊。
當(dāng)利用 Ioc 容器解析服務(wù)名時,例如解析服務(wù)名 BroadcastingFactory:
class BroadcastServiceProvider extends ServiceProvider { protected $defer = true; public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }
在Application的make方法里會通過別名BroadcastingFactory查找是否有對應(yīng)的延遲注冊的服務(wù)提供器,如果有的話那么
就先通過registerDeferredProvider方法注冊服務(wù)提供器。
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 數(shù)組可以得知,BroadcastingFactory 為延遲服務(wù),接著程序會利用函數(shù) loadDeferredProvider 來加載延遲服務(wù)提供器,調(diào)用服務(wù)提供器的 register 函數(shù),若當(dāng)前的框架還未注冊完全部服務(wù)。那么將會放入服務(wù)啟動的回調(diào)函數(shù)中,以待服務(wù)啟動時調(diào)用:
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); }); } }
還是拿服務(wù)提供器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設(shè)置為BroadcastManager::class的別名 $this->app->alias( BroadcastManager::class, BroadcastingFactory::class ); } public function provides() { return [ BroadcastManager::class, BroadcastingFactory::class, BroadcasterContract::class, ]; } }
函數(shù) register 為類 BroadcastingFactory 向 服務(wù)容器綁定了特定的實(shí)現(xiàn)類 BroadcastManager,Application中的 make 函數(shù)里執(zhí)行parent::make($abstract) 通過服務(wù)容器的make就會正確的解析出服務(wù) BroadcastingFactory。
因此函數(shù) provides() 返回的元素一定都是 register() 向 服務(wù)容器中綁定的類名或者別名。這樣當(dāng)我們利用App::make() 解析這些類名的時候,服務(wù)容器才會根據(jù)服務(wù)提供器的 register() 函數(shù)中綁定的實(shí)現(xiàn)類,正確解析出服務(wù)功能。
啟動ApplicationApplication的啟動由類 IlluminateFoundationBootstrapBootProviders 負(fù)責(zé):
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"]); } } }
引導(dǎo)應(yīng)用Application的serviceProviders屬性中記錄的所有服務(wù)提供器,就是依次調(diào)用這些服務(wù)提供器的boot方法,引導(dǎo)完成后$this->booted = true 就代表應(yīng)用Application正式啟動了,可以開始處理請求了。這里額外說一句,之所以等到所有服務(wù)提供器都注冊完后再來進(jìn)行引導(dǎo)是因?yàn)橛锌赡茉谝粋€服務(wù)提供器的boot方法里調(diào)用了其他服務(wù)提供器注冊的服務(wù),所以需要等到所有即時注冊的服務(wù)提供器都register完成后再來boot。
本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問閱讀。
本文參考鏈接:
[1] [2],這兩篇文章讓我在學(xué)習(xí)服務(wù)提供器時提供了不少幫助
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/28138.html
摘要:擴(kuò)展用戶認(rèn)證系統(tǒng)上一節(jié)我們介紹了系統(tǒng)實(shí)現(xiàn)的一些細(xì)節(jié)知道了是如何應(yīng)用看守器和用戶提供器來進(jìn)行用戶認(rèn)證的,但是針對我們自己開發(fā)的項(xiàng)目或多或少地我們都會需要在自帶的看守器和用戶提供器基礎(chǔ)之上做一些定制化來適應(yīng)項(xiàng)目,本節(jié)我會列舉一個在做項(xiàng)目時遇到的 擴(kuò)展用戶認(rèn)證系統(tǒng) 上一節(jié)我們介紹了Laravel Auth系統(tǒng)實(shí)現(xiàn)的一些細(xì)節(jié)知道了Laravel是如何應(yīng)用看守器和用戶提供器來進(jìn)行用戶認(rèn)證的,但是...
摘要:一個服務(wù)提供器必須包含至少一種方法。服務(wù)提供器一旦被注冊,就可被用于程序的各個地方。注意服務(wù)提供器的變量來自類中。啟動服務(wù)當(dāng)所有的服務(wù)提供器注冊之后,他們就變成了已啟動狀態(tài)。再次提示,把服務(wù)提供器作為一種組織工具來使用。 聲明:本文并非博主原創(chuàng),而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當(dāng)然也不是原汁原味的翻譯,能保證90%...
摘要:官網(wǎng)源碼解讀號外號外歡迎大家我們開發(fā)組定了一個就線下聚一次的小目標(biāo)里面的框架算是非常重的了這里的重先不具體到性能層面主要是框架的設(shè)計思想和框架集成的服務(wù)讓框架可以既可以快速解決很多問題又可以輕松擴(kuò)展中的框架有在應(yīng)該無出其右了這次解讀的源碼 官網(wǎng): https://www.swoft.org/源碼解讀: http://naotu.baidu.com/file/8... 號外號外, 歡迎大...
摘要:可以為服務(wù)提供者的方法設(shè)置類型提示。方法將在所有其他服務(wù)提供者均已注冊之后調(diào)用。所有服務(wù)提供者都在配置文件中注冊??梢赃x擇推遲服務(wù)提供者的注冊,直到真正需要注冊綁定時,這樣可以提供應(yīng)用程序的性能。 本文最早發(fā)布于 Rootrl的Blog 導(dǎo)言 Laravel是一款先進(jìn)的現(xiàn)代化框架,里面有一些概念非常重要。在上手Laravel之前,我認(rèn)為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP...
摘要:系統(tǒng)的核心是由的認(rèn)證組件的看守器和提供器組成。使用的認(rèn)證系統(tǒng),幾乎所有東西都已經(jīng)為你配置好了。其配置文件位于,其中包含了用于調(diào)整認(rèn)證服務(wù)行為的注釋清晰的選項(xiàng)配置。 用戶認(rèn)證系統(tǒng)(基礎(chǔ)介紹) 使用過Laravel的開發(fā)者都知道,Laravel自帶了一個認(rèn)證系統(tǒng)來提供基本的用戶注冊、登錄、認(rèn)證、找回密碼,如果Auth系統(tǒng)里提供的基礎(chǔ)功能不滿足需求還可以很方便的在這些基礎(chǔ)功能上進(jìn)行擴(kuò)展。這篇...
閱讀 2999·2021-11-23 09:51
閱讀 2820·2021-11-11 16:55
閱讀 2935·2021-10-14 09:43
閱讀 1403·2021-09-23 11:22
閱讀 1045·2019-08-30 11:04
閱讀 1674·2019-08-29 11:10
閱讀 970·2019-08-27 10:56
閱讀 3125·2019-08-26 12:01