📘

Laravelサービスプロバイダを解剖

2024/01/02に公開

サービスプロバイダについて

Laravelのサービスプロバイダとは、Laravelアプリケーションのサービスコンテナにサービスをバインドするクラスです。
例えばデータベースのプロバイダの場合、異なるデータベース接続の設定と管理を行い、アプリケーションがデータベースと効率的に通信できるようにしています。

サービスプロバイダの利点はさまざまありますが、次の2つが大きいと思います。

  1. サービスプロバイダを通じて、依存性注入の設定を一箇所に集中させることができ、コードの整理と再利用性が向上させることができる
  2. アプリケーションの起動時に必要な各種サービスの初期化を一箇所で管理でき、初期化プロセスがシンプルになり、アプリケーションの起動時間の最適化に繋がる

registerメソッドについて

サービスプロバイダのregisterメソッドについて解説します。
registerメソッドはサービスコンテナへのサービスのバインディングに特化しており、アプリケーションのサービスコンテナにサービスを登録するために使用されます。

例えばRepositoryパターンを採用している場合、そのインターフェイスと実装クラスを次のように登録することが可能です。

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->bind(
        \App\Models\PostRepository::class,
        \App\Repositories\Post\EloquentRepository::class
    );
}

https://zenn.dev/kou_hikaru/articles/662cc57d7d6526

bootメソッドについて

次に、bootメソッドについて解説します。
bootメソッドは、全てのプロバイダが登録を終えてから実行されます。これにより、他のサービスプロバイダに依存するサービスや設定を安全に登録することが可能です。
特定の初期処理が必要な場合は、ここに記述する必要があります。

例えばUserモデルのイベントを監視したい場合に、Observerを利用するとします。
その場合は、次のコードで登録することが可能です。

public function boot(): void
{
    User::observe(\App\Observers\UserObserver::class);
}

デフォルトのサービスプロバイダ

ここでは、Laravel側が事前に定義しているサービスプロバイダについて解説します。
Laravel側で事前に定義しているサービスプロバイダは次のようなものがあります。

// DefaultProviders の一部
/**
 * Create a new default provider collection.
 *
 * @return void
 */
public function __construct(?array $providers = null)
{
    $this->providers = $providers ?: [
        \Illuminate\Auth\AuthServiceProvider::class,
        \Illuminate\Broadcasting\BroadcastServiceProvider::class,
        \Illuminate\Bus\BusServiceProvider::class,
        \Illuminate\Cache\CacheServiceProvider::class,
        \Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        \Illuminate\Cookie\CookieServiceProvider::class,
        \Illuminate\Database\DatabaseServiceProvider::class,
        \Illuminate\Encryption\EncryptionServiceProvider::class,
        \Illuminate\Filesystem\FilesystemServiceProvider::class,
        \Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        \Illuminate\Hashing\HashServiceProvider::class,
        \Illuminate\Mail\MailServiceProvider::class,
        \Illuminate\Notifications\NotificationServiceProvider::class,
        \Illuminate\Pagination\PaginationServiceProvider::class,
        \Illuminate\Pipeline\PipelineServiceProvider::class,
        \Illuminate\Queue\QueueServiceProvider::class,
        \Illuminate\Redis\RedisServiceProvider::class,
        \Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        \Illuminate\Session\SessionServiceProvider::class,
        \Illuminate\Translation\TranslationServiceProvider::class,
        \Illuminate\Validation\ValidationServiceProvider::class,
        \Illuminate\View\ViewServiceProvider::class,
    ];
}
// config/app.php の一部
'providers' => ServiceProvider::defaultProviders()->merge([
    /*
     * Package Service Providers...
     */

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    // App\Providers\BroadcastServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
])->toArray(),

その中から1つ例として、RouteServiceProviderをのぞいてみます。

class RouteServiceProvider extends ServiceProvider
{
    /**
     * The path to your application's "home" route.
     *
     * Typically, users are redirected here after authentication.
     *
     * @var string
     */
    public const HOME = '/home';

    /**
     * Define your route model bindings, pattern filters, and other route configuration.
     */
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });
    }
}

ここでは、boot()メソッドのみが実装されています。

内容としては、RateLimiter::forを使用して、APIのレート制限を設定しています。
次に、$this->routesメソッドを使用して、アプリケーションのルートを定義しています。ここでは、apiとwebの2つのルートグループが定義されています。
apiルートグループは、apiミドルウェアを使用し、apiというプレフィックスが付けられ、routes/api.phpファイルに定義されています。
webルートグループは、webミドルウェアを使用し、routes/web.phpファイルに定義されています

要するにアプリケーションのルーティングに関する初期処理がここでは記述されていることが分かります。

カスタムサービスプロバイダ

ここでは、カスタムサービスプロバイダについて解説します。
今回はCollectionのMacroをサービスプロバイダで定義していきます。
次のコマンドでプロバイダを作成します。

php artisan make:provider MacroServiceProvider

作成したサービスプロバイダで、次のようにコレクションのマクロを定義します。

<?php

namespace App\Providers;

use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;

class MacroServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        // UUIDでコレクションをマップするマクロ
        if (! Collection::hasMacro('mapUuid')) {
            Collection::macro('mapUuid', function () {

                /** @var Collection $this */
                $collect = $this;

                return $collect->map(function ($value) {
                    return $value['uuid'];
                })->toArray();
            });
        }
    }
}

次は、作成したプロバイダがアプリケーションで実行されるように次のように定義する必要があります。

// config/app.php
'providers' => ServiceProvider::defaultProviders()->merge([
    /*
     * Package Service Providers...
     */

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    // App\Providers\BroadcastServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,

    /*
     * Custom Service Providers...
     */
    App\Providers\MacroServiceProvider::class,
])->toArray(),

このようにカスタムサービスプロバイダを使用してコレクションメソッドを実装することにより、これらのメソッドはアプリケーション全体で再利用可能になります。

遅延プロバイダとは

ここでは遅延プロバイダについて解説します。
遅延プロバイダとは、そのサービスが実際に必要になるまでそのプロバイダのロードを遅らせるというものです。
そのため、アプリケーション全体で毎回のリクエストでロードされず、パフォーマンスの向上やリソースの節約に繋がります。

では、メールアドレスの重複チェックを例に遅延プロバイダで実装します。
サービスプロバイダを次のように実装します。

<?php

namespace App\Providers;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class IsUniqueEmailProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        $this->app->bind(
            \App\Services\User\IsUniqueEmailInterface::class,
            function ($app) {
                return new \App\Services\User\IsUniqueEmailAction(
                    $app->make(\App\Repositories\User\UserRepositoryInterface::class)
                );
            }
        );
    }

    public function provides(): array
    {
        return [\App\Services\User\IsUniqueEmailInterface::class];
    }
}

先ほど同様に実行するために、プロバイダを定義します。

'providers' => ServiceProvider::defaultProviders()->merge([
    /*
     * Package Service Providers...
     */

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    // App\Providers\BroadcastServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,

    /*
     * Custom Service Providers...
     */
    App\Providers\MacroServiceProvider::class,
    App\Providers\IsUniqueEmailProvider::class,
])->toArray(),

プロバイダを遅延ロードするには、\Illuminate\Contracts\Support\DeferrableProviderインターフェイスを実装し、providesメソッドを定義する必要があります。
providesメソッドはそのプロバイダで登録したサービスコンテナ結合を返します。

このように遅延プロバイダで実装することで、メールアドレスの重複チェックが必要になったタイミングの時のみロードすることが可能になります。
今回の場合だと、ユーザー登録が比較的頻度の低い操作であれば、このサービスが毎回のリクエストでロードされるのを防ぐことができます。

最後に

この記事では、Laravelの機能の1つであるサービスプロバイダに焦点を当てました。
サービスプロバイダは、Laravelアプリケーションの核となる概念であり、アプリケーションの初期化、サービスの登録と設定、依存性の管理などに不可欠です。
Laravelには多くのデフォルトサービスプロバイダが含まれており、これらがフレームワークのさまざまな機能をサポートしていることが理解いただけたかと思います。
この記事を通じて、Laravelのサービスプロバイダの構造と使い方についての理解を深めることに繋がると嬉しいです。

参考URL

https://laravel.com/docs/10.x/providers

GitHubで編集を提案

Discussion