オープンソースこねこね

Webプログラミングなどについてあれこれ。

PHPでcronのように定期実行をするプログラムを書いた

kohkimakimoto/workerphp · GitHub

自分で使う分には最低限、必要な機能を実装できたので紹介します。以下のようなPHPファイルを記述してPHPコマンドラインから実行するとプロセスが起動しっぱなしになりcront_timeで指定したスケジュールでジョブを定期実行してくれます。

<?php
require_once __DIR__.'/vendor/autoload.php';

$worker = new \Kohkimakimoto\Worker\Worker();

// job for every minute.
$worker->job("hello", ['cron_time' => '* * * * *', 'command' => function(){
    echo "Hello world\n";
}]);

// job runs a shell command.
$worker->job("uptime", ['cron_time' => '10 * * * *', 'command' => "uptime"]);

$worker->start();

ジョブはクロージャPHPのコードとして書くか、シェルのコマンドを文字列で直接指定することができます。インストールはcomposer installするだけですが、その他詳しい情報はリポジトリのREADMEを参照してください。内部的にジョブはプロセスをforkして実行させているので、PHPpcntlエクステンションがインストールされている必要があります。linuxならyumとかでインストールできると思います。

cronが普通に使える環境ならまあそれを使えばいいんですが、個人の用途でherokuの無料枠dynoを使って定期実行処理をしたかったので、こんなの作りました。

Web API

cronにない拡張機能として、簡易なWeb APIを提供するHTTPサーバを組み込んでいます。

$worker->httpServer->listen();

のように書くと、8080ポートを開きます。ポートを変更したいときは

$worker->httpServer->listen(8888, 'localhost');

のようにもかけます。HTTPサーバが立ち上がったら、

$ curl -XGET http://localhost:8080/{ジョブ名}

でジョブの情報を取得

$ curl -XPOST http://localhost:8080/{ジョブ名}

でジョブを実行することができます。また$worker->httpServer->setAPIKey()を使えば、文字列一致だけで判定する簡易な認証機能をつけられます。 ちなみに、私は以下の様な設定でherokuのPHPスタック上で動かしています。

<?php
require_once __DIR__.'/vendor/autoload.php';
date_default_timezone_set('Asia/Tokyo');

use Kohkimakimoto\Worker\Worker;

$worker = new Worker();
// herokuはhttpのportを動的に割り当てるので環境変数から取得する
$worker->httpServer->listen(getenv('PORT'));
// apiにアクセスするときのキー
$worker->httpServer->setAPIKey('vjdioaewdg49q3tg...');
// 5分毎にメモリ使用量などをログに出力する設定
$worker->stats->on();

$worker->job("hoge", ['cron_time' => '* * * * *', 'max_processes' => 1, 'command' => function(){
    // ...
}]);

$worker->job("foo", ['cron_time' => '* * * * *', 'max_processes' => 1, 'command' => function(){
    // ...
}]);

// 30分に一回dynoをwebからアクセスしてスリープさせないようにする。
$worker->job("wakeup_dyno", ['cron_time' => '*/30 * * * *', 'max_processes' => 1, 'command' => function(){
    file_get_contents("https://my_worker_xxx.herokuapp.com/?apiKey=vjdioaewdg49q3tg...");
}]);

$worker->start();

とりあえず、1日動かしっぱなしにしてみたが問題はなさそうです。