一年ぶりにブログ書きます岩原です。
今回は、割とややこしいSymfonyの環境変数周りについてまとめてみました。

対象読者

  • Symfonyのことをある程度理解している
  • Symfonyにおける環境変数ファイルの優先度を知りたい。

結論

新規のSymfonyプロジェクト作成した際に生成される.envファイルに全て書いてあります。
要約すると、優先度は

.env < .env.local < .env.$APP_ENV < .env.$APP_ENV.local < マシンに設定されている環境変数

となるようです。

では、本当にそうなのか検証してみたいと思います。

検証

検証は、環境変数をダンプするコマンドを作成し、実際に環境変数をダンプすることで行います。
以下のコマンドでも環境変数のダンプは可能ですが、service.yamlなどで参照している環境変数をダンプするので、新しいコマンド作っても手間はあまり変わらないと思います。

php bin/console debug:container --env-vars

環境

検証環境は以下のとおりです。念の為bashやmacOSのバージョンを載せてはいますが、影響はないでしょう。

  • Symfony5.2
  • PHP 7.4
  • macOS 10.15.7
  • bash 3.2.57

コード

src/Command/DumpEnvCommand.php

環境変数をダンプするコマンドです。
コンストラクタで環境変数を受け取って、そのままoutputへ出力します。

<?php


namespace App\Command;


use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class DumpEnvCommand extends Command
{
    protected static $defaultName = 'app:dump-env';
    /**
     * @var string
     */
    private $env1;
    /**
     * @var string
     */
    private $env2;
    /**
     * @var string
     */
    private $env3;
    /**
     * @var string
     */
    private $env4;
    /**
     * @var string
     */
    private $env5;

    public function __construct(string $env1, string $env2, string $env3, string $env4, string $env5)
    {
        parent::__construct();
        $this->env1 = $env1;
        $this->env2 = $env2;
        $this->env3 = $env3;
        $this->env4 = $env4;
        $this->env5 = $env5;
    }

    protected function configure()
    {
        $this->setDescription('環境変数を出力する');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {

        $output->writeln($this->env1);
        $output->writeln($this->env2);
        $output->writeln($this->env3);
        $output->writeln($this->env4);
        $output->writeln($this->env5);
        return Command::SUCCESS;
    }
}

config/services.yaml

追加したコマンドの設定を追加します。
ここでコンストラクタに環境変数を渡すように設定します。

〜〜省略〜〜
    App\Controller\:
        resource: '../src/Controller/'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
    App\Command\DumpEnvCommand:
        tags: ['console.command']
        arguments:
            - '%env(ENV_01)%'
            - '%env(ENV_02)%'
            - '%env(ENV_03)%'
            - '%env(ENV_04)%'
            - '%env(ENV_05)%'

.env

以下の環境変数を末尾に追加します。
デフォルト設定になるはずなので、わかるようにしておきます。


ENV_01=デフォルト1
ENV_02=デフォルト2
ENV_03=デフォルト3
ENV_04=デフォルト4
ENV_05=デフォルト5

.env.local

ファイルを追加して、以下の環境変数を末尾に追加します。
これはローカルでのデフォルト設定になるはずなのでわかるようにしておきます。


ENV_02=ローカル2
ENV_03=ローカル3
ENV_04=ローカル4
ENV_05=ローカル5

.env.dev

ファイルを追加して、以下の環境変数を末尾に追加します。 これはdev環境でのデフォルト設定になるはずなのでわかるようにしておきます。


ENV_03=環境3
ENV_04=環境4
ENV_05=環境5

.env.dev.local

ファイルを追加して、以下の環境変数を末尾に追加します。 これはdev環境かつローカルでのデフォルト設定になるはずなのでわかるようにしておきます。


ENV_04=環境ローカル4
ENV_05=環境ローカル5

コマンド実行

envコマンドで一時的に環境変数を設定して、以下のようにコマンドを実行します。

env ENV_05=マシン5 php bin/console app:dump-env

結果

結果は以下の通りになりました。

env ENV_05=マシン5 php bin/console app:dump-env
デフォルト1
ローカル2
環境3
環境ローカル4
マシン5

ということで、結果は.envに書いてあったとおり、

.env < .env.local < .env.$APP_ENV < .env.$APP_ENV.local < マシンに設定されている環境変数

となりました。

余談

.env.local.phpについて

環境変数にまつわるファイルとしては、.env.local.phpというものがあったりします。
.env.local.phpは環境変数読み込み高速化のためのファイルで、.env系のファイルに定義されている環境変数をphpの配列として出力します。
このファイルは\Symfony\Component\Dotenv\Dotenv::bootEnvメソッドの中でincludeされ、読み出されます。
.env系のファイルの読み込みも同じ\Symfony\Component\Dotenv\Dotenv::bootEnvで行われます。 \Symfony\Component\Dotenv\Dotenv::bootEnvメソッド自体は、bin/consolepublic/index.phpなどのエントリポイント内で呼ばれているので、コマンド実行毎やリクエスト毎に毎回読まれる形になります。
なので、毎回複数のファイルI/Oを挟むよりは一度のincludeで済ませて高速化するアプローチなのだと思われます。
なお、このファイルが有ると.env系のファイルはすべて無視されるので注意しましょう。

このファイルを生成するには、以下のコマンドを実行します(dev環境の場合)。

composer dump-env dev

実行すると、以下のようなファイルが出力されます。

<?php

// This file was generated by running "composer dump-env dev"

return array (
  'APP_ENV' => 'dev',
  'APP_SECRET' => '(念の為消去)',
  'ENV_01' => 'デフォルト1',
  'ENV_02' => 'ローカル2',
  'ENV_03' => '環境3',
  'ENV_04' => '環境ローカル4',
  'ENV_05' => '環境ローカル5',
);