環境変数を扱う、"Dotenv"

[Symfony Component Advent Calendar 2022](https://qiita.com/advent-calendar/2022/symfony-component)の　6日目の記事です。

## 最初に
[Symfony](https://symfony.com)はPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

> Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
  （SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。）

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。


## 環境変数を扱う、"Dotenv"

[Dotenv](https://symfony.com/doc/current/configuration.html)は、`.env`ファイルを使って環境変数を扱います。こちらもSymfony以外で動作します。

### インストール

```shell
composer require symfony/dotenv
```

### .envとは

`.env`とは環境ごとに異なる値を定義するためのファイルです。たとえばデータベースの接続情報を直接プログラムに記載すると、テスト環境や本番環境での接続先がひとつになってしまいますが、環境ごとに`.env`に設定し、その情報をもとにDBに接続すれば、環境ごとに接続先を変えることができます。

```:.env
APP_ENV=dev
DATABASE_HOST=localhost
DATABASE_NAME=my_db
DATABASE_USER=root
DATABASE_PASSWORD=strong_password
```

※環境ごとの.envの作り方に関しては後述

### .envを読み込む

.envを読み込むには`Dotenv`クラスを使います。

```php

use Symfony\Component\Dotenv\Dotenv;

$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/.env');


// loads .env, .env.local, and .env.$APP_ENV.local or .env.$APP_ENV
$dotenv->loadEnv(__DIR__.'/.env');
```

`load()`メソッドの場合は純粋に指定した`.env`ファイルのみを参照します。`loadEnv()`メソッドの場合は、該当するファイルに加えて、`.env.local`, '.env.{APP_ENV}.local`, '.env.{APP_ENV}`ファイルwも参照します。

### 環境変数を利用する（通常版）

通常は`$_ENV`変数を利用すれば値を利用できます。

```php

$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/.env');

print_r($_ENV);

Array
(
    [APP_ENV] => dev
    [DATABASE_HOST] => localhost
    [DATABASE_NAME] => my_db
    [DATABASE_USER] => root
    [DATABASE_PASSWORD] => strong_password
    [SYMFONY_DOTENV_VARS] => APP_ENV,DATABASE_HOST,DATABASE_NAME,DATABASE_USER,DATABASE_PASSWORD
)

```

追加で`SYMFONY_DOTENV_VARS`というキーに対して、Dotenvを使って設定したキーのリストが格納されています。

### 環境変数を利用する（Symfony版）

Symfonyでも`$_ENV`にアクセスすれば利用できますが、基本的には環境変数の値は**パラメータに設定した後に、DIでそれぞれのクラスに渡します。**
一見めんどくさそうに見えますが、**環境変数の値に依存しなくなります。**

```yaml:services.yaml
parameters:
    app.database_host: '%env(DATABASE_HOST)%'

services:
    APP\Service\SomeService:
        arguments:
            $host: '%app.database_host%'
```

```php:SomeService.php
class SomeService
{
    public function __construct(private readonly string $host)
    {
        echo($this->host); // -> localhost
    }
}
```

### Symfonyでの.envの考え方

Symfonyではいくつか.envの種類が用意されています。それぞれの役割は以下の通りです。


| ファイル名 | 用途 |
|---|---|
| .env | アプリケーションで必要な環境変数の名前とデフォルト値を定義 |
| .env.local | 実行マシン上で扱う環境変数の値を追加・上書きします |
| .env.{環境名} | APP_ENVで定義された環境名で実行する際に、扱う環境変数のデフォルト値を追加・上書きします |
| .env.{環境名}.local | 実行マシン上でAPP_ENVで定義された環境名で実行する際に、扱う環境変数の値を追加・上書きします |

`.env`, `.env.{環境名}`にはデフォルト値をセットします。イメージ的には**扱う環境変数の名前をセットする**と思ってもらうとよいかもしれません。
ここに設定された環境変数がないとアプリケーションが動かないためにセットするもので、よって、Symfonyでは **`.env`, `.env.{環境名}`はコミット対象です。**

`.env.local`, `.env.{環境名}.local`は主に歯値を上書きするために利用します。上記で挙げた例のデータベース情報などのセンシティブな情報は、環境ごとにこちら側に定義するのが理想です。よって、 **`.env.local`, `.env.{環境名}.local`はコミット対象外です。**
（Symfonyインストール時に、`.gitignore`に指定されています）

さらに、実行マシンに設定された環境変数(`$_ENV`, `$_SERVER`)で上書きをするので、それぞれの優先順位は以下のようになります。

| 優先順位 | ファイル |
|:---:|---|
| 1 | 実行マシンの環境変数 |
| 2 | .env.{環境名}.local |
| 3 | .env.{環境名} |
| 4 | .env.local |
| 5 | .env |

## まとめ

今回はDotenvを紹介しました。Symfonyでの`.env`の取り扱いや値の利用方法は他のフレームワークと比較すると特殊ですが、『なるべく環境に依存することなく、動かせるところまでは動かす』という力強さが感じられます。