【CakePHP 2.x】シェルクラスを使った非同期処理

過去の内容が分かりづらかったので、大幅に修正しました。
過去の内容は一番最後に残しています。


シェルクラスを使うメリットとしては、
Modelが使えるところだと思う。
既存処理を使いまわせるのは便利。
ということで、CakePHPでシェルクラスを使った非同期処理をまとめます。

非同期でPHPスクリプトを実行したいだけなら、
以下でOKだと思います。
http://d.hatena.ne.jp/pospome/20130209/1360388825


まずはCakePHPシェルを実行させるためにPathを通す。
Pathを通さなくてもフルパス指定してシェルを実行させることもできるが、
勉強がてらPathを通す。

「/etc/profile」の最後に以下を追記する。

                    • -

export PATH=$PATH:/var/www/html/lap_crawler_system/app/Console

                    • -

Pathはサーバへのログイン毎に再読み込みされるので、追記したらログインし直す。
ログインし直したら、テキトーな場所で cake --help を実行してみる。
Helpが表示されれば成功。


シェルクラスを作る。
シェルクラスは「app/Console/Command」に「MyTestShell.php」のような名前で作る。
最後に Shell と付けるのがポイント。
内容は以下のようにする。

<?php
class MyTestShell extends AppShell {

    public function main() {
		echo 'main!';
    }

	public function myAction(){
		echo 'my action!';
		echo $this->args[0];
	}
}


実行するにはクラス名をスネークケースにしてcakeコマンドを実行する。
#cake my_test

cakeに実行権限がなくてエラーになる場合は権限を与える。
シェルクラスに main() を作ると、そこが自動的にエントリーポイントになる。

main() 以外のメソッドを実行する場合はコマンド実行時に指定する。
メソッドはスネークケースにする必要はない。
そのままでいい。
#cake my_test myAction

引数もコマンド実行時に指定する。
#cake my_test myAction myArgs

これでシェルクラスは完成。
シェルクラスではモデル、コンポーネント、コントローラーも使用可能。
モデルは $uses を定義する。
コンポーネントは ComponentCollection と使用したいコンポーネントを読み込んで、
インスタンス化する。
今回はクラス内のどこからでも呼び出せるようにstartup()でメンバ変数にしておく。
局所的に使うのであれば、使うとこでインスタンス化してもいい。
コントローラーも同じ。

コンポーネントの設定は以下のサイトを参照させていただきました。
ありがとうございました。
http://ihiro81.hatenablog.jp/entry/20111110/1320935758

<?php
App::uses('ComponentCollection', 'Controller');
App::uses('TestComponent', 'Controller/Component'); //コンポーネントのクラス名を指定する。
App::uses('AppController', 'Controller'); //使いたいコントローラー
class TestShell extends Shell {
    $users = array('User');
    public function startup() {
        $collection = new ComponentCollection();
        $this->Test = new TestComponent($collection); //コンポーネントをインスタンス化
        $this->App = new AppController(); //コントローラーをインスタンス化
        parent::startup();
    }
    public function main(){
        $this->Test->send(); //コンポーネントを使う
        $this->App->getNum(); //コントローラーを使う
    }
}

あとは非同期で実行してあげるだけ。
非同期で実行するにはPHPの exec() を使う。
シェル実行用のテストスクリプト「test.php」を用意する。
内容はシェルを非同期で実行させるだけ。

<?php
exec('cake my_test > /dev/null &');

次に、CakePHPシェルクラスを修正する。

<?php
class MyTestShell extends AppShell {
    public function main() {
        sleep(5);
        $fp = fopen('/var/www/html/cakephp_dir/app/Console/Command/aaa.txt', 'w');
        fclose($fp);
    }
}

5秒スリープしてからファイルを作らせるだけ。
echoを使ってもコンソール上に表示されないので、
ファイル作成で処理が実行されているかを確認する。

あとは、テストスクリプトの test.php を実行するだけ。
# php test.php

これで5秒後にファイルが作成されるはず。
test.phpのexec()をControllerで実行すればシェルを非同期で実行できるが、
cakeコマンドをフルパス指定する必要がある。

exec('/var/www/html/cakephp_dir/app/Console/cake my_test > /dev/null &');

以上です。


以下は過去の内容になります。
我ながら分かりづらい・・・・。


【過去の内容】
バックグラウンドの非同期処理はPHPの exec() を使えばできるが、
CakePHPのコードをそのまま流用したかったので、
Modelを使う必要があった。


調べてみると、CakePHPにはバッチ用のシェルクラスがあったので、
それを使用する。

「/cakephp/app/Console/Command/」直下に「TestShell.php」を作る。
クラスの命名規則として最後に「Shell」を付ける必要がある。

<?php
class TestShell extends AppShell {
    var $uses = array('Model');
    public function testAction(){
        //任意の処理
    }
}


あとは実行するだけ。
今回は実行するのにPHPの exec() にコマンドを渡す。
コマンドは以下になる。

php var/www/html/cakephp/app/Console/cake.php -app /var/www/html/cakephp/app/ test testAction /dev/null >/dev/null &

phpコマンドで、Consoleディレクトリ内の cake.php を実行して、
引数に appディレクトリへのパスと
「Shell」抜きのクラスファイル名とアクション名を渡す。

シェルに引数を与える場合は、
アクション名の後に引数を渡す。

php var/www/html/cakephp/app/Console/cake.php -app /var/www/html/cakephp/app/ test testAction hikisuu1 hikisuu2 /dev/null >/dev/null &

コントローラーでは引数が「$this->args」に配列形式で入っているので、
ここから利用できる。

これでOK