CakePHP 4 で別フォルダに置くコントローラを Bake で作る

はじめに

今回は下記2つの投稿の続きで、CakePHP 4 で別フォルダに設置するコントローラを Bake で自動生成する方法をご紹介します。

検証に使用したのは CakePHP 4.1.5 (cakephp/app 4.1.1) です。

目次
  1. 下準備
  2. Bake テンプレート追加
  3. 独自の Bake オプションを追加
  4. テスト生成
  5. おわりに

1. 下準備

今回もこれまで同様に /src/Ajax の中に Ajax 用のコントローラを格納するケースを想定します。

最終的には下記のようなファイル構成になります。

/path/to/cakephp4/
  ├ src/
  │  ├ Ajax/
  │  │  └ AppController.php
  │  │
  │  ├ Command/
  │  │  ├ AjaxCommand.php
  │  │  └ AjaxTestCommand.php
  │  (他省略)
  │
  ├ templates/
  │  ├ bake/
  │  │  ├ Ajax/
  │  │  │  └ controller.twig
  │  (他省略)
  │
  ├ tests/
  │  ├ TestCase/
  │  │  ├ Ajax/
   (他省略)

Ajax/AppController.php は「CakePHP 4 で一部のコントローラを別フォルダに設置する方法」でご紹介した内容と同じですが、念のため掲載しておきます。

namespace は App\Controller\Ajax にしています。

/src/Ajax/AppController.php
<?php
declare(strict_types=1);

namespace App\Controller\Ajax;

use Cake\Controller\Controller;

class AppController extends Controller
{
}

AppController 以外のファイルにつきましては、後ほどご紹介します。

2. Bake テンプレート追加

Ajax 用コントローラの Bake テンプレートを用意します。

/templates/bake/Ajax/controller.twig
<?php
declare(strict_types=1);

namespace {{ namespace }}\Controller\Ajax;

class {{ name }}Controller extends AppController
{
    public function index()
    {
        $vars = [];

        $this->set($vars);
        $this->viewBuilder()
            ->setClassName('Json')
            ->setOption('serialize', array_keys($vars))
            ->setOption('jsonOptions', JSON_FORCE_OBJECT);
    }
}

今回はシンプルにするために plugin や prefix のコードなどを除外しています。
必要に応じて /vendor/cakephp/bake/templates/bake/Controller/controller.twig などを参考に実装してください。

3. 独自の Bake オプションを追加

Ajax 用コントローラを自動生成するための Bake コマンドのオプションを作ります。

今回はオプション名を ajax にしています。

/src/Command/AjaxCommand.php
<?php
declare(strict_types=1);

namespace App\Command;

use Bake\Command\SimpleBakeCommand;

class AjaxCommand extends SimpleBakeCommand
{
    public $pathFragment = 'Ajax/';

    public function fileName($name): string
    {
        return "{$name}Controller.php";
    }

    public function name(): string
    {
        return 'Ajax';
    }

    public function template(): string
    {
        return 'Ajax/controller';
    }
}

$pathFragment は出力先のフォルダで、上記だと /src/Ajax にコントローラが生成されます。

fileName() では Bake で生成するファイル名を指定します。

name() の戻り値は、後ほどテストコードを Bake する際に使用します。

template() はテンプレートファイルのパスで、/templates/bake/Ajax/controller.twig を指しています。

この AjaxCommand.php を追加すると、下記のように Bake コマンドのオプション一覧に ajax が表示されます。

$ cd /path/to/cakephp4
$ bin/cake bake -h
...
App:
 - bake ajax
...

あとは下記のようにコマンドを実行すれば /src/Ajax にコントローラが作られます。

$ bin/cake bake ajax SampleOne

できあがったコントローラの $var を下記のように変更し、
ブラウザで /ajax/sample-one にアクセスして動作確認をしてください。

config/route.php の変更をしていない方は、前々回の記事を参考に追記してください。

/src/Ajax/SampleOneController.php
$vars = ['msg' => 'This is sample one'];

4.テスト生成

通常の Bake コマンドでは --no-test オプションをつけない限り、テストファイルも自動生成されます。
今回追加した ajax オプションでもテストを自動生成するように機能を追加します。

まずは下記 AjaxTestCommand.php を作ります。

/src/Command/AjaxTestCommand.php
<?php
declare(strict_types=1);

namespace App\Command;

use Bake\Command\TestCommand;
use Cake\Core\Configure;

class AjaxTestCommand extends TestCommand
{
    public function testCaseFileName(string $type, string $className): string
    {
        $namespace = $this->plugin ?? Configure::read('App.namespace');

        $base = $this->getBasePath();
        $file = str_replace("{$namespace}\\Controller\\", '', $className). 'Test.php';
        $path = str_replace(['/', '\\'], DS, "{$base}{$file}");

        return $path;
    }
}

これはテストファイルの出力先を /tests/TestCase/Ajax に変更するためのもので、
\vendor\cakephp\bake\src\Command\TestCommand.php の
testCaseFileName() をオーバーライドしています。

次に AjaxCommand.php を下記のように変更します。

/src/Command/AjaxCommand.php
...
use Bake\Command\SimpleBakeCommand;
// ↓ 追加
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

class AjaxCommand extends SimpleBakeCommand
{
    ...
    // ↓ 追加
    public function bakeTest(string $className, Arguments $args, ConsoleIo $io): void
    {
        if ($args->getOption('no-test')) {
            return;
        }
        $test = new AjaxTestCommand();
        if (!isset($test->classSuffixes[$this->name()])) {
            $test->classSuffixes[$this->name()] = 'Controller';
        }

        $name = ucfirst($this->name());
        if (!isset($test->classTypes[$name])) {
            $test->classTypes[$name] = 'Controller\Ajax';
        }

        $test->plugin = $this->plugin;
        $test->bake($this->name(), $className, $args, $io);
    }
}

この bakeTest() については下記公式ドキュメントのやや下の方にサンプルがあります。

以上でテスト生成の機能追加は完了です。

先ほど同じように bake ajax コマンドを実行してください。
/tests/TestCase/Ajax/SampleTwoControllerTest.php が生成されます。

$ bin/cake bake ajax SampleTwo

Creating file /path/to/cakephp4/src/Ajax/SampleTwoController.php
Wrote `/path/to/cakephp4/src/Ajax/SampleTwoController.php`

Baking test case for App\Controller\Ajax\SampleTwoController ...

Creating file /path/to/cakephp4/tests/TestCase/Ajax/SampleTwoControllerTest.php
Wrote `/path/to/cakephp4/tests/TestCase/Ajax/SampleTwoControllerTest.php`

あとは適宜テストコードを変更して、下記コマンドなどで実行してみてください。

$ vendor/bin/phpunit tests/TestCase/Ajax

なお、この機能を追加しても --no-test をつけて bake すればテストは作られません。

$ bin/cake ajax SampleThree --no-test

5. おわりに

Bake オプションを自作できる仕組みがあるのは便利ですね。
今まで使ったことが無かったので勉強になりました。

自動化プログラムも使用するほどスキルが上がり、だんだん速く実装できるようになります。そうすると、例えば「ファイルコピペして手直し」よりも短時間で作れたり、ミスが減って品質向上に繋がる場合があります。

そういったチャンスが来たときにすぐ使えるように、
普段から少しずつ Bake を使って慣れるようにすると良いのかなと考えています。

さて Bake といえばテンプレートをプラグインとして実装する「Bake Theme」の機能があります。
次はそれを使って実装できるか試してみようかな、と思っています。