目次
BootStrap
事件触发时注册延迟服务提供器
即时注册服务提供器
服务解析时注册延迟服务提供器
启动Application
ホームページ バックエンド開発 PHPチュートリアル Laravelサービスプロバイダー(ServiceProvider)の解釈

Laravelサービスプロバイダー(ServiceProvider)の解釈

Jul 06, 2018 pm 03:03 PM
laravel php

这篇文章主要介绍了关于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(&#39;events&#39;)->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, &#39;register&#39;)) {
            $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, &#39;boot&#39;)) {
            return $this->call([$provider, &#39;boot&#39;]);
        }
    }
}
ログイン後にコピー

可以看出,服务提供器的注册过程:

  • 判断当前服务提供器是否被注册过,如注册过直接返回对象

  • 解析服务提供器

  • 调用服务提供器的 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, &#39;boot&#39;)) {
            return $this->call([$provider, &#39;boot&#39;]);
        }
    }
}
ログイン後にコピー

引导应用Application的serviceProviders属性中记录的所有服务提供器,就是依次调用这些服务提供器的boot方法,引导完成后$this->booted = true 就代表应用Application正式启动了,可以开始处理请求了。这里额外说一句,之所以等到所有服务提供器都注册完后再来进行引导是因为有可能在一个服务提供器的boot方法里调用了其他服务提供器注册的服务,所以需要等到所有即时注册的服务提供器都register完成后再来boot。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

以上がLaravelサービスプロバイダー(ServiceProvider)の解釈の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Ubuntu および Debian 用の PHP 8.4 インストールおよびアップグレード ガイド Ubuntu および Debian 用の PHP 8.4 インストールおよびアップグレード ガイド Dec 24, 2024 pm 04:42 PM

PHP 8.4 では、いくつかの新機能、セキュリティの改善、パフォーマンスの改善が行われ、かなりの量の機能の非推奨と削除が行われています。 このガイドでは、Ubuntu、Debian、またはその派生版に PHP 8.4 をインストールする方法、または PHP 8.4 にアップグレードする方法について説明します。

PHP 開発用に Visual Studio Code (VS Code) をセットアップする方法 PHP 開発用に Visual Studio Code (VS Code) をセットアップする方法 Dec 20, 2024 am 11:31 AM

Visual Studio Code (VS Code とも呼ばれる) は、すべての主要なオペレーティング システムで利用できる無料のソース コード エディター (統合開発環境 (IDE)) です。 多くのプログラミング言語の拡張機能の大規模なコレクションを備えた VS Code は、

PHPでHTML/XMLを解析および処理するにはどうすればよいですか? PHPでHTML/XMLを解析および処理するにはどうすればよいですか? Feb 07, 2025 am 11:57 AM

このチュートリアルでは、PHPを使用してXMLドキュメントを効率的に処理する方法を示しています。 XML(拡張可能なマークアップ言語)は、人間の読みやすさとマシン解析の両方に合わせて設計された多用途のテキストベースのマークアップ言語です。一般的にデータストレージに使用されます

JSON Web Tokens(JWT)とPHP APIでのユースケースを説明してください。 JSON Web Tokens(JWT)とPHP APIでのユースケースを説明してください。 Apr 05, 2025 am 12:04 AM

JWTは、JSONに基づくオープン標準であり、主にアイデンティティ認証と情報交換のために、当事者間で情報を安全に送信するために使用されます。 1。JWTは、ヘッダー、ペイロード、署名の3つの部分で構成されています。 2。JWTの実用的な原則には、JWTの生成、JWTの検証、ペイロードの解析という3つのステップが含まれます。 3. PHPでの認証にJWTを使用する場合、JWTを生成および検証でき、ユーザーの役割と許可情報を高度な使用に含めることができます。 4.一般的なエラーには、署名検証障害、トークンの有効期限、およびペイロードが大きくなります。デバッグスキルには、デバッグツールの使用とロギングが含まれます。 5.パフォーマンスの最適化とベストプラクティスには、適切な署名アルゴリズムの使用、有効期間を合理的に設定することが含まれます。

母音を文字列にカウントするPHPプログラム 母音を文字列にカウントするPHPプログラム Feb 07, 2025 pm 12:12 PM

文字列は、文字、数字、シンボルを含む一連の文字です。このチュートリアルでは、さまざまな方法を使用してPHPの特定の文字列内の母音の数を計算する方法を学びます。英語の母音は、a、e、i、o、u、そしてそれらは大文字または小文字である可能性があります。 母音とは何ですか? 母音は、特定の発音を表すアルファベットのある文字です。大文字と小文字など、英語には5つの母音があります。 a、e、i、o、u 例1 入力:string = "tutorialspoint" 出力:6 説明する 文字列「TutorialSpoint」の母音は、u、o、i、a、o、iです。合計で6元があります

PHPでの後期静的結合を説明します(静的::)。 PHPでの後期静的結合を説明します(静的::)。 Apr 03, 2025 am 12:04 AM

静的結合(静的::) PHPで後期静的結合(LSB)を実装し、クラスを定義するのではなく、静的コンテキストで呼び出しクラスを参照できるようにします。 1)解析プロセスは実行時に実行されます。2)継承関係のコールクラスを検索します。3)パフォーマンスオーバーヘッドをもたらす可能性があります。

PHPマジックメソッド(__construct、__destruct、__call、__get、__setなど)とは何ですか? PHPマジックメソッド(__construct、__destruct、__call、__get、__setなど)とは何ですか? Apr 03, 2025 am 12:03 AM

PHPの魔法の方法は何ですか? PHPの魔法の方法には次のものが含まれます。1。\ _ \ _コンストラクト、オブジェクトの初期化に使用されます。 2。\ _ \ _リソースのクリーンアップに使用される破壊。 3。\ _ \ _呼び出し、存在しないメソッド呼び出しを処理します。 4。\ _ \ _ get、dynamic属性アクセスを実装します。 5。\ _ \ _セット、動的属性設定を実装します。これらの方法は、特定の状況で自動的に呼び出され、コードの柔軟性と効率を向上させます。

Laravelで電子メールの送信が失敗したときに返品コードを取得する方法は? Laravelで電子メールの送信が失敗したときに返品コードを取得する方法は? Apr 01, 2025 pm 02:45 PM

Laravelの電子メールの送信が失敗したときに戻りコードを取得する方法。 Laravelを使用してアプリケーションを開発する場合、検証コードを送信する必要がある状況に遭遇することがよくあります。そして実際には...

See all articles