内容简介: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 使用体验
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Practical Algorithms for Programmers
Andrew Binstock、John Rex / Addison-Wesley Professional / 1995-06-29 / USD 39.99
Most algorithm books today are either academic textbooks or rehashes of the same tired set of algorithms. Practical Algorithms for Programmers is the first book to give complete code implementations o......一起来看看 《Practical Algorithms for Programmers》 这本书的介绍吧!