Laravelの.envには特別扱いされる文字列があった

# まとめ

Laravelの.envではいくつかの文字列が特別扱いされて文字列ではない値に自動的に変換されます。機能としては前からあったようですが、公式ドキュメントに[その記述](https://laravel.com/docs/5.6/configuration#environment-variable-types)が追加されたのは2018年3月末の[このコミット](https://github.com/laravel/docs/commit/4360e449904edbae4a72e1da1b00c949d5a39f9d#diff-a66b35d20295cb764719ac8bd35837ec)の模様。2018年4月2日現在では[日本語版](https://readouble.com/laravel/5.6/ja/configuration.html)には反映されていません。

| .envの記述 | env()ヘルパーの値 |
|:--|:--|
| true | (bool) true |
| (true) | (bool) true |
| false | (bool) false |
| (false) | (bool) false |
| empty | (string) '' |
| (empty) | (string) '' |
| null | (null) null |
| (null) | (null) null |

ついでにこれら特別扱いされる文字列を変換させずにそのまま文字列として値が取れるように書きたければ、たとえば'true'なら以下のように書く必要があります。[^1]

```
FOO="\"true\""
```

# 経緯

その値がboolであるような設定値を用意したいと思いました。仮にその名前を'app.my_flag', .envの中では'MY_FLAG'と書くつもりだったとします。プログラム中ではconfig('app.my_flag')でその値にアクセスできる感じ。

最初はこうしました。

.envに追加:

```
MY_FLAG=false
```

config/app.phpに追加:

```php
    'my_flag' => \App\MyHelper::env_bool('MY_FLAG'),
```

`\App\MyHelper::env_bool`は自作のヘルパー関数です。

```php
    public static function env_bool($key, $default = false)
    {
        $result = env($key);

        if (is_null($result)) {
            return $default;
        }

        if ($result === 'true') {
            return true;
        }

        if ($result === 'false') {
            return false;
        }

        throw new \Exception(sprintf('invalid bool: key=%s, value=%s', $key, var_export($result, true)));
    }

```

とりあえず動作確認でtinkerコマンドを打つといきなりエラーです。

```console
% php artisan tinker

In MyHelper.php line 21:

  invalid bool: key=MY_FLAG, value=true


```

ありゃー、なんでenv()がはじめから`true`返すんだと思ってソース追っかけたら、こんな風になってたわけですよ。

```php
if (! function_exists('env')) {
    /**
     * 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;
    }
}
```

知らんかったーと思ってヘルパー関数のマニュアルを確認したら初めに書いた記述にいきつきました。`env_bool`なんてはじめから書く必要はなかったのです。しかしよくよく調べたらドキュメントの更新はつい3日前なのでこんなの知るわけがないですわな。間が悪いw

で””で囲まれてたらはがす処理もあるようなので`true`って文字列を書きたいときはとりあえず""で囲めばいいんじゃないかなという当たりを付けて、

.envに追加:

```
MY_STRING="true"
```

config/app.phpに追加:

```php
    'my_string' => env('MY_STRING'),
```

と準備して試してみましたがだめでした。

```console
% php artisan tinker
Psy Shell v0.8.17 (PHP 7.2.4 — cli) by Justin Hileman
>>> config('app.my_string')
=> true
>>>
```

よくよくソースを読むと以下のような事情でした。

* envヘルパーは`getenv()`で取った値の文字列が""で囲まれていたら、両端の""を削ります。(上記のとおり)

* `.env`ファイルを読みこんで環境変数に設定する役割は[phpdotenv](https://github.com/vlucas/phpdotenv)パッケージの仕事ですが、これは""で囲まれた文字列は""をはがします。文字列に「"」を含めたければ「\」でエスケープする必要があります。

ということで""を剥がすひとがもう一人いたわけですね。

```
MY_STRING="\"true\""
```

とすることで、めでたくちゃんと取れるようになりました。

```console
% php artisan tinker
Psy Shell v0.8.17 (PHP 7.2.4 — cli) by Justin Hileman
>>> config('app.my_string')
=> "true"
>>>
```

[^1]: 余談ですが、両端に「"」をもつ文字列を値としたいときは、`FOO="\"\"foo\"\""`のようにする必要があります。