Laravel核心解讀--服務提供器(ServiceProvider)

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

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

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

BootStrap

首先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' => []];
}
  • 緩存文件中 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。

本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。

本文參考連接:
[1] [2],這兩篇文章讓我在學習服務提供器時提供了很多幫助

相關文章
相關標籤/搜索