直接用 JsonResource 不符合 JSON:API 规范,因其不输出顶层 data、type、id、relationships 等必需字段,也不处理 links、meta 和关系标准化;需改用 cloudcreativity/laravel-json-api 包,继承 Resource 类并显式定义 $type、attributes() 和 relationships()。

为什么直接用 JsonResource 会不符合 JSON:API 规范
JSON:API 要求顶层必须是 data、included、meta 等固定键,而 Laravel 默认的 JsonResource 只输出扁平结构(比如直接返回模型属性),不带 type、id、relationships 字段,也不处理资源标识符(resource identifier objects)。更关键的是,它默认不加 links 和 self,也没有对 toOne/toMany 关系做标准化包装。
常见错误现象:
— 返回 {"name": "foo", "email": "bar@example.com"},而不是 {"data": {"type": "users", "id": "1", "attributes": {...}, "relationships": {...}}}
— 关系字段直接塞进 attributes,导致前端解析失败
— 缺少 type 字段,被 JSON:API 客户端(如 Ember Data)直接拒绝
用 JsonApiResource 替代原生 JsonResource 的实操要点
Laravel 官方不提供 JSON:API 原生支持,得靠社区包。推荐用 cloudcreativity/laravel-json-api —— 它不是装饰器,而是完整重写了序列化流程,强制约束输出结构。
安装后,资源类需继承 CloudCreativity\LaravelJsonApi\Document\ResourceObject 或更常用的 CloudCreativity\LaravelJsonApi\Document\Resource:
立即学习“PHP免费学习笔记(深入)”;
class UserResource extends Resource
{
protected $type = 'users';
protected function attributes($resource): array
{
return [
'name' => $resource->name,
'email' => $resource->email,
];
}
protected function relationships($resource, $isPrimary, array $include): array
{
return [
'posts' => [
self::RELATED => $resource->whenLoaded('posts'),
self::ALWAYS_INCLUDE => true,
],
];
}
}
注意:
— $type 必须显式声明,不能靠模型名自动推断
— attributes() 只放非关系字段,关系必须进 relationships()
— self::RELATED 是包定义的常量,不是字符串字面量
— 不要覆盖 toArray(),这个包已接管整个序列化链
如何正确处理分页、元数据和链接
JSON:API 要求分页信息放在 links 和 meta 下,而非 Laravel 默认的 pagination 键。该包会自动把 Eloquent 分页器转成符合规范的 first、next、last 链接,但前提是控制器里用对方法:
✅ 正确写法:
use CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController;
class UsersController extends JsonApiController
{
protected $resourceType = 'users';
public function index()
{
return $this->reply()->content(
User::query()->paginate(15)
);
}
}
❌ 错误写法:
— 手动 new Paginator 后调用 toArray()
— 在资源类里硬编码 links 数组(会被覆盖)
— 用 response()->json() 包裹资源实例(绕过包的中间件和序列化逻辑)
自定义 meta(如总记录数)要通过 withMeta():
return $this->reply()->content($paginator)->withMeta([
'total' => $paginator->total(),
]);
Eloquent 关系预加载与 JSON:API 的 include 参数联动
JSON:API 允许客户端用 ?include=posts,posts.author 请求嵌套关系,但 Eloquent 默认不会自动预加载。这个包能解析 include 查询参数并触发预加载,前提是:
— 控制器继承 JsonApiController
— 资源类中 relationships() 方法返回的关联必须与模型实际定义一致(大小写、驼峰/下划线)
— 模型关系方法名必须是标准命名(如 posts(),不能是 getPosts())
常见坑:
— include=profile 但模型里定义的是 userProfile() → 预加载失败,返回空关系
— 在 relationships() 里用了 $resource->posts->load('author') 手动加载 → 破坏懒加载优化,且可能重复查询
— 忘记在模型中声明 protected $casts = ['id' => 'string'] → 当 ID 是 UUID 时,JSON:API 要求 id 必须是字符串,否则验证失败
复杂点在于:如果某个关系需要条件过滤(比如只加载「已发布」的文章),不能在 relationships() 里写业务逻辑,得用包提供的 relationshipResolver 或自定义 QueryParameters 类干预查询构建过程 —— 这部分文档分散,容易卡住。



















