tsyama記

プログラミングとそのほか

CakePHP bakeプラグインのすすめ

はじめに

こちらはCakePHP Advent Calendar 2018 - Qiita 4日目の記事です。
昨日は@tenkomaさんのCakePHP2 アプリでも PHPUnit と PhpStormを連携させるでした。


CakePHPerのみなさんは普段bakeコマンドを活用しているでしょうか?

bakeコマンドはCakePHPで必要な各種ファイルを自動生成してくれます。Webフレームワークだとよくあるやつですね。
今回はこのbakeコマンドを拡張するプラグインを作ってみようという話をします。

bakeプラグインはある程度決まった処理の組み合わせで簡単に作成することができます。特にまだプラグインを作ったことがないという人は、プラグインの作成からリリースまでの流れを通してcomposerを軸としたPHPのエコシステムを手軽に体験してみましょう。

プラグインの始め方

参考として今回は、bakeコマンドで生成する各種Entityクラスの基底クラスとなるAppEntityクラス、そしてTableクラスの基底クラスとなるAppTableクラスを作成するためのbakeコマンドを作成してみます。

まずはプラグインの始め方です。CakePHPではpluginもbakeコマンドから作り始めることができます。

bin/cake bake plugin AppClassBaker

これでpluginsディレクトリ以下にAppClassBakerディレクトリが作成されます。このディレクトリがプラグインの本体になるので、あらかじめ大元のCakePHPプロジェクトとは別にGit管理しておいてください。後ほどこいつをPackagistに登録しなければなりません。

ちなみにGitHubに置くリポジトリの名前はtsyama/cakephp-app-class-bakerとしました。
prefixをcakephp-にするとか、ケバブケースとか、CakePHPプラグインの推奨されている命名規約があるようなので、特に理由がなければ従っておきましょう。

https://book.cakephp.org/3.0/ja/intro/conventions.html#id7

あとはプラグインの中に自動生成されるcomposer.jsonもあわせてパッケージ名を変更しておきましょう。これを正しく設定しないとあとでPackagistに登録するときに怒られてしまいます。

{
    "name": "tsyama/cakephp-app-class-baker",
    "description": "AppClassBaker plugin for CakePHP",
    "type": "cakephp-plugin",
    "license": "MIT",
...

bakeプラグインを作る

今回はbin/cake bake app_entityコマンドで処理を使えるようにしてみます。
CakePHPでは簡単にbakeコマンドを作るためのSimpleBakeTaskクラスが用意されています。

bakeTask作成

先程作成したプラグイン内にplugins/AppClassBaker/src/Shell/Task/AppEntityTask.phpを作り、SimpleBakeTaskを拡張したクラスを作成します。

<?php
namespace AppClassBaker\Shell\Task;

use Bake\Shell\Task\SimpleBakeTask;

class AppEntityTask extends SimpleBakeTask
{
    public $pathFragment = 'Model/Entity/';

    public function name()
    {
        return 'app_entity';
    }

    public function fileName($name)
    {
        return 'AppEntity.php';
    }

    public function template()
    {
        return 'AppClassBaker.Model/Entity/app_entity';
    }
}

SimpleBakeTaskには3つのメソッドを実装する必要があります。それぞれ下記の文字列を返すようにします。

  • name()
    • 該当のbakeコマンドで作成されるファイルの総称を返します(Entityファイルを作成するbakeタスクにはentity、Tableならtableなど)
    • 生成されるファイル自体には影響を与えないようですが、コマンド実行時のログに使用されます
  • fileName($name)
    • bakeコマンドで生成されるファイルの名前を返します
    • bakeコマンドに渡された引数が$nameに渡されます
  • template()
    • 後述するテンプレートファイルのパスを返します

bakeTemplate作成

あわせてbakeTaskで呼び出すbakeTemplateも作成します。
プラグイン内のplugins/AppClassBaker/src/Template/Bake/Model/Entity/にapp_entity.twigを作成します。

<?php
namespace {{ namespace }}\Model\Entity;

use Cake\ORM\Entity;

class AppEntity extends Entity
{
}

bakeテンプレートファイルはPHPテンプレートエンジンのTwigを使用して作成します。今回はそこまで動的要素のないテンプレートファイルを作成しましたが、作り込みたい場合は事前にTwigの記述を予習しておくと良いでしょう。

ここまでできれば、すでにbakeコマンドが実行できるはずです。

bin/cake bake app_entity x

を実行してみましょう。プラグイン外のsrc/Model/Entity/にAppEntity.phpが生成されます。やりましたね。(SimpleBakeTaskを継承するとコマンドの引数が必須になってしまうため、仮の引数を入れています)

プラグインをちょっと作り込む

このままでも一応プラグインの体はなしていますが、もう少しブラッシュアップしていきましょう。

引数なしで実行できるようにする

先述の不要な引数問題のように、SimpleBakeTaskクラスではとても簡単にbakeタスクが作成できる代わりに、タスクの融通がほとんど効きません。タスクを作り込みたい場合はBakeTaskクラスを使用しましょう。先程作成したものを下記のように書き換えました。

<?php
namespace AppClassBaker\Shell\Task;

use Cake\Console\Shell;
use Bake\Shell\Task\BakeTask;

class AppEntityTask extends BakeTask
{
    public $pathFragment = 'Model/Entity/';

    public $tasks = [
        'Bake.BakeTemplate',
    ];

    public function main($name = null)
    {
        parent::main();
        $this->bake();
    }

    public function bake()
    {
        $this->out("\n" . "Baking app-entity files...", 1, Shell::QUIET);

        $out = $this->bakeAppEntity();
        return $out;
    }

    public function bakeAppEntity()
    {
        $contents = $this->BakeTemplate->generate('AppClassBaker.Model/Entity/app_entity');
        $path = $this->getPath();
        $filename = $path . 'AppEntity.php';
        $this->createFile($filename, $contents);

        return $contents;
    }
}

作りたいプラグインに合わせていろいろ書き換えてみましょう。

既存のEntityテンプレートを修正する

さらに、bin/cake bake modelで生成されるEntityファイルについて、EntityクラスではなくAppEntityクラスを継承するようにしましょう。
既存のbakeコマンドのテンプレートを書き換えるだけであれば、plugins/AppClassBaker/src/Template/Bake/Model/entity.twigにテンプレートを置くだけです。

作成したテンプレートは、bin/cake bake model articles --theme=AppClassBakerのようにすると呼び出すことができます。また、いちいち--themeオプションをつけたくない場合は、bootstrap.phpに下記の記述を追記しておくことでデフォルトのテーマに設定することができます。

Configure::write('Bake.theme', 'AppClassBaker');

プラグインを公開する

あとはライブラリとしてcomposerでインストールできるように公開作業を行います。
あらかじめGitHubにpushしておいてください。

packagist.org

PackagistではGitHubアカウントでログインができます。
ログインしたら"Submit"からリポジトリの登録を行います。

f:id:tsyama-desu:20181202151533p:plain

リポジトリ命名などに問題がなければ1ポチで登録が完了します。

最後にローカルでpluginsに作成したリポジトリを一旦退避し、composer経由でinstallしてみて動作を確認しましょう。

おわりに

先述の通り、bakeを拡張するプラグインはできることが決まっているぶん、比較的簡単に実装できるようになっています。基本的にファイルを生成するだけなので本番環境のvendorに含める必要もないですし、自分用のくだらないプラグインを貯め込んで地味な効率化を積み重ねていくのも面白いかもしれませんね。

以上、本日はCakePHPのbakeプラグインを作ってみようぜという話題でした。

5日目の明日は@yKanazawaさんです!