CakePHP3はウェブフレームワークとしてだけでなく、コマンドラインアプリケーションを開発するためのフレームワークも備えている。開発者はCake\Console\Shell
を継承することで、CLIアプリのコマンドを実装できる。
CakePHPのコンソールは、サブコマンドを実装する仕組み、処理を再利用可能にするためにタスクという仕組みを提供している。また、シェルの起動時に呼び出されるフックメソッドというものがある。
タスクはCake\Console\Shell
を継承したクラスで、コマンドクラスの$tasks
にクラス名を追加するとそのコマンドのコンポーネントとして使うことができるようになる。さらに、そのタスクをサブコマンドとして宣言すると、コンソールからタスククラスを呼び出せるようになる。
final class FooShell extends Shell
{
public $tasks = ['Bar', 'Hoge']; // BarTaskとHogeTaskクラスをタスクとして登録
public function getOptionParser()
{
$parser = parent::getOptionParser();
// サブコマンドとして宣言
$parser->addSubcommand('bar', [
'parser' => $this->Bar->getOptionParser(),
]);
$parser->addSubcommand('hoge', [
'parser' => $this->Hoge->getOptionParser(),
]);
return $parser;
}
}
フックメソッドはシェルの起動時に何かを初期化したりする目的に用意されているものだ。今のところCakeには2つのフックメソッドがある。
-
Cake\Console\Shell::initialize
シェルを初期化し、サブクラスのコンストラクターとして動作します。またシェルの実行に先立って、 タスクの設定を行います。
-
Cake\Console\Shell::startup
シェルを起動して、ウェルカムメッセージを表示します。 コマンドや main の実行に先立ってチェックや設定を可能とします。
タスクのフックメソッドとシェルのフックメソッドの呼び出される順はどうなるか?
「タスク」に「サブコマンド」そして「フックメソッド」を組み合わせるとこのような疑問が湧いてきた。公式ドキュメントを見た限りこの疑問に対する答えはなさそうである。そこで、どの順で呼び出されるのか調査してみた。なおCakePHPのバージョンは3.5.15である。
まずコマンドクラスを作る:
<?php
declare(strict_types=1);
namespace App\Shell;
use App\Shell\Task\BarTask;
use App\Shell\Task\HogeTask;
use Cake\Console\Shell;
/**
* @property-read BarTask $Bar
* @property-read HogeTask $Hoge
*/
final class FooShell extends Shell
{
public $tasks = ['Bar', 'Hoge'];
public function initialize()
{
$this->info(__METHOD__);
parent::initialize();
}
public function startup()
{
$this->info(__METHOD__);
parent::startup();
}
public function main()
{
$this->info(__METHOD__);
}
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->addSubcommand('bar', [
'parser' => $this->Bar->getOptionParser(),
]);
$parser->addSubcommand('hoge', [
'parser' => $this->Hoge->getOptionParser(),
]);
return $parser;
}
}
つぎに、タスククラスを2つ作る。ひとつめ:
<?php
declare(strict_types=1);
namespace App\Shell\Task;
use Cake\Console\Shell;
final class BarTask extends Shell
{
public function initialize()
{
$this->info(__METHOD__);
parent::initialize();
}
public function startup()
{
$this->info(__METHOD__);
parent::startup();
}
public function main()
{
$this->info(__METHOD__);
}
}
ふたつめ:
<?php
declare(strict_types=1);
namespace App\Shell\Task;
use Cake\Console\Shell;
final class HogeTask extends Shell
{
public function initialize()
{
$this->info(__METHOD__);
parent::initialize();
}
public function startup()
{
$this->info(__METHOD__);
parent::startup();
}
public function main()
{
$this->info(__METHOD__);
}
}
--help
を実行してみる。コマンド→サブコマンド1→サブコマンド2の順でinitialize
が呼び出された。
$ cake foo --help
App\Shell\FooShell::initialize
App\Shell\Task\BarTask::initialize
App\Shell\Task\HogeTask::initialize
Usage:
cake foo [subcommand] [-h] [-q] [-v]
Subcommands:
bar
hoge
To see help on a subcommand use `cake foo [subcommand] --help`
Options:
--help, -h Display this help.
--quiet, -q Enable quiet output.
--verbose, -v Enable verbose output.
コマンドのmain
を実行してみる。すると、全コマンドのinitialize
→コマンドのstartup
の順で呼び出された。
$ cake foo
App\Shell\FooShell::initialize
App\Shell\Task\BarTask::initialize
App\Shell\Task\HogeTask::initialize
App\Shell\FooShell::startup
App\Shell\FooShell::main
次にサブコマンドのmain
を実行してみる。全コマンドのinitialize
→ 親コマンドのstartup
→サブコマンドのstartup
の順になった。ここで注目したいのが、サブコマンドを実行したときでも親コマンドのstartup
が呼び出されるところである。
$ cake foo bar
App\Shell\FooShell::initialize
App\Shell\Task\BarTask::initialize
App\Shell\Task\HogeTask::initialize
App\Shell\FooShell::startup
App\Shell\Task\BarTask::startup
App\Shell\Task\BarTask::main
# 結論
まとめると、CakePHPのコマンドとサブコマンドになったタスクのフックメソッドの呼び出し順は次のとおりである。
- コマンドを実行したとき
- 親コマンドの
initialize
- 全タスクの
initialize
- 親コマンドの
startup
- 親コマンドの
main
- 親コマンドの
- サブコマンドを実行したとき
- 親コマンドの
initialize
- 全タスクの
initialize
- 親コマンドの
startup
- サブコマンド(タスク)の
startup
- サブコマンド(タスク)の
main
- 親コマンドの
このうち、特筆すべきは後者のサブコマンドを実行したときに、親コマンドのstartup
が呼び出されることだ。startup
を実装するときはサブコマンドが実行されても差し支えないように考慮する必要があるだろう。