api资源是laravel处理模型到json转换的事实标准,需用php artisan make:resource生成,通过toarray()定义结构,依赖预加载防n+1,集合需调用->response()保留分页信息,字段映射优先用模型访问器,条件输出用when/whenloaded,空值处理在控制器完成。

API资源在 Laravel 中不是“可选插件”,而是处理模型到 JSON 转换的事实标准——不用它,你就得在控制器里手动 array_map、拼字段、判空、处理分页、防 N+1,最后还容易漏掉敏感字段过滤。
资源类生成和基础用法
生成资源类只需一条命令:php artisan make:resource UserResource。生成后,app/Http/Resources/UserResource.php 里的 toArray() 方法就是你定义输出结构的唯一入口。
关键点在于:$this 是模型实例,不是数组或 Collection,所有字段访问都走 Eloquent 属性读取逻辑(会触发访问器、关系延迟加载等)。
- 日期必须显式格式化,
$this->created_at默认是 Carbon 实例,直接返回会转成 ISO 字符串,但如果你要Y-m-d格式,得写$this->created_at->format('Y-m-d') - 不要在
toArray()里调用$this->load()或$this->relation->xxx—— 这会导致每个资源实例都触发一次查询 - 关联数据必须靠控制器预加载,比如
User::with('posts')->get(),然后在资源里用$this->whenLoaded('posts', fn() => PostResource::collection($this->posts))
集合返回时分页信息丢失怎么办
Laravel 10+ 默认关闭了 ResourceCollection 对分页器的自动识别,UserResource::collection($users) 返回的只是纯数据数组,links 和 meta 全没了。
这不是 bug,是设计变更:框架不再隐式包装 Paginator,要求你显式控制响应行为。
- 正确做法是调用
->response():return UserResource::collection($users)->response(); - 如果用了自定义集合类(如
UserCollection),确保它继承ResourceCollection,且没重写paginationInformation()导致逻辑被覆盖 - 别用
new UserResource($users)处理集合——这是单资源写法,$users是分页器时会报错Call to a member function toArray() on null - 保留 URL 查询参数(如
page=2&sort=name)需手动追加:$users->appends(request()->query())
字段映射与条件输出怎么写才安全
前端要 user_name,数据库是 name;要 is_active,模型存的是 active 字段——硬编码 key 易错且难维护。
优先级顺序是:模型访问器 + $appends > 资源类内重命名 > 直接写死 key。
- 在模型中定义访问器:
protected function getUserNameAttribute() { return $this->name; },再在$appends = ['user_name'],资源里直接用$this->user_name - 用
$this->when()控制字段可见性,比如只对管理员返回邮箱:'email' => $this->when($request->user()?->isAdmin(), $this->email) -
$this->whenLoaded()只取已加载的数据,不触发新查询;而$this->when($this->relation, ...)会触发 getter,可能引发 N+1 - 空值兜底必须在控制器做:
return new UserResource($user ?? new User()),资源类本身不处理null输入
为什么资源类里不能直接访问未加载的关系
因为资源类执行时,Eloquent 模型已经从数据库取出,但关联数据是否加载,取决于控制器有没有调用 with()。没预加载就写 $this->profile->avatar,Laravel 会在每个资源实例里执行一次 SQL 查询。
100 条用户数据,就会发出 100 次 SELECT * FROM profiles WHERE user_id = ? —— 这就是典型的 N+1,性能崩得比日志还快。
- 正确姿势:控制器查的时候就加载好,
User::with('profile')->paginate(20) - 资源里只用
$this->whenLoaded('profile', fn() => new ProfileResource($this->profile)) - 想动态加载(比如按角色决定是否查地址),在控制器里用
when()构建查询条件,而不是在资源里补救 - 闭包里用
use ($companyId)做动态关联查询虽可行,但属于高阶技巧,容易绕过预加载,应先确认是否真无法预加载
最常被忽略的一点:资源类不负责 HTTP 状态码、顶层包裹键(如 data)、错误统一格式——这些该由控制器或全局响应宏处理。把所有逻辑塞进资源类,等于把路由层职责也扛了,后期改一个字段名,得翻三处代码。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











