Laravel: Laravel控制器与请求处理
最后更新:2026-08-26
控制器是 Laravel 的"业务指挥官"——接收请求、协调模型和视图、返回响应,一切业务逻辑从这里调度。
1. 你将学到
- 基础控制器与单一动作控制器
__invoke - 资源控制器:
--resource标志与 CRUD 方法映射 - API 资源控制器:
--api标志与路由绑定 - 依赖注入:构造函数与方法级 DI
- 控制器中间件分配
2. 一个后端开发者的真实故事
(1) 痛点:一个 2000 行的 God Controller
Bob 早期把 ShopMetrics 所有逻辑塞进一个 ShopController——商品管理、订单处理、用户认证、报表生成全在一起。代码膨胀到 2000 行后,每次改一个功能都可能破坏另一个。Charlie 接手时花了 3 天才理清代码结构,Alice 等一个小需求等了两周。
(2) 资源控制器的解法
Laravel 资源控制器把 CRUD 操作拆成 7 个独立方法,每个方法只做一件事。配合路由绑定,URL 和方法自动对应。
# One command generates a complete CRUD controller
php artisan make:controller ShopController --resource
# Creates: index(), create(), store(), show(), edit(), update(), destroy()
(3) 收益
Bob 用资源控制器重构后,每个方法不超过 30 行,Alice 的小需求从两周缩短到两天,Charlie 接手新功能不再担心破坏旧功能。
3. 基础控制器
(1) 创建与结构
php artisan make:controller HomeController
# Creates: app/Http/Controllers/HomeController.php
// app/Http/Controllers/HomeController.php
class HomeController extends Controller
{
public function index(): View
{
return view('home.index');
}
public function about(): View
{
return view('home.about');
}
}
(2) 单一动作控制器 __invoke
当一个控制器只需要一个方法时,使用 __invoke 替代命名方法。
php artisan make:controller GenerateReportController --invokable
// app/Http/Controllers/GenerateReportController.php
class GenerateReportController extends Controller
{
public function __invoke(Request $request): RedirectResponse
{
$report = ReportGenerator::create($request->all());
return redirect()->route('reports.show', $report->id);
}
}
// Route registration
Route::post('/reports/generate', GenerateReportController::class);
| 维度 | 常规控制器 | 单一动作控制器 |
|---|---|---|
| 方法数 | 多个 | 1 个 __invoke |
| 路由注册 | [Ctrl::class, 'method'] |
Ctrl::class |
| 适用场景 | 相关操作集合 | 单一职责操作 |
| 示例 | ShopController | GenerateReportController |
▶ 示例:ShopMetrics 单一动作控制器
// app/Http/Controllers/ExportOrdersController.php
class ExportOrdersController extends Controller
{
public function __invoke(Request $request): StreamedResponse
{
$shop = Shop::findOrFail($request->shop_id);
$csv = OrderExporter::toCsv($shop->orders);
return response()->streamDownload(
callback: fn () => print($csv),
name: "orders-{$shop->slug}.csv",
headers: ['Content-Type' => 'text/csv'],
);
}
}
// routes/web.php
Route::post('/shops/{shop}/export', ExportOrdersController::class)
->name('shops.export');
输出:
// 执行成功
4. 资源控制器
(1) 创建资源控制器
php artisan make:controller ShopController --resource
自动生成 7 个 CRUD 方法:
| HTTP 动词 | URI | 方法 | 用途 |
|---|---|---|---|
| GET | /shops | index | 列表 |
| GET | /shops/create | create | 新建表单 |
| POST | /shops | store | 保存新记录 |
| GET | /shops/{shop} | show | 详情 |
| GET | /shops/{shop}/edit | edit | 编辑表单 |
| PUT/PATCH | /shops/{shop} | update | 更新 |
| DELETE | /shops/{shop} | destroy | 删除 |
(2) 路由注册
// Single line registers all 7 routes
Route::resource('shops', ShopController::class);
// Limit to specific methods only
Route::resource('shops', ShopController::class)->only([
'index', 'show', 'store', 'update', 'destroy',
]);
// Exclude specific methods
Route::resource('shops', ShopController::class)->except([
'create', 'edit',
]);
▶ 示例:ShopMetrics Shop 资源控制器
// app/Http/Controllers/ShopController.php
class ShopController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('tenant.resolve')->except('index', 'show');
}
public function index(): View
{
$shops = Shop::with('tenant')->paginate(15);
return view('shops.index', compact('shops'));
}
public function create(): View
{
return view('shops.create');
}
public function store(StoreShopRequest $request): RedirectResponse
{
$shop = Shop::create($request->validated());
return redirect()->route('shops.show', $shop)
->with('success', 'Shop created successfully.');
}
public function show(Shop $shop): View
{
$shop->load('products', 'orders');
return view('shops.show', compact('shop'));
}
public function edit(Shop $shop): View
{
return view('shops.edit', compact('shop'));
}
public function update(UpdateShopRequest $request, Shop $shop): RedirectResponse
{
$shop->update($request->validated());
return redirect()->route('shops.show', $shop)
->with('success', 'Shop updated successfully.');
}
public function destroy(Shop $shop): RedirectResponse
{
$shop->delete();
return redirect()->route('shops.index')
->with('success', 'Shop deleted successfully.');
}
}
输出:
// 执行成功
5. API 资源控制器
(1) 创建 API 控制器
php artisan make:controller Api/ShopController --api
--api 等同于 --resource --except=create,edit,因为 API 不需要表单页面。
| 方法 | Web 资源 | API 资源 |
|---|---|---|
| index | ✅ | ✅ |
| create | ✅ | ❌ |
| store | ✅ | ✅ |
| show | ✅ | ✅ |
| edit | ✅ | ❌ |
| update | ✅ | ✅ |
| destroy | ✅ | ✅ |
▶ 示例:ShopMetrics API 控制器
// app/Http/Controllers/Api/ShopController.php
class ShopController extends Controller
{
public function __construct()
{
$this->middleware('auth:sanctum');
}
public function index(Request $request): JsonResponse
{
$shops = Shop::query()
->when($request->search, fn($q, $search) => $q->where('name', 'like', "%{$search}%"))
->paginate($request->per_page ?? 15);
return ShopResource::collection($shops);
}
public function store(StoreShopRequest $request): JsonResponse
{
$shop = Shop::create($request->validated());
return new ShopResource($shop);
}
public function show(Shop $shop): JsonResponse
{
return new ShopResource($shop->load('products', 'orders'));
}
public function update(UpdateShopRequest $request, Shop $shop): JsonResponse
{
$shop->update($request->validated());
return new ShopResource($shop);
}
public function destroy(Shop $shop): Response
{
$shop->delete();
return response()->noContent();
}
}
输出:
// 执行成功
6. 依赖注入
(1) 构造函数注入
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
private PaymentGateway $payment,
) {}
public function store(StoreOrderRequest $request): RedirectResponse
{
$order = $this->orderService->create($request->validated());
$this->payment->charge($order);
return redirect()->route('orders.show', $order);
}
}
(2) 方法级注入
class ReportController extends Controller
{
public function show(Request $request, Report $report): View
{
// $request injected by container
// $report resolved via Route Model Binding
return view('reports.show', compact('report'));
}
}
(3) Route Model Binding
// Implicit binding — type-hint in controller method
Route::get('/shops/{shop}', [ShopController::class, 'show']);
public function show(Shop $shop): View
{
// $shop is automatically fetched from DB by {shop}
// Equivalent to: Shop::findOrFail($shop)
return view('shops.show', compact('shop'));
}
// Custom key — bind by slug instead of id
Route::get('/shops/{shop:slug}', [ShopController::class, 'show']);
// Now: /shops/alice-store → Shop where slug = 'alice-store'
| 注入方式 | 适用场景 | 生命周期 |
|---|---|---|
| 构造函数 | 控制器所有方法都需要 | 整个请求 |
| 方法级 | 仅特定方法需要 | 单个方法 |
| Route Model Binding | 从 URL 自动获取模型 | 单个方法 |
▶ 示例:ShopMetrics 带依赖注入的订单控制器
// app/Http/Controllers/OrderController.php
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
) {
$this->middleware('auth');
}
public function index(Request $request): View
{
$orders = $request->user()->orders()
->with('shop', 'products')
->latest()
->paginate(15);
return view('orders.index', compact('orders'));
}
public function show(Order $order): View
{
$this->authorize('view', $order);
$order->load('items.product', 'shop', 'payment');
return view('orders.show', compact('order'));
}
}
输出:
// 执行成功
7. 控制器中间件
(1) 构造函数分配
class ShopController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('tenant.resolve')->except('index');
$this->middleware('can:update,shop')->only('update', 'edit');
}
}
(2) 路由级别分配
Route::middleware(['auth', 'admin'])->group(function () {
Route::resource('plans', PlanController::class);
});
| 分配位置 | 粒度 | 适用场景 |
|---|---|---|
| 构造函数 | 方法级 | 控制器内不同方法需不同中间件 |
| 路由定义 | 路由组 | 一组路由共享中间件 |
| Kernel 全局 | 全局 | 所有请求都需执行 |
▶ 示例:ShopMetrics 管理后台控制器
// app/Http/Controllers/Admin/PlanController.php
class PlanController extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'role:admin']);
}
public function index(): View
{
$plans = Plan::withCount('subscriptions')->get();
return view('admin.plans.index', compact('plans'));
}
public function store(StorePlanRequest $request): RedirectResponse
{
Plan::create($request->validated());
return redirect()->route('admin.plans.index')
->with('success', 'Plan created.');
}
}
输出:
// 执行成功
8. 请求→控制器→模型→视图响应链
sequenceDiagram
participant C as Client
participant R as Router
participant M as Middleware
participant Ctrl as Controller
participant Model as Model
participant V as View
C->>R: HTTP Request
R->>M: Run middleware pipeline
M->>Ctrl: Call controller method
Ctrl->>Model: Query data
Model-->>Ctrl: Return results
Ctrl->>V: Pass data to view
V-->>Ctrl: Rendered HTML
Ctrl-->>C: HTTP Response
9. 综合示例:ShopMetrics 产品 CRUD 控制器
// ============================================
// Comprehensive: ShopMetrics ProductController
// Covers: resource controller, DI, middleware, model binding
// ============================================
// app/Http/Controllers/ProductController.php
class ProductController extends Controller
{
public function __construct(
private ProductService $productService,
) {
$this->middleware('auth');
$this->middleware('tenant.resolve');
}
public function index(Request $request): View
{
$products = Product::query()
->where('tenant_id', tenant()->id)
->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
->when($request->category, fn($q, $c) => $q->where('category_id', $c))
->with('category')
->orderBy($request->sort ?? 'created_at', $request->direction ?? 'desc')
->paginate(20);
return view('products.index', compact('products'));
}
public function create(): View
{
$categories = Category::forTenant(tenant()->id)->get();
return view('products.create', compact('categories'));
}
public function store(StoreProductRequest $request): RedirectResponse
{
$product = $this->productService->create(
tenant()->id,
$request->validated(),
);
return redirect()->route('products.show', $product)
->with('success', 'Product created successfully.');
}
public function show(Product $product): View
{
$this->authorize('view', $product);
$product->load('category', 'orderItems.order');
return view('products.show', compact('product'));
}
public function edit(Product $product): View
{
$this->authorize('update', $product);
$categories = Category::forTenant(tenant()->id)->get();
return view('products.edit', compact('product', 'categories'));
}
public function update(UpdateProductRequest $request, Product $product): RedirectResponse
{
$this->authorize('update', $product);
$product->update($request->validated());
return redirect()->route('products.show', $product)
->with('success', 'Product updated successfully.');
}
public function destroy(Product $product): RedirectResponse
{
$this->authorize('delete', $product);
$product->delete();
return redirect()->route('products.index')
->with('success', 'Product deleted successfully.');
}
}
❓ 常见问题
Route::bind() 自定义解析逻辑,或在模型上覆盖 resolveRouteBindingQuery() 方法。📖 小节
- 控制器负责接收请求、协调模型和视图、返回响应
- 单一动作控制器用
__invoke处理不属于 CRUD 的单一操作 - 资源控制器自动映射 7 个 CRUD 方法到 HTTP 动词
- API 控制器省略 create/edit,配合 apiResource 使用
- 依赖注入让控制器不需要 new 对象,容器自动解析依赖
- Route Model Binding 自动将 URL 参数解析为模型实例
📝 作业
-
基础题(⭐):使用
make:controller --resource创建 ShopMetrics 的 ProductController,注册资源路由,实现 index 和 show 方法返回简单的视图。 -
进阶题(⭐⭐):创建一个
ExportOrdersController单一动作控制器,实现 CSV 导出功能,注入 OrderService 处理数据转换,使用streamDownload返回文件。 -
挑战题(⭐⭐⭐):设计一个 TenantProductController,使用 Route Model Binding 解析
{tenant}和{product},实现租户隔离的产品 CRUD,确保用户只能操作自己租户下的产品。