🕌

[Symfony] .envの値をもとに、注入するオブジェクトを変える

2022/03/27に公開

Symfonyはオートワイヤリング機能で、コンストラクタに指定した型を判断し、オブジェクトを自動的に注入してくれます。
コンストラクタに指定した型がインターフェースの場合も自動で実装クラスを見つけ出し注入してくれますが、複数の実装クラスがある場合はどのクラスにするか明記する必要があります。
どのオブジェクトを注入するか、.envの値をもとに切り替えてみます。

インターフェースを作る

サンプルとして、通知を行うクラスをつくろうと思います。まずは通知インターフェースを作ります。

src/Service/NotifierInterface.php
<?php

interface Notifier
{
    public function notice(string $message): void;
}

実装クラスを作る

続いて実装クラスを作ります。Slack, Chatwork, 何もしないクラスを作ります。(中身は省略)

src/Service/SlackNotifier.php
<?php

class SlackNotifier implements NotifierInterface
{
    public function notice(string $message): void
    {
        print_r('Slackへ通知');
    }
}
src/Service/ChatworkNotifier.php
<?php

class ChatworkNotifier implements NotifierInterface
{
    public function notice(string $message): void
    {
        print_r('Chatworkへ通知');
    }
}
src/Service/NullNotifier.php
<?php

class NullNotifier implements NotifierInterface
{
    public function notice(string $message): void
    {
        print_r('なにもしない');
    }
}

NotifierInterfaceを持つクラスを作る

NotifierInterfaceを持つクラスを作ります。今回はCommandにしてみます。

src/Command/NoticeCommand.php
<?php

namespace App\Command;

use App\Service\NotifierInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'app:notice',
    description: 'Add a short description for your command',
)]
class NoticeCommand extends Command
{
    public function __construct(private NotifierInterface $notifier, string $name = null)
    {
        parent::__construct($name);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->notifier->notice('メッセージ');
        
        return Command::SUCCESS;
    }
}

設定する

まず、.envに切り替え用の環境変数 NOTICE_MODE を用意します。

NOTICE_MODE=slack

続いて、services.yamlに.envの値を使ったパラメータ notice_mode と、注入の設定を記述します。

config/services.yaml
parameters:
    notice_mode: '%env(NOTICE_MODE)%'

services:
... 中略 ...

    App\Command\NoticeCommand:
        arguments:
            $notifier: '@=parameter("notice_mode") == "slack" ? service("App\\Service\\SlackNotifier") : parameter("notice_mode") == "chatwork" ? service("App\\Service\\ChatworkNotifier") : service("App\\Service\\NullNotifier")'

@= から始める値はExpression Language Componentというものを利用して、条件によって注入するクラスを変更することができます。
特殊な関数が利用でき、parameter()service() を使うことができ、 parameter() は設定されているパラメータの値を、 service() は設定・もしくはオートワイヤリングされているサービスを返します。
例えば、parameter("notice_mode") == "slack" ? A : B と指定することで、パラメータの notice_mode が 'slack' の場合Aを、ちがう場合はBを選択してくれます。
Bの部分に parameter("notice_mode") == "chatwork" ? C : D のように、さらに条件分岐させることができます。
この設定では以下のようにオブジェクトが注入されます。

notice_modeの値 注入されるオブジェクトのクラス
slack SlackNotifier
chatwork ChatworkNotifier
その他 NullNotifier

ですが、このままでは動かず。。

ExpressionLanguageをインストールする

デフォルトではExpressionLanguage Componentがインストールされていないので、インストールします。

composer require expression-language

結果

これで、動作するようになります。

# .envのNOTICE_MODEがslackの場合
bin/console app:notice

Slackへ通知

# .envのNOTICE_MODEがchatworkの場合
bin/console app:notice

Chatworkへ通知

# .envが未設定、slack/chatwork以外の場合
bin/console app:notice

なにもしない

Discussion