はじめに
株式会社レコチョクでバックエンドエンジニアをしている早瀬です。
新卒3年目で、普段はmurketのフロントやAPIの開発を主に行っています。
好きなバンドは UNISON SQUARE GARDEN, ill hiss clover, phatmans after schoolです。
先日CakePHPのコマンドオブジェクトを初めて触ったので、本記事ではコマンドオブジェクトの使い方などについて触れていこうかと思います。
本記事で取り扱うCakePHPは4.x系です。
コマンドオブジェクト
CakePHPでバッチを作成する際には、コマンドオブジェクトを使用します。
コマンドオブジェクトはbakeコマンドを実行することで作成することができます。
Hello Worldを実装してみる。
例としてHello World!を実装してみます。
以下コマンドを実行します。
$ ./bin/cake bake command hello
すると、src/command
ディレクトリの中にHelloCommand.php
が生成されます。
以下コードがコマンドを実装する際の雛形になります。
(tests/TestCase/Command
ディレクトリの中にHelloCommandTest.php
も生成されますが、今回は触れないものとします。)
<?php
declare(strict_types=1);
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
/**
* Hello command.
*/
class HelloCommand extends Command
{
/**
* Hook method for defining this command's option parser.
*
* @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options
* @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
* @return \Cake\Console\ConsoleOptionParser The built parser.
*/
public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser = parent::buildOptionParser($parser);
return $parser;
}
/**
* Implement this method with your command's logic.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return null|void|int The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
}
}
Hello Worldを実装するためにexecute()
の中身に以下を追加します。
public function execute(Arguments $args, ConsoleIo $io)
{
+ $io->out('Hello world.');
}
ターミナルで以下コマンドを実行すると実行結果はこのようになると思います。
$ ./bin/cake hello
Hello world.
コマンドオブジェクトを実行する場合は ./bin/cake hello
のように、クラス名から"Command"を除いた部分をキャメルケースで入力することで実行できます。
クラス名がreplaceTitleCommand
の場合は ./bin/cake replaceTitle
という形で入力すると実行できます。
オプションで処理を切り替える
コマンドオブジェクトはオプションを用いることで、処理を分岐させることができます。
以下プログラムは検証環境と本番環境によって処理を切り替える例です。
<?php
declare(strict_types=1);
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
/**
* Switch command.
*/
class SwitchCommand extends Command
{
/**
* Hook method for defining this command's option parser.
*
* @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options
* @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
* @return \Cake\Console\ConsoleOptionParser The built parser.
*/
public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser = parent::buildOptionParser($parser);
$parser->addOption('isProduction', [
'help' => 'true if production environment.',
'boolean' => true,
]);
$parser->addOption('numServer', [
'help' => 'number of servers',
'default' => 1,
]);
$parser->addArgument('configFile', [
'help' => 'configFile path.',
]);
return $parser;
}
/**
* Implement this method with your command's logic.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return null|void|int The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
$isProduction = $args->getOption('isProduction');
$numServer = $args->getOption('numServer');
$configFile = $args->getArgument('configFile') ?? 'default.conf';
$message = 'This Environment is Development.';
if ($isProduction) {
$message = 'This Environment is Production.';
}
$io->out($message);
$io->out($numServer);
$io->out($configFile);
}
}
上記ファイルを実行すると以下のようになります。
$./bin/cake switch
This Environment is Development.
1
default.conf
$ ./bin/cake switch --isProduction --numServer=8 custom.conf
This Environment is Production.
8
custom.conf
buildOption()
buildOptionsはコマンドラインで指定できるオプションの設定を記載します。
public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser = parent::buildOptionParser($parser);
$parser->addOption('isProduction', [
'help' => 'true if production environment.',
'boolean' => true,
]);
$parser->addOption('numServer', [
'help' => 'number of servers',
'default' => 1,
]);
$parser->addArgument('configFile', [
'help' => 'configFile path.',
]);
return $parser;
}
addOption()の第1引数で使用したいオプション名を、第2引数ではオプションを指定できます。
また、addArgument()は引数として取る値の設定をできます。
addOption(),addArguments()の第2引数でhelp
を設定すると、--help
を実行した際に説明を表示できます。
$ ./bin/cake switch --help
Usage:
cake hello [-h] [--isProduction] [-q] [-v] [<configFile>]
Options:
--help, -h Display this help.
--isProduction true if production environment.
--quiet, -q Enable quiet output.
--verbose, -v Enable verbose output.
Arguments:
configFile configFile path. (optional)
execute()
executeはコマンドラインで実行する際に実際に行われる処理を記述する場所です。
/**
* Implement this method with your command's logic.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return null|void|int The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
$isProduction = $args->getOption('isProduction');
$numServer = $args->getOption('numServer');
$configFile = $args->getArgument('configFile') ?? 'default.conf';
$message = 'This Environment is Development.';
if ($isProduction) {
$message = 'This Environment is Production.';
}
$io->out($message);
$io->out($numServer);
$io->out($configFile);
}
getOption(<オプション名>)を実行することでオプションの値を取ることができ、getArgument(<引数名>)を使用すると引数の値を取得できます。
addOption()の第2引数でboolean => true
を選択していた場合、オプションが指定されていればtrue, 指定されていなければfalseになります。
booleanを指定していない場合は、コマンドラインから直接値を受け取ることができます。
DBを利用する
バッチ開発をするにあたって、サービスで使っているデータベースの中身を書き換えるようなバッチを作成したいと言う場合もありえます。
コマンドオブジェクトではTableオブジェクトを使用することができるので、サービスで使用中のDBに対しても処理を行うことも可能です。
Tableオブジェクトが存在しない場合はbakeコマンドから作成することができます。
<?php
declare(strict_types=1);
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
/**
* Database command.
*/
class DatabaseCommand extends Command
{
/*
* @var \App\Model\Table\xxxTable
*/
private $xxxTable;
/**
* initialize
*
* @return void
*/
public function initialize(): void
{
parent::initialize();
$this->xxxTable = TableRegistry::getTableLocator()->get(<テーブルオブジェクト名>);
}
/**
* Implement this method with your command's logic.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return null|void|int The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
$data = $this->xxxTable->find()->first();
$io->out($data);
}
}
テーブルオブジェクトの取得はinitialize()で実行すると、後述処理が楽に記述できます。
initialize()はオブジェクトのコンストラクタに当たる部分で、execute()が実行される前に呼び出される処理です。
getTableLocatorを使用してテーブルオブジェクトを呼び出すことで、実務で使用しているDBにアクセスすることができます。
Componentを使用する
コマンドオブジェクトからテーブルオブジェクトにアクセスできるように、コマンドオブジェクトからComponentへのアクセスも可能です。
<?php
declare(strict_types=1);
namespace App\Command;
use App\Controller\Component\xxxComponent;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
/**
* Component command.
*/
class ComponentCommand extends Command
{
/**
* @var \App\Controller\Component\xxxComponent
*/
protected $xxxComponent;
/**
* initialize
*
* @return void
*/
public function initialize(): void
{
parent::initialize();
$this->xxxComponent = new xxxComponent(new ComponentRegistry());
}
/**
* Implement this method with your command's logic.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return null|void|int The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
$response = $this->xxxComponent->methodName()
$io->out($response);
}
}
ComponentRegistryを用いることで、サービスで利用しているComponentを呼び出すこともできます。
こちらも他の関数で使用できるようにinitialize()で呼び出すと、プライベート関数内でも呼び出すことができるため便利です。
まとめ
CakePHPのコマンドオブジェクトを用いたバッチ開発方法をまとめました。
特にComponentの使用については公式ドキュメントに記載されていないので、CakePHP初学者は詰まるポイントの1つかと思われます。
バッチをCakePHPで実装しようとしている人の手助けになれば幸いです。
最後まで読んでいただきありがとうございました。
本記事をもってレコチョク Advent Calendar 2022を終了したいと思います。
ここまでお付き合いいただき、誠にありがとうございました。