mcpField
L
laravel-controllers

laravel-controllers

github.com/leeovery開発4341 回インストール31 週間前に更新
リポジトリ

laravel-controllers について

Laravel 開発者向けに、HTTP 処理とドメインロジックを分ける薄いコントローラーの規約を示すスキルです。Web と API の構成、リソース命名、クエリオブジェクトの使い方を扱います。

主な機能

  1. 01HTTP 処理のみを担当するコントローラー
  2. 02ドメインロジックを Action に委譲
  3. 03リソースコントローラーを複数形で命名
  4. 04Web とバージョン付き API の分離
  5. 05API クエリにクエリオブジェクトを使用

活用シーン

  1. 01Laravel コントローラー構成の確認
  2. 02リソースおよびネストしたルートの設計
  3. 03HTTP コントローラーから業務ルールを分離

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/leeovery/claude-laravel --skill laravel-controllers

インストール後、Claude Code で/laravel-controllersを入力して読み込まれたか確認します。

同梱ファイルreferences/query-objects.md

詳細

Laravel コントローラー

コントローラーは極めて薄く保ちます。HTTP に関する処理のみを担当し、ドメインロジックは一切含めません。

関連ガイド:

  • query-objects.md - API のフィルタリング/並べ替えに使うクエリオブジェクト
  • Actions - アクションにドメインロジックを記述します
  • form-requests.md - バリデーション層
  • DTOs - データ転送用 DTO
  • structure.md - Web と API の構成

基本方針

コントローラーで行うのは、次の処理のみです。

  1. 依存関係を型指定する
  2. バリデーションを行う(Form Request 経由)
  3. アクションを呼び出す
  4. レスポンスを返す(リソース、リダイレクト、ビュー)

コントローラーでは、次の処理を行ってはいけません。

  • ドメインロジックを含める
  • データベースに直接クエリを実行する
  • 計算を行う
  • 複雑なビジネスルールを処理する

コントローラーの命名規則

コントローラーには、主要リソースの複数形を名前として使用します。

標準的なリソースコントローラー
// ✅ CORRECT - Plural resource names
CalendarsController      // manages calendar resources
EventsController         // manages event resources
OrdersController         // manages order resources
UsersController          // manages user resources
// ❌ INCORRECT - Singular form
CalendarController
EventController
ネストされたリソースコントローラー

ネストされたリソースでは、親リソース名と子リソース名を組み合わせます。

// Route: /calendars/{calendar}/events
CalendarEventsController  // manages events within a calendar

// Route: /orders/{order}/items
OrderItemsController      // manages items within an order

パターン: {ParentSingular}{ChildPlural}Controller

ルート:

// Standard resource routes
Route::resource('calendars', CalendarsController::class);

// Nested resource routes
Route::resource('calendars.events', CalendarEventsController::class);

RESTful メソッドのみを使用する

コントローラーでは、Laravel の標準的な RESTful メソッド名のみを使用してください。

標準的な RESTful メソッド

Web アプリケーションの場合(フォームあり):

  • index - リソースの一覧を表示する
  • create - 新しいリソースを作成するフォームを表示する
  • store - 新しく作成したリソースを保存する
  • show - 指定したリソースを表示する
  • edit - リソースを編集するフォームを表示する
  • update - 指定したリソースを更新する
  • destroy - 指定したリソースを削除する

API の場合(フォームビューなし):

  • index、show、store、update、destroy
  • API に create または edit メソッドを含めてはいけません(これらは HTML フォーム用です)
使用禁止のメソッド名

リソースコントローラーで、カスタムメソッド名を使用しないでください。

// ❌ INCORRECT
class OrdersController extends Controller
{
    public function all() { }      // Use index
    public function get() { }      // Use show
    public function add() { }      // Use store
    public function remove() { }   // Use destroy
    public function cancel() { }   // Extract to CancelOrderController
}
RESTful ではないアクション:単一アクションコントローラーに切り出す

標準的な RESTful メソッドに当てはまらないエンドポイントが必要な場合は、専用の単一アクションコントローラーに切り出します。

// app/Http/Api/V1/Controllers/CancelOrderController.php
class CancelOrderController extends Controller
{
    public function __invoke(
        Order $order,
        CancelOrderAction $action
    ): OrderResource {
        $order = $action($order);
        return OrderResource::make($order);
    }
}

ルート:

Route::apiResource('orders', OrdersController::class);
Route::post('/orders/{order:uuid}/cancel', CancelOrderController::class);

RESTful ではないアクションに単一アクションコントローラーを使う理由:

  • 単一責任の原則
  • コントローラー名から目的が明確になる
  • 個別にテストできる
  • リソースコントローラーが肥大化するのを防ぐ

Web 層と公開 API

Web 層コントローラー

目的: アプリケーションの Web 層(別のフロントエンド向け API、Blade ビュー、または Inertia)を提供します。

配置場所: app/Http/Web/Controllers/

ルート: routes/web.php

特徴:

  • バージョン管理しない
  • 自由に変更できる
  • 非公開(自アプリケーションのみが利用)
公開 API コントローラー

目的: 外部/サードパーティによる利用を想定します。

配置場所: app/Http/Api/V1/Controllers/

ルート: routes/api/v1.php

特徴:

  • バージョン管理する(/api/v1、/api/v2)
  • 安定した契約を提供する
  • 互換性を損なう変更には新しいバージョンが必要

主な違い: 名前空間(Http\Web と Http\Api\V1)。コントローラーの構成は同じです。

コントローラーの全体例

<?php

declare(strict_types=1);

namespace App\Http\Web\Controllers;

use App\Actions\Order\CreateOrderAction;
use App\Actions\Order\DeleteOrderAction;
use App\Actions\Order\UpdateOrderAction;
use App\Data\Transformers\Web\OrderDataTransformer;
use App\Http\Controllers\Controller;
use App\Http\Web\Queries\OrderIndexQuery;
use App\Http\Web\Requests\CreateOrderRequest;
use App\Http\Web\Requests\UpdateOrderRequest;
use App\Http\Web\Resources\OrderResource;
use App\Models\Order;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Response;

class OrdersController extends Controller
{
    public function index(OrderIndexQuery $query): AnonymousResourceCollection
    {
        return OrderResource::collection($query->jsonPaginate());
    }

    public function show(Order $order): OrderResource
    {
        return OrderResource::make($order->load('items', 'customer'));
    }

    public function store(
        CreateOrderRequest $request,
        CreateOrderAction $action
    ): OrderResource {
        $order = $action(
            user(),
            OrderDataTransformer::fromRequest($request)
        );

        return OrderResource::make($order);
    }

    public function update(
        UpdateOrderRequest $request,
        Order $order,
        UpdateOrderAction $action
    ): OrderResource {
        $order = $action(
            $order,
            OrderDataTransformer::fromRequest($request)
        );

        return OrderResource::make($order);
    }

    public function destroy(
        Order $order,
        DeleteOrderAction $action
    ): Response {
        $action($order);

        return response()->noContent();
    }
}

API コントローラーの場合: 名前空間(App\Http\Api\V1\Controllers)が異なるだけで、構成は同じです。

クエリオブジェクト

API のフィルタリング、並べ替え、関連データの読み込みには、Spatie Query Builder とクエリオブジェクトを使用します。

public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}

→ クエリオブジェクトの完全ガイド:query-objects.md

認可

public function store(
    CreateOrderRequest $request,
    CreateOrderAction $action
): OrderResource {
    $this->authorize('create', Order::class);

    $order = $action(user(), OrderDataTransformer::fromRequest($request));

    return OrderResource::make($order);
}

または、ルートミドルウェアを使用します。

Route::post('/orders', [OrdersController::class, 'store'])
    ->can('create', Order::class);

レスポンスの種類

JSON リソース
public function show(Order $order): OrderResource
{
    return OrderResource::make($order);
}
コレクションリソース
public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}
201 Created
return OrderResource::make($order)->response()->setStatusCode(201);
204 No Content
return response()->noContent();
リダイレクト
return redirect()->route('orders.show', $order);

ルートモデルバインディング

コントローラーを簡潔にするため、ルートモデルバインディングを使用します。

Route::get('/orders/{order}', [OrdersController::class, 'show']);
Route::get('/orders/{order:uuid}', [OrdersController::class, 'show']); // Custom key

コントローラーはモデルを自動的に受け取ります。

public function show(Order $order): OrderResource
{
    return OrderResource::make($order->load('items', 'customer'));
}

コントローラーのテスト

コントローラーのフィーチャーテスト:

use function Pest\Laravel\actingAs;
use function Pest\Laravel\postJson;

it('creates an order', function () {
    $user = User::factory()->create();
    $data = CreateOrderData::testFactory()->make();

    actingAs($user)
        ->postJson('/orders', $data->toArray())
        ->assertCreated()
        ->assertJsonStructure(['data' => ['id', 'status']]);
});

it('requires authentication', function () {
    postJson('/orders', [])->assertUnauthorized();
});

it('validates required fields', function () {
    actingAs(User::factory()->create())
        ->postJson('/orders', [])
        ->assertUnprocessable()
        ->assertJsonValidationErrors(['customer_email', 'items']);
});

よくある間違い

❌ コントローラーにドメインロジックを記述する
// BAD
public function store(Request $request)
{
    $order = Order::create($request->validated());
    $order->items()->createMany($request->items);
    $total = $order->items->sum('total');
    $order->update(['total' => $total]);
}
✅ アクションに委譲する
// GOOD
public function store(
    CreateOrderRequest $request,
    CreateOrderAction $action
): OrderResource {
    $order = $action(
        user(),
        OrderDataTransformer::fromRequest($request)
    );

    return OrderResource::make($order);
}
❌ コントローラーでのデータベースクエリ
// BAD
public function index()
{
    $orders = Order::with('items')
        ->where('status', 'pending')
        ->latest()
        ->paginate();
}
✅ クエリーオブジェクトを使用する
// GOOD
public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}

まとめ

コントローラーは HTTP アダプターです:

  1. HTTP リクエストを受け取る
  2. Form Request で検証する
  3. Action を呼び出す(必要に応じて DTO を使用する)
  4. Resource を使用して HTTP レスポンスを返す

ドメインロジックはすべて、コントローラーではなく Action に記述します。

コメント