Laravel Providers——服務提供者的註冊與啟動原始碼解析

leoyang發表於2017-08-14

前言

本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/lar...
服務提供者是 laravel 框架的重要組成部分,承載著各種服務,自定義的應用以及所有 Laravel 的核心服務都是通過服務提供者啟動。本文將會介紹服務提供者的原始碼分析,關於服務提供者的使用,請參考官方文件 :服務提供者

 

服務提供者的註冊

服務提供者的啟動由類 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 負責,該類用於載入所有服務提供者的 register 函式,並儲存延遲載入的服務的資訊,以便實現延遲載入。

class RegisterProviders
{
    public function bootstrap(Application $app)
    {
        $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']);
    }
}

載入服務快取檔案

laravel 會把所有的服務整理起來,作為快取寫在快取檔案中:

return 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 (
      ),
      ...
    ),
  • 快取檔案中 providers 放入了所有自定義和框架核心的服務。
  • eager 陣列中放入了所有需要立即啟動的服務提供者。
  • deferred 陣列中放入了所有需要延遲載入的服務提供者。
  • when 放入了延遲載入需要啟用的事件。

載入服務提供者快取檔案:

public function loadManifest()
{
    if ($this->files->exists($this->manifestPath)) {
        $manifest = $this->files->getRequire($this->manifestPath);

        if ($manifest) {
            return array_merge(['when' => []], $manifest);
        }
    }
}

編譯服務提供者

laravel 中的服務提供者沒有快取檔案或者有變動,那麼就會重新生成快取檔案:

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' => []];
}
  • 如果服務提供者是需要立即註冊的,那麼將會放入快取檔案中 eager 陣列中。
  • 如果服務提供者是延遲載入的,那麼其函式 provides() 通常會提供服務別名,這個服務別名通常是向服務容器中註冊的別名,別名將會放入快取檔案的 deferred 陣列中。
  • 延遲載入若有 event 事件啟用,那麼可以在 when 函式中寫入事件類,並寫入快取檔案的 when 陣列中。

延遲服務提供者事件註冊

延遲服務提供者除了利用 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 來呼叫:

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)
    {
        $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);
}

我們之前說過,延遲服務提供者的啟用註冊有兩種方法:事件與服務解析。

當特定的事件被激發後,就會呼叫 Applicationregister 函式,進而呼叫服務提供者的 register 函式,實現服務的註冊。

當利用 Ioc 容器解析服務名時,例如解析服務名 BroadcastingFactory

class BroadcastServiceProvider extends ServiceProvider
{
    protected $defer = true;

    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}
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);
        });
    }
}

關於服務提供者的註冊函式:

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();
        });

        $this->app->alias(
            BroadcastManager::class, BroadcastingFactory::class
        );
    }

    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

函式 register 為類 BroadcastingFactoryIoc 容器繫結了特定的實現類 BroadcastManager,這樣 Ioc 容器中的 make 函式:

public function make($abstract)
{
    $abstract = $this->getAlias($abstract);

    if (isset($this->deferredServices[$abstract])) {
        $this->loadDeferredProvider($abstract);
    }

    return parent::make($abstract);
}

parent::make($abstract) 就會正確的解析服務 BroadcastingFactory

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

 

服務容器的啟動

服務容器的啟動由類 \Illuminate\Foundation\Bootstrap\BootProviders::class 負責:

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']);
        }
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章