#[Autowire]アトリビュートを使ったTipsです
#[Autowire]アトリビュートについて
#[Autowire] アトリビュートは、コンストラクタなどに対して依存注入するものを指定することができるアトリビュートです。
Symfonyはオートワイヤリングの機能により、基本的には指定をしなくてもオブジェクトが自動注入されます。しかし、数値や文字列などは設定なしでは注入することができません。今まではconfig/services.yamlにその設定を行ってきました。
~ 省略 ~
parameters:
app:
manager_emails:
- 'hoge@qiita.com'
- 'foo@qiita.com'
services:
App\Service\SendItemUpdatedMail:
arguments:
$emails: '%app.manager_emails%' <- ここで指定して↑のパラメータのメールアドレスを渡す
<?php
namespace App\Service;
class SendItemUpdatedMail
{
public function __construct(
private readonly array $emails
)
{
}
}
このように設定すれば、メールアドレスを SendItemUpdatedMail に渡すことができます。
ただ、このメールアドレスを渡すために config/services.yaml に設定書くのがめんどくさいです。そこで活躍するのが #[Autowire] アトリビュートです。
~ 省略 ~
parameters:
app:
manager_emails:
- 'hoge@qiita.com'
- 'foo@qiita.com'
- services:
- App\Service\SendItemUpdatedMail:
- arguments:
- $emails: '%app.manager_emails%'
<?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
namespace App\Service;
class ManagerEmailFetcher
{
public function fetch(): array
{
// ファイルを読み込んで配列で返す処理。今回は省略
return $emails;
}
}
<?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 式の結果を注入することができます。
このSymfony Expressionには、サービスコンテナに設定されたクラスを実行することができます。サービスコンテナには自動的にクラスが登録されるので、特になにか設定せずともサービスクラスを実行できます。
$expressionLanguage = new ExpressionLanguage();
// ManagerEmailFetcher::fetch() の実行結果が返る
$emails = $expressionLanguage->evaluate('service("App\\\Service\\\ManagerEmailFetcher").fetch()');
これを利用して、 #[Autowire] で ManagerEmailFetcher::fetch() の実行結果を注入できます。
<?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 は当初通り配列でメールアドレスを注入されるので、テストも修正する必要がなく処理もシンプルな状態を保てます。