Laravel: Laravel控制器与请求处理

最后更新:2026-08-26

控制器是 Laravel 的"业务指挥官"——接收请求、协调模型和视图、返回响应,一切业务逻辑从这里调度。

1. 你将学到


2. 一个后端开发者的真实故事

(1) 痛点:一个 2000 行的 God Controller

Bob 早期把 ShopMetrics 所有逻辑塞进一个 ShopController——商品管理、订单处理、用户认证、报表生成全在一起。代码膨胀到 2000 行后,每次改一个功能都可能破坏另一个。Charlie 接手时花了 3 天才理清代码结构,Alice 等一个小需求等了两周。

(2) 资源控制器的解法

Laravel 资源控制器把 CRUD 操作拆成 7 个独立方法,每个方法只做一件事。配合路由绑定,URL 和方法自动对应。

BASH
# 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) 创建与结构

BASH
php artisan make:controller HomeController
# Creates: app/Http/Controllers/HomeController.php
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 替代命名方法。

BASH
php artisan make:controller GenerateReportController --invokable
PHP
// 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 单一动作控制器

PHP
// 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');

输出:

TEXT 📖 仅展示
// 执行成功

4. 资源控制器

(1) 创建资源控制器

BASH
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) 路由注册

PHP
// 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 资源控制器

PHP
// 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.');
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

5. API 资源控制器

(1) 创建 API 控制器

BASH
php artisan make:controller Api/ShopController --api

--api 等同于 --resource --except=create,edit,因为 API 不需要表单页面。

方法 Web 资源 API 资源
index
create
store
show
edit
update
destroy

▶ 示例:ShopMetrics API 控制器

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

输出:

TEXT 📖 仅展示
// 执行成功

6. 依赖注入

(1) 构造函数注入

PHP
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) 方法级注入

PHP
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

PHP
// 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 带依赖注入的订单控制器

PHP
// 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'));
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

7. 控制器中间件

(1) 构造函数分配

PHP
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) 路由级别分配

PHP
Route::middleware(['auth', 'admin'])->group(function () {
    Route::resource('plans', PlanController::class);
});
分配位置 粒度 适用场景
构造函数 方法级 控制器内不同方法需不同中间件
路由定义 路由组 一组路由共享中间件
Kernel 全局 全局 所有请求都需执行

▶ 示例:ShopMetrics 管理后台控制器

PHP
// 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.');
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

8. 请求→控制器→模型→视图响应链

100%
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 控制器

PHP
// ============================================
// 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.');
    }
}

❓ 常见问题

Q 控制器应该多"瘦"?
A 控制器应该只做"协调"——接收请求、调用 Service/Model、返回响应。业务逻辑放在 Service 类,数据访问放在 Model/Repository,控制器保持 10-30 行/方法。
Q 什么时候用单一动作控制器?
A 当一个操作不属于任何 CRUD 资源时,如"导出报表""发送邮件""生成发票"。如果控制器只有一个公共方法,就用 __invoke。
Q Route Model Binding 找不到记录怎么办?
A 默认返回 404。可以自定义:在 RouteServiceProvider 中用 Route::bind() 自定义解析逻辑,或在模型上覆盖 resolveRouteBindingQuery() 方法。
Q 依赖注入和 new 有什么区别?
A new 手动创建对象,需自己处理依赖链;DI 由 Laravel 容器自动解析依赖并注入,支持接口绑定、单例管理、Mock 测试。
Q API 控制器为什么要返回 Resource 而不是直接 json?
A Resource 类统一了 JSON 输出格式,可以隐藏敏感字段、转换字段名、嵌套关联数据。直接返回模型会将所有字段暴露,格式也不一致。
Q 控制器里能用 DB::raw 写 SQL 吗?
A 可以但不推荐。复杂查询应该用 Eloquent Scope 或 Query Builder 方法。DB::raw 跳过了 Eloquent 的安全层,容易引入 SQL 注入。

📖 小节


📝 作业

  1. 基础题(⭐):使用 make:controller --resource 创建 ShopMetrics 的 ProductController,注册资源路由,实现 index 和 show 方法返回简单的视图。

  2. 进阶题(⭐⭐):创建一个 ExportOrdersController 单一动作控制器,实现 CSV 导出功能,注入 OrderService 处理数据转换,使用 streamDownload 返回文件。

  3. 挑战题(⭐⭐⭐):设计一个 TenantProductController,使用 Route Model Binding 解析 {tenant}{product},实现租户隔离的产品 CRUD,确保用户只能操作自己租户下的产品。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏