[Laravel 11]Artisanコマンドをカスタムしてしまおう[tips]

## はじめに
こんばんは、ゆきおです。
なんとなく思い付きでArtisanコマンドを作ってみたくなりました。
LaravelといえばADR(Action,Domain,Responder)というアーキテクチャが有名です（多分
コントローラー層を3つに分割したやつですね。
なので「make:controller」ならぬ「make:adr」を作ります。

今回は前回の記事の環境をそのまま使っちゃうのでSailコマンドになっています。
バージョンはデフォルトのLaravel11で作りました。
いかんせんLaravelが久しぶりでうろ覚えの中GPTで作ったので足りない部分もあるかもしれませんが参考までにどぞ。

## コマンドを作成する
Artisanコマンドを作成するにはArtisanコマンドを作成するためのArtisanコマンドを入力します（？）
```php
sail artisan make:command MakeADR
```
これだけでConsole/Commands/にファイルが生成されます。
ので、そこにコマンドの中身を実装します。

```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class MakeADR extends Command
{
    protected $signature = 'make:adr {name}';
    protected $description = 'Create a new ADR (Action-Domain-Responder) structure';

    public function handle(): void
    {
        $name = $this->argument('name');

        $this->createAction($name);
        $this->createDomain($name);
        $this->createResponder($name);

        $this->info('ADR structure created successfully.');
    }

    protected function createAction($name): void
    {
        $actionTemplate = $this->getStub('Action');

        $actionTemplate = str_replace('{{name}}', $name, $actionTemplate);

        $filePath = app_path("Http/Actions/{$name}Action.php");

        if (!File::isDirectory(dirname($filePath))) {
            File::makeDirectory(dirname($filePath), 0755, true);
        }

        File::put($filePath, $actionTemplate);
    }

    protected function createDomain($name): void
    {
        $domainTemplate = $this->getStub('Domain');

        $domainTemplate = str_replace('{{name}}', $name, $domainTemplate);

        $filePath = app_path("Domains/{$name}Domain.php");

        if (!File::isDirectory(dirname($filePath))) {
            File::makeDirectory(dirname($filePath), 0755, true);
        }

        File::put($filePath, $domainTemplate);
    }

    protected function createResponder($name): void
    {
        $responderTemplate = $this->getStub('Responder');

        $responderTemplate = str_replace('{{name}}', $name, $responderTemplate);

        $filePath = app_path("Http/Responders/{$name}Responder.php");

        if (!File::isDirectory(dirname($filePath))) {
            File::makeDirectory(dirname($filePath), 0755, true);
        }

        File::put($filePath, $responderTemplate);
    }

    protected function getStub($type): false|string
    {
        return file_get_contents(resource_path("stubs/$type.stub"));
    }
}

```

次にコマンドで生成するファイルの中身である「Stub」というのを作成します。

## Stubを作成する
Resources配下にStubを作成します。
内部でPHPファイルに置き換えてくれるので.stubというファイル名になっております。

### Action.stub

```php
<?php

namespace App\Http\Actions;

use App\Domains\{{name}}Domain;
use App\Http\Responders\{{name}}Responder;
use \Illuminate\Http\JsonResponse;

class {{name}}Action
{
    protected {{name}}Domain $domain;
    protected {{name}}Responder $responder;

    public function __construct({{name}}Domain $domain, {{name}}Responder $responder)
    {
        $this->domain = $domain;
        $this->responder = $responder;
    }

    public function __invoke(): JsonResponse
    {
        $data = $this->domain->handle();
        return $this->responder->respond($data);
    }
}
```

### Domain.stub
```php
<?php

namespace App\Domains;

class {{name}}Domain
{
    public function handle(): array
    {
        // Domain logic here
        return [];
    }
}
```

### Responder.stub
```php
<?php

namespace App\Http\Responders;

use Illuminate\Http\JsonResponse;

class {{name}}Responder
{
    public function respond($data): JsonResponse
    {
        return response()->json($data);
    }
}
```
これで素材は揃ったので、コマンドを登録します。

## コマンドをプロジェクトに登録する
ちょっと迷子になったのですが、Laravel11からはKarnel.phpがなくなったんですね。
bootstrap/app.phpに統合されたようです。
なのでそこにコマンドを登録してあげます。

```php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })
    ->withCommands([
        App\Console\Commands\MakeADR::class, //withCommands([])に登録する
    ])
    ->create();
```

これで登録ができましたのでいざコマンドを叩いてみます。
```php
sail artisan make:adr Sample
```
コマンドの最後にファイル名を渡します。
これで各フォルダとファイルが生成されているはずです。
何度か試しに叩いてみました。中身はStubの通りです。
![スクリーンショット 2024-07-12 222709.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562437/41f65ff1-8e8b-9fbe-94a4-36d92f3fcd74.png)

## おわり
いざやってみるとササっとできてしまいました。GPT様様ですね。
最近のLaravel/PHPの流れ的にちゃんと型を書くというのを意識しました。
GPTは最初そこまでやってくれなかったので型を書き足して作成しました。
Stubの内容もちょっと合っているか自信ないのでこの機会に復習しておきます（汗

「こんなコマンドあったら便利なんだよなー」とか思ったときに、無ければ作ってみるか！と思い立って効率を上げてみてはいかがでしょうか。

ありがとうございました。