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

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

  • 公開日
  • カテゴリ:CakePHP
  • タグ:PHP,MySQL,cron,cache,Database,3.5,Shell,Task
CakePHPのシェルとタスクでcron定時処理を実装する。ついでにデータベースバックアップも。

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

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

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

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

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

Contents

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

開発環境

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

  • MySQL 5.7
  • 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="sample@example.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 以外を使用しなくてはいけない場合を除いては、定時処理もフレームワーク内で書く事をおすすめします。ソースも一緒に管理できるし、データベース情報も二重管理にならないので煩雑になりません。

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

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級