CakePHP3のbakeによるコード自動生成のプラクティス

  • 12
    Like
  • 0
    Comment

この記事は CakePHP3 Advent Calendar 2016 の 9日目の記事です。

CakePHP といえば必要なソースコードを自動生成してくれる bake コマンドがとっても便利です。CakePHP3 になってより使いやすくなりました。

普段の業務で使いはじめて結構経ちましたので、いくつかプラクティスを紹介してみたいと思います。(ベストとは書かない)

bake で生成したクラスは継承して使おう

bake で生成されるコードの中には DB スキーマの情報を元にして生成されるコード が含まれます。

なので、以下のようなケースのときに困ります。(時系列)

  1. DB に users テーブルを作成する
  2. bake model して UsersTable クラスを生成する
  3. 生成された UsersTable クラスに何らかの追加実装を行う
  4. DB の users テーブルにカラムを追加する
  5. スキーマ情報を元にして生成されたコードを更新するため、再度 bake model して上書きする
  6. UsersTable に追加した実装が実装が消え去る😢

このような事態を避けるため、自動生成されたコードは常に継承して使うようにしています。

継承するとこのような感じです。

src/Model/Entity/ExtUser.php
// Entity クラスの場合
class ExtUser extends User {
    // 省略
}
src/Model/Table/ExtUsersTable.php
// Table クラスの場合
class ExtUsersTable extends UsersTable
    public function initialize(array $config)
    {
        parent::initialize($config);

        // Entity クラスは自動生成されたクラスではなく継承したクラスを使う
        $this->entityClass('App\Model\Entity\ExtUser');
    }
{

自動生成クラスと拡張クラスを識別できるようにするため、クラス名に固定の接頭辞 Ext を付けるようにしています。名前空間も変えてしまった方がいいかもしれません。

同じような要領で、テストコードで使用する Fixture も継承して利用しています。Fixture のコードは DB スキーマを元にしたコードが多く含まれるため、継承して利用する方法が有効です。

詳しい方法は割愛しますが、イメージとしては下記のような感じです。

class ExtUsersFixture extends UsersFixture
{
    public function init()
    {
        // テストデータを上書き
        $this->records = [
            ['id' => 101, 'name' => '鈴木', 'age' => 24],
            ['id' => 102, 'name' => '上田', 'age' => 55],
            ['id' => 103, 'name' => '山本', 'age' => 17],
        ];
    }
}

これでいつでも Bakeable な状態を保てます🍰

bake のテンプレートをカスタマイズしよう

テンプレートファイルを src/Template/Bake/ 以下に設置するだけで簡単に生成されるコードの内容を書き換えられるので、必要に応じてどんどんカスタマイズするといいと思います。

具体的な方法は他のサイトでも紹介されていますのでここでは割愛します。

自動生成されるクラスの継承関係にワンクッション挟もう

Bake でテーブルクラスを生成すると Cake\ORM\Table を継承した状態でクラスが生成されるため、テーブルクラスの共通処理を挟み込む余地 がありません。

そこで Bake のカスタマイズして、App\Model\AppTable など自分のクラスを継承させてワンクッション挟むようにしておくと便利です。

src/Model/Table/UsersTable.php
class UsersTable extends Table {}

👇 (テンプレート書き換え後)

src/Model/Table/UsersTable.php
class UsersTable extends AppTable {}

ワンクッション挟んだあとの最終的な継承関係は以下のようになりました。

  • App\Model\ExtUsersTable
    • 👇 継承
  • App\Model\UsersTable (bake で自動生成されるクラス)
    • 👇 継承
  • App\Model\AppTable (ワンクッション挟み込んだクラス)
    • 👇 継承
  • Cake\ORM\Table

全テーブルの共通処理を AppTable クラスに、テーブル固有のビジネスロジックを ExtUsersTable に実装していきます。生成された UsersTable は編集しません。

bake の実行を自動化しよう

ここまでやるとスキーマ更新後の bake コマンドの実行も自動化したくなりませんか?

CakePHP3 のマイグレーション を使用している前提ですが、マイグレーションコマンド実行後に bake コマンドが実行されるようにすれば自動化出来ます。

今回はそれを行うシェルスクリプトを作ってみました。bin/cake migrations をオーバーラップしたスクリプトなので、引数などの仕様は bin/cake migrations と同じになります。

bin/migration_bake.sh
#!/bin/bash -eu

readonly CAKE_BIN_DIR=$(cd $(dirname $0); pwd)

# マイグレーション実行 (引数は全て引き渡す)
${CAKE_BIN_DIR}/cake migrations $@

if [ $# -eq 0 ]; then
  echo "コマンド名が指定されていないため終了します。"
  exit
fi

# マイグレーションコマンド名
# e.g. create, dump, help, list, mark_migrated, migrate, rollback, seed, status
readonly COMMAND_NAME=$1


if ! [ ${COMMAND_NAME} = "migrate" -o ${COMMAND_NAME} = "rollback"  ]; then
  echo "migrate か rollback コマンドではないため bake せずに終了します。"
  exit
fi

# 全テーブルを対象として bake 実行 (Table, Entity, Fixture, Test が生成される)
# --force を付けると非インタラクティブに実行できる
${CAKE_BIN_DIR}/cake bake model all --force

使い方の例:

  • マイグレーションする場合: bin/migration_bake.sh migrate
  • ロールバックする場合: bin/migration_bake.sh rollback

まとめ

bake でどんどん仕事を減らしていきましょう🍰

参考リンク