Laravel服務提供器(ServiceProvider)的解讀

來源:互聯網
上載者:User
這篇文章主要介紹了關於Laravel服務提供器(ServiceProvider)的解讀,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

服務提供器是所有 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,        ];    }}

在服務提供器BroadcastServiceProviderregister中, 為BroadcastingFactory的類名綁定了類實現BroadcastManager,這樣就能通過服務容器來make出通過BroadcastingFactory::class綁定的服務BroadcastManger對象供應用程式使用了。

本文主要時來梳理一下laravel是如何註冊、和初始化這些服務的,關於如何編寫自己的服務提供器,可以參考官方文檔

BootStrap

首先laravel註冊和引導應用需要的服務是發生在尋找路由處理用戶端請求之前的Bootstrap階段的,在架構的入口檔案裡我們可以看到,架構在執行個體化了Application對象後從服務容器中解析出了HTTP Kernel對象

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);$response = $kernel->handle(    $request = Illuminate\Http\Request::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 = [             '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 函數,並儲存消極式載入的服務的資訊,以便實現消極式載入。

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 => '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']);        }    }}

可以看出,服務提供器的註冊過程:

  • 判斷當前服務提供器是否被註冊過,如註冊過直接返回對象

  • 解析服務提供器

  • 調用服務提供器的 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 向 服務容器綁定了特定的實作類別 BroadcastManagerApplication中的 make 函數裡執行parent::make($abstract) 通過服務容器的make就會正確的解析出服務 BroadcastingFactory

因此函數 provides() 返回的元素一定都是 register() 向 服務容器中綁定的類名或者別名。這樣當我們利用App::make() 解析這些類名的時候,服務容器才會根據服務提供器的 register() 函數中綁定的實作類別,正確解析出服務功能。

啟動Application

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。

以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.