この記事は CakePHP Advent Calendar 2019 11日目の記事です。

昨年の記事では「あまり CakePHP プロジェクトに関われなかった」と書いていましたが、今年は関わる機会も多かったのでそこで実施した内容について書こうと思います。
昨年の記事でも少し触れていた .env に関する対応です。

こんな人に読んでほしい

  • CakePHP3 を使って開発をしている
  • DB 接続先など環境依存になる情報は .env のみで管理したい
  • composer install 時に app.php に対して行う処理を .env に対して行うようにしたい

なぜこの記事を書こうと思ったか

こちらの手順通りに、プロジェクトのひな形を作成します。

$ # アプリケーション名は test とします
$ php composer.phar create-project --prefer-dist cakephp/app test

この記事の投稿時点で、プロジェクトルートから見た際に以下ファイルが存在すると思います。

  • config/.env.default
  • config/app.php
  • config/app.default.php

.env.default.env にコピーして利用することになります。
app.phpcomposer install 時に app.default.php から自動生成され、ここを各環境用にカスタマイズで利用する値を定義します。
その結果、環境依存な情報を .envapp.php の双方に書くことになり、面倒だなと感じたことが発端です。

この件についてはこのあたりでも議論されていたようです。

対応したこと

.env の読み込みを有効にする

デフォルトでは無効化されているので有効化します。

// config/bootstrap.php

// 55行目付近の以下のコメントアウトをはずす
if (!env('APP_NAME') && file_exists(CONFIG . '.env')) {
    $dotenv = new \josegonzalez\Dotenv\Loader([CONFIG . '.env']);
    $dotenv->parse()
        ->putenv()
        ->toEnv()
        ->toServer();
}

AWS Elastic Beanstalk や Heroku のように GUI から環境変数を設定する場合 APP_NAME を指定しておけばこの処理は実行されません。

app.php で環境変数を参照するように変更する

テスト用にデータベースの接続情報を修正します。

// config/app.php

// ホスト名、ユーザ名、タイムゾーンなどを env 関数を利用して環境変数から取得する
// env 関数は2つめの引数に未定義時のデフォルト値を設定できる
'Datasources' => [
    'default' => [
        'className' => Connection::class,
        'driver' => Mysql::class,
        'persistent' => false,
        'host' => env('DATABASE_HOST', '127.0.0.1'),
        'port' => env('DATABASE_PORT', 3306),
        'username' => env('DATABASE_USERNAME'),
        'password' => env('DATABASE_PASSWORD'),
        'database' => env('DATABASE_NAME', 'test'),
        'encoding' => env('DATABASE_ENCODING', 'utf8mb4'),
        'timezone' => env('DATABASE_TIMEZONE', 'UTC'),
        'flags' => [],
        'cacheMetadata' => true,
        'log' => false,
        'quoteIdentifiers' => false,
        //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
        // DATABASE_URL を利用する場合はこちらで値を設定する
        'url' => env('DATABASE_URL', null),
    ],
],

なお、初回インストール時に SECURITY_SALT が自動で設定されていますが、これは .env から取得するので修正しておきます。

// config/app.php

'Security' => [
    # 'salt' => env('SECURITY_SALT', 'xxxxxx'),
    'salt' => env('SECURITY_SALT'),
],

composer install 時に自動で .env を生成する

// src/Console/Instraller.php

// app.php となっている箇所を .env に修正する

    /**
     * Create the config/.env file if it does not exist.  // app.php -> .env
     *
     * @param string $dir The application's root directory.
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
     * @return void
     */
    public static function createAppConfig($dir, $io)
    {
        $appConfig = $dir . '/config/.env'; // app.php -> .env
        $defaultConfig = $dir . '/config/.env.default'; // app.default.php -> .env.default
        if (!file_exists($appConfig)) {
            copy($defaultConfig, $appConfig);
            $io->write('Created `config/.env` file'); // app.php -> .env
        }
    }

    /**
     * Set the security.salt value in the application's config file.
     *
     * @param string $dir The application's root directory.
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
     * @return void
     */
    public static function setSecuritySalt($dir, $io)
    {
        $newKey = hash('sha256', Security::randomBytes(64));
        static::setSecuritySaltInFile($dir, $io, $newKey, '.env'); // app.php -> .env
    }

composer install

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Package phpunit/phpunit-mock-objects is abandoned, you should avoid using it. No replacement was suggested.
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump
> App\Console\Installer::postInstall
Created `config/.env` file
Set Folder Permissions ? (Default to Y) [Y,n]? Y
Updated Security.salt value in config/.env

app.default.php を削除

$ rm config/app.default.php

.gitignore を修正

# .gitignore

# CakePHP specific files #
##########################
# /config/app.php はバージョン管理に含めるので以下を削除
# /config/app.php
/config/.env
/logs/*
/tmp/*
/vendor/*

.env を編集する

# config/.env

export DATABASE_HOST="127.0.0.1"
export DATABASE_PORT=3306
export DATABASE_USERNAME="testuser"
export DATABASE_PASSWORD="password"
export DATABASE_NAME="test_db"
export DATABASE_ENCODING="utf8"
export DATABASE_TIMEZONE="Asia/Tokyo"

ローカルサーバを起動

./bin/cake server

http://localhost:8765 にアクセスします。
添付のような画面が表示され、データベース接続情報が正しい場合は Database 欄に CakePHP is able to connect to the database. と表示されることが確認できます。

まとめ

いかがだったでしょうか。
RDBMS や Cache のエンジンを変えるといった対応の場合は app.php に分岐を追加する必要はあります。
ただ、そういった場合でも環境変数を利用して分岐を追加し、app.php 自体はバージョン管理に含めてしまえば良いと考えています。
もっと良いやり方がある場合は教えていただけると嬉しいです。