Autowireアトリビュートでサービスクラスのメソッド実行結果を注入する

`#[Autowire]`アトリビュートを使ったTipsです

## #[Autowire]アトリビュートについて

`#[Autowire]` アトリビュートは、コンストラクタなどに対して依存注入するものを指定することができるアトリビュートです。

Symfonyはオートワイヤリングの機能により、基本的には指定をしなくてもオブジェクトが自動注入されます。しかし、数値や文字列などは設定なしでは注入することができません。今までは`config/services.yaml`にその設定を行ってきました。

```yaml:config/services.yaml
~ 省略 ~

parameters:
    app:
        manager_emails:
            - 'hoge@qiita.com'
            - 'foo@qiita.com'
services:
    App\Service\SendItemUpdatedMail:
        arguments:
            $emails: '%app.manager_emails%' <- ここで指定して↑のパラメータのメールアドレスを渡す
```

```php:SendItemUpdatedMail.php
<?php

namespace App\Service;

class SendItemUpdatedMail
{
    public function __construct(
        private readonly array $emails
    )
    {
    }
}
```

このように設定すれば、メールアドレスを `SendItemUpdatedMail` に渡すことができます。
ただ、このメールアドレスを渡すために `config/services.yaml` に設定書くのがめんどくさいです。そこで活躍するのが `#[Autowire]` アトリビュートです。

```diff:config/services.yaml
~ 省略 ~

parameters:
    app:
        manager_emails:
            - 'hoge@qiita.com'
            - 'foo@qiita.com'
- services:
-     App\Service\SendItemUpdatedMail:
-         arguments:
-             $emails: '%app.manager_emails%'
```

```diff:SendItemUpdatedMail.php
<?php

namespace App\Service;

+ use Symfony\Component\DependencyInjection\Attribute\Autowire;

class SendItemUpdatedMail
{
    public function __construct(
+       #[Autowire(param: 'app.manager_emails')]
        private readonly array $emails
    )
    {
    }
}
```

このようにクラス内に `#[Autowire]` アトリビュートを指定することで、直接何を渡して欲しいか指示することができます。上記の例では、`param` 引数を使って、パラメータ名を指定して注入しています。

## サービスクラスのメソッド実行結果を注入する

### サービスクラスを注入しないとならなくなるケース

さて、先ほどの例のようにメールアドレスのリストがパラメータであれば上記のようなやり方で解決します。
これが、『メールアドレスリストはファイルで扱うことにします！』と仕様変更が入ったらどうでしょうか？

残念ながら、ファイルの中身を直接注入することはできません。
このような場合、ファイルを読み込んで配列などにするためのサービスクラスを作って、それをDIする形が一般的なやり方ではないでしょうか。

```php:ManagerEmailFetcher.php
<?php

namespace App\Service;

class ManagerEmailFetcher
{
    public function fetch(): array
    {
        // ファイルを読み込んで配列で返す処理。今回は省略

        return $emails;
    }
}
```

```php:SendItemUpdatedMail.php
<?php

namespace App\Service;

class SendItemUpdatedMail
{
    public function __construct(
        private readonly ManagerEmailFetcher $fetcher
    )
    {
    }

    public function send(): void
    {
        $emails = $this->fetcher->fetch();

        // メールを送る処理。省略
    }
}
```

こうなります。読者のみなさんは**きっとテストを書いてる** でしょうから、テストも `ManagerEmailFetcher` をモックするようなテストに変更しないといけません。めんどくさいですね。`ManagerEmailFetcher::fetch()` の処理結果を注入できたら、テスト修正しなくていいですもんね。

### Symfony Expressionによる、メソッド実行結果の注入

そんな便利な注入ないかなとおもったらありました。 `#[Autowire]` アトリビュートには `expression` という引数で [Symfony Expression](https://qiita.com/ippey_s/items/8fbadf36ffba5dfcfaf8) 式の結果を注入することができます。

このSymfony Expressionには、サービスコンテナに設定されたクラスを実行することができます。サービスコンテナには自動的にクラスが登録されるので、特になにか設定せずともサービスクラスを実行できます。

```php
$expressionLanguage = new ExpressionLanguage();
// ManagerEmailFetcher::fetch() の実行結果が返る
$emails = $expressionLanguage->evaluate('service("App\\\Service\\\ManagerEmailFetcher").fetch()');
```

これを利用して、 `#[Autowire]` で `ManagerEmailFetcher::fetch()` の実行結果を注入できます。

```diff:SendItemUpdatedMail.php
<?php

namespace App\Service;

+ use Symfony\Component\DependencyInjection\Attribute\Autowire;

class SendItemUpdatedMail
{
    public function __construct(
+       #[Autowire(expression: 'service("App\\\Service\\\ManagerEmailFetcher").fetch()')]
+       private readonly array $emails
-       private readonly ManagerEmailFetcher $fetcher
    )
    {
    }

    public function send(): void
    {    
 -      $emails = $this->fetcher->fetch();

        // メールを送る処理。省略
    }
}
```

このようにすれば、 `SendItemUpdateMail` は当初通り配列でメールアドレスを注入されるので、テストも修正する必要がなく処理もシンプルな状態を保てます。