向前一步 —— JSON:API Resourece
JSON:API Resource
上篇教程学院君给大家介绍了如何在 Laravel 项目中统一 REST API 返回的 JSON 数据格式,并引出了业界统一规范 —— JSON API。JSON API 虽好,但是需要服务端承担很多额外开发工作,好在社区已经有人将其封装成基础组件,我们做个拿来主义者直接在项目中使用即可,接下来的两篇教程学院君将以 Laravel 项目为例给大家介绍几个常用的第三方 JSON API 实现扩展包。
很多人可能上手使用的都是上篇教程中介绍的 Laravel 自带的 API Resource,沿着 API Resource 的轨迹往前探索,有一个可以和 API Resource 无缝集成的扩展包实现:timacdonald/json-api,通过它的名字,我们就能直观了解 —— JSON:API Resource for Laravel,JSON API 版本的 Resource 实现。
本小节代码已经提交到 github 仓库:https://github.com/geekr-dev/blog.git 的
v2
版本。
使用入门
使用该扩展包之前,需要先安装:
composer require timacdonald/json-api
然后让所有之前编写的 API Resource 类改为继承自 JsonApiResource
,并编写对应的属性和关联关系映射方法:
<?php
namespace App\Http\Resources;
use App\Models\Comment;
use App\Models\User;
use TiMacDonald\JsonApi\JsonApiResource;
class PostResource extends JsonApiResource
{
public function toAttributes($request): array
{
return [
'title' => $this->title,
'slug' => $this->slug,
'content' => $this->content,
'views' => $this->views,
'created_at' => $this->created_at->toDateTimeString(),
'updated_at' => $this->updated_at->toDateTimeString(),
];
}
public function toRelationships($request): array
{
return [
'author' => fn () => new UserResource($this->author),
'comments' => fn () => CommentResource::collection($this->comments),
];
}
}
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class UserResource extends JsonApiResource
{
public function toAttributes($request): array
{
return [
'name' => $this->name,
];
}
}
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class CommentResource extends JsonApiResource
{
public function toAttributes($request): array
{
return [
'content' => $this->content,
];
}
public function toRelationships($request): array
{
return [
'author' => fn () => new UserResource($this->author)
];
}
}
其中 toAttributes
方法定义的属性字段会映射 JSON API 的 attributes
,toRelationships
方法定义的关联资源会映射到 JSON API 的 relationships
以及 included
,而 id
和 type
由组件底层帮我们自动维护,无需显式设置。
当然,你也可以按照自己的需要自定义 id 和 type 逻辑,细节参考该扩展包的官方文档,这里不详细展开了。
你可以把 JSON:API Resource 扩展包看作是 Laravel API Resource 的一层封装,以让其适配 JSON API 规范(类似一个适配器),其实 JsonApiResource
也是继承了 Illuminate\Http\Resources\Json\JsonResource
,所以使用这个扩展包的好处是除了资源类需要调整代码外,上层调用的代码不用做任何更改,完全兼容原来的 API Resource。
现在我们直接访问 /posts/1
路由,返回结果的 JSON 数据结构如下:
数据结构已经和 JSON API 完全一致了:
- 资源标识属性:ID和类型
- 资源主体属性:
attributes
- 关联资源数据:
relationships
&&included
关联资源及链接
咦?怎么没有把关联资源返回?这其实就是 JSON API 的规范之一,所有嵌套关联资源支持按需加载,要想返回,需要通过 include
参数指定,多个关联资源用逗号分隔:
可以看到,我们可以指定想要加载的任意关联资源,还支持嵌套的关联资源,这样一来,接口就变得更加灵活了,除此之外,还为我们后续基于性能维度对接口进行优化提供了更多空间。
不过返回数据资源主体和关联资源的 meta
和 links
都还是空的,需要自行编写相应的代码,实现方法和 attributes
和 relationships
一样:
// PostResource
protected function toLinks($request): array
{
return [
Link::self(route('posts.show', $this->id)),
];
}
// UserResource
protected function toLinks($request): array
{
return [
Link::self(route('users.show', $this->id)),
];
}
// CommentResource
protected function toLinks($request): array
{
return [
Link::self(route('posts.comments.show', [$this->post_id, $this->id]))
];
}
一般不关注 meta 字段,可以先不管。
再次访问 /posts/1
路由,这个时候可以看到 included
和资源主体的 links
值有了:
但是 relationships
里面的 links
还是空的:
这是扩展包实现的问题,构建 relationships
返回值数据的时候,底层调用的是 JsonApiResource
的 toResourceLink
方法:
/**
* TODO: @see docs-link
* @see https://jsonapi.org/format/#document-resource-object-linkage
*/
public function toResourceLink(Request $request): RelationshipLink
{
return new RelationshipLink(
new ResourceIdentifier($this->resolveId($request), $this->resolveType($request))
);
}
可以看到,该方法目前只支持生成 id
和 type
两个关联资源主体字段,要支持 links 字段,需要在对应关联资源类中重写父类的 toResourceLink
方法。但是对于 comments
这种需要在 data
以外获取某篇文章所有关联评论的链接又该如何设置呢,写到后面就发现又出现各种 trick 和魔术方法了,并且我觉得这类问题应该有组件底层去维护,不应该有开发者通过各种 trick 去修复。
按需显示字段
除了关联关系支持按需获取外,按照 JSON API 规范的说明,JSON 返回数据中的字段也要支持按需获取,这一点 JSON:API Resource 扩展包已经提供了支持:
JSON:API Resource 的问题
除了上面提到的 relationships
中不包含 links
问题之外,关于 JSON API 规范中提到的过滤器、排序、分页等更高阶的 API 接口规范 JSON:API Resource 也没有任何实现,所以虽然相比于 API Resource,JSON:API Resource 扩展包通过封装它向 JSON API 规范前进了一步,但是也是不彻底的,针对一些相对简单的场景我们可以使用它来满足 JSON API 的基本规范,好处是在上层调用的业务代码中完全兼容 API Resource,只需要调整自定义的资源类代码即可,不过如果有完美主义倾向的话,还是需要继续往前探索,找到更完美的 JSON API 实现方案。
1 条评论
写到后面就发现又出现各种 trick 和魔术方法了,并且我觉得这类问题应该“有”组件底层去维护