目次

目次

CakePHPを用いたバッチ開発

アバター画像
早瀬 大智
アバター画像
早瀬 大智
最終更新日2022/12/25 投稿日2022/12/25

はじめに

株式会社レコチョクでバックエンドエンジニアをしている早瀬です。 新卒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を終了したいと思います。 ここまでお付き合いいただき、誠にありがとうございました。

アバター画像

早瀬 大智

目次