laravel-controllers

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel Controllers

Laravel 控制器

Controllers are extremely thin. They handle HTTP concerns only and contain zero domain logic.
Related guides:
  • query-objects.md - Query objects for API filtering/sorting
  • Actions - Actions contain the domain logic
  • form-requests.md - Validation layer
  • DTOs - DTOs for data transfer
  • structure.md - Web vs API organization
控制器极度轻量,仅负责处理HTTP相关事宜不包含任何领域逻辑
相关指南:
  • query-objects.md - 用于API筛选/排序的查询对象
  • Actions - 包含领域逻辑的动作类
  • form-requests.md - 验证层
  • DTOs - 用于数据传输的DTO
  • structure.md - Web与API架构组织

Philosophy

设计理念

Controllers should ONLY:
  1. Type-hint dependencies
  2. Validate (via Form Requests)
  3. Call actions
  4. Return responses (resources, redirects, views)
Controllers should NEVER:
  • Contain domain logic
  • Make database queries directly
  • Perform calculations
  • Handle complex business rules
控制器应当仅负责以下内容:
  1. 类型提示依赖项
  2. 通过Form Requests进行验证
  3. 调用动作类
  4. 返回响应(资源、重定向、视图)
控制器绝对不能:
  • 包含领域逻辑
  • 直接执行数据库查询
  • 进行计算操作
  • 处理复杂业务规则

Controller Naming Conventions

控制器命名规范

Controllers should be named using the PLURAL form of the main resource:
控制器应使用主资源的复数形式命名:

Standard Resource Controllers

标准资源控制器

php
// ✅ CORRECT - Plural resource names
CalendarsController      // manages calendar resources
EventsController         // manages event resources
OrdersController         // manages order resources
UsersController          // manages user resources
php
// ❌ INCORRECT - Singular form
CalendarController
EventController
php
// ✅ 正确 - 复数资源名称
CalendarsController      // 管理日历资源
EventsController         // 管理事件资源
OrdersController         // 管理订单资源
UsersController          // 管理用户资源
php
// ❌ 错误 - 单数形式
CalendarController
EventController

Nested Resource Controllers

嵌套资源控制器

For nested resources, combine both resource names (parent + child):
php
// Route: /calendars/{calendar}/events
CalendarEventsController  // manages events within a calendar

// Route: /orders/{order}/items
OrderItemsController      // manages items within an order
Pattern:
{ParentSingular}{ChildPlural}Controller
Routes:
php
// Standard resource routes
Route::resource('calendars', CalendarsController::class);

// Nested resource routes
Route::resource('calendars.events', CalendarEventsController::class);
对于嵌套资源,需组合父资源和子资源的名称:
php
// 路由: /calendars/{calendar}/events
CalendarEventsController  // 管理日历下的事件资源

// 路由: /orders/{order}/items
OrderItemsController      // 管理订单下的商品资源
命名模式:
{ParentSingular}{ChildPlural}Controller
路由示例:
php
// 标准资源路由
Route::resource('calendars', CalendarsController::class);

// 嵌套资源路由
Route::resource('calendars.events', CalendarEventsController::class);

RESTful Methods Only

仅使用RESTful方法

Controllers must only use Laravel's standard RESTful method names.
控制器必须仅使用Laravel的标准RESTful方法名称。

Standard RESTful Methods

标准RESTful方法

For web applications (with forms):
  • index
    - Display a listing of the resource
  • create
    - Show the form for creating a new resource
  • store
    - Store a newly created resource
  • show
    - Display the specified resource
  • edit
    - Show the form for editing the resource
  • update
    - Update the specified resource
  • destroy
    - Remove the specified resource
For APIs (no form views):
  • index
    ,
    show
    ,
    store
    ,
    update
    ,
    destroy
  • APIs must NOT include
    create
    or
    edit
    methods
    (those are for HTML forms)
适用于Web应用(含表单):
  • index
    - 展示资源列表
  • create
    - 显示创建新资源的表单
  • store
    - 存储新创建的资源
  • show
    - 展示指定资源详情
  • edit
    - 显示编辑资源的表单
  • update
    - 更新指定资源
  • destroy
    - 删除指定资源
适用于API(无表单视图):
  • index
    ,
    show
    ,
    store
    ,
    update
    ,
    destroy
  • API绝对不能包含
    create
    edit
    方法
    (这些方法用于HTML表单)

Forbidden Method Names

禁止使用的方法名称

Never use custom method names in resource controllers:
php
// ❌ 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
}
资源控制器中绝不能使用自定义方法名称:
php
// ❌ 错误
class OrdersController extends Controller
{
    public function all() { }      // 应使用index
    public function get() { }      // 应使用show
    public function add() { }      // 应使用store
    public function remove() { }   // 应使用destroy
    public function cancel() { }   // 应提取到CancelOrderController
}

Non-RESTful Actions: Extract to Invokable Controllers

非RESTful动作:提取为可调用控制器

If you need an endpoint that doesn't fit standard RESTful methods, extract it to its own invokable controller:
php
// 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);
    }
}
Routes:
php
Route::apiResource('orders', OrdersController::class);
Route::post('/orders/{order:uuid}/cancel', CancelOrderController::class);
Why invokable controllers for non-RESTful actions?
  • Single Responsibility Principle
  • Clear intent from controller name
  • Independently testable
  • Prevents bloated resource controllers
如果需要不符合标准RESTful方法的端点,应将其提取为独立的可调用控制器
php
// 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);
    }
}
路由示例:
php
Route::apiResource('orders', OrdersController::class);
Route::post('/orders/{order:uuid}/cancel', CancelOrderController::class);
为什么非RESTful动作要使用可调用控制器?
  • 遵循单一职责原则
  • 控制器名称清晰表达意图
  • 可独立测试
  • 避免资源控制器过度臃肿

Web Layer vs Public API

Web层与公开API

Web Layer Controllers

Web层控制器

Purpose: Serve your application's web layer (API for separate frontend, Blade views, or Inertia)
Location:
app/Http/Web/Controllers/
Routes:
routes/web.php
Characteristics:
  • Not versioned
  • Can change freely
  • Private (only your app consumes)
用途: 为应用的Web层提供服务(独立前端的API、Blade视图或Inertia)
位置:
app/Http/Web/Controllers/
路由文件:
routes/web.php
特点:
  • 无版本控制
  • 可自由修改
  • 私有(仅自身应用调用)

Public API Controllers

公开API控制器

Purpose: For external/third-party consumption
Location:
app/Http/Api/V1/Controllers/
Routes:
routes/api/v1.php
Characteristics:
  • Versioned (
    /api/v1
    ,
    /api/v2
    )
  • Stable contract
  • Breaking changes require new version
Key difference: Namespace (
Http\Web
vs
Http\Api\V1
). Controller structure is identical.
用途: 供外部/第三方调用
位置:
app/Http/Api/V1/Controllers/
路由文件:
routes/api/v1.php
特点:
  • 带版本控制(
    /api/v1
    ,
    /api/v2
  • 稳定的契约
  • 破坏性变更需要发布新版本
核心区别: 命名空间不同(
Http\Web
vs
Http\Api\V1
),控制器结构完全一致。

Full Controller Example

完整控制器示例

php
<?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();
    }
}
For API controllers: Same structure, different namespace (
App\Http\Api\V1\Controllers
).
php
<?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
)。

Query Objects

查询对象

For API filtering, sorting, and includes, use Query Objects with Spatie Query Builder:
php
public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}
→ Complete query objects guide: query-objects.md
对于API的筛选、排序和关联查询,建议结合Spatie Query Builder使用查询对象
php
public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}
→ 完整查询对象指南:query-objects.md

Authorization

授权

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

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

    return OrderResource::make($order);
}
Or use route middleware:
php
Route::post('/orders', [OrdersController::class, 'store'])
    ->can('create', Order::class);
php
public function store(
    CreateOrderRequest $request,
    CreateOrderAction $action
): OrderResource {
    $this->authorize('create', Order::class);

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

    return OrderResource::make($order);
}
或使用路由中间件:
php
Route::post('/orders', [OrdersController::class, 'store'])
    ->can('create', Order::class);

Response Types

响应类型

JSON Resource

JSON资源

php
public function show(Order $order): OrderResource
{
    return OrderResource::make($order);
}
php
public function show(Order $order): OrderResource
{
    return OrderResource::make($order);
}

Collection Resource

集合资源

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

201 Created

201 Created

php
return OrderResource::make($order)->response()->setStatusCode(201);
php
return OrderResource::make($order)->response()->setStatusCode(201);

204 No Content

204 No Content

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

Redirect

重定向

php
return redirect()->route('orders.show', $order);
php
return redirect()->route('orders.show', $order);

Route Model Binding

路由模型绑定

Use route model binding for cleaner controllers:
php
Route::get('/orders/{order}', [OrdersController::class, 'show']);
Route::get('/orders/{order:uuid}', [OrdersController::class, 'show']); // Custom key
Controller automatically receives model:
php
public function show(Order $order): OrderResource
{
    return OrderResource::make($order->load('items', 'customer'));
}
使用路由模型绑定让控制器代码更简洁:
php
Route::get('/orders/{order}', [OrdersController::class, 'show']);
Route::get('/orders/{order:uuid}', [OrdersController::class, 'show']); // 自定义键
控制器会自动接收模型实例:
php
public function show(Order $order): OrderResource
{
    return OrderResource::make($order->load('items', 'customer'));
}

Controller Testing

控制器测试

Feature tests for controllers:
php
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']);
});
控制器的功能测试:
php
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']);
});

Common Mistakes

常见错误

❌ Domain Logic in Controller

❌ 控制器中包含领域逻辑

php
// 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]);
}
php
// 错误示例
public function store(Request $request)
{
    $order = Order::create($request->validated());
    $order->items()->createMany($request->items);
    $total = $order->items->sum('total');
    $order->update(['total' => $total]);
}

✅ Delegate to Action

✅ 委托给动作类

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

    return OrderResource::make($order);
}
php
// 正确示例
public function store(
    CreateOrderRequest $request,
    CreateOrderAction $action
): OrderResource {
    $order = $action(
        user(),
        OrderDataTransformer::fromRequest($request)
    );

    return OrderResource::make($order);
}

❌ Database Queries in Controller

❌ 控制器中执行数据库查询

php
// BAD
public function index()
{
    $orders = Order::with('items')
        ->where('status', 'pending')
        ->latest()
        ->paginate();
}
php
// 错误示例
public function index()
{
    $orders = Order::with('items')
        ->where('status', 'pending')
        ->latest()
        ->paginate();
}

✅ Use Query Object

✅ 使用查询对象

php
// GOOD
public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}
php
// 正确示例
public function index(OrderIndexQuery $query): AnonymousResourceCollection
{
    return OrderResource::collection($query->jsonPaginate());
}

Summary

总结

Controllers are HTTP adapters:
  1. Receive HTTP request
  2. Validate via Form Request
  3. Call Action (with DTO if needed)
  4. Return HTTP response via Resource
Every line of domain logic belongs in an Action, not a Controller.
控制器是HTTP适配器:
  1. 接收HTTP请求
  2. 通过Form Request进行验证
  3. 调用动作类(如需可搭配DTO)
  4. 通过Resource返回HTTP响应
所有领域逻辑都应放在动作类中,而非控制器内。