CodeIgniterの学習 52 - CodeIgniterをバッチとして呼び出せるようにする

今日はCodeIgniterをコマンドラインから呼び出す方法を試してみる。(Cron_job_bootstrapperを使用)
どうしてもバッチからスクレイピングをしなくてはならない処理があったので試してみた。

幾つか解決方法があるみたいだが、Cron_job_bootstrapperが気に入ったので使ってみる。

元ネタは、
http://codeigniter.com/wiki/Cron_job_bootstrapper/
というかほぼこのまま。

環境に合わせて、ちょこっとだけ微変更。


設置手順

手順1)ソースダウンロード
http://codeigniter.com/wiki/Cron_job_bootstrapper/のFile:cron-1.1.zipをダウンロードする。



手順2)ファイル設置
解凍したcron.phpを、
/var/vhosts/xxx.example.com/CodeIgniter_1.7.0/application/cron/cron.php
として設置する。(Linux環境、UTF-8 , 改行コードはLFで設置。他の環境は知らん)


ちなみにウチのapplicationディレクトリはドキュメントルート配下では無い。

  • /var/vhosts/xxx.example.com/CodeIgniter_1.7.0/html/が、ドキュメントルートで、
  • /var/vhosts/xxx.example.com/CodeIgniter_1.7.0/application/
  • /var/vhosts/xxx.example.com/CodeIgniter_1.7.0/system/

という様に、application,systemディレクトリは、CodeIgniterのドキュメントルートから追い出している。


手順3)ソースを環境に合わせる

一部俺好みに変更。

変更点0)元ソース1行目の

#!/usr/bin/php

#!/usr/bin/php -q

に変更。

ログファイル名とディレクトリ構成の変更
変更点1)元ソース(cron.php)の24行目付近の

<?php
 //上略
    define('CRON_CI_INDEX', '/var/www/vhosts/intranet/index.php');   // Your CodeIgniter main index.php file
    define('CRON', TRUE);   // Test for this in your controllers if you only want them accessible via cron
 //下略
 ?>

<?php
 //上略
    $current_dir = dirname(__FILE__);
    $log_dir =realpath(dirname(dirname($current_dir)) . '/system/logs');//ログ出力はsystem/logsにする
    $ci_index_file = realpath(dirname(dirname($current_dir)) . '/html/index.php'); // Your CodeIgniter main index.php file
    $log_file_name = 'cron-'.date('Y-m-d',time()).'.log';
    define('CRON_CI_INDEX', $ci_index_file);
    define('CRON', TRUE);   // Test for this in your controllers if you only want them accessible via cron
 //下略
 ?>

とした。(ウチのディレクトリ構成は手順2)のようになっている。)



変更点2)元ソース(cron.php)の64行目付近の

<?php
 //上略
    if(!defined('CRON_LOG')) define('CRON_LOG', 'cron.log');
    if(!defined('CRON_TIME_LIMIT')) define('CRON_TIME_LIMIT', 0);
 //下略
 ?>

を、

<?php
 //上略
    if(!defined('CRON_LOG')){
        define('CRON_LOG', $log_dir . '/'.$log_file_name);
    }
    if(!defined('CRON_TIME_LIMIT')) define('CRON_TIME_LIMIT', 0);
 //下略
 ?>

と変えることで、デフォルトで system/logs/cron-yyyy-mm-dd.log
として保存するように変更した。

尚、通常のビューを含むコントローラに接続すると、ログ上にもその結果が保存される。

(echo ならechoの結果、var_dump()ならvar_dump()の結果。)



ob_end_flushの変更
ダウンロードしたcron.phpのままだと、CodeIgniter標準のログの、system/logs/log-yyyy-mm-dd.php

Severity: Notice --> ob_end_flush(): failed to delete and flush buffer. No buffer to de
lete or flush.

みたいな実行時Noticeが出現していたので、cron.phpのob_end_flushを微修正する。


変更点3)元ソース(cron.php)の86行目付近の

<?php
 //上略
    if(CRON_FLUSH_BUFFERS === TRUE) {
        while(@ob_end_flush());		// display buffer contents
    } else {
        ob_end_clean();
    }
 //下略
 ?>

を、

<?php
 //上略
    //if(CRON_FLUSH_BUFFERS === TRUE) {
    if(defined('CRON_FLUSH_BUFFERS') and CRON_FLUSH_BUFFERS === TRUE) {
        //while(@ob_end_flush());		// display buffer contents
        while (ob_get_level() > 0) {
            ob_end_flush();
        }
    } else {
        ob_end_clean();
    }
 //下略
 ?>

に変更。これで不要なob_end_flush();は呼ばれなくなった。



手順4)実行権限付与
cron.phpに実行権限を与える。

chown 実行ユーザ名:実行ユーザグループ ./cron.php ; chmod 700 ./cron.php



手順5)動作確認

実行権限の有るユーザーで、cron.phpを引数無しで実行してみると、

[dix3@hoge]# /var/vhosts/xxx.example.com/CodeIgniter_1.7.0/application/cron/cron.php
Usage: cron.php --run=/controller/method [--show-output][-S] [--log-file=logfile] [--time-limit=N]

みたいに使い方が出てくる。

  • cron.php --run=/tasklist/index -S とか
  • cron.php --run=/tasklist/edit -S とか
  • cron.php --run=/scrapetest -S とか

コンソール上から呼んでみる。

[dix3@hoge]# /var/vhosts/xxx.example.com/CodeIgniter_1.7.0/application/cron/cron.php --run=/scrapetest -S | less
string(438) "<HTML>
<HEAD>
  <TITLE>Example Web Page</TITLE>
</HEAD>
<body>
<p>You have reached this web page by typing &quot;example.com&quot;,
&quot;example.net&quot;,
  or &quot;example.org&quot; into your web browser.</p>
<p>These domain names are reserved for use in documentation and are not available
  for registration. See <a href="http://www.rfc-editor.org/rfc/rfc2606.txt">RFC
  2606</a>, Section 3.</p>
</BODY>
</HTML>

"

こんな感じで、scrapetestコントローラ側での結果が返ってきた。
(ソースは省略するが、スクレイピング用ライブラリを読み込んで外部に接続して得た結果をvar_dump();で出力している)

バッチ処理等をコントローラに記述すると、呼べることが分かる。


尚、-Sをつけて端末上に吐かなくても、system/logs/cron-yyyy-mm-dd.log
に同じ結果が記録されているので、

実際のバッチ処理だと、
/var/vhosts/xxx.example.com/CodeIgniter_1.7.0/application/cron/cron.php --run=/コントローラ名/メソッド名/(引数) >/dev/null 2>&1

みたいなのをcrontabにでも仕込めばいいだろう。



また、直接関係無いが、フックで開発時に常に表示しているプロファイラ等も、

application/hooks/MyClasses.php

<?php
 //上略
if ( config_item( 'my_debugger' )) {
 if(defined('CRON') && CRON ){
  //TODO: cron専用のプロファイラを追加する?
 }else{
  $CI = &get_instance();
  $CI -> output -> enable_profiler( true );
 }
 //下略
}
 ?>

みたいに、CRONが有効の時には通さないようにした。
これでsystem/logs/cron-yyyy-mm-dd.log側に余計なログが吐かれなくなる。



これでバッチプログラムもCodeIgniterで書けるようになった。
めでたしめでたし。




(08/12/14追記)

cron.phpのソース内にも記述が有るとおり、

GOTCHA: Do not load any authentication or session libraries in
controllers you want to run via cron. If you do, they probably
won't run right.

ってことで、cron経由で起動するプログラムは、
当然ながら、認証(すなわちsession)やsessionを使った処理は正常に使えない。



また、バッチプログラム側では、ブラウザ経由で直接実行させない様に対策が必要。

例えば、バッチ専用コントローラーのファイル先頭を、

<?php if ( ! (defined( 'BASEPATH' ) && defined( 'CRON' ) && CRON ) ) exit( 'No direct script access allowed' );

の様に CRON を見るようにするとか、メソッド内で同様の分岐を追加するとか。
作るものによっては、何らかの条件分岐を付けて制限が必要になってくると思う。


注意してね。