株式会社クイックのWebサービス開発blog

HAPPYなサービスプランナー・エンジニア・デザイナーのブログです。

Laravelの.envについて調べてみた

どうもソフトウェアエンジニアのぱふゅーむです。
最近Laravelの.env周りで少しトラブったので復習の意味も込めて、
Laravelの.envの仕様について調べてみました。

今回は下記2点を中心に.envについて説明していきます!

  • .envファイルがどのように環境変数として設定されるか
  • .envファイルの値を取得する際に気をつけなければならないこと

.envファイルがロードされるタイミング

.envはIlluminate\Foundation\Bootstrap\LoadEnvironmentVariablesのbootstrapメソッドでロードされています。
.envをロードする前に$app->configurationIsCached()でキャッシュの存在をチェックしているようです 。

<?php

/**
 * Bootstrap the given application.
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @return void
 */
public function bootstrap(Application $app)
{
    if ($app->configurationIsCached()) {
        return;
    }

    $this->checkForSpecificEnvironmentFile($app);

    try {
        (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
    } catch (InvalidPathException $e) {
        //
    }
}

ちなみにbootstrapメソッドが呼ばれるタイミングはpublic/index.phpの以下の部分です。
メッセージがイカしてます笑

<?php

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

キャッシュの存在をチェックするconfigurationIsCachedメソッドが定義されているのはIlluminate\Foundation\Applicationです。

<?php

/**
 * Determine if the application configuration is cached.
 *
 * @return bool
 */
public function configurationIsCached()
{
    return file_exists($this->getCachedConfigPath());
}

/**
 * Get the path to the configuration cache file.
 *
 * @return string
 */
public function getCachedConfigPath()
{
    return $this->bootstrapPath().'/cache/config.php';
}

bootstrap/cache/config.phpが存在する場合は、.envをロードせずbootstrap/cache/config.phpを返しているようです。
bootstrap/cache/config.phpphp artisan config:cacheで作成されるファイルです。
コマンドを叩いて作成されたファイルを確認してみました。

bootstrap/cache/config.php

<?php

return array (
  'app' => 
  array (
    'name' => 'hoge',
    'env' => 'local',
    'debug' => true,
    'url' => 'https://hoge/fuga',
    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',
    'fallback_locale' => 'ja',
    'key' => '',
    'cipher' => 'AES-256-CBC',
    'log' => 'single',
    'log_level' => 'debug',
    'providers' => 
    array (
      0 => 'Illuminate\\Auth\\AuthServiceProvider',
      1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      2 => 'Illuminate\\Bus\\BusServiceProvider',
      3 => 'Illuminate\\Cache\\CacheServiceProvider',
      ...
    'aliases' => 
    array (
      ...
  'auth' => array(
    ...

config/配下のファイルが全てキャッシュされているのが分かります。 php artisan config:cacheを叩くと.envを読み込まず、こちらのキャッシュファイルを読み込むようになります。

cacheされたファイルが存在しない場合、.envファイルの値はどのように環境変数に設定されるのでしょうか?
コードを追ってみると以下のように定義されていました。

Dotenv/Loader

<?php

/**
 * Set an environment variable.
 *
 * This is done using:
 * - putenv,
 * - $_ENV,
 * - $_SERVER.
 *
 * The environment variable value is stripped of single and double quotes.
 *
 * @param string      $name
 * @param string|null $value
 *
 * @return void
 */
public function setEnvironmentVariable($name, $value = null)
{
    list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);

    // Don't overwrite existing environment variables if we're immutable
    // Ruby's dotenv does this with `ENV[key] ||= value`.
    if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
        return;
    }

    // If PHP is running as an Apache module and an existing
    // Apache environment variable exists, overwrite it
    if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
        apache_setenv($name, $value);
    }

    if (function_exists('putenv')) {
        putenv("$name=$value");
    }

    $_ENV[$name] = $value;
    $_SERVER[$name] = $value;
}

最終的にはputenv環境変数に定義していました。
つまり.envに設定された値は、カレントのリクエストを実行している間のみしか環境変数として存在しません。

cacheした場合に気をつけなければならないこと

Laravelには環境変数を取得するヘルパ、env()が用意されています。
Illuminate\Support\helpers/phpに定義されているので中身を見てみます。

<?php

/**
 * Gets the value of an environment variable.
 *
 * @param  string  $key
 * @param  mixed   $default
 * @return mixed
 */
function env($key, $default = null)
{
    $value = getenv($key);

    if ($value === false) {
        return value($default);
    }

    switch (strtolower($value)) {
        case 'true':
        case '(true)':
            return true;
        case 'false':
        case '(false)':
            return false;
        case 'empty':
        case '(empty)':
            return '';
        case 'null':
        case '(null)':
            return;
    }

    if (strlen($value) > 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) {
        return substr($value, 1, -1);
    }

    return $value;
}

基本的にはgetenv()で取得した環境変数を返しているだけです。
つまり、php artisan config:cache時にControllerや、Model等config/配下以外で.envファイルの値をenv()ヘルパを使用して取得しようとすると .envファイルをロードしないため、nullが返ってきてしまいます。
その回避策として.envの値を参照する時はヘルパ、config()を使用します。

Illuminate\Foundation\helpers.phpをみてみます。

<?php

/**
 * Get / set the specified configuration value.
 *
 * If an array is passed as the key, we will assume you want to set an array of values.
 *
 * @param  array|string  $key
 * @param  mixed  $default
 * @return mixed
 */
function config($key = null, $default = null)
{
    if (is_null($key)) {
        return app('config');
    }

    if (is_array($key)) {
        return app('config')->set($key);
    }

    return app('config')->get($key, $default);
}

configの値を取得してくれていますね。
env()はconfig内のみで使用するよう注意が必要です。

.envの運用について

今回の調査で.envファイルの値がどのように環境変数として設定されるか分かり、勉強になりました。
.envは便利なファイルですが、一歩使い方を誤るとクリティカルなバグの温床になってしまいます。
ちなみに私が参画しているプロジェクトでは.envを運用していません。
正しく言うと.envは設定していますが、.envに設定した値を直接apache環境変数に設定しています。

この運用は下記のようなメリットがあげられます。

  • configをキャッシュした時に、上記に記述したようなバグを防げる
  • アプリケーションより下のレイヤーで設定することによって、若干だがパフォーマンスの向上が期待できる

しかし反対に、devやstgなど各環境毎に.envの値をapacheに設定してあげる必要があるため、 煩わしさもあります。

今回の調査を参考に、.envのベストな運用を考えていきたいと思います。

Enjoy developing!



\\『真のユーザーファーストでマーケットを創造する』「ありがとう」で溢れる仲間を募集中です!! // 919.jp