gbradley / laravel-api-resource
更简单的Laravel API
Requires
- php: >=7.1.0
README
本包扩展了Laravel的基础类,提供了一种更加简洁而强大的方式来控制API资源。
目标
- 减少代码重复;API资源编写和维护所需时间更少
- 改进的关系处理,包括按需加载
- 更多数据包装控制
- 默认情况下不会影响现有的API
要求
- Laravel 5.8+
安装
$ composer require gbradley/laravel-api-resource
使用
扩展新的资源类
在你的资源中,而不是扩展Laravel的Resource,改为扩展GBradley\ApiResource\Resource
namespace App\Http\Resources;
use GBradley\ApiResource\Resource;
class PostResource extends Resource
{
...
}
就这样!你现在可以像使用任何普通资源一样使用这个资源。本包的主要目标不是改变资源的现有行为。
更快的属性定义
在我们的示例资源中,让我们定义一些属性。通常你必须单独完成这项工作,这可能会相当冗长,并且有很多重复。相反,你现在可以使用mergeAttributes()来快速定义你希望从底层模型中使用的属性。这减少了如下
class PostResource extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
...
];
}
到这个
class PostResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'title', ...),
];
}
这还有一个额外的优点,即保留转换为date或datetime属性时的日期格式。通常你必须在资源中再次指定格式,但现在这会为你处理。
当然,如果需要,你可以混合新旧两种风格
class PostResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'title', ...),
'dated' => $this->dated->format('d/m/Y'),
];
}
关系处理
Laravel的资源有两种添加关系的方法
直接加载 - 这始终加载关系,通常会导致执行非懒加载查询以生成前端可能不需要的数据。
条件加载 - 这只在关系已经被加载时返回关系,将加载延迟到控制器。然而,你的业务逻辑也可能加载关系(例如在事件处理器中),这意味着你的响应结构可能会受到内部副作用的影响。
相反,本包允许控制器明确定义资源可以公开哪些关系,资源确定它将公开哪些关系。这些可以是必需关系,始终公开,也可以是可选关系,如果当前请求中指定了,则公开。结果是灵活的系统,同时还利用了懒加载。
首先,打开你的控制器。不要实例化资源,也不要使用collection()方法,而是使用静态build方法,该方法可以接受模型、集合或分页器实例
return PostResource::build($model);
这提供了一个流式接口来指定关系。要指定始终公开的关系,将名称数组传递给withRelations()
return PostResource::build($model)
->withRelations('blog', 'author');
使用相同的方式,使用withOptionalRelations()来定义只有当它们在当前请求的load参数中时才会公开的关系
return PostResource::build($model)
->withRelations('blog', 'author')
->withOptionalRelations('comments.author');
如上例所示,这些方法还接受嵌套关系。
现在控制器已经定义了允许资源公开的内容,你可以使用mergeWhenExplicitlyLoaded()配置资源这样做。此方法接受资源可以公开的关系名称数组
class PostResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'title'),
$this->mergeWhenExplicitlyLoaded([
'blog', 'author', 'comments'
]),
];
}
转换关系
当按顺序传递项目时,将使用相关对象的 toArray 方法进行暴露。如果您希望将它们转换为其他资源,请将关系作为键,将所需资源类作为值传递。这些技术可以组合使用。
class PostResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'title'),
$this->mergeWhenExplicitlyLoaded([
'blog',
'author',
'comments' => CommentResource::class,
]),
];
}
在这种情况下,我们现在也可以定义 CommentResource 以在显式加载时暴露其 author 关系。
class CommentResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'content'),
$this->mergeWhenExplicitlyLoaded([
'author',
]),
];
}
这导致请求如下
`GET /post/1?load[]=comments.author`
返回如下
{
'id' : 1,
'name' : 'Blog post 1',
'blog' : {
'id' => 123,
'title' => 'My Blog',
},
'author' : {
'id' : 456,
'name' : 'Graham',
},
'comments' : [
{
'id' : 789,
'content' : 'Nice post!',
'author' : {
'id' : 987,
'name' : 'Taylor Otwell'
}
}
]
}
多态关系
对于多态关系,首先在您的资源中添加一个 {relation}_type 属性。然后,在 mergeWhenExplicitlyLoaded() 内部指定可能的类及其关系的映射。
class CommentResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'content', 'commentable_type'),
$this->mergeWhenExplicitlyLoaded([
'author',
'commentable' => [
Post::class => PostResource::class,
Page::class => PageResource::class,
]
]),
];
}
上下文数据
有时您可能想根据在请求或正在转换的模型中找不到的信息修改您的资源转换。
要使任意数据可供您的资源使用,请对从 build() 返回的资源构建器调用 withContext()。
$context = [
'foo' => [
'bar' => 1
]
];
return PostResource::build($model)
->withRelations('blog')
->withContext($context);
您可以在资源内部使用 getContext() 访问上下文数据。您还可以使用点符号来检索数据的一部分
$data = $this->getContext('foo.bar');
您可以使用 mergeContext() 将所有或部分上下文合并到表示中
class PostResource extends Resource
{
public function toArray($request)
{
return [
$this->mergeAttributes('id', 'title'),
$this->mergeWhenExplicitlyLoaded([
'blog' => BlogResource::class,
]),
$this->mergeContext('foo.bar')
];
}
提供给资源的任何上下文数据都将传递到通过关系加载的任何其他资源。在上面的示例中,上下文数据将在 BlogResource 实例上可用。
数据包装
Laravel 的资源允许您禁用数据包装,但它会对模型和集合都这样做。尽管返回顶级 JSON 数组的潜在安全风险似乎在所有浏览器中都已解决,但有些人可能更喜欢避免包装单个模型,但保留包装集合。
为此,请在新的资源类上调用 withoutWrapping(),然后调用 wrapCollection()
public function boot()
{
Resource::withoutWrapping();
Resource::wrapCollection('data');
}