mcpField
L
laravel-controllers

laravel-controllers

github.com/leeovery开发4341 次安装31 周前更新
代码仓库

关于 laravel-controllers

面向 Laravel 开发者的控制器规范,强调控制器只处理 HTTP 事务,并将领域逻辑交给其他层。内容涵盖 Web 与 API 控制器、资源命名和查询对象。

主要功能

  1. 01控制器仅处理 HTTP 事务
  2. 02将领域逻辑交给 Action
  3. 03资源控制器使用复数命名
  4. 04区分 Web 与版本化 API 控制器
  5. 05用查询对象处理 API 查询

适用场景

  1. 01检查 Laravel 控制器结构
  2. 02设计资源及嵌套资源路由
  3. 03避免在 HTTP 控制器中编写业务规则

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/laravel-controllers确认已加载。

附带文件references/query-objects.md

详细介绍

Laravel 控制器

控制器应当极其精简,只处理 HTTP 相关事务,不包含任何领域逻辑。

相关指南:

理念

控制器应当仅执行以下操作:

  1. 使用类型提示声明依赖项
  2. 进行验证(通过 Form Request)
  3. 调用 Action
  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 已创建
return OrderResource::make($order)->response()->setStatusCode(201);
204 无内容
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]);
}
✅ 委托给 Action
// 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. 通过表单请求进行验证
  3. 调用 Action(必要时传入 DTO)
  4. 通过资源返回 HTTP 响应

所有领域逻辑都应放在 Action 中,而不是控制器中。

评论

支持 Markdown