PHP
laravel
lumen

lumen5.5でLaravel5.5のようにクロージャーコマンドが定義出来るようにする

発端は以下のteratailの質問でした。

PHP - Lumen でクロージャコマンドを定義したい(91955)|teratail

正直、できないんじゃないかなって思ったのですがそれっぽい動作は出来たのでこちらにまとめておきます。
(意外と上手く出来てるんだなぁと実感しました。内部の作りとか結構違うようにみえるのでほんと無理だと思いました)

はじめに

これはあくまでもクロージャーコマンドを使えるようにするための手段です。
他の部分は検証しておりません。

.envを用意しておく

これはドキュメント読めばわかります。

ArtisanをFacadeに登録する、Facadeを使えるようにする

app.phpの26行目あたりにあるコメントアウトされた$app->withFacades();を外して、以下のように書き換えます。

bootstrap/app.php
// $app->withFacades();

$app->withFacades(true, [
    'Illuminate\Support\Facades\Artisan' => 'Artisan',
]);

console.phpファイルを作っておく

routes/console.phpファイルを作っておきます。

routes/console.php
<?php

Artisan::command('test', function () {
    $this->comment("test");
})->describe('Display an test');

app\Foundation\Consoleディレクトリを作る

こちらはご自身で作ってください。

ClosureCommandクラスを移植する

LumenはLaravelのコアとなるFoundationディレクトリを読み込まないので、こちらに含まれるClosureCommandクラスを移植します。

移植といっても至って簡単です。

https://github.com/laravel/framework/blob/5.5/src/Illuminate/Foundation/Console/ClosureCommand.php

をそのままコピーしてきて、app\Foundation\Console\ClosureCommand.phpとして作成します。
そしてこのままでは名前空間が異なるのでそこだけ変更します。

app\Foundation\Console\ClosureCommand.php
<?php

namespace Illuminate\Foundation\Console;

namespace App\Foundation\Console;
// に変える

use Closure;
use ReflectionFunction;
use Illuminate\Console\Command;
以下省略

app\Console\Kernel.phpを編集する

最後にこのファイルを編集します。

メンバ変数を追加しておきます。

    /**
     * Indicates if the Closure commands have been loaded.
     *
     * @var bool
     */
    protected $commandsLoaded = false;

scheduleメソッドの下に以下のメソッド群を追加します。

app\Console\Kernel.php
    protected function schedule(Schedule $schedule)
    {
        //
    }

// ここから追加

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        require base_path('routes/console.php');
    }

    /**
     * Register a Closure based command with the application.
     *
     * @param  string  $signature
     * @param  \Closure  $callback
     * @return \App\Foundation\Console\ClosureCommand
     */
    public function command($signature, Closure $callback)
    {
        $command = new ClosureCommand($signature, $callback);
        Artisan::starting(function ($artisan) use ($command) {
            $artisan->add($command);
        });
        return $command;
    }

    /**
     * Run the console application.
     *
     * @param  \Symfony\Component\Console\Input\InputInterface  $input
     * @param  \Symfony\Component\Console\Output\OutputInterface  $output
     * @return int
     */
    public function handle($input, $output = null)
    {
        try {
            if (! $this->commandsLoaded) {
                $this->commands();
                $this->commandsLoaded = true;
            }
        }  catch (Throwable $e) {
            $e = new FatalThrowableError($e);
            $this->reportException($e);
            $this->renderException($output, $e);
            return 1;
        }

        return parent::handle($input, $output);
    }

    /**
     * Run an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @return int
     */
    public function call($command, array $parameters = [])
    {
        if (! $this->commandsLoaded) {
            $this->commands();
            $this->commandsLoaded = true;
        }
        return parent::call($command, $parameters);
    }

// ここまで
}

その後、必要なクラス等をuse文に追加しておきます。

app\Console\Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;

// 以下が追加したもの
use Throwable;
use Closure;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use App\Foundation\Console\ClosureCommand;
use Illuminate\Console\Application as Artisan;

一応軽く説明しておくと、
https://github.com/laravel/laravel/blob/master/app/Console/Kernel.php#L36-L41
のcommandsメソッドを一部移植して
https://github.com/laravel/framework/blob/5.5/src/Illuminate/Foundation/Console/Kernel.php#L179-L188
のcommandメソッドを完全移植して
handleメソッドの箇所はもともとLaravelだとbootstrapメソッドを読んでいた所を超簡略化させてcommandsメソッドを読むように追加しました。
そのまま追加したものを移植しても良かったのですが、バージョンによる変更もあるかとおもったのでこの対応です。

終わり。

コンソールを利用して、php artisan testと打ってみます。

$ php artisan test
test

と表示されるので、これで作成出来たことになりますね。