Laravelでパラメータの異なる複数のサービスを定義する方法

# はじめに

次のような、コンストラクタで`endpoint`と`token`を受け取り、APIの操作をするクラスがあったとします。

```php
<?php

namespace App\Service;

class ApiClient
{
    public function __construct(private string $endpoint, private string $token)
    {}

    public function run()
    {
        ...
    }
}

```

このクラスを、DIできるようにするには、サービスプロバイダでLaravelのコンテナに登録しますよね。

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use App\Service\ApiClient;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(ApiClient::class, fn () => new ApiClient('https://hoge.example.com', 'xxxxxxxxxx'));
    }
}


```

```php:呼び出し側
<?php

namespace App\Usecase;

use App\Service\ApiClient;

class FugaUsecase
{
    // DIされる
    public function __construct(private ApiClient $apiClient)
    {}

    public function exec()
    {
        $this->apiClient->run();
    }
}

```

しかし、異なる`endpoint`と`token`で`ApiClient`を使いたいケースが出てきたらどうしますか？
ん？`ApiClient`の中身をコピペしたクラスをもう一つ作る？さらに異なる`endpoint`と`token`で`ApiClient`を使いたいケースが出てきたら？修正が発生した時は全部のコードを直しますか？

いいえ、実はそれ、Providerの設定だけでできますよ！という話です。

```php:別の呼び出し側
<?php

namespace App\Usecase;

use App\Service\ApiClient;

class FugaUsecase
{
    // endpointとtokenの異なる ApiClient が欲しい！
    public function __construct(private ApiClient $apiClient)
    {}

    public function exec()
    {
        $this->apiClient->run();
    }
}

```

# やり方

サービスコンテナへ登録する時、`$this->app->bind()`を使用しますが、この第一引数、実はクラス名じゃなくても大丈夫なんです。ここで指定する値は「サービスをコンテナに登録する際のID」として使われます。ここの値をクラス名と一致させることで「型を使ったDI」ができるようになりますが、その機能を使わないのであれば、必ずしもクラス名にする必要はありません。

よって、まず異なる`endpoint`と`token`を持つ2種類の`ApiClient`を**異なるIDで**コンテナに登録します。
コンテナからは`$this->app->make(<ID>)`で登録したサービスを取得することができるので、必要に応じてサービスを取得し、依存性を解決します。

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use App\Service\ApiClient;
use App\Usecase\FugaUsecase;
use App\Usecase\HogeUsecase;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // 2種類のサービスを登録
        $this->app->bind('api_client.hoge', fn () => new ApiClient('https://hoge.example.com', 'xxxxxxxxxx'));
        $this->app->bind('api_client.fuga', fn () => new ApiClient('https://fuga.example.com', 'yyyyyyyyyy'));

        // コンテナから取得した ApiClient を使ってサービスを登録する
        $this->app->bind(HogeUsecase::class, fn (Application $app) => new HogeUsecase($app->make('api_client.hoge')));
        $this->app->bind(FugaUsecase::class, fn (Application $app) => new FugaUsecase($app->make('api_client.fuga')));
    }
}
```

また、`ApiClient`を使い回す必要がないのであれば、次のように依存性解決と同時にサービスの定義を行うこともできます。

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use App\Service\ApiClient;
use App\Usecase\FugaUsecase;
use App\Usecase\HogeUsecase;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(
            HogeUsecase::class,
            function (Application $app) {
                $client = $app->make(ApiClient::class, ['endpoint' => 'https://hoge.example.com', 'token' => 'xxxxxxxxxx']);
                return new HogeUsecase($client);
            }
        );

        $this->app->bind(
            FugaUsecase::class,
            function (Application $app) {
                $client = $app->make(ApiClient::class, ['endpoint' => 'https://fuga.example.com', 'token' => 'yyyyyyyyyy']);
                return new FugaUsecase($client);
            }
        );
    }
}
```

# 終わりに
今回は、重複コードを発生させずに、パラメータの異なる複数のサービスを定義する方法を紹介しました。自前で依存性を管理したり、サービスプロバイダのあまり使わない機能を使ったりするので、初心者には少しハードルが高いかもしれません。

しかし「パラメータの異なるクラスを沢山作りたいとき」や「対象のサービスがかなり大きいクラス」である場合は重複コードを作るよりも、大きなメリットになるので、覚えておいて損はない機能だと思います。使えるタイミングがあれば是非使ってみてください。