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
を編集しましょう。Make
Command.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 MakeRepositoryCommand
Repository用のコマンドでは、先程作成した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.
どうでしょうか?無事にファイルは生成されましたか?
他にもよく作るファイルのテンプレートなどがある場合は、今回のようにコマンド化しておくと便利かなと思いますので
ぜひ活用してみてください。
それでは今回はこのへんで。