はじめに
Laravelを使用する際にService層とRepository層を使用することがあるかと思います。自分が今後Laravelを使用する上でかなり使いそうだったのでコマンドで作成できるようにしてみました。
環境
PHP:8.3.8
Laravel:11.11.1
composer:2.7.6
OS:Ubuntu 22.04.4 LTS
Laravelってコマンド作れるの?
はい、コマンドが作れます。詳細については以下の記事によくまとまっておりますので気になる方は読んでみてください。今回は「コマンドが作れる」という部分だけ押さえておけば問題ないです。
すぐに使いたい方は以下の部分を確認ください。
完成したコード
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
class CreateServiceCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:service {className : The name of the service class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new service class';
/**
* Directory path for services.
*
* @const string
*/
private const SERVICES_PATH = 'app/Services/';
/**
* The class name of the service to create.
*
* @var string
*/
private string $className;
/**
* Execute the console command.
*/
public function handle(): void
{
$this->className = Str::studly($this->argument('className'));
if ($this->className === '') {
$this->error('The service class name is invalid. Please provide a valid class name.');
return;
}
$this->createServiceClass();
}
/**
* Create the service class file.
*/
private function createServiceClass(): void
{
$directoryPath = $this->getServiceDirectoryPath();
// Create the directory if it doesn't exist
if (!File::exists($directoryPath)) {
File::makeDirectory($directoryPath, 0755, true);
}
$filePath = $this->getServiceFilePath();
$fileContent = $this->getServiceClassTemplate();
$this->writeFile($filePath, $fileContent);
}
/**
* Get the full file path of the service class.
*
* @return string
*/
private function getServiceFilePath(): string
{
return $this->getServiceDirectoryPath() . $this->className . '.php';
}
/**
* Get the full directory path for service classes.
*
* @return string
*/
private function getServiceDirectoryPath(): string
{
return base_path(self::SERVICES_PATH);
}
/**
* Get the content of the service class.
*
* @return string
*/
private function getServiceClassTemplate(): string
{
return "<?php\n\nnamespace App\\Services\\{$this->className};\n\nclass {$this->className}\n{\n // Implement your service methods here\n}\n";
}
/**
* Write content to a file, handling existence and errors.
*
* @param string $filePath
* @param string $fileContent
*/
private function writeFile(string $filePath, string $fileContent): void
{
if (File::exists($filePath)) {
$this->warn("File {$filePath} already exists.");
} else {
try {
File::put($filePath, $fileContent);
$this->info("Service {$this->className} created successfully.");
} catch (\Exception $e) {
$this->error("Failed to create file: {$filePath}. Error: " . $e->getMessage());
}
}
}
}
やりかた
以下コマンドを実行します。
$ php artisan make:command CreateServiceCommand
上記のコードを実行するとapp/Console/Commands/CreateServiceCommand
が作成されます。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CreateServiceCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:create-service-command';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
//
}
}
コンソールコマンドの名前や引数、オプションを定義
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:create-service-command';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:service {className : The name of the service class}';
今回作成するコマンドはServiceを作成するコマンドであり、ほかのコマンドと見た目をそろえたかったのでapp:create-service-command
からmake:service {className : The name of the service class}
に変更しました。
className
は必須の引数、The name of the service class
はclassName
引数の説明です。
現在のソースコード
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CreateServiceCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:service {className : The name of the service class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
//
}
}
コンソールコマンドの説明文を設定
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new service class';
こちらも他のmake:
コマンドを参考にして設定しました。
make:controller Create a new controller class
make:model Create a new Eloquent model class
make:view Create a new view
現在のソースコード
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CreateServiceCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:service {className : The name of the service class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new service class';
/**
* Execute the console command.
*/
public function handle()
{
//
}
}
サービスクラスを格納するディレクトリのパスを定義
/**
* Directory path for services.
*
* @const string
*/
private const SERVICES_PATH = 'app/Services/';
共通しているのでパスは固定していいと思います。
現在のソースコード
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CreateServiceCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:service {className : The name of the service class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new service class';
/**
* Directory path for services.
*
* @const string
*/
private const SERVICES_PATH = 'app/Services/';
/**
* Execute the console command.
*/
public function handle()
{
//
}
}
handle処理を追加する
こちらは一気に追加します。
/**
* Execute the console command.
*/
public function handle()
{
//
}
/**
* Execute the console command.
*/
public function handle(): void
{
$this->className = Str::studly($this->argument('className'));
if ($this->className === '') {
$this->error('The service class name is invalid. Please provide a valid class name.');
return;
}
$this->createServiceClass();
}
/**
* Create the service class file.
*/
private function createServiceClass(): void
{
$directoryPath = $this->getServiceDirectoryPath();
// Create the directory if it doesn't exist
if (!File::exists($directoryPath)) {
File::makeDirectory($directoryPath, 0755, true);
}
$filePath = $this->getServiceFilePath();
$fileContent = $this->getServiceClassTemplate();
$this->writeFile($filePath, $fileContent);
}
/**
* Get the full file path of the service class.
*
* @return string
*/
private function getServiceFilePath(): string
{
return $this->getServiceDirectoryPath() . $this->className . '.php';
}
/**
* Get the full directory path for service classes.
*
* @return string
*/
private function getServiceDirectoryPath(): string
{
return base_path(self::SERVICES_PATH);
}
/**
* Get the content of the service class.
*
* @return string
*/
private function getServiceClassTemplate(): string
{
return "<?php\n\nnamespace App\\Services\\{$this->className};\n\nclass {$this->className}\n{\n // Implement your service methods here\n}\n";
}
/**
* Write content to a file, handling existence and errors.
*
* @param string $filePath
* @param string $fileContent
*/
private function writeFile(string $filePath, string $fileContent): void
{
if (File::exists($filePath)) {
$this->warn("File {$filePath} already exists.");
} else {
try {
File::put($filePath, $fileContent);
$this->info("Service {$this->className} created successfully.");
} catch (\Exception $e) {
$this->error("Failed to create file: {$filePath}. Error: " . $e->getMessage());
}
}
}
上記のコードについて説明していきます。
handle()
/**
* Execute the console command.
*/
public function handle(): void
{
$this->className = Str::studly($this->argument('className'));
if ($this->className === '') {
$this->error('The service class name is invalid. Please provide a valid class name.');
return;
}
$this->createServiceClass();
}
handle()
で行っていることは
- サービスクラスネームの取得
- クラスネームの存在チェック
- サービスクラスの作成
となります。
createServiceClass()
/**
* Create the service class file.
*/
private function createServiceClass(): void
{
$directoryPath = $this->getServiceDirectoryPath();
// Create the directory if it doesn't exist
if (!File::exists($directoryPath)) {
File::makeDirectory($directoryPath, 0755, true);
}
$filePath = $this->getServiceFilePath();
$fileContent = $this->getServiceClassTemplate();
$this->writeFile($filePath, $fileContent);
}
createServiceClass()
で行っていることは
- Serviceディレクトリパスを取得する
- Serviceディレクトが存在しないとき、Serviceディレクトを作成する
- Serviceファイルを作成するパスを取得する
- Serviceクラスのテンプレートを取得する
- Serviceファイルを作成する
といった流れとなります。
getServiceFilePath()
/**
* Get the full file path of the service class.
*
* @return string
*/
private function getServiceFilePath(): string
{
return $this->getServiceDirectoryPath() . $this->className . '.php';
}
getServiceFilePath()
は以下の通りです。
- サービスファイルを作成するパスを返却する
getServiceDirectoryPath()
/**
* Get the full directory path for service classes.
*
* @return string
*/
private function getServiceDirectoryPath(): string
{
return base_path(self::SERVICES_PATH);
}
getServiceDirectoryPath()
は以下の通りです。
- サービスディレクトリのパスを返却する
getServiceClassTemplate()
/**
* Get the content of the service class.
*
* @return string
*/
private function getServiceClassTemplate(): string
{
return "<?php\n\nnamespace App\\Services\\{$this->className};\n\nclass {$this->className}\n{\n // Implement your service methods here\n}\n";
}
getServiceClassTemplate()
は以下の通りです。
- サービスファイルに記述するサービスファイルのテンプレートを返却する
writeFile()
/**
* Write content to a file, handling existence and errors.
*
* @param string $filePath
* @param string $fileContent
*/
private function writeFile(string $filePath, string $fileContent): void
{
if (File::exists($filePath)) {
$this->warn("File {$filePath} already exists.");
} else {
try {
File::put($filePath, $fileContent);
$this->info("Service {$this->className} created successfully.");
} catch (\Exception $e) {
$this->error("Failed to create file: {$filePath}. Error: " . $e->getMessage());
}
}
}
writeFile()
は以下の通りです。
- サービスファイルが既に存在していた場合はwarnを出力する
- それ以外の場合はファイルを作成する
実行してみる
完成したコード
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
class CreateServiceCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:service {className : The name of the service class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new service class';
/**
* Directory path for services.
*
* @const string
*/
private const SERVICES_PATH = 'app/Services/';
/**
* The class name of the service to create.
*
* @var string
*/
private string $className;
/**
* Execute the console command.
*/
public function handle(): void
{
$this->className = Str::studly($this->argument('className'));
if ($this->className === '') {
$this->error('The service class name is invalid. Please provide a valid class name.');
return;
}
$this->createServiceClass();
}
/**
* Create the service class file.
*/
private function createServiceClass(): void
{
$directoryPath = $this->getServiceDirectoryPath();
// Create the directory if it doesn't exist
if (!File::exists($directoryPath)) {
File::makeDirectory($directoryPath, 0755, true);
}
$filePath = $this->getServiceFilePath();
$fileContent = $this->getServiceClassTemplate();
$this->writeFile($filePath, $fileContent);
}
/**
* Get the full file path of the service class.
*
* @return string
*/
private function getServiceFilePath(): string
{
return $this->getServiceDirectoryPath() . $this->className . '.php';
}
/**
* Get the full directory path for service classes.
*
* @return string
*/
private function getServiceDirectoryPath(): string
{
return base_path(self::SERVICES_PATH);
}
/**
* Get the content of the service class.
*
* @return string
*/
private function getServiceClassTemplate(): string
{
return "<?php\n\nnamespace App\\Services\\{$this->className};\n\nclass {$this->className}\n{\n // Implement your service methods here\n}\n";
}
/**
* Write content to a file, handling existence and errors.
*
* @param string $filePath
* @param string $fileContent
*/
private function writeFile(string $filePath, string $fileContent): void
{
if (File::exists($filePath)) {
$this->warn("File {$filePath} already exists.");
} else {
try {
File::put($filePath, $fileContent);
$this->info("Service {$this->className} created successfully.");
} catch (\Exception $e) {
$this->error("Failed to create file: {$filePath}. Error: " . $e->getMessage());
}
}
}
}
上記のコードができましたでしょうか?できましたら以下コマンドを実行してみてください。
php artisan list
~中略~
make:seeder Create a new seeder class
make:service Create a new service class
make:session-table [session:table] Create a migration for the session database table
~中略~
上記のようになっていればコマンドが作られています。コマンドを実行してみましょう。
php artisan make:service SampleService
Service SampleService created successfully.
上記のように出力されたら正常終了してますので確認してみてください。
<?php
namespace App\Services\SampleService;
class SampleService
{
// Implement your service methods here
}
さいごに
今回はServiceクラスを作成するコマンドを実装しました。もっと言えばServiceInterfaceを一緒に作成するコマンドを作れば効率的かと思いますが「Interfaceをどこに配置するか」などが人によって異なると思いますので一旦Serviceクラスを作成するコマンドにしました(Serviceクラスの配置場所についての議論もちらほら見たことがありますがInterfaceよりかは少ないかなぁと思います)。
Repositoryクラスに関してはパスなどを変えてあげればすぐに作成できるのでそちらの記事を書くことはないと思います。