LaravelでServiceやRepositoryをartisanコマンドで生成できるようにする
みなさん、こんにちは。
ソリューションSecの長谷川です。
以前、「MVC+Service+Repositoryの概念」という記事を書き
社内でもLaravelのプロジェクトを構築した際には、その概念を取り入れているのですが
2025年9月時点(=Laravel12)で、Laravelの標準はまだMVC(Model-View-Controller)を基本構造としており
ServiceおよびRepositoryのレイヤーを公式で推奨・提供しているわけではありません。
そもそも、EloquentがAcive Recordパターンを採用しているため
Repositoryを挟まずに直接モデルを使うのがLaravel流だったり
ビジネスロジックをControllerやModelに書いても動くように設定されているので
Service層が必須ではないというのもあると思います。
なので、今回はModelやControllerと同様にartisanコマンドで
ServiceやRepositoryのファイルを作れるようにしていこうと思います。
テンプレートとなるファイルを作成する
コマンドで生成されるファイルのテンプレートとなるものは「スタブ(Stub)」と呼ばれています。
まずはこのファイルから作成していきましょう。
appディレクトリやpublicディレクトリと同階層に「stubs」というディレクトリを作成します。
(すでにある場合は不要です)
ここに次のファイルを作成していきます。
Service用テンプレート
<?php
namespace {{ namespace }};
class {{ class }}
{
    //
}Repository用テンプレート
<?php
namespace {{ namespace }};
use {{ rootNamespace }}Repositories\Interfaces\{{ class }}Interface;
class {{ class }} implements {{ class }}Interface
{
    //
}{{ rootName }}のあとに\を追加してしまうと、{{ rootNamespace }}にも\が含まれていて、二重になってしまうので注意してください。
RepositoryInterface用テンプレート
<?php
namespace {{ namespace }};
interface {{ class }}
{
    //
}RepositoryInterfaceを必要としない場合は、repository.stubの内容はservice.stubと同じで問題ないです。
artisan用コマンドを作成する
次に、先程のテンプレートを用いてartisanコマンドから生成できるようにするため
コマンドファイルを作っていきます。
コマンドファイルを作るときはphp artisan make:command XXXXで作成できます。
Service用コマンドを作成
下記コマンドを入力して、コマンド用のファイルを作成します。
php artisan make:command ServiceMakeCommand作成されたapp/Console/Commands/Serviceを編集しましょう。MakeCommand.php
とはいえ、結構書き直しますので、変更したところをわかりやすくしておきます。
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
class ServiceMakeCommand extends GeneratorCommand
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'make:service';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new service class';
    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Service';
    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return base_path('stubs/service.stub');
    }
    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return "{$rootNamespace}\Services";
    }
}
make:commandでは基本的にIlluminate\Console\Commandを継承したクラスを生成してくれますがmake:controllerなどを行っているファイルを見てみると、Illuminate\Console\GeneratorCommandを継承しているのでそちらを使用します。
また、通常のコマンドの場合、コマンド実行時の名前は$signatureで定義していますが
GeneratorCommandでは$nameに定義します。
あとは、コマンドで生成されるクラスの種類を$typeで定義したりgetStub()とgetDefaultNamespace()の抽象メソッドの中身を定義すればOKです。
Repository用コマンドを作成
さて、Repository用コマンドも基本的には同じではあるのですが
今回のケースではRepositoryInterfaceも一緒に作りたいとおもいます。
なので、Repository用のコマンドとRepositoryInterface用のコマンドを作ります。
先に、RepositoryInterface用コマンドから作りましょう。
だいたい同じなので、細かい説明は省きます。
php artisan make:command RepositoryInteraceMakeCommand<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
class RepositoryInterfaceMakeCommand extends GeneratorCommand
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'make:repository-interface';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new repository interface';
    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Repository Interface';
    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return base_path('stubs/repository.interface.stub');
    }
    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return "{$rootNamespace}\Repositories\Interfaces";
    }
}
次にRepository用のコマンドを作成します。
php artisan make:command MakeRepositoryCommandRepository用のコマンドでは、先程作成したRepositoryInterface用のコマンドも同時に実行して
Repositoryが作成されたらRepositoryInterfaceも同時に作成されるようにします。
なので、下記のようにhandle()メソッド内でRepositoryInterfaceのコマンドを呼ぶようにします。
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
class RepositoryMakeCommand extends GeneratorCommand
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'make:repository';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new repository class';
    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Repository';
    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return base_path('stubs/repository.stub');
    }
    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return "{$rootNamespace}\Repositories";
    }
    /**
     * Execute the console command.
     */
    public function handle()
    {
        parent::handle();
        $this->call('make:repository-interface', [
            'name' => $this->argument('name') . 'Interface',
        ]);
    }
}
準備はこれで完了
さて、ここで今回のディレクトリ構造を見ておきましょう。
/
├ /app/
│  ├─ /Console/Commands/
│  │  ├ RepositoryInterfaceMakeCommand.php
│  │  ├ RepositoryMakeCommand.php
│  │  └ ServiceMakeCommand.php
│  └ /Repositories/ <- Repository生成先
│     └ /Interfaces/ <- RepositoryInterface生成先
└ /stubs/
   ├ repository.interface.stub
   ├ repository.stub
   └ service.stub上記にあるように、コマンドで生成されるファイルはそれぞれのディレクトリに格納される予定です。
それでは、コマンドを実行してみましょう。
root@xxxxxx:/var/www/html# php artisan make:service User
   INFO  Service [app/Services/User.php] created successfully.
root@xxxxxx:/var/www/html# php artisan make:repository User
   INFO  Repository [app/Repositories/User.php] created successfully.  
   INFO  Repository Interface [app/Repositories/Interfaces/UserInterface.php] created successfully. どうでしょうか?無事にファイルは生成されましたか?
他にもよく作るファイルのテンプレートなどがある場合は、今回のようにコマンド化しておくと便利かなと思いますので
ぜひ活用してみてください。
それでは今回はこのへんで。

 
        


