web-application-framework laravel

LaravelのService Containerについて調査しました。

参考ページ

環境

  • Laravel 11

もくじ

Service Containerとは

公式ページによると、Service ContainerはPHPのクラスの依存関係を管理し、コンストラクタやセッターを通して依存関係を注入するツールとのことです。

上記の説明だけだとよく分からなかったのですが、こちらのページを参考にさせていただくと、あるクラスのインスタンスを作成したいときに、依存関係を意識しないで作成できるツールのようです。

例えば以下の2つのクラスが定義されているとします。

class ClassA
{
    public function __construct(ClassB $class_b)
    {
    }
}

class ClassB
{
    public function __construct()
    {
    }
}

通常ClassAのインスタンスを作成したいとき、以下のようにClassBのインスタンスも作成する必要があります。

$class_b = new ClassB();
$class_a = new ClassA($class_b);

LaravelのService Containerを使うと、以下のようにClassBのインスタンスの作成を意識せずにClassAのインスタンスを作成することができます。このとき内部ではClassBのインスタンスも作成されます。

$class_a = App::make(ClassA::class);

上記では make 関数でインスタンスを作成していますが、通常はControllerなどの引数に指定すると、自動でインスタンスを作成してくれます。
例えば以下のようにTestControllerのコンストラクタの引数にClassAを指定すると、TestControllerのインスタンス作成時に自動でClassAのインスタンスを作成してくれます。

class TestController extends Controller
{
    public function __construct(ClassA $class_a)
    {
    }
}

依存関係の解決方法を定義する

クラスに依存関係が無かったり、他のクラスにのみ依存する場合、Service Containerは自動で依存関係を解決します。(上記の例など)
しかし、依存関係の解決方法を自分で定義することもできます。
例えば以下のクラスが定義されているとします。

class ClassA
{
    public function __construct(int $arg)
    {
    }
}

コンストラクタにintの引数があるため、Service Containerは自動で依存関係を解決できません。
そこで必ず $arg に1を入れてClassAのインスタンスを作成するように、解決方法を以下のように定義します。

App::bind(ClassA::class, function () {
    return new ClassA(1);
});

これによって $arg に1が入ったClassAのインスタンスが自動で作成されるようになります。

わかりやすいようにClassAのコンストラクタにログを入れ、make 関数でインスタンスを作成します。

class ClassA
{
    public function __construct(int $arg)
    {
        Log::info("arg = {$arg}");
    }
}
$class_a = App::make(ClassA::class);

実行すると、以下のようなログが出力されます。

arg = 1

Service Providerで依存関係の解決方法を定義する

公式ページによると、依存関係の解決方法はService Providerの register 関数で定義すると良いみたいです。
Service ProviderはLaravelアプリケーションの様々なもの(Service Containerの依存関係の解決方法やevent listenerなど)を登録するところです。

試しにService Providerに依存関係の解決方法を定義してみます。

Service Providerを作成します。
以下のコマンドを実行して、TestServiceProviderを作成します。

php artisan make:provider TestServiceProvider

ClassAとClassBを作成し、TestServiceProviderに依存関係の解決方法を定義します。

TestServiceProvider.php :

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;

class TestServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        // 依存関係の解決方法の定義を追加
        $this->app->bind(ClassB::class, function () {
            return new ClassB(1);
        });
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        //
    }
}

// ClassAを追加
class ClassA
{
    public function __construct(ClassB $class_b)
    {
        Log::info('ClassA instance is created.');
    }
}

// ClassBを追加
class ClassB
{
    public function __construct(int $arg)
    {
        Log::info("ClassB instance is created. arg = {$arg}");
    }
}

ClassAのインスタンスを作成するため、適当なRouteのクロージャの引数にClassAを指定します。
web.php などに以下を定義します。

Route::get('/', function (ClassA $class_a) {});

ブラウザなどでルートにアクセスすると、以下のようなログが出力されます。

ClassB instance is created. arg = 1
ClassA instance is created.

Service ProviderでService Containerの依存関係の解決方法を定義しました。