laravel10でartisanのコマンドを自作

# artisanの自作コマンド作成

## 経緯
スクラッチ開発で作られたアプリをlaravelに移行する案件をやることになりました。
ServiceとRepositoryパターンを使用することになったが、ServiceやServiceのインターフェース、RepositoryやRepositoryのインターフェースをたくさん新規で作っていくのがどうもめんどくさく、bashで作ろうとしていたところartisanコマンドをカスタマイズできるということなのでartisanで作ることにしました。

laravelでは
php artisan xxx
のような自作コマンドを作れるみたいなので、作ってみました。

## laravel自作コマンドの仕組み
カーネルがCommands配下のファイルを読み込んでるみたいです。
```php:Kernel.php
protected function commands(): void
{
    $this->load(__DIR__.'/Commands');

    /*
        他の処理
    */
}
```

## 作ってみる
とりま作ってみましょう
### 雛形

ベースはこのような形です。
```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class CreateCls extends Command
{
    protected $signature = 'create:cls';

    protected $description = 'Command description';

    public function handle()
    {

    }

}
```
#### $signature
$signatureは引数の部分
つまり上記だと
```
$ php artisan create:cls
```
で実行できる

#### $description
説明

#### hundle()

上記のコマンド実行した際に呼び出されるメソッドです。
つまりこの中にコマンドを実行したときにしてほしいことを書いていきます。

### 引数
引数も設定できます。

```
protected $signature = 'create:cls {arg1} {arg2?} {--t|test}';
```

#### 必須の引数
{xxx}形式で書きます。
上記のsignatureだと{arg1}が該当し、下記で受け取れます。
```
$arg1 = $this->argument('arg1');
```
必須の引数です。

#### 任意の引数
{xxx?}形式で書きます。
上記のsignatureだと{arg2?}が該当し、下記で受け取れます。
```
$arg2 = $this->argument('arg2');
```
また引数が指定されてない場合はnullになります。

### オプション形式

{--x|xxx}形式で書きます。
上記のsignatureだと{-t|test}が該当し、下記で受け取れます。
```
$test = $this->option('test');
```
変数の中はbooleanになっており、if文などで指定されたかどうかをチェックできます。

```
if($test)
{
    // オプションを指定された時の処理
}
```

## サービスとレポジトリクラス作成コマンド
```php:CreateCls.php
<?php

namespace App\Console\Commands;

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

class CreateCls extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'create:cls {name} {--i|interface} {--r|repository} {--s|service}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        $name = $this->argument('name');
        $service = $this->option('service');
        $repository = $this->option('repository');
        $interface = $this->option('interface');
        
        $dir = 'Services';
        $type = 'Service';
        if($repository)
        {
            $dir = 'Repositories';
            $type = 'Repository';
        }

        if($interface)
        {
            $dir .= '/Interfaces';
            $type .= 'Interface';
        }

        $directory = app_path($dir);  // app/Services
        $filePath = "{$directory}/{$name}{$type}.php";

        $template = match ($type) {
            'Service' => $this->serviceTemplate($name),
            'Repository' => $this->repositoryTemplate($name),
            'ServiceInterface' => $this->serviceInterfaceTemplate($name),
            'RepositoryInterface' => $this->repositoryInterfaceTemplate($name),
            default => $this->serviceTemplate($name),
        };

        // Servicesディレクトリがない場合は作成
        if (! File::exists($directory)) {
            File::makeDirectory($directory, 0755, true);
        }

        // ファイルが既に存在する場合はエラーメッセージを表示
        if (File::exists($filePath)) {
            $this->error("The {$type} class already exists: {$filePath}");
            return;
        }
           
        // ファイル作成
        File::put($filePath, $template);

        $this->info("{$type} class created: {$filePath}");
    }

    private function serviceTemplate(string $name)
    {
        $if = 'App\\Services\\Interfaces\\'.$name.'ServiceInterface';
        return <<<EOL
<?php

namespace App\Services;

use Illuminate\Http\RedirectResponse;
use {$if};

readonly class {$name}Service implements {$name}ServiceInterface
{
    public function __construct(
    
    )
    {

    }
    /**
     * example
     */
    public function example():array
    {
        return [

        ];
    }
}      
EOL;
    }

    private function repositoryTemplate(string $name)
    {
        $model = 'App\\Models\\'.$name;
        $if = 'App\\Repositories\\Interfaces\\'.$name.'RepositoryInterface';
        return <<<EOL
<?php

namespace App\Repositories;

use {$if};
use {$model};
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;

readonly class {$name}Repository implements {$name}RepositoryInterface
{
    public function find():Collection
    {
        return ;
    }
}
EOL;
    }

    private function serviceInterfaceTemplate(string $name)
    {
        return <<<EOL
<?php

namespace App\Services\Interfaces;

use Illuminate\Http\RedirectResponse;

interface {$name}ServiceInterface
{
    public function example():array;
}
EOL;
    }

    private function repositoryInterfaceTemplate(string $name)
    {
        $model = 'App\\Models\\'.$name;
        return <<<EOL
<?php

namespace App\Repositories\Interfaces;

use {$model};
use Illuminate\Database\Eloquent\Collection;

interface {$name}RepositoryInterface
{
    public function find():Collection;
}
EOL;
    }
}

```

コマンドは下記のようにすると作成できます。

```
# Serviceクラス作成コマンド(app/Servicesに作成)
$ php artisan create:cls Test -s

# Serviceクラスのインターフェース作成コマンド(app/Services/Interfacesに作成)
$ php artisan create:cls Test -s -i

# Repositoryクラス作成コマンド(app/Repositoryに作成)
$ php artisan create:cls Test -r

# Repositoryクラスのインターフェース作成コマンド(app/Repository/Interfacesに作成)
$ php artisan create:cls Test -r -i
```

以上となります。