内容简介:Dingo API 中的转化器(Transformer)有点类似 Laravel 框架自带的API 资源类,都是用于对返回的响应数据进行格式化,通过转化器,你可以轻松实现将对象转化为数组,并支持整型和布尔类型之间的转化,以及分页结果和嵌套关联。这篇教程我们主要讨论转化器在 Dingo API 中的使用,这里的转化器包括以下两层意思:在介绍 Dingo 转化器使用之前,有必要大致了解下其底层实现原理。
转化器简介
Dingo API 中的转化器(Transformer)有点类似 Laravel 框架自带的API 资源类,都是用于对返回的响应数据进行格式化,通过转化器,你可以轻松实现将对象转化为数组,并支持整型和布尔类型之间的转化,以及分页结果和嵌套关联。
这篇教程我们主要讨论转化器在 Dingo API 中的使用,这里的转化器包括以下两层意思:
- 转化层(transformation layer):准备和处理转化器的库;
- 转化器(transformer):获取原始数据并将其转化为数组格式的类,转化器的具体处理方式取决于转化层。
在介绍 Dingo 转化器使用之前,有必要大致了解下其底层实现原理。
Fractal 概述
Dingo API 底层使用 Fractal 作为默认的转化层,Fractal 库能够为复杂的数据输出提供表示和转化层,常用于基于 JSON 的 RESTful API,作为一个数据转化层,Fractal 具备以下特点:
- 在数据源与最终输出数据之间进行隔离,从而避免数据源格式的变化对接口调用方的影响;
- 提供系统的数据类型转化支持,避免大量的
foreach和到处进行强制数据类型转化(如(bool),(int)等); - 支持复杂数据结构的嵌入和嵌套关联;
- 使用 HAL 和 JSON-API 等标准进行数据转化,但也支持自定义格式;
- 支持对数据结果进行分页;
- 可以简化 API 接口输出数据构建的复杂性。
为了更好的理解 Dingo 转化器的创建和使用,我们先简单介绍下 Fractal 的使用。
Fractal 使用入门
使用 Fractal 之前,需要先通过 Composer 安装相应的扩展包:
composer require league/fractal
不过,由于我们先前已经安装过 Dingo 扩展包,而 Dingo 扩展包又依赖了 Fractal 扩展包,所以该扩展包已经随着 Dingo 扩展包的安装而安装了,不需要重复安装。
Fractal 有几个术语需要解释,理解了这些术语之后,就基本掌握了 Fractal,从而为 Dingo 转化器的使用打下基础。
资源
所谓「资源」指的是用于表示数据的对象,资源主要分为两类:
League\Fractal\Resource\Item League\Fractal\Resource\Collection
Item 和 Collection 构造器接收任意你想要发送的数据作为第一个参数,以及一个对应的「转化器」作为第二个参数(对应源码位于 League\Fractal\Resource\ResourceAbstract 基类中):
/**
* Create a new resource instance.
*
* @param mixed $data
* @param callable|TransformerAbstract|null $transformer
* @param string $resourceKey
*/
public function __construct($data = null, $transformer = null, $resourceKey = null)
{
$this->data = $data;
$this->transformer = $transformer;
$this->resourceKey = $resourceKey;
}
转化器是一个用于定义输出数据格式的类或回调函数。下面我们以单个资源为例,在 Laravel 中基于 Fractal 定义一个 API 接口:
Route::get('/fractal/resource/item', function () {
$task = \App\Task::findOrFail(1);
$resource = new \League\Fractal\Resource\Item($task, function (\App\Task $task) {
return [
'id' => $task->id,
'text' => $task->text,
'is_completed' => $task->is_completed ? 'yes' : 'no'
];
});
$fractal = new \League\Fractal\Manager();
return $fractal->createData($resource)->toJson();
});
这里我们通过传入闭包函数来定义转化器,关于转化器类的实现后面转化器部分会介绍。如果是集合资源的话,处理方式类似:
Route::get('/fractal/resource/collection', function () {
$tasks = \App\Task::all();
$resource = new \League\Fractal\Resource\Collection($tasks, function (\App\Task $task) {
return [
'id' => $task->id,
'text' => $task->text,
'is_completed' => $task->is_completed ? 'yes' : 'no'
];
});
$fractal = new \League\Fractal\Manager();
return $fractal->createData($resource)->toJson();
});
序列化器
在 Fractal 中,我们可以通过设置序列化器来指定数据的转化格式,在 API 接口中有很多可以选择的数据输出格式,最著名的就是 HAL 和 JSON-API ,Fractal 默认支持 ArraySerializer 、 DataArraySerializer 、 JsonApiSerializer 三种序列化器,此外,还支持自定义序列化器。不同的序列化器的区别主要体现在数据命名空间的组织上,通过这些序列化器,你可以在 Fractal 中快速实现不同数据输出格式的切换,而不需要对转化器做任何修改。
首先我们来看下 ArraySerializer 的数据输出格式:
Route::get('/fractal/serializers', function () {
$task = \App\Task::findOrFail(1);
$resource = new \League\Fractal\Resource\Item($task, function (\App\Task $task) {
return [
'id' => $task->id,
'text' => $task->text,
'is_completed' => $task->is_completed ? 'yes' : 'no'
];
});
$fractal = new \League\Fractal\Manager();
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
return $fractal->createData($resource)->toJson();
});
可以看到,我们通过调用 Fractal 管理器实例上的 setSerializer 方法来设置序列化器,以上代码返回响应数据格式如下:
再来看下 DataArraySerializer 的数据输出格式,其它代码不变,将序列号器设置那行代码修改如下:
$fractal->setSerializer(new \League\Fractal\Serializer\DataArraySerializer());
对应返回响应输出格式如下,与 ArraySerializer 相比,多出了一层 data 包裹:
需要指出的是, DataArraySerializer 是 Fractal 默认的数据输出格式。
最后,再看下 JsonApiSerializer 的数据输出格式,还是调整序列号器设置那行代码:
$fractal->setSerializer(new \League\Fractal\Serializer\JsonApiSerializer());
返回响应对应数据格式如下,该格式遵循 JSON-API 标准:
如果以上都不能满足你的需求,还可以创建一个继承自 SerializerAbstract 基类的子类来自定义返回响应的数据格式。
转化器
在「资源」部分,我们已经提到了「转化器」的概念,只是那里是通过回调函数来实现的,只能一次性使用,现在,我们通过独立的类来实现,以提高代码的可复用性。
转化器类必须继承自 League\Fractal\TransformerAbstract 基类,并且至少实现 transform() 方法。我们在代码任务项目中创建一个保存在 app/Transformers 目录下的转化器类 TaskTransformer ,并初始化代码如下:
<?php
namespace App\Transformers;
use App\Task;
use League\Fractal\TransformerAbstract;
class TaskTransformer extends TransformerAbstract
{
public function transform(Task $task)
{
return [
'id' => $task->id,
'text' => $task->text,
'completed' => $task->is_completed ? 'yes' : 'no',
'link' => route('tasks.show', ['id' => $task->id])
];
}
}
这样一来,我们就可以改写之前的资源转化代码如下:
// 获取单个资源 $task = \App\Task::findOrFail(1); $resource = new \League\Fractal\Resource\Item($task, new \App\Transformers\TaskTransformer()); // 获取资源集合 $tasks = \App\Task::all(); $resources = new \League\Fractal\Resource\Collection($tasks, new \App\Transformers\TaskTransformer());
除此之外,我们还可以在模型字段之外,引入额外的数据,比如关联模型:
<?php
namespace App\Transformers;
use App\Task;
use League\Fractal\TransformerAbstract;
class TaskTransformer extends TransformerAbstract
{
protected $availableIncludes = ['user'];
public function transform(Task $task)
{
return [
'id' => $task->id,
'text' => $task->text,
'completed' => $task->is_completed ? 'yes' : 'no',
'link' => route('tasks.show', ['id' => $task->id])
];
}
public function includeUser(Task $task)
{
$user = $task->user;
return $this->item($user, new UserTransformer());
}
}
由于在上述代码中引入了新的转化器类 UserTransformer ,所以需要创建它:
<?php
namespace App\Transformers;
use App\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->name
];
}
}
然后修改返回响应数据代码如下,通过 parseIncludes 方法引入要包含的额外字段:
return $fractal->parseIncludes('user')->createData($resource)->toJson();
这样一来,就可以在返回的响应数据中看到 user 字段了:
除此之外,Fractal 还支持引入默认额外字段、排除指定字段、引入 URL 查询参数字段等,更多细节请参考 官方文档 ,这里就不一一列举了。
分页
Fractal 提供了两种解决方案来支持分页数据结果,分别是分页器和游标,下面我们简单演示下如何使用它们。
使用分页器
分页器可以提供丰富的分页结果信息,包括项目总数、上一页/下一页链接等,但相应的代价是可能会带来额外的性能开销,比如每次调用都要统计项目总数,如果对性能要求比较苛刻,可以考虑使用游标来获取分页结果。
当我们使用分页器的时候,创建的分页器类必须实现 League\Fractal\Pagination\PaginatorInterface 接口,然后将实例化后的分页器对象传入 League\Fractal\Resource\Collection::setPaginator() 方法。
为了与当前流行的 PHP 框架兼容,Fractal 提供了以下适配器,方便我们快速在相应的 PHP 框架中集成 Fractal:
League\Fractal\Pagination\IlluminatePaginatorAdapter League\Fractal\Pagination\PagerfantaPaginatorAdapter League\Fractal\Pagination\PhalconFrameworkPaginatorAdapter League\Fractal\Pagination\ZendFrameworkPaginatorAdapter
至于为什么使用分页适配器,是为了将不同框架实现的分页器转化为符合 Fractal 规范的分页器。
当然,我们这里以 Laravel 框架为例,演示在 Laravel 项目中基于 Fractal 使用分页适配器对分页结果进行处理:
Route::get('fractal/paginator', function () {
$paginator = \App\Task::paginate();
$tasks = $paginator->getCollection();
$resource = new \League\Fractal\Resource\Collection($tasks, new \App\Transformers\TaskTransformer());
$resource->setPaginator(new \League\Fractal\Pagination\IlluminatePaginatorAdapter($paginator));
$fractal = new \League\Fractal\Manager();
return $fractal->createData($resource)->toJson();
});
对应返回响应的数据格式如下:
使用游标
如果数据结果集特别大,运行 select count(*) from sometable 会有很大的性能开销,可以考虑使用游标来分批获取分页结果,游标的使用方式也很简单,和分页器类似,首先需要定义一个实现了 League\Fractal\Pagination\CursorInterface 接口的游标类,实例化之后将对应的游标对象传递到 League\Fractal\Resource\Collection::setCursor() 方法即可。
Fractal 为我们提供了一个非常基础的游标类 League\Fractal\Pagination\Cursor ,我们基于它在 Laravel 框架中演示如果通过游标返回分页结果:
Route::get('fractal/cursor', function (Request $request) {
$current = $request->input('current');
$previous = $request->input('previous');
$limit = $request->input('limit', 10);
if ($current) {
$tasks = \App\Task::where('id', '>', $current)->take($limit)->get();
} else {
$tasks =\App\Task::take($limit)->get();
}
$next = $tasks->last()->id;
$cursor = new \League\Fractal\Pagination\Cursor($current, $previous, $next, $tasks->count());
$resource = new \League\Fractal\Resource\Collection($tasks, new \App\Transformers\TaskTransformer());
$resource->setCursor($cursor);
$fractal = new \League\Fractal\Manager();
return $fractal->createData($resource)->toJson();
});
通过游标获取分页结果类似限定查询,不会统计项目总数,在性能要优于分页器,上述分页结果返回响应数据格式如下:
关于 Fractal 的使用我们就简单介绍到这里,更多细节请参考 官方文档 ,下一篇我们将介绍 Dingo API 中如何基于 Fractal 实现转化器以及转化器在 Dingo API 中的使用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用 Dingo API 扩展包快速构建 Laravel RESTful API(六) —— 转化器及响应构建器的高级使用
- 使用 Dingo API 扩展包快速构建 Laravel RESTful API(五) —— 转化器篇(下):结合响应构建器构...
- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
创业时, 我们在知乎聊什么?
知乎 / 中信出版社 / 2014-1 / 42.00元
★前所未有的互联网出版实验,500万知友亲手甄选内容,知乎三年创业问答精华大集结 ★史上最真诚创业书,用互联网思维讲透创业的逻辑 ★在知乎,最强大互联网创业群体真实分享创业路上的荣耀与隐忧 ★从Idea到步入正轨,创业公司如何招人、做技术、卖产品、找融资、建团队、处理法务? 他们在知乎聊创业: 创新工场创始人李开复 天使投资人 徐小平 小米科技创始人 雷军......一起来看看 《创业时, 我们在知乎聊什么?》 这本书的介绍吧!