服務提供器是全部 Laravel 應用程序引導中心。你的應用程序自定義的服務、第三方資源包提供的服務以及 Laravel 的全部核心服務都是經過服務提供器進行註冊(register)和引導(boot)的。php
拿一個Laravel框架自帶的服務提供器來舉例子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
對象供應用程序使用了。git
本文主要時來梳理一下laravel是如何註冊、和初始化這些服務的,關於如何編寫本身的服務提供器,能夠參考官方文檔github
首先laravel註冊和引導應用須要的服務是發生在尋找路由處理客戶端請求以前的Bootstrap階段的,在框架的入口文件裏咱們能夠看到,框架在實例化了Application
對象後從服務容器中解析出了HTTP Kernel
對象bootstrap
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() );
在Kernel處理請求時會先讓請求經過中間件而後在發送請求給路由對應的控制器方法, 在這以前有一個BootStrap階段來引導啓動Laravel應用程序,以下面代碼所示。segmentfault
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 = [ '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->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
啓動應用程序的最後兩部就是註冊服務提供這和啓動提供者,若是對前面幾個階段具體時怎麼實現的能夠參考這篇文章。在這裏咱們主要關注服務提供器的註冊和啓動。緩存
先來看註冊服務提供器,服務提供器的註冊由類 \Illuminate\Foundation\Bootstrap\RegisterProviders::class
負責,該類用於加載全部服務提供器的 register 函數,並保存延遲加載的服務的信息,以便實現延遲加載。app
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' => []]; }
生成的緩存文件內容以下:
array ( 'providers' => array ( 0 => 'Illuminate\\Auth\\AuthServiceProvider', 1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', ... ), 'eager' => array ( 0 => 'Illuminate\\Auth\\AuthServiceProvider', 1 => 'Illuminate\\Cookie\\CookieServiceProvider', ... ), 'deferred' => array ( 'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', ... ), 'when' => array ( 'Illuminate\\Broadcasting\\BroadcastServiceProvider' => 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']); } } }
能夠看出,服務提供器的註冊過程:
延遲服務提供器首先須要添加到 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() 函數中綁定的實現類,正確解析出服務功能。
Application的啓動由類 \Illuminate\Foundation\Bootstrap\BootProviders
負責:
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源碼學習裏,歡迎訪問閱讀。