おじゃまぷよ系エンジニアメモ

アプリエンジニアからサーバーとインフラエンジニアに転身しました

phpのSlim3をコマンドラインから実行して使う

月1ペースを目標に更新したかったけど、全然更新してなかった ということでphpの軽量フレームワークSlim3を最近使うことが多く、以前コマンドラインから実行するバッチ専用のシステムとして作ったのでその時の情報をメモです。

github.com

Slim3でプロジェクトを作る

www.slimframework.com

公式ドキュメントを元に空のプロジェクトを作ります。
今回は「batch-sample」というプロジェクト名で作成します。

index.phpファイルを修正する

batch-sample/public/index.php

<?php

require __DIR__ . '/../vendor/autoload.php';

$settings = require __DIR__ . '/../src/settings.php';

$commandName = $GLOBALS['argv'][1];


$settings['environment'] = \Slim\Http\Environment::mock(
    [
        'REQUEST_URI' => '/' . $commandName,
    ]
);
$app = new \Slim\App($settings);

require __DIR__ . '/../src/dependencies.php';

require __DIR__ . '/../src/middleware.php';

require __DIR__ . '/../src/routes.php';

$app->run();

ここが恐らく一番のポイントです。 \Slim\Http\Environment::mockでコマンドラインから受け取ったコマンド名をURIに設定して、それに対応するルーティングの処理を呼び出すようにします。 Enviroment:mockはUnitTestのときに、Httpリクエストのmockとして使われます。それをそのまま流用してコマンドラインから実行したときにコマンド名からmockのリクエストを作って実行するという流れです。

バッチの主処理を記述するクラスを作る

batch-sample/src/Action/SampleAction

<?php

use Psr\Container\ContainerInterface;
use Slim\Http\Request;
use Slim\Http\Response;

class SampleAction {

    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function __invoke(Request $request, Response $response, $args)
    {
        $response->write("hello world");
        return $response;
    }
}

ルーティングでこのActionをバインドする

batch-sample/src/route.php

<?php

require_once __DIR__.'/Action/SampleAction.php';

$app->get("/sample_command", SampleAction::class);

とりあえず実行する

ここまで作ったらとりあえず実行してみます。 実行方法はbatch-sampleディレクトリで「php public/index sample_command」です。 実行してみるとコンソール上に「hello world」が出力されます。
非常に簡単ですね。

もう少し改良する

例えばWebからもフックできるようにindex.phpをいじります。 WebApplicationとCliApplicationクラスのようなものを作って、PHP_SAPIを見てどちらのApplicationクラスを実行するか分岐させます

batch-sample/src/Application/CliApplication.php

<?php
//コマンドラインから起動されたとき甩のクラス
class CliApplication
{
    public function run()
    {
        $settings = require __DIR__ . '/../../src/settings.php';

        array_shift($GLOBALS['argv']);
        $commandName = $GLOBALS['argv'][0];

        $settings['environment'] = \Slim\Http\Environment::mock(
            [
                'REQUEST_URI' => '/' . $commandName,
            ]
        );
        $app = new \Slim\App($settings);

        require __DIR__ . '/../../src/dependencies.php';

        require __DIR__ . '/../../src/middleware.php';

        require __DIR__ . '/../../src/routes.php';

        $app->run();
    }
}

batch-sample/src/Application/WebApplication.php

//Webからフックされたとき用のクラス
<?php
class WebApplication
{
    public function run()
    {
        if (PHP_SAPI == 'cli-server') {
            // To help the built-in PHP dev server, check if the request was actually for
            // something which should probably be served as a static file
            $url = parse_url($_SERVER['REQUEST_URI']);
            $file = __DIR__ . $url['path'];
            if (is_file($file)) {
                return false;
            }
        }

        $settings = require __DIR__ . '/../../src/settings.php';
        $app = new \Slim\App($settings);

        require __DIR__ . '/../../src/dependencies.php';

        require __DIR__ . '/../../src/middleware.php';

        require __DIR__ . '/../../src/routes.php';

        $app->run();
    }
}

batch-sample/public/index.php

<?php

date_default_timezone_set('Asia/Tokyo');
require __DIR__ . '/../vendor/autoload.php';

use Batch\Sample\Application\CliApplication;
use Batch\Sample\Application\WebApplication;


if(PHP_SAPI == "cli"){
    (new CliApplication())->run();
}else{
    (new WebApplication())->run();
}

これでビルトインサーバーを起動して「http://localhost:8090/sample_command」にアクセスすれば同じバッチ処理が起動します。

さいごに

さらっとした説明はここまでにして、その他にも多重起動防止や実際にDBを操作する処理も含めたものはgithubにあげておきます。 サブシステムとして特定のバッチ処理をしたいというときにもしかしたら使えるかもしれませんね。

github.com

githubに上げてる方はクラス設計がここのサンプルとはだいぶ変わっています。といってもActionクラスがServiceクラスを決定し、Serviceクラスはバッチ処理を行うための複数のModelを使用する…ってだけですが、適度に分割されてると思いますしテストコードも問題なく書けるはずです。 Slim3は軽量なフレームワークなだけあってクラス設計がフルスタックよりも楽しいです。
APIサーバー用のフレームワークとしてもかなりアリだと思うので今作ってるアプリのサーバー側はSlim3にしようかと思っているところ。
Slim3は最近すごくお気に入りなので、APIサーバーを作ってまた何か知見を得られれば何か書きたいです。