RitoLabo

CakePHPのシェルとタスクでcron定時処理を実装する。ついでにデータベースバックアップも。

  • 公開:
  • カテゴリ: PHP CakePHP
  • タグ: PHP,MySQL,cron,cache,Database,3.5,Shell,Task

WEBアプリケーション開発の中で、データベースのバックアップなどの、裏で走らせる定時処理を構築する要件というのは少なくありません。

バックアップに限らず、何かの処理を走らせたり、通知したり…。

昔であればシェルスクリプトを書いたり、フレームワークを使わない素のプログラミング言語を使って書いたりしていましたが、データベース情報が二重管理になったり、ソース管理がアプリケーションと別管理になったりしてあまり効率が良くないのも事実です。(もちろん、場合によりますが)

ですが、PHPフレームワークを使うとそれらも簡単に構築・登録・稼働を行う事が出来ます。

今回は、CakePHPのシェルとタスクを使ってcronで定時処理を実装します。

アジェンダ
  1. 開発環境
  2. シェルの作成
  3. タスクの作成
    1. タスク処理実装
  4. シェルの実装
  5. シェルの手動実行
  6. cron設定
    1. 起動確認
    2. 設定ファイル作成
  7. シェルヘルパー

開発環境

今回の開発環境は以下の通りです。

  • Linux CentOS 7
  • MySQL 5.7
  • Apache 2.4
  • PHP 7.2
  • CakePHP 3.5

CakePHPのルートディレクトリを cakephp/ とします。

シェルの作成

CakePHPでは、シェルクラスを用いてコンソールアプリケーションを構築していきます。シェルファイルはその柱であり、1つのバックグラウンドタスクの頂点に位置します。まずはシェルを作成・定義していきます。

シェルクラスはbakeコマンドで生成可能です。CakePHPルートディレクトリへ移動し、以下のコマンドを叩きます。

# CakePHPのルートディレクトリへ移動する
cd /path/to/cakephp

# bakeコマンドでシェルクラスファイルを生成する
bin/cake bake shell DatabaseBackup

# 実行結果
[demo@localhost cakephp]# bin/cake bake shell DatabaseBackup

Creating file /var/www/html/cakephp/src/Shell/DatabaseBackupShell.php
Wrote `/var/www/html/cakephp/src/Shell/DatabaseBackupShell.php`

Baking test case for App\Shell\DatabaseBackupShell ...

Creating file /var/www/html/cakephp/tests/TestCase/Shell/DatabaseBackupShellTest.php
Wrote `/var/www/html/cakephp/tests/TestCase/Shell/DatabaseBackupShellTest.php`

cakephp/src/Shell 配下に DatabaseBackupShell.php が生成されます。

cakephp
├─ src
│   ├─ Shell
│   │   ├─
DatabaseBackupShell.php
cakephp/src/Shell/DatabaseBackupShell.php
<?php
namespace App\Shell;

use Cake\Console\Shell;

/**
* DatabaseBackup shell command.
*/
class DatabaseBackupShell extends Shell
{

/**
* Manage the available sub-commands along with their arguments and help
*
* @see http://book.cakephp.org/3.0/en/console-and-shells.html#configuring-options-and-generating-help
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();

return $parser;
}

/**
* main() method.
*
* @return bool|int|null Success or error code.
*/
public function main()
{
$this->out($this->OptionParser->help());
}
}

もしbakeコマンドではなく手動でファイルを作成する場合ですが、クラス名は Shell サフィックス(接尾辞)を 付けてファイル名と一致させる必要があるので注意してください。

ファイルが生成できたらひとまずOK。シェルは一旦置いておきます。

タスクの作成

次に、タスクの作成を行います。

タスクとは、実際に処理を行う部分の事です。処理はシェルにも書けますが、ある理由(後述します)から、メイン処理はタスクに切り出す事をお勧めします。

ちなみに、すごくざっくり言うと、シェルとタスクは、さながらコントローラとコンポーネントみたいな関係のものだと思うとわかりやすいかもしれません。

それではタスクファイルを作成します。これもbakeコマンドで生成できるので、CakePHPルートディレクトリへ移動し、以下のコマンドを叩きます。

# CakePHPのルートディレクトリへ移動する
cd /path/to/cakephp

# bakeコマンドでタスクファイルを生成する
bin/cake bake task Mysqldump

# 実行結果
[demo@localhost cakephp]# bin/cake bake task Mysqldump

Creating file /var/www/html/cakephp/src/Shell/Task/MysqldumpTask.php
Wrote `/var/www/html/cakephp/src/Shell/Task/MysqldumpTask.php`

Baking test case for App\Shell\Task\MysqldumpTask ...

Creating file /var/www/html/cakephp/tests/TestCase/Shell/Task/MysqldumpTaskTest.php
Wrote `/var/www/html/cakephp/tests/TestCase/Shell/Task/MysqldumpTaskTest.php`

cakephp/src/Shell/Task 配下に Mysqldump.php が生成されます。

cakephp
├─ src
│   ├─ Shell
│   │   └─ Task
│   │   └─
Mysqldump.php
cakephp/src/Shell/Task/Mysqldump.php
<?php
namespace App\Shell\Task;

use Cake\Console\Shell;

/**
* Mysqldump shell task.
*/
class MysqldumpTask extends Shell
{

/**
* Manage the available sub-commands along with their arguments and help
*
* @see http://book.cakephp.org/3.0/en/console-and-shells.html#configuring-options-and-generating-help
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();

return $parser;
}

/**
* main() method.
*
* @return bool|int|null Success or error code.
*/
public function main()
{
}
}

ここでよく見ている人なら気が付くと思います。そうです、タスクもシェルクラスです。名前空間が違う以外は全く同じ構造になります。

ただし、シェルの方はコンソール出力の記述があるのに対して、タスクは処理がメインなのでそういった記述はありません。こういう部分からも、シェルとタスクの関係が垣間見えてきます。

タスク処理実装

それではタスクへ処理を記述していきます。今回はシンプルにmysqldumpを行いデータベースのバックアップを取る処理にします。Mysqldump.php を以下のように記述します。

cakephp/src/Shell/Task/Mysqldump.php
<?php
namespace App\Shell\Task;

use Cake\Console\Shell;

class MysqldumpTask extends Shell
{
public function getOptionParser()
{
$parser = parent::getOptionParser();

return $parser;
}

public function main()
{
$this->execMysqldump();
}

/**
* mysqldumpを実行する
* @return mixed
*/
private function execMysqldump()
{
$date = date('Ymd-His');
$command = sprintf('mysqldump -u %s -p%s %s > %sbackup.sql', env('DB_USERNAME'), env('DB_PASSWORD'), env('DB_SCHEMA'), STORAGE, $date);
exec($command, $output, $result);
return $result;
}
}

execMysqldump()メソッドを宣言して、その中でシンプルにmysqldumpコマンドを叩いています。 ファイル名・出力先などは適宜自身の環境に置き換えてください。

ちなみにここでは、cakephp/storage ディレクトリを作成し、その中にSQLファイルを出力する流れにしています。

そして、定義したexecMysqldump()メソッドを、main()メソッドの中でコールし実行しています。

タスクの実装はこれで完了です。

シェルの実装

タスクの実装が完了したので、これらをシェルで実行するように定義していきます。

cakephp/src/Shell/DatabaseBackupShell.php
<?php
namespace App\Shell;

use Cake\Console\Shell;

class DatabaseBackupShell extends Shell
{
public $tasks = ['Mysqldump']; // ← タスクの読み込み

public function getOptionParser()
{
$parser = parent::getOptionParser();

return $parser;
}

public function main()
{
// タスクの実行
$this->Mysqldump->main();
}
}

メンバ変数 $tasks に先ほど作成したタスクを読み込ませ、main()メソッドでMysqldumpタスクのmain()メソッドを実行します。

これで一連の処理構築が完了しました。

シェルの手動実行

cronに登録する前に、これらの処理が問題なく動くかどうかを確認します。CakePHPルートディレクトリへ移動し、以下のコマンドを叩きます。

# CakePHPのルートディレクトリへ移動する
cd /path/to/cakephp

# DatabaseBackupシェルを稼働させる
bin/cake database_backup

# 実行結果
[demo@localhost cakephp]# ll storage/
-rw-r--r--. 1 demo demo 23342 Mar 23 16:37 backup.sql

問題なくバックアップが行われました。

ちなみに、実行するコマンド名は、シェルクラス名から判断されます。今回は、DatabaseBackupShell なので、これをスネークケースにした形 database_backup がコマンド名になった。という事です。

cron設定

作成したコンソールアプリケーションを、cronに登録して定時処理として実行してみます。

起動確認

まずは起動確認。以下のコマンドを叩いてcronが動いているか確認します。

# cronが起動しているか確認
service crond status

# 実行結果
[demo@localhost ~]# service crond status
Redirecting to /bin/systemctl status crond.service
crond.service - Command Scheduler
Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor preset: enabled)
Active:
active (running) since Fri 2018-03-24 12:14:27 UTC; 4h 41min ago
Main PID: 684 (crond)
CGroup: /system.slice/crond.service
└─684 /usr/sbin/crond -n

Mar 24 12:14:38 localhost.localdomain systemd[1]: Started Command Scheduler.
Mar 24 12:14:38 localhost.localdomain systemd[1]: Starting Command Scheduler...
Mar 24 12:14:39 localhost.localdomain crond[684]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 20% if used.)
Mar 24 12:14:40 localhost.localdomain crond[684]: (CRON) INFO (running with inotify support)

動いていますね。止まっている場合は

service crond start

で起動させてください。

設定ファイル作成

次に、cronを実行させるための設定ファイルを作成します。

vim /etc/cron.d/database_backup
----------------------------------------------------------
MAILTO="admin@mailaddress.com"
* * * * * root /var/www/html/cakephp/bin/cake database_backup
----------------------------------------------------------

これで設定完了です。確認すると、バックアップが行われた事が確認できます。

ログを確認すると、cronが動作したのも確認できます。

tail -f /var/log/cron
----------------------------------------------------------
Mar 24 12:21:53 localhost CROND[9493]: (root) CMD (/var/www/html/cakephp/bin/cake database_backup)
----------------------------------------------------------

シェルヘルパー

最後になりましたが、軽くシェルヘルパーを紹介しておきます。

これは、コンソール上でcronではなく、手動でコマンドを叩いて実行する際に、コンソール画面上に様々な情報を表示できるヘルパーです。

例えば、シェルクラスでこんな感じに実装したとします。

public function main()
{
// 2行の改行
$this->out($this->nl(2));
// 横線
$this->hr();

// テキスト出力
$this->out('I am CakePHP shell !!');
// 横線
$this->hr();

$data = [
['Header 1', 'Header', 'Long Header'],
['short', 'Longish thing', 'short'],
['Longer thing', 'short', 'Longest Value'],
];
// テーブル描画
$this->helper('Table')->output($data);

// 2行の改行
$this->out($this->nl(2));
}

これを実行すると、以下のような出力が返ってきます。

[demo@localhost cakephp/] cake database_backup

---------------------------------------------------------------
I am CakePHP shell !!
---------------------------------------------------------------
+--------------+---------------+---------------+
| Header 1 | Header | Long Header |
+--------------+---------------+---------------+
| short | Longish thing | short |
| Longer thing | short | Longest Value |
+--------------+---------------+---------------+

このようにして、コンソール実行の出力を操作する事も可能です。

冒頭で、メイン処理はシェルに書かずタスクに書いた方が良いと言いましたが、こういった描画を行う場合がある事を考えれば、やはりメイン処理はタスクに切り分けた方が可読性の面からも良いと思います。

まとめ

以上で作業は完了です。

CakePHPなどのPHPフレームワークにてWEBアプリケーションを構築しているのであれば、特にPHP以外を使用しなくてはいけない場合を除いては、定時処理もフレームワーク内で書く事をおすすめします。ソースも一緒に管理できるし、データベース情報も二重管理にならないので煩雑になりません。

そして、シェルとタスクは兄弟。是非試してみてください。