Laravel Artisanコンソールとカスタムコマンド
ArtisanはLaravelの「スイスアーミーナイフ」です—100以上の組み込みコマンドがすべての操作・保守タスクをカバーし, カスタムコマンドで反復作業をワンクリックで処理できます。
1. 学ぶこと
- 組み込みコマンドの完全一覧:migrate/cache/config/route/list
- カスタムコマンドの作成:
make:commandとSignature構文 - コマンドの引数とオプション:{argument} / {--option}の定義
- コマンドスケジューリング:app/Console/Kernel.phpのCronタスク
- インタラクティブコマンド:confirm/choice/ask/anticipate
2. 運用エンジニアの実話
(1) 痛み:毎日20の運用タスクを手動で実行している
毎朝, Charlieは次のタスクを手動で実行しなければなりません:期限切れセッションのクリア, 日次レポートの生成, サブスクリプションステータスの同期, 期限切れリマインドメールの送信, データベースのバックアップ...5つのターミナルウィンドウに分散された20のタスク—1つでも忘れると大変です。Bobは言いました。「これって全部スケジュールタスクじゃないの?なんで手動でやってるの?」
(2) Artisanコマンドとスケジューリングによる解決策
Artisanカスタムコマンドは反復タスクを1つのコマンドにまとめ, Scheduleが自動的にスケジュール実行します—Charlieは毎日1回実行ログを確認するだけで済みます。
# 1つのコマンドで全て実行
php artisan shopmetrics:daily-maintenance
# またはスケジューラーに午前2時に自動実行させる
php artisan schedule:run
(3) 成果
CharlieがArtisanでスケジューリングを設定した後, 20のタスクがすべて自動実行され, 失敗率は5%から0%に低下しました。
3. 組み込みコマンドの完全一覧
(1) 主要コマンドのカテゴリ
| カテゴリ | コマンド | 説明 |
|---|---|---|
| アプリケーション | about |
環境情報の概要 |
down / up |
メンテナンスモード切替 | |
env |
.env設定の表示 | |
| データベース | migrate |
マイグレーション実行 |
migrate:rollback |
マイグレーションロールバック | |
migrate:fresh |
データベース再構築 | |
db:seed |
データ投入 | |
db:show |
データベース情報 | |
| キャッシュ | cache:clear |
キャッシュクリア |
config:cache / clear |
設定キャッシュ | |
route:cache / clear |
ルートキャッシュ | |
view:cache / clear |
ビューキャッシュ | |
| ルート | route:list |
全ルートの一覧表示 |
| キュー | queue:work |
ワーカー起動 |
queue:failed |
失敗タスクの確認 | |
queue:retry |
失敗タスクのリトライ | |
| 生成 | make:model |
Modelの作成 |
make:controller |
コントローラーの作成 | |
make:migration |
マイグレーションの作成 | |
make:command |
コマンドの作成 |
(1) ▶ サンプル:主要Artisanコマンドクイックリファレンス
# アプリ情報
php artisan about
php artisan env
# データベース操作
php artisan migrate --seed
php artisan db:show --counts
# キャッシュ管理
php artisan cache:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize # 設定 + ルート + ビューのキャッシュ
# メンテナンスモード
php artisan down --secret="maintenance-token" # ?secret=でアクセス許可
php artisan up
# キュー管理
php artisan queue:work --queue=high,default
php artisan queue:failed
php artisan queue:retry all
# ルート確認
php artisan route:list --path=api
php artisan route:list --columns=method,uri,name
出力:
# コマンド実行成功
4. カスタムコマンド
(1) コマンドの作成
php artisan make:command ProcessTenantAnalytics
# 作成される:app/Console/Commands/ProcessTenantAnalytics.php
(2) コマンドSignature構文
// app/Console/Commands/ProcessTenantAnalytics.php
class ProcessTenantAnalytics extends Command
{
// Signature構文:{argument} {--option}
protected $signature = 'shopmetrics:analytics
{tenant? : Tenant ID or slug (optional)}
{--type=monthly : Report type (daily|weekly|monthly)}
{--force : Force re-calculation}
{--format=csv : Output format (csv|json)}';
protected $description = 'Process analytics for tenants';
public function handle(): int
{
$tenant = $this->argument('tenant');
$type = $this->option('type');
$force = $this->option('force');
if ($tenant) {
$this->processSingleTenant($tenant, $type, $force);
} else {
$this->processAllTenants($type, $force);
}
return self::SUCCESS;
}
}
(3) Signature構文ルール
| 構文 | 説明 | 例 |
|---|---|---|
{name} |
必須引数 | {tenant} |
{name?} |
オプション引数 | {tenant?} |
{name=default} |
デフォルト値付き | {type=monthly} |
{--option} |
ブールオプション | --force |
{--option=default} |
値付きオプション | --format=csv |
{--O|shortcut} |
短縮オプション | --force|f |
(1) ▶ サンプル:ShopMetricsテナント管理コマンド
// app/Console/Commands/ManageTenant.php
class ManageTenant extends Command
{
protected $signature = 'shopmetrics:tenant
{action : Action to perform (list|suspend|activate|stats)}
{tenant? : Tenant ID or slug}
{--with-users : Include user statistics}';
protected $description = 'Manage ShopMetrics tenants';
public function handle(): int
{
$action = $this->argument('action');
match ($action) {
'list' => $this->listTenants(),
'suspend' => $this->suspendTenant(),
'activate' => $this->activateTenant(),
'stats' => $this->showTenantStats(),
default => $this->error("Unknown action: {$action}"),
};
return self::SUCCESS;
}
private function listTenants(): void
{
$tenants = Tenant::withCount(['shops', 'users'])->get();
$this->table(
['ID', 'Name', 'Slug', 'Status', 'Shops', 'Users'],
$tenants->map(fn ($t) => [
$t->id, $t->name, $t->slug, $t->status,
$t->shops_count, $t->users_count,
])
);
}
private function suspendTenant(): void
{
$identifier = $this->argument('tenant') ?? $this->ask('Enter tenant ID or slug:');
$tenant = $this->resolveTenant($identifier);
$tenant->update(['status' => 'suspended']);
$this->info("Tenant {$tenant->name} has been suspended.");
}
private function resolveTenant(string $identifier): Tenant
{
return is_numeric($identifier)
? Tenant::findOrFail($identifier)
: Tenant::whereSlug($identifier)->firstOrFail();
}
}
出力:
// 実行成功
5. コマンドの引数とオプション
(1) パラメータの取得
// 引数
$name = $this->argument('name'); // 単一引数
$all = $this->arguments(); // 全引数を配列で取得
// オプション
$force = $this->option('force'); // 単一オプション (ブールまたは値)
$all = $this->options(); // 全オプションを配列で取得
(2) 配列パラメータ
// 配列引数付きのSignature
protected $signature = 'shopmetrics:report
{tenants* : One or more tenant IDs}
{--type=monthly}';
// 使用方法
php artisan shopmetrics:report 1 2 3 --type=weekly
// アクセス
$tenants = $this->argument('tenants'); // [1, 2, 3]
(3) 出力メソッド
| メソッド | 説明 | サンプル出力 |
|---|---|---|
info() |
緑の情報 | ✓ Done |
error() |
赤のエラー | ✗ Failed |
warn() |
黄の警告 | ⚠ Warning |
line() |
プレーンテキスト | プレーンテキスト |
table() |
テーブル | フォーマットテーブル |
progressBar() |
プログレスバー | ██████░░ 60% |
(1) ▶ サンプル:プログレスバー付きShopMetricsデータクリーニングコマンド
// app/Console/Commands/CleanupExpiredData.php
class CleanupExpiredData extends Command
{
protected $signature = 'shopmetrics:cleanup
{--days=90 : Delete data older than N days}
{--dry-run : Show what would be deleted}';
protected $description = 'Clean up expired data (old reports, expired tokens)';
public function handle(): int
{
$days = $this->option('days');
$dryRun = $this->option('dry-run');
$cutoff = now()->subDays($days);
// 期限切れトークンのクリーニング
$expiredTokens = Sanctum::$personalAccessTokenModel::where('last_used_at', '<', $cutoff);
$this->info(($dryRun ? 'Would delete' : 'Deleting') . " {$expiredTokens->count()} expired tokens.");
// S3の古いレポートのクリーニング
$bar = $this->output->createProgressBar(Tenant::count());
$deletedFiles = 0;
Tenant::chunk(100, function ($tenants) use ($cutoff, $dryRun, &$deletedFiles, $bar) {
foreach ($tenants as $tenant) {
$files = Storage::disk('s3')->allFiles("reports/{$tenant->slug}");
foreach ($files as $file) {
if (Storage::disk('s3')->lastModified($file) < $cutoff->timestamp) {
if (!$dryRun) Storage::disk('s3')->delete($file);
$deletedFiles++;
}
}
$bar->advance();
}
});
$bar->finish();
$this->newLine();
$this->info(($dryRun ? 'Would delete' : 'Deleted') . " {$deletedFiles} old report files.");
if (!$dryRun) {
$expiredTokens->delete();
}
return self::SUCCESS;
}
}
出力:
// 実行成功
6. コマンドスケジューリング
(1) スケジューリングの定義
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('shopmetrics:analytics --type=daily')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('admin@shopmetrics.io');
Schedule::command('shopmetrics:cleanup --days=90')
->weekly()
->sundays()
->at('03:00')
->onOneServer();
Schedule::command('shopmetrics:sync-subscriptions')
->dailyAt('06:00')
->onOneServer()
->withoutOverlapping();
Schedule::job(new ProcessMonthlyAnalyticsJob)
->monthlyOn(1, '00:00')
->onOneServer();
(2) スケジューリング頻度オプション
| メソッド | 頻度 | 同等のCron |
|---|---|---|
everyMinute() |
毎分 | * * * * * |
everyFiveMinutes() |
5分ごと | */5 * * * * |
hourly() |
毎時 | 0 * * * * |
daily() |
毎日午前12時 | 0 0 * * * |
dailyAt('14:00') |
毎日午後2時 | 0 14 * * * |
weekly() |
毎週日曜日の深夜 | 0 0 * * 0 |
monthly() |
毎月1日の深夜 | 0 0 1 * * |
cron('...') |
カスタムCron | 任意 |
(3) スケジューリング制約
| メソッド | 説明 |
|---|---|
onOneServer() |
1台のサーバーのみで実行 |
withoutOverlapping() |
重複実行を禁止 |
runInBackground() |
バックグラウンドで実行 |
when(Closure) |
条件付き実行 |
environments('prod') |
指定環境のみ |
emailOutputOnFailure() |
失敗時にメール通知 |
(1) ▶ サンプル:ShopMetricsスケジューリングの完全設定
// routes/console.php
Schedule::command('shopmetrics:analytics --type=daily')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping(60)
->emailOutputOnFailure('ops@shopmetrics.io');
Schedule::command('shopmetrics:cleanup --days=90')
->weeklyOn(Schedule::SUNDAY, '03:00')
->onOneServer();
Schedule::command('shopmetrics:send-expiring-notifications')
->dailyAt('08:00')
->when(fn () => Subscription::expiringSoon()->exists());
Schedule::command('queue:prune-failed --hours=168')
->daily();
Schedule::command('queue:prune-batches --hours=168')
->daily();
// サーバーのCronエントリ
// * * * * * cd /var/www/shopmetrics && php artisan schedule:run >> /dev/null 2>&1
出力:
// 実行成功
7. インタラクティブコマンド
(1) インタラクションメソッド
// 入力の要求
$name = $this->ask('What is the tenant name?');
// デフォルト値付きの入力要求
$email = $this->ask('Email address?', 'admin@example.com');
// シークレット入力 (パスワード)
$password = $this->secret('Enter password:');
// 確認 (yes/no)
if ($this->confirm('Do you wish to continue?', true)) {
// デフォルト:はい
}
// 選択 (単一選択)
$type = $this->choice(
'Select report type',
['daily', 'weekly', 'monthly'],
0 // デフォルトインデックス
);
// 自動補完
$name = $this->anticipate('Tenant name', Tenant::pluck('name')->toArray());
(2) 総合インタラクションワークフロー
// app/Console/Commands/SetupTenant.php
class SetupTenant extends Command
{
protected $signature = 'shopmetrics:tenant-setup';
protected $description = 'Interactive tenant setup wizard';
public function handle(): int
{
$this->info('=== ShopMetrics Tenant Setup Wizard ===');
$name = $this->ask('Tenant name');
$slug = $this->anticipate('Slug', [Str::slug($name)]);
$domain = $this->ask('Custom domain (optional)', $slug . '.shopmetrics.io');
$plan = $this->choice('Select plan', ['Starter', 'Pro', 'Enterprise'], 1);
$ownerEmail = $this->ask('Owner email');
$this->table(
['Field', 'Value'],
[['Name', $name], ['Slug', $slug], ['Domain', $domain], ['Plan', $plan], ['Owner', $ownerEmail]],
);
if (!$this->confirm('Create this tenant?', true)) {
$this->warn('Cancelled.');
return self::FAILURE;
}
$tenant = Tenant::create(compact('name', 'slug', 'domain'));
User::factory()->create([
'tenant_id' => $tenant->id,
'email' => $ownerEmail,
'role' => 'tenant_owner',
]);
$this->info("Tenant {$name} created successfully!");
return self::SUCCESS;
}
}
(1) ▶ サンプル:ShopMetricsインタラクティブデータエクスポートコマンド
// app/Console/Commands/ExportData.php
class ExportData extends Command
{
protected $signature = 'shopmetrics:export';
protected $description = 'Interactive data export tool';
public function handle(): int
{
$type = $this->choice('What to export?', [
'orders' => 'Orders',
'products' => 'Products',
'analytics' => 'Analytics Report',
]);
$tenant = $this->anticipate('Tenant (leave blank for all)', Tenant::pluck('name')->push('All')->toArray());
$format = $this->choice('Format?', ['csv', 'xlsx', 'json'], 0);
$dateFrom = $this->ask('Date from (Y-m-d, optional)');
$dateTo = $this->ask('Date to (Y-m-d, optional)');
$this->info("Exporting {$type} for {$tenant} in {$format} format...");
$query = match ($type) {
'orders' => Order::query(),
'products' => Product::query(),
'analytics' => AnalyticsReport::query(),
};
if ($tenant !== 'All') {
$tenantModel = Tenant::whereName($tenant)->firstOrFail();
$query->where('tenant_id', $tenantModel->id);
}
if ($dateFrom) $query->where('created_at', '>=', $dateFrom);
if ($dateTo) $query->where('created_at', '<=', $dateTo);
$count = $query->count();
$this->info("Found {$count} records.");
if (!$this->confirm("Export {$count} records?", true)) {
return self::FAILURE;
}
$path = ExportService::export($query, $format);
$this->info("Export saved to: {$path}");
return self::SUCCESS;
}
}
出力:
// 実行成功
8. 総合サンプル:ShopMetrics運用コマンドセット
// ============================================
// 総合:ShopMetrics運用コマンド
// 対象:signature, options, progress, scheduling, interactive
// ============================================
// app/Console/Commands/DailyMaintenance.php
class DailyMaintenance extends Command
{
protected $signature = 'shopmetrics:daily-maintenance
{--skip-analytics : Skip analytics processing}
{--skip-cleanup : Skip data cleanup}
{--notify : Send completion notification}';
protected $description = 'Run daily maintenance tasks';
public function handle(): int
{
$this->info('Starting daily maintenance...');
if (!$this->option('skip-analytics')) {
$this->processAnalytics();
}
if (!$this->option('skip-cleanup')) {
$this->cleanupExpiredData();
}
$this->syncSubscriptions();
if ($this->option('notify')) {
$this->sendCompletionNotification();
}
$this->info('Daily maintenance completed.');
return self::SUCCESS;
}
private function processAnalytics(): void
{
$this->info('Processing daily analytics...');
$tenants = Tenant::active()->get();
$bar = $this->output->createProgressBar($tenants->count());
foreach ($tenants as $tenant) {
GenerateReportJob::dispatch($tenant, 'daily', 'json');
$bar->advance();
}
$bar->finish();
$this->newLine();
}
private function cleanupExpiredData(): void
{
$this->call('shopmetrics:cleanup', ['--days' => 90]);
}
private function syncSubscriptions(): void
{
$this->call('shopmetrics:sync-subscriptions');
}
private function sendCompletionNotification(): void
{
$admin = User::where('role', 'super_admin')->first();
$admin?->notify(new DailyMaintenanceCompleted());
}
}
// スケジュール設定
// routes/console.php
Schedule::command('shopmetrics:daily-maintenance --notify')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('ops@shopmetrics.io');
❓ よくある質問
make:commandとmake:command --commandの違いは何ですか?--command=xxxでカスタムコマンド名を指定します:php artisan make:command SendEmails --command=emails:send。省略した場合, デフォルト名app:console-commands:send-emailsが使用されます。常にカスタムコマンド名を指定することをお勧めします。* * * * * php artisan schedule:run。すべてのタスクがコード内で定義されるため, バージョン管理とテストが可能です。各タスクに個別のCronジョブを設定しないでください。$this->artisan('command:name', ['arg' => 'value'])でコマンドを実行し, 出力が->expectsOutput('Done')であることをアサートするか, データベースの変更を検証します。--dry-runオプションを追加します。->appendOutputTo(storage_path('logs/command.log'))を使用してログを追加します。コマンド内ではLog::info()を使用します。失敗時のメール通知にはemailOutputOnFailure()を使用します。📖 まとめ
- Artisanには100以上の組み込みコマンドがあり, マイグレーション, キャッシュ, キュー, ルートなどの運用をカバーします
- カスタムコマンドはSignature構文でパラメータとオプションを定義します
- 手動Cronジョブの代わりにLaravel Scheduleでコマンドスケジューリングを自動化します
- インタラクティブコマンドは
ask,confirm,choiceでガイド付き体験を提供します - テーブル出力とプログレスバーでコマンドをよりプロフェッショナルに見せます
- onOneServer/withoutOverlapping:複数インスタンスの同時実行を防止します
📝 練習問題
-
基本問題 (⭐):
shopmetrics:tenant-statsという名前のコマンドを作成し,tenantパラメータを受け付け, そのテナントの店舗数, 注文数, 総売上を出力し,tableコマンドでフォーマットしてください。 -
応用問題 (⭐⭐):
shopmetrics:daily-maintenanceコマンドを作成し, アナリティクス処理 (プログレスバー付き)+ 期限切れデータのクリーンアップ + サブスクリプション同期を含め, Scheduleを設定して毎日午前2時に自動実行してください。 -
チャレンジ (⭐⭐⭐):インタラクティブな
shopmetrics:tenant-setupウィザードコマンドを実装してください—テナント名, slug, プラン, 管理者メールアドレスを順番に入力し, 各ステップでバリデーションを行い, 最後に作成を確認します。テナント名には自動補完 (anticipate)の候補を含めてください。



