laravel-controllers
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLaravel 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:
- Type-hint dependencies
- Validate (via Form Requests)
- Call actions
- Return responses (resources, redirects, views)
Controllers should NEVER:
- Contain domain logic
- Make database queries directly
- Perform calculations
- Handle complex business rules
控制器应当仅负责以下内容:
- 类型提示依赖项
- 通过Form Requests进行验证
- 调用动作类
- 返回响应(资源、重定向、视图)
控制器绝对不能:
- 包含领域逻辑
- 直接执行数据库查询
- 进行计算操作
- 处理复杂业务规则
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 resourcesphp
// ❌ INCORRECT - Singular form
CalendarController
EventControllerphp
// ✅ 正确 - 复数资源名称
CalendarsController // 管理日历资源
EventsController // 管理事件资源
OrdersController // 管理订单资源
UsersController // 管理用户资源php
// ❌ 错误 - 单数形式
CalendarController
EventControllerNested 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 orderPattern:
{ParentSingular}{ChildPlural}ControllerRoutes:
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):
- - Display a listing of the resource
index - - Show the form for creating a new resource
create - - Store a newly created resource
store - - Display the specified resource
show - - Show the form for editing the resource
edit - - Update the specified resource
update - - Remove the specified resource
destroy
For APIs (no form views):
- ,
index,show,store,updatedestroy - APIs must NOT include or
createmethods (those are for HTML forms)edit
适用于Web应用(含表单):
- - 展示资源列表
index - - 显示创建新资源的表单
create - - 存储新创建的资源
store - - 展示指定资源详情
show - - 显示编辑资源的表单
edit - - 更新指定资源
update - - 删除指定资源
destroy
适用于API(无表单视图):
- ,
index,show,store,updatedestroy - API绝对不能包含或
create方法(这些方法用于HTML表单)edit
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.phpCharacteristics:
- 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.phpCharacteristics:
- Versioned (,
/api/v1)/api/v2 - Stable contract
- Breaking changes require new version
Key difference: Namespace ( vs ). Controller structure is identical.
Http\WebHttp\Api\V1用途: 供外部/第三方调用
位置:
app/Http/Api/V1/Controllers/路由文件:
routes/api/v1.php特点:
- 带版本控制(,
/api/v1)/api/v2 - 稳定的契约
- 破坏性变更需要发布新版本
核心区别: 命名空间不同( vs ),控制器结构完全一致。
Http\WebHttp\Api\V1Full 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\Controllersphp
<?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\ControllersQuery 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 keyController 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:
- Receive HTTP request
- Validate via Form Request
- Call Action (with DTO if needed)
- Return HTTP response via Resource
Every line of domain logic belongs in an Action, not a Controller.
控制器是HTTP适配器:
- 接收HTTP请求
- 通过Form Request进行验证
- 调用动作类(如需可搭配DTO)
- 通过Resource返回HTTP响应
所有领域逻辑都应放在动作类中,而非控制器内。