mcpField
L
laravel-controllers

laravel-controllers

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

关于 laravel-controllers

面向 Laravel 开发者的控制器设计指南,规定控制器仅处理 HTTP 职责,并将业务逻辑交给 Action。还涵盖 REST 命名、Web 与公共 API 组织、查询对象和路由模型绑定。

主要功能

  1. 01控制器保持精简
  2. 02采用标准 REST 方法
  3. 03非 REST 操作拆分为单动作控制器
  4. 04区分 Web 与版本化 API
  5. 05使用查询对象处理 API 查询
  6. 06提供授权与模型绑定模式

适用场景

  1. 01规划资源控制器的命名与方法
  2. 02将业务规则从控制器移至 Action
  3. 03组织 Web 层和公共 API 控制器
  4. 04为 API 筛选、排序及关联数据设计查询

安装

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

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

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

附带文件references/query-objects.md

详细介绍

Laravel 控制器

控制器应当极为精简。它们只处理 HTTP 相关工作,不包含任何领域逻辑。

相关指南:

理念

控制器只能:

  1. 声明依赖类型
  2. 验证(通过 Form Requests)
  3. 调用 Actions
  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);

路由模型绑定

使用路由模型绑定,让控制器更简洁:

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());
}

评论

支持 Markdown