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をバインドし、BroadcastingFactory::classでバインドされたサービスBroadcastMangerオブジェクト供給用プログラムをサービスコンテナを介してmakeで使用できるようにした.
本文は主に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
    

アプリケーションの起動の最後の2つは、登録サービス提供と起動プロバイダであり、前のいくつかの段階で具体的にどのように実現されたかについてはこの文章を参照することができます.ここでは主にサービスプロバイダの登録と起動に注目します.
まず、サービスプロバイダの登録は、すべてのサービスプロバイダのregister関数をロードし、遅延ロードされたサービスの情報を保存して遅延ロードを実現するクラス\Illuminate\Foundation\Bootstrap\RegisterProviders::classが担当する.
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関数
  • 現在のサービスプロバイダが登録済みであることを示す
  • フレームワークが登録済みのすべてのサービスコンテナをロードした場合、call呼び出しであるため依存注入をサポートするサービスプロバイダのboot関数が起動される.

  • サービス解決時登録遅延サービスプロバイダ
    遅延サービスプロバイダはまずApplicationに追加する必要があります
    public function addDeferredServices(array $services)
    {
        $this->deferredServices = array_merge($this->deferredServices, $services);
    }
    

    以前に述べたように、遅延サービスプロバイダのアクティブ化登録には、イベントとサービス解析の2つの方法があります.
    特定のイベントが励起されると、アプリケーションの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をバインドし、Applicationmake関数でparent::make($abstract)を実行すると、サービスコンテナのmakeによってサービスBroadcastingFactoryが正しく解析される.
    したがって、関数provides()が返す要素は、必ずregister()サービスコンテナにバインドされたクラス名または別名である.このように,App::make()を用いてこれらのクラス名を解析すると,サービスコンテナはサービスプロバイダのregister()関数にバインドされた実装クラスに基づいてサービス機能を正確に解析する.
    アプリケーションの起動
    アプリケーションの起動はクラス\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']);
            }
        }
    }

    アプリケーションアプリケーションを起動するサービスプロバイダ属性に記録されているすべてのサービスプロバイダは、これらのサービスプロバイダを順次呼び出すbootメソッドであり、起動が完了すると$this->booted = trueがアプリケーションApplicationを代表して正式に起動し、要求の処理を開始することができる.ここでさらに、すべてのサービスプロバイダが登録されてから起動するのは、1つのサービスプロバイダのbootメソッドで他のサービスプロバイダが登録したサービスを呼び出す可能性があるため、すべてのインスタント登録されたサービスプロバイダがregisterで完了してからbootに来る必要があるからです.
    本文はすでにシリーズの文章Laravelソース学習に収録されています.
    本文はリンクを参考にします:[1][2]この2つの文章は私にサービスプロバイダを学ぶ時多くの助けを提供しました