我们使用 Laravel 开发 API 接口时,一般可能会使用 Dingo API,Dingo API 默认使用的是 thephpleague-fractal。
即使我们不使用 Dingo API,开发 API 接口时,我们也可以使用 thephpleague-fractal。
接下来,让我们简单了解下 thephpleague-fractal,到目前为止,我也不太清楚 thephpleague-fractal 如何使用,下面开始学习笔记
github 地址:
https://github.com/thephpleague/fractal
官网:
http://fractal.thephpleague.com/
翻译:
简介(Introduction):
Fractal 是什么?
Fractal 为复杂的数据输出提供了一个表示和转换层,就像在处理的 RESTful APIs 数据一样,并且可以很好地与 JSON 一起使用。可以将其视为 JSON/YAML 等的视图层。
在构建 API 时,我们通常只是从数据库获取数据,并将其传递给 json_encode()。对于 '普通' API 而言,这可能是可以接受的,但如果它们被公众使用,或被移动应用使用,那么这将很快导致输出不一致。
Fratal 要实现的目标:
在源数据和输出之前创建一个 'barrier',这样做的目的是,让架构的更改不会影响用户使用。
系统性的数据的类型转换,可以避免 foreach() 循环和 (bool) 一切
对于复杂数据结构,引入依赖关系(a.k.a - also known as - 也叫做,嵌入(embedding)、嵌套(nesting)或 单边加载(side-loading))
使用 HAL 和 JSON-API 等标准,但也允许自定义序列化
支持数据结果分页,适用于小型和大型数据集
通常会简化 '非普通' API 的输出数据的复杂度
简单示例(Simple Example):
简单起见,下面的示例将几个不同的部分放在一起,看起来就像一个文件。实际开发中,我们应该将 manager 初始化、数据集合和 JSON 转换,放置到我们应用中的不同文件。
<?php
use League\Fractal\Manager;
use League\Fractal\Resource\Collection;
// 创建一个顶层实例
$fractal = new Manager();
// 从某种来源获取数据
// 历史上出于性能原因,SQL 引擎的大多数 PHP 扩展返回的都是 string 类型。
// 我们稍后会修复它,目前以下面这个 array 替代
$books = [
[
'id' => '1',
'title' => 'Hogfather',
'yr' => '1998',
'author_name' => 'Philip K Dick',
'author_email' => 'philip@example.org',
],
[
'id' => '2',
'title' => 'Game Of Kill Everyone',
'yr' => '2014',
'author_name' => 'George R. R. Satan',
'author_email' => 'george@example.org',
]
];
// 将数组(或集合)传递给一个资源,该资源也有一个 'Transformer'
// 该 'Transformer' 可以是一个回调或一个 Transformer 对象的新实例
// 我们在这里限制回调函数的变量类型是 'array',因为上面 $books 的每个条目都是一个 array 类型
$resource = new Collection($books, function(array $book){
return [
'id' => (int) $book['id'],
'title' => $book['title'],
'year' => (int) $book['yr'],
'author' => [
'name' => $book['author_name'],
'email' => $book['author_email'],
],
'links' => [
'rel' => 'self',
'uri' => '/books/' . $book['id'],
],
];
});
// 将返回的资源,转换为一个结构化数组(方便 XML 视图或自动 YAML 转换)
$array = $fractal->createData($resource)->toArray();
// 将返回的资源,转换为 JSON 字符串
echo $fractal->createData($resource)->toJson();
值得注意的是,回调函数是使用真实 'Transformers' 的一个非常不好的替代。Transformers 允许我们重用转换器,并且使得控制器代码更简洁。
安装(Installation):
系统依赖:
PHP >= 5.4.0,推荐使用 PHP 的最新稳定版
Composer:
composer install league/fractal
几乎所有的现代框架都默认支持了 Composer 自动加载,对于其他情况,确保引入以下文件:
<?php
// 引入 Composer 自动加载
require 'vendor/autoload.php'
Going Solo(单独使用):
也可以不使用 Composer,通过注册 autoloader 函数,来使用 Fractal:
spl_autoload_register(function ($class) {
$prefix = 'League\\Fractal\\';
$base_dir = __DIR__ . '/src/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
或使用任意其他的 PSR-4 兼容的 autoloader
词汇表(Glossary):
Cursor
Cursor 是一种非智能形式的分页,它不需要知道数据库中数据的总数。这样就不知道下一页是否存在,意味着,API 客户端需要一直发送 HTTP 请求,直到没有数据(404)
Include
数据通常与其他数据关联。例如:用户有帖子、帖子有回复、回复属于帖子等。当在 RESTful APIs 里表示时,这些数据通常 "included"(插入、嵌套等) 到资源中。transformer 可以包含一个 includePosts() 方法,该方法希望返回一个资源,以便将返回的资源放到父资源中。
Manager
Fractal 有一个命名为 Manager 的类,负责维护一个记录,记录着哪些嵌入数据已经被请求过,并递归地将嵌套数据转换为 array、JSON、YAML等。
Pagination
分页是将内容划分为多个页面的过程,与 Fractal 相关的过程可分为两种方式:Cursor 和 Pagination。
Paginator
Paginator 是一种智能形式的分页,它需要知道数据库中数据的总数。它会添加一个 'paginator' 数据到响应的元数据,包含了上一页和下一页的链接。
Resource
资源是一个对象,充当着通用数据的包装器。每个资源都附加一个转换器,以便最终转换为准备好进行序列化和输出(对数据进行转换,最后返回一个资源,然后调用不同的序列化方法,最终输出)。
Serializer
Serializer 是以某种方式构建你的转换后的数据。API 有许多输出结构,流行的 2 个结构是:HAL 和 JSON-API。Twitter 和 Facebook 输出的数据互不相同,Google 也不同。Serializer 让我们在 Transformers 上可进行最小的修改,然后在不同的输出格式之间切换。
Transformer
Transformers 可以是类,或者匿名函数,负责获取一个资源数据实例,并将其转换为一个基本数组。这个过程用来处理我们的数据存储,避免对象关系阻抗不匹配,并允许我们根据需要,从不同的数据存储中将各种元素结合起来。数据来自这些不同的数据存储,并处理成更易于管理的、准备好的可进行序列化的数据格式。
Resources
资源是表示数据的对象,并且也涉及到 Transformer 的相关知识,Transformer 是指明如何输出数据的一个对象或回调函数。
存在两种类型的资源:
League\Fractal\Resource\Item - 单个资源,可能是数据存储中的一个条目
League\Fractal\Resource\Collection - 一个资源集合
Item 和 Collection 构造函数,接收你希望发送给它的任意类型的数据作为第一个参数,以及一个 'Transformer' 作为第二个参数。
下面的示例,使用的是回调函数 Transformer,而不是创建 Transformer 类,纯粹是为了演示(项目中,推荐使用 Transformer 类)
Item 示例:
use Acme\Model\Book;
use League\Fractal;
$book = Book::find($id);
$resource = new Fractal\Resource\Item($book, function(Book $book){
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
'self' => '/books/'.$book->id,
]
];
});
Collection 示例:
use Acme\Model\Book;
use League\Fractal;
$books = Book::all();
$resource = new Fractal\Resource\Collection($books, function(Book $book) {
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
'self' => '/books/'.$book->id,
]
];
});
在这个示例中,$books 是 Acme\Model\Book 实例的一个数组,或一个实现了 ArrayIterator 的集合类
Serializers
Serializer 是以某种方式构建你的转换后的数据。API 有许多输出结构,流行的 2 个结构是:HAL 和 JSON-API。Twitter 和 Facebook 输出的数据互不相同,Google 也不同。这些 Serializers 之间的大多数差异是,如何定义数据的命名空间。
Serializer 让我们在 Transformers 上可进行最小的修改,然后在不同的输出格式之间切换。
Fractal 的一个非常基本的用法如下所示,可能已经在其他章节看到过了:
use Acme\Model\Book;
use Acme\Transformer\BookTransformer;
use League\Fractal\Manager;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\DataArraySerializer;
$manager = new Manager();
$manager->setSerializer(new DataArraySerializer());
// 某种 ORM 调用
$book = Book::find($id);
// 从数据中创建一个资源并运行所有 transformers
$resource = new Item($book, $new BookTransformer(), 'book');
// 传入资源,生成最终数据
$manager->createData($resource)->toArray();
// 输出:
[
'data' => [
'id' => 'Foo',
'title' => 'Foo',
'year' => 1991,
],
];
这里的新功能是 $manager->setSerializer(new DataArraySerializer()) 部分。
DataArraySerializer 是 Fractal 中默认的 Serializer 的名字,还有更多的 Serializers。
DataArraySerializer
该序列化并不符合每个人的口味,因为它给输出添加了一个 'data' 的命名空间:
// Item
[
'data' => [
'foo' => 'bar'
],
];
// Collection
[
'data' => [
[
'foo' => 'bar'
]
],
];
这很方便,因为它允许在 Items 和 Collections 中为元数据(像:pagination 或 totals)预留空间。
// 带有 meta 的 Item
[
'data' => [
'foo' => 'bar'
],
'meta' => [
...
]
];
// 带有 meta 的 Collection
[
'data' => [
[
'foo' => 'bar'
]
],
'meta' => [
...
]
];
使用 'data' 命名空间,非常适合元数据(meta)和引入资源(included resources)。意味着也可以为那些引入资源也可以添加元数据。
// 引入资源的 Item 使用 meta
[
'data' => [
'foo' => 'bar'
// 这就是一个 '引入资源',它自身也可以添加 'meta'
'comments' => [
'data' => [
...
],
'meta' => [
...
]
]
],
];
ArraySerializer
有时候,我们也想为 items 移除 'data' 命名空间,这可以通过使用 ArraySerializer 来实现。除了 items 的命名空间之外,大体相同。Collections 保留了 'data' 命名空间,以避免在添加元数据(meta data)时混淆 JSON。
use League\Fractal\Serializer\ArraySerializer;
$manager->setSerializer(new ArraySerializer());
// Item
[
'foo' => 'bar'
];
// Collection 仍然具有 'data' 命名空间
[
'data' => [
'foo' => 'bar'
]
];
元数据(meta data)对于 items 比较合适,但对于 Collection 有一点混淆:
// 带有 meta 的 Item
[
'foo' => 'bar'
'meta' => [
...
]
];
// 带有 meta 的 Collection
[
[
'foo' => 'bar'
]
'meta' => [
...
]
];
带有 meta data 的 Collection 返回的json,会自动给个 '0' 下标,有点不友好
添加一个命名的键到列表,混淆了 JSON:
{“0”:{“foo”:”bar”},”meta”:{}}
这里的 "0",是因为你不能混合索引键和非索引键(xxx...xxx不好翻译)
这就是为什么不建议使用 ArraySerializer,但是如果你不使用元数据,那么...继续
JsonApiSerializer
这是 JSON-API 标准(v1.0)的表示。它实现了最常用的功能,例如:
Primary Data
Resource Objects
Resource Identifier Objects
Compound Documents
Meta Information
Links
Relacationships
Inclusion of Related Resources
目前还未包含的功能:
Sparse Fieldsets
Sorting
Pagination
Filtering
由于 Fractal 是一个输出数据结构的库,serializers 只能转换 HTTP 响应的内容。因此,以下功能只能由我们自己实现:
Content Negotiation
HTTP Response Codes
Error Objects
更多信息请参阅官方 JSON API 规范
JSON API 对于我们的资源需要一个 Resource Key,正如每个对象的 id 一样。(就是需要一个标识)
use League\Fractal\Serializer\JsonApiSerializer;
$manager->setSerializer(new JsonApiSerializer());
// 注意,第三个参数就是 Resource Key(必须给出第三个参数,标识当前 resource)
$resource = new Item($book, new JsonApiBookTransformer(), 'books');
$resource = new Collection($books, new JsonApiBookTransformer(), 'books');
Resource Key 用于为其指定一个命名空间:
// Item
[
'data' => [
'type' => 'books', // 这里就是我们传递的 resource key
'id' => 1,
'attributes' => [
'foo' => 'bar'
],
],
];
// Collection
[
'data' => [
[
'type' => 'books', // 这里就是我们传递的 resource key
'id' => 1,
'attributes' => [
'foo' => 'bar'
],
]
],
];
就像 DataArraySerializer,非常适用于元数据:
// 带有 meta 的 Item
[
'data' => [
'type' => 'books',
'id' => 1,
'attributes' => [
'foo' => 'bar'
]
],
'meta' => [
...
]
];
// 带有 meta 的 Collection
[
'data' => [
[
'type' => 'books',
'id' => 1,
'attributes' => [
'foo' => 'bar'
]
]
],
'meta' => [
...
]
];
给 item 响应添加一个 '资源'(通过 include 进来的资源),如下所示:
// Item with a related resource
[
'data' => [
'type' => 'books',
'id' => 1,
'attributes' => [
'foo' => 'bar'
],
'relationships' => [
'author' => [
'data' => [
'type' => 'people',
'id' => '1',
]
]
]
],
'included' => [
[
'type' => 'people',
'id' => 1,
'attributes' => [
'name' => 'Dave'
]
]
]
];
如果你想启用 links 支持,只需要在你的 Serializer 上设置一个 baseUrl
use League\Fractal\Serializer\JsonApiSerializer;
// 设置 $baseUrl
$baseUrl = 'http://example.com';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
同样的资源,添加 links 后,如下所示:
// Item with a related resource and links support
[
'data' => [
'type' => 'books',
'id' => 1,
'attributes' => [
'foo' => 'bar'
],
'links' => [
'self' => 'http://example.com/books/1'
],
'relationships' => [
'author' => [
'links' => [
'self' => 'http://example.com/books/1/relationships/author',
'related' => 'http://example.com/books/1/author'
],
'data' => [
'type' => 'people',
'id' => '1',
]
]
]
],
'included' => [
[
'type' => 'people',
'id' => 1,
'attributes' => [
'name' => 'Dave'
],
'links' => [
'self' => 'http://example.com/people/1'
]
]
]
];
自定义 Serializers
我们可以实现 SerializerAbstract 来制作自己的 Serializers
use Acme\Serializer\CustomSerializer;
$manager->setSerializer(new CustomSerializer());
Serializers 的结构将在某些时候发生改变,来允许以不同方式处理 items 和 collections,并改进 side-loading 逻辑。密切关注更改日志,但不要害怕创建更改日志。
Transformer
在 Resource 章节,展示了回调函数方式的 transformers,但是使用上受限:
<?php
use Acme\Model\Book;
use League\Fractal;
$books = Book::all();
$resource = new Fractal\Resource\Collection($books, function(Book $book) {
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => $book->yr,
'author' => [
'name' => $book->author_name,
'email' => $book->author_email,
],
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
]
];
});
在某些情况下,使用这种方式是比较便捷的,但大多数数据需要在多个位置进行多次转换, 因此创建 transformers 类的方式,可以减少代码重复
Transformer 类
为了重用 transformers(推荐),可以定义、实例化、传递 类,来代替回调函数。
这些类必须继承 League\Fractal\TransformerAbstract,并且至少包含名称为 transform() 的方法。
方法声明可以接受各种类型的输入,就像回调函数的参数一样:
<?php
namespace Acme\Transformer;
use Acme\Model\Book;
use League\Fractal;
class BookTransformer extends Fractal\TransformerAbstract
{
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
}
一旦 Transformer 类被定义,就可以将其作为实例,传递给 resource 构造函数
<?php
use Acme\Transformer\BookTransformer;
use League\Fractal;
$resource = new Fractal\Resource\Item($book, new BookTransformer);
$resource = new Fractal\Resource\Collection($books, new BookTransformer);
引入数据(Including Data)
此刻我们定义的 transformer,主要只提供了一个用来处理数组转换的方法,将源数据(或从模型返回的任意内容)转换为一个简单的数组。以智能方式来引入数据,这个问题很难解决,因为数据有各种各样的关系。许多开发人员尝试在 '不发送过多的 HTTP 请求' 和 '不下载超出需要的更多数据' 之间寻找一个完美平衡,同时也兼顾灵活性。
仍旧使用 book 的示例,BookTransformer,我们可能希望规范化我们的数据库,并将2个 author_* 字段取出,放入它们自己的表中。这个引入可以是可选的,以减少 JSON 响应,定义如下:
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* @return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* @return \League\Fractal\Resource\Item
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
这些引入是可用的,但不能被请求,除非 Manager::parseIncludes() 方法被调用:
<?php
use League\Fractal;
$fractal = new Fractal\Manager();
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
有了这个设置,引入可以做一些很棒的事情。如果一个客户端应用请求的 URL 为 '/books?include=author',那么它们将会在响应中看到 author 的数据。
这些引入也可以以 '.' 符号被嵌套,来引入其他资源中的资源。例如:
/books?include=author,publishers.somethingelse
注意:
publishers 也会被引入,somethingelse 嵌套在它下面。 可以当成是 publishers,publishers.somethingelse 的简写
嵌套层级限制为 10 级。使用 Manager::setRecursionLimit(5) 方法,传递你喜欢的任意数字,可以增加或减少嵌套的层级,指定到我们设置的层级。可能 4 或 5 会是一个比较好的层级,这取决于 API。
默认引入
正如可选的引入一样,默认引入定义在 transformer 的 $defaultIncludes 属性上
protected $defaultIncludes = [
'author'
];
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
输出上看起来是相同的,就好像用户请求了 '?include=author'(每个 URL 请求不需要传入 'include=author',默认程序就会添加上该引入)
排除引入
Manager::parseExcludes() 方法可用于在单个响应中忽略默认引入
<?php
use League\Fractal;
$fractal = new Fractal\Manager();
$fractal->parseExcludes('author');
同样的 '.' 符号也可用于 Manager::parseExcludes()
对于排除引入,使用了 '.' 符号,只会排除路径中的深层次的嵌套,不会排除嵌套的父级
为了省略 BookTransformer 的默认 author 引入和 AuthorTransformer 默认的嵌套的 editor 引入,可以传递:author.editor,author。因为单独传递 author.editor,只会从响应中省略 author.editor 资源。
解析后的排除,最终决定了是否会在响应中看到某个引入。这意味着,即使包含在 Manager::parseIncludes() 中的引入,也可以被 Manager::parseExcludes() 忽略掉。(其实就是我们经常看到的:否定会比肯定更优先,Apache 的 Deny 优先 Allow)
引入参数(Include Parameters)
在引入其他资源时, 语法可用于为引入方法提供额外的参数。这些参数是在 URL 中构造的:
?include=comments:limit(5|1):order(created_at|desc)
此语法将通过 'League\Fractal\ParamBag' 对象进行解析和提供,作为第二个参数传递到引入方法中。
<?php
use League\Fractal\ParamBag;
// ... transformer stuff ...
private $validParams = ['limit', 'order'];
/**
* Include Comments
*
* @param Book $book
* @param \League\Fractal\ParamBag|null
* @return \League\Fractal\Resource\Item
*/
public function includeComments(Book $book, ParamBag $params = null)
{
if ($params === null) {
return $book->comments;
}
// Optional params validation
$usedParams = array_keys(iterator_to_array($params));
if ($invalidParams = array_diff($usedParams, $this->validParams)) {
throw new \Exception(sprintf(
'Invalid param(s): "%s". Valid param(s): "%s"',
implode(',', $usedParams),
implode(',', $this->validParams)
));
}
// Processing
list($limit, $offset) = $params->get('limit');
list($orderCol, $orderBy) = $params->get('order');
$comments = $book->comments
->take($limit)
->skip($offset)
->orderBy($orderCol, $orderBy)
->get();
return $this->collection($comments, new CommentTransformer);
}
参数有一个名称,以及多个值,总是返回一个数组,即便只有一个参数,返回的也是一个数组。可以通过 get() 方法来访问,也可以通过数组的形式来访问。因此,$params->get('limit') 和 $params['limit'] 是等效的
贪婪加载 vs 懒加载(Eager-Loading vs Lazy-Loading)
上面的示例正好使用 ORM 的懒加载功能。懒加载可能非常慢,因为每次转换一个条目时,它都不得不关闭并查找其他数据,导致大量 SQL 请求。
通过检查 $_GET['include'] 的值,可以很容易地使用贪婪加载,并且使用它来生成一个关系列表,来贪婪加载 ORM(不懂...)
Pagination
使用大型数据集时,为端点提供分页选项,显然是很有意义的,否则获取数据会变得很慢。为避免将自己的分页输出写入每个端点,Fractal 提供了 2 种解决方案:
Paginator
Cursor
使用 Paginator
Paginator 提供了有关结果集的更多信息,包括:总数,上一页/下一页 链接,只有在有更多可用数据时才显示。此智能分页的代价是,每次调用必须计算数据的总条数
对于某些数据集,这可能不是问题,但对于其他一些肯定是。如果只考虑速度,请考虑使用 Cursor 替代。
创建 Paginator 对象,必须实现 'League\Fractal\Pagination\PaginatorInterface' 以及它的指定方法。实例化的对象之后必须传递给 League\Fractal\Resource\Collection::setPaginator() 方法
Fractal 目前支持以下适配器:
Laravel 的 illuminate/pagination 包作为 League\Fractal\Pagination\IlluminatePaginatorAdapter
pagerfanta/pagerfanta 包作为 League\Fractal\Pagination\PagerfantaPaginatorAdapter
Phalcon Paginator(phalcon/cphalcon) 作为 League\Fractal\Pagination\PhalconFrameworkPaginatorAdapter
Zend Framework 的 zendframework/zend-paginator 包作为 League\Fractal\Pagination\ZendFrameworkPaginatorAdapter
Laravel 分页
我们可以使用 Laravel 的 Eloquent 或 Query Builder 的 paginate() 方法来达到分页目的:
use League\Fractal\Resource\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Acme\Model\Book;
use Acme\Transformer\BookTransformer;
$paginator = Book::paginate();
$books = $paginator->getCollection();
$resource = new Collection($books, new BookTransformer());
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
Phalcon 分页
看文档
Symfony 分页
使用的是 'pagerfanta/pagerfanta' 包
看文档
在分页链接中总包含已经存在的查询字符串
在上面的例子中,上一页和下一页,将简单提供一个 '?page=#',而忽略了所有其他已存在的查询字符串。为了在这些链接中自动包含所有的查询字符串值,我们可以进行如下修改:
use Acme\Model\Book;
$year = Input::get('year');
$paginator = Book::where('year', '=', $year)->paginate(20);
// 注意:
// 使用 '$appends()' 追加上除了 'page' 的其他查询字符串
$queryParams = array_diff_key($_GET, array_flip(['page']));
$paginator->appends($queryParams);
$paginatorAdapter = new IlluminatePaginatorAdapter($paginator);
$resource->setPaginator($paginatorAdapter);
使用 Cursor
当我们拥有大量数据集,并且运行 'SELECT COUNT(*) FROM ...' 并不需要,我们需要一种合适的获取数据的方法。其中一种方法是使用 Cursor,向后端程序指示从哪里开始获取数据。我们可以使用 'League\Fractal\Resource\Collection::setCursor()' 方法来在集合上设置一个新的 cursor。
Cursor 必须实现 'League\Fractal\Pagination\CursorInterface' 以及它指定的方法
Fractal 目前支持一个非常基本的适配器: League\Fractal\Pagination\Cursor。它真的很容易使用:
use Acme\Model\Book;
use Acme\Transformer\BookTransformer;
use League\Fractal\Pagination\Cursor;
use League\Fractal\Resource\Collection;
$currentCursor = Input::get('cursor', null);
$previousCursor = Input::get('previous', null);
$limit = Input::get('limit', 10);
if ($currentCursor) {
$books = Book::where('id', '>', $currentCursor)->take($limit)->get();
} else {
$books = Book::take($limit)->get();
}
$newCursor = $books->last()->id;
$cursor = new Cursor($currentCursor, $previousCursor, $newCursor, $books->count());
$resource = new Collection($books, new BookTransformer);
$resource->setCursor($cursor);
上面的示例是基于 Laravel 的 'illuminate\database' 包,但我们可以在任意我们想要的环境中执行此操作。上面的 cursor 正好是 id 字段,但是它也可以是很简单的偏移数字。无论选择什么来表示 cursor,可以使用 base64_encode() 和 base64_decode() 来编码 cursor 值,来确保 API 用户不会使用 cursor 来做任何太多聪明的事情。他们只需要将 cursor 传递给新的 URL,不需要做其他任何计算。
Cursor 使用示例:
GET /books?cursor=5&limit=5
{
"books": [
{ "id": 6 },
{ "id": 7 },
{ "id": 8 },
{ "id": 9 },
{ "id": 10 }
],
"meta": {
"cursor": {
"previous": null,
"current": 5,
"next": 10,
"count": 5
}
}
}
在下一个请求中,我们向前移动 cursor
设置 cursor 为上一个请求中的 next 值
设置 previous 为上一个请求中的 current 值
limit 是可选的,我们可以设置为上一个请求中的 count 值,来保持相同条数的数据
GET /books?cursor=10&previous=5&limit=5
{
"books": [
{ "id": 11 },
{ "id": 12 },
{ "id": 13 },
{ "id": 14 },
{ "id": 15 }
],
"meta": {
"cursor": {
"previous": 5,
"current": 10,
"next": 15,
"count": 5
}
}
}