2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel で `php artisan config:cache` すると `.env` と `.env.[APP_ENV]` の2つから環境変数が読み込みされる事がある

Posted at

概要

Laravel はパフォーマンスチューニングの一環として php artisan config:cache (php artisan optimize) で設定キャッシュを生成するかと思います。

このとき、環境変数 APP_ENV が設定されていなければ .env から環境変数を読み込んでから設定キャッシュが生成される動作を期待すると思います。

しかし、私は .envAPP_ENV を設定していたら、.env.[APP_ENV] も読み込まれて設定キャッシュが生成される経験をしました。

背景

あるプロジェクトで動作中の Laravel v7 アプリケーションの動作確認用の環境(仮想サーバークローン)を用意して
データベースなどの接続先も動作確認用の環境にセットアップすることになりました。

本番環境からコピーされた .env を元に動作確認用の .env を作ることになりましたが、
diff で設定変更箇所を簡単に確認したいという考えで .env をコピーして .env.production を作成しました。

その後 php artisan config:cache してアプリケーションにアクセスすると、データベース接続エラーが発生。

ログを見ると何故か本番環境のデータベースにアクセスしようとしていました(ネットワークが異なるので接続エラーで失敗していた)。

切り分け

設定キャッシュを確認すると、書き換えたはずのデータベース接続情報が本番環境のものになっていることを確認しました。

その設定は .env.production にしか存在しないはずなので、設定キャッシュの上書きに失敗したのかと思い
キャッシュ削除、キャッシュ生成などを繰り返すと .env.production が読み込まれたり、読み込まれたりすることを発見。

Laravel v7 はバージョンが古いから、切り分けのために Laravel v12 のプロジェクトを一から作り出して .env.env.production をコピーして設定をキャッシュすると、
やはり .env.production も読み込みされていました。

Laravel の動作がおかしいのではと思いソースコードを調査した結果、 Laravel の中では環境変数と設定データの生存期間が異なることがわかりました。

  • .env などから読み込んだ環境変数は Illuminate\Support\Envstatic 宣言されている変数で管理されている(ほぼグローバル変数)
  • 設定データは Laravel アプリケーション Illuminate\Foundation\Application (以降 Application 表記します)オブジェクトごとに管理されている

php artisan config:cache が実行されると Illuminate\Foundation\Console\ConfigCacheCommand (以降 ConfigCacheCommand のみ表記します)が実行されるのですが、
ConfigCacheCommand を実行する前に Application が生成されています(以降「コマンド実行 Application」とします)。
しかし、 ConfigCacheCommand を実行する時にはもう一つ Application を生成しており(以降「設定キャッシュ Application」とします)、
こちらの Application が管理している設定データがキャッシュされていました。

「コマンド実行 Application」と「設定キャッシュ Application」の動作は設定キャッシュ有無によって変化しますが、
ここで開設する動作は設定キャッシュが無いときの動作です。

「コマンド実行 Application」は生成されると環境変数を .env から読み込み、設定データも構築します。

読み込まれた環境変数は前述の通り、事実上のグローバル変数に格納されることになります。
この時点の環境変数を元に生成された設定データは「コマンド実行 Application」だけが参照します。

ConfigCacheCommand は設定データのキャッシュを生成するのですが、
既に生成されている「コマンド実行 Application」の設定データはキャッシュに使いません。

「設定キャッシュ Application」を新たに生成し、このオブジェクトの設定データをキャッシュします: https://github.com/laravel/framework/blob/v12.16.0/src/Illuminate/Foundation/Console/ConfigCacheCommand.php#L78-L92

問題は「設定キャッシュ Application」が生成された時点で .env に設定されていた APP_ENV 環境変数の値を参照できる状態にあるということです。

設定キャッシュが無いので「設定キャッシュ Application」は設定データを一から構築しようとしますが、
APP_ENV 環境変数がセットされているので .env.[APP_ENV] があれば
そちらから設定ファイルの読み込みを行います。

.env.[APP_ENV] から読み込まれた環境変数は事実上のグローバル変数に上書きされる形になるので
「設定キャッシュ Application」が参照する設定データは .env.env.[APP_ENV] の2つの環境設定ファイルを元に構築されてしまいます。

そして、読み込み順序の都合上 .env.[APP_ENV] の値が優先されるので、私が遭遇したような問題が発生することがわかりました。

Laravel への報告

2つの .env ファイルが読み込みされる原因が分かったので、 Laravel に Issue として報告しました。

少なくとも Laravel v7 から存在している動作なので、ある程度のやり取りが発生するかと思っていましたが
Issue にも記載した次のドキュメントで明確に説明されているということでクローズされました。

Laravel determines if an APP_ENV environment variable has been externally provided or if the --env CLI argument has been specified

クローズはされましたが「Laravel が APP_ENV 環境変数が外部から提供されたとき」と記載されているので
.env に記載の環境変数を Laravel がロードしているのだから、これは該当しないのでは無いかと思い
返信をくださった方にメンションしましたが数日返信も無かったので
Laravel フレームワークでこの動作が変更される事は無さそうです。

対策

Laravel フレームワークで動作が変更される事は無いようなので、
「Laravel を使用する際は迂闊に .env.[APP_ENV] と名前が一致するファイルを生成しない」というルールを周知するしかなさそうです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?