本节摘要:控制器干完活,交付物要发得标准:页面用 view、跳转用 redirect 并带闪存、文件用二进制流、JSON 用 API 资源统一装箱。本节把各类响应的写法过一遍,重点讲 API Resource 这个"打包层"怎么隔离数据库结构与对外契约,让字段改名不再引发客户端连环崩。读完你能为一个模块定出一套稳定的交付规范。
工头把活干完,东西不能散着扔出门。散装交付有三个下场:模型字段一改,接口跟着变,客户端全线崩;不同人写的接口,返回结构一人一个样;调试时看不出哪个字段是给谁用的。响应层的任务就是装箱:里面是什么货,外面箱子长什么样,两回事分开管。
<?php public function demo(Request $request) { // 一、视图交付:装修队出场(第六章主角) return view('orders.show', ['order' => $order]); // 二、跳转交付:带闪存消息,下一页 flash 取用 return redirect()->route('orders.show', ['order' => $order->id]) ->with('status', '工单已更新'); // 三、文件下载:本地盘或云盘统一走 Storage return Storage::download('reports/2026-08.pdf', '八月报表.pdf'); // 四、JSON 快写:小接口临时用,字段直接定 return response()->json(['ok' => true, 'id' => $order->id], 201); }
跳转那行值得多说一句:with 存进 session 的闪存数据只在下一次请求存活,Blade 里 if (session('status')) 展示一条操作回执,是全 Laravel 最常用的用户反馈通道。响应头与 Cookie 也能链式加工:
<?php return response($content, 200) ->header('Content-Type', 'application/pdf') ->cookie('shift', 'day', 60);
接口一多,response()->json 手拼数组就是灾难现场:日期格式不统一、敏感字段漏藏、关联数据想不想带全看当天心情。API 资源(Resource)是模型与 JSON 之间的变换层,装箱规则一处声明:
php artisan make:resource OrderResource
<?php namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; class OrderResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'sn' => $this->sn, 'title' => $this->title, 'status' => $this->status->label(), // 枚举翻译成中文标签 'budget_yuan' => number_format($this->budget / 100, 2), 'crew' => WorkerResource::collection($this->whenLoaded('workers')), 'created_at' => $this->created_at->toIso8601String(), ]; } }
<?php // 控制器侧:单件用 make,集合用 collection public function show(Order $order): OrderResource { return new OrderResource($order->load('workers')); } public function index(): AnonymousResourceCollection { return OrderResource::collection( Order::with('workers')->latest()->paginate(20) ); }
三个细节体现装箱的价值。whenLoaded 只在关联被预加载时才输出 crew 字段,接口不会偷偷查库;字段名 budget_yuan 是对外契约,库里存分、对外给元,存储结构想怎么改都不影响客户端;日期统一 toIso8601String,全项目一个口径。

列表接口要带上分页信息,资源集合的分页输出天然有序:data 里是本页数据,links 与 meta 里是页码与总数,客户端按同一套约定翻页。状态码的取舍也有一套朴素原则:创建成功 201、删除成功 204、参数不合格 422(上一节验证自动回的)、没登录 401、没权限 403。状态码用对,客户端的错误处理就少一半代码。
💡 关键直觉:接口契约的稳定性比实现省事重要。宁可多写一个 Resource 类,也不要让客户端跟着你的数据库结构过日子——后者省的时间,都会在第一次重构时加倍还回去。
Resource 里的条件字段怎么写?
when 条件让字段按需出现:this->when(request->user()?->is_admin, fn () => $this->internal_note) 只对管理员输出内部备注。与 whenLoaded 搭配,一个 Resource 能按调用方身份与加载深度输出不同"装箱规格",且全部声明在一处。
列表的分页信息要不要自己拼?
不要。资源集合接 paginate 后,框架自动输出 data、links、meta 三段结构,总数、当前页、上下页地址齐全,客户端按约定翻页。自己拼分页字段是后面接入新客户端时口径不一致的常见起点。
响应太慢,先查控制器还是先查缓存?
先查数据层。响应慢的罪魁通常是控制器里藏着的循环查询与未预加载关联(第五章的旁听法照搬过来),缓存是数据层干净之后的选项。上手就加缓存的坏处是把"该查得快"的问题盖住了,之后每次失效都是一次卡顿。
工头的活干完了,接下来轮到施工队正式进场:第五章模型施工,从打地基(迁移)到砌数据这栋楼(Eloquent)。