CakePHP2.x系でバッチプログラムを作る | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

コマンドライン処理をPHPで作りたい場合、わざわざCakePHPでバッチプログラムを作らなくても、っていうのはあるかもしれませんがWebをCakePHPで作っているのであれば、そこで作ったコンポーネントやモデルなどを再利用できるため何かと便利だったりします。



CakePHP2.xでバッチプログラムの作り方


CakePHPのバッチプログラムは、下記のパスに保存します。


/path/to/cakephp/app/Console/Command

バッチプログラム名は、指定したいプログラム名に「Shell.php」をつけて定義します。

プログラム名が長い場合、キャメルケースを使って定義している方がよいでしょう(理由は後述)。

そして、バッチプログラム本体は、同ディレクトリ内に存在するAppShellクラスのサブクラスとして定義します。


<?php

class HogeDataImportShell extends AppShell {

    /**
     * バッチ開始前の処理
      */
    public function startup()
    {
        parent::startup();
        $this->cleanup();
    }

    /**
     * バッチのメイン処理
      */
    public function main() {
        $thi->out("バッチ処理を開始します");
    }

    /**
     * 独自のデータを初期化する処理
     */
    public function cleanup() {
        $this->foo = NULL;
        $this->bar = array();
    }

必要なのは、main()のみでこちらはバッチ実行時に自動的に呼び出される関数です。

startup()は必須ではありませんが、バッチ実行前に呼び出したい処理があればここに書いておきます。

cleanupは単に独自関数でバッチの処理の中でも共通で使うものや切り出したい処理があればバッチ内に関数を定義していくこともできます。


バッチの実行は下記のように、cakeコマンドを通して実行します。


$ /path/to/cakephp/app/Console/cake HogeDataImport dry-run
Welcome to CakePHP v2.3.7 Console
---------------------------------------------------------------
App : app
Path: /var/www/cakephp/app/
---------------------------------------------------------------
バッチ処理を開始します

cakeコマンドの後にプログラム名(Shell.phpを除く)を指定し、引数を渡したい場合はその後にスペース区切りで指定していきます(上記の場合は「dry-run」が引数)

または、下記のようにcakePHPによって認識されるアプリケーション名を指定しても実行できます。


$ cake hoge_data_import

アプリケーション名は、キャメルケースで書かれたプログラムをアンダーバーで区切ったものとして認識されているようで、cakeコマンドだけを実行してみるとアプリケーション名がはっきりわかります。


$ /path/to/cakephp/app/Console/cake

Welcome to CakePHP v2.3.7 Console
---------------------------------------------------------------
App : app
Path: /var/www/cakephp/app/
---------------------------------------------------------------
Current Paths:

 -app: app
 -working: /var/www/cakephp/app
 -root: /var/www/cakephp
 -core: /var/www/cakephp/lib

Changing Paths:

Your working path should be the same as your application path. To change your path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp

Available Shells:

[CORE] acl, api, bake, command_list, console, i18n, schema, server, test, testsuite, upgrade

[app] hoge_data_import, fuga_menu_import

To run an app or core command, type cake shell_name [args]
To run a plugin command, type cake Plugin.shell_name [args]
To get help on a specific command, type cake shell_name --help

上記の場合、hoge_data_import(プログラム名はHogeDataImport)とfuga_menu_import(プログラム名はFugaMenuImport)がCakePHPのバッチとして認識されています。



バッチプログラムを作るうえで覚えておくこと


これだけでバッチプログラムを作ることができるようになったわけですが、プログラムをより便利に作るために幾つか覚えておいた方がよいことがあります。


・ バッチで引数の受け取り方


まず、先ほどのバッチ実行時に指定した引数ですが、バッチプログラム内では下記のように引数を受け取ることができます。


if (preg_match("/^dry-run$/", $this->args[0]) === 1) {
    $this->dryRunMode = TRUE;
}


$this->args内に引数で指定した順番にデータが配列として格納されます。


・ バッチでモデルの使い方


Controllerで呼び出しているモデルを同様に使うことができます。

呼び出し方も同様です。


class HogeDataImportShell extends AppShell {
    public $uses = array('Foo', 'Bar');

    public function main() {
        $data = $this->Foo->find('first');
    }
}

メンバ変数として呼び出したいモデルを配列に格納しておきます。

使い方もControllerと同様ですね。


・ バッチでコンポーネントの使い方


Controllerの共通処理として定義しているコンポーネントをバッチからも呼び出せたら処理が統一できて非常に便利です。

幾つか呼び出し方はあるみたいですが、少し方法が複雑です。


App::uses('ComponentCollection', 'Controller');
App::uses('CommonComponent', 'Controller/Component');

class HogeDataImportShell extends AppShell {

    public function startup() {
        $collection = new ComponentCollection();
        $this->Common = new CommonComponent($collection);
    {
    public function main() {
        $this->Common->getData();
    }
}

上記のようにComponentCollectionを生成した上で、呼び出したいコンポーネント(CommonComponent)のオブジェクトを生成します。

オブジェクトが生成できれば使い方はControllerでの使い方と同様です。


・ バッチでコントローラの使い方


バッチからController内のメソッドを利用したいという場合は、下記のように利用可能です。


App::uses('HogeController', 'Controller');

class HogeDataImportShell extends AppShell {
    public function startup()
    {
        parent::startup();
        $this->cont = new HogeController();
    }

    public main()
    {
        $this->cont->searchData('foo');
    }
}

上記のようにApp:usesでControllerの呼び出しをすれば、後はオブジェクトを生成してそれを通してController内のメソッドを呼び出しできます。


・ サイレンとモードでの実行


これはおまけ的なものですが、CakePHPのバッチを実行すると最初にヘッダが出力されて少し煩わしいことがあります。


$ cake hoge
Welcome to CakePHP v2.3.7 Console
---------------------------------------------------------------
App : app
Path: /var/www/cakephp/app/
---------------------------------------------------------------

これの消し方として、「-q」のオプションを指定する方法があります。


$ cake -q hoge

ただし、この方法はバッチプログラム内で標準出力している($this->out)ものも出力されなくなってしまいますが・・・。

または、バッチプログラム内でstartup関数をオーバーライドして消す(parent::startup()を呼び出さない)という方法でも出力を抑制できたりします。