[PHP] Fractal

1. 什么是 Fractal?

Fractal provides a presentation and transformation layer for complex data output, the like found in RESTful APIs, and works really well with JSON. Think of this as a view layer for your JSON/YAML/etc.
Fractal 提供复杂数据书册的表示层和转换层,就像在 RESTful APIs中找到的一样,并且和JSON工作得很好。可以想象成 JSON/YAML得格式的视图层。

When building an API it is common for people to just grab stuff from the database and pass it to json_encode(). This might be passable for “trivial” APIs but if they are in use by the public, or used by mobile applications then this will quickly lead to inconsistent output.
人们再构建接口时,通常直接从数据库获取数据,然后传递给json_encode().这对小接口来说可能是行得通的,但是如果借口被公开使用或者被手机应用使用可能很快导致不同的输出。

简单的例子

For the sake of simplicity, this example has been put together as though it was one file. In reality you would spread the manager initiation, data collection and JSON conversion into separate parts of your application.
为了简单起见,这个例子被放在一个文件里,就好像是一个文件, 在真实环境中,你的应用可以将manager初始化,数据收集,和JSON转换分开。

 '1',
        'title' => 'Hogfather',
        'yr' => '1998',
        'author_name' => 'Philip K Dick',
        'author_email' => '[email protected]',
    ],
    [
        'id' => '2',
        'title' => 'Game Of Kill Everyone',
        'yr' => '2014',
        'author_name' => 'George R. R. Satan',
        'author_email' => '[email protected]',
    ]
];

// Pass this array (collection) into a resource, which will also have a "Transformer"
// This "Transformer" can be a callback or a new instance of a Transformer object
// We type hint for array, because each item in the $books var is an 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'],
            ]
        ]
    ];
});

// Turn that into a structured array (handy for XML views or auto-YAML converting)
$array = $fractal->createData($resource)->toArray();

// Turn all of that into a JSON string
echo $fractal->createData($resource)->toJson();

// Outputs: {"data":[{"id":1,"title":"Hogfather","year":1998,"author":{"name":"Philip K Dick","email":"[email protected]"}},{"id":2,"title":"Game Of Kill Everyone","year":2014,"author":{"name":"George R. R. Satan","email":"[email protected]"}}]}

It is worth noting that callbacks are a fairly shoddy replacement for using real Transformers. They allow you to reuse transformers and keep your controllers lightweight.
值得注意的是,回调是对使用真正的Transformers来说,是一个并不怎么优雅的方案。Transformers允许重用使得控制器更加轻量级.

2.安装

Fractal is available on Packagist and can be installed using Composer:

$ composer require league/fractal

3.术语表

Learn more about the general concepts of Fractal.

  • Cursor
    A cursor is an unintelligent form of Pagination, which does not require a total count of how much data is in the database. This makes it impossible to know if the "next" page exists, meaning an API client would need to keep making HTTP Requests until no data could be found (404).
    游标是一种不智能的分页形式,这并不需要计算数据库中有多少数据。这使得不可能知道“下一页”是否存在,这意味着API客户端需要一直发出HTTP请求,直到找不到数据为止(404)。
  • Include
    Data usually has relationships to other data. Users have posts, posts have comments, comments belong to posts, etc. When represented in RESTful APIs this data is usually "included" (a.k.a embedded or nested) into the resource. A transformer will contain includePosts() methods, which will expect a resource to be returned, so it can be placed inside the parent resource.
    数据通常与其他数据有关系。用户有文章,文章有评论,评论属于文章,等等。当在RESTful api中表示这些数据时,通常是“包含”的(a.k。嵌入或嵌套的)到资源中。transformer将包含includePosts()方法,该方法期望返回一个资源,因此可以将其放置在父资源中。
  • Manager
    Fractal has a class named Manager, which is responsible for maintaining a record of what embedded data has been requested, and converting the nested data into arrays, JSON, YAML, etc. recursively.
    Fractal有一个名为Manager的类,它负责维护已请求嵌入数据的记录,并递归地将嵌套数据转换为数组,JSON,YAML等。
  • Pagination
    Pagination is the process of dividing content into pages, which in relation to Fractal is done in two alternative ways: Cursors and Paginators.t分页是将内容划分为页面的过程,Fractal相关的过程以两种可选方式完成:游标和分页。
  • Paginator
    A paginator is an intelligent form of Pagination, which will require a total count of how much data is in the database. This adds a "paginator" item to the response meta data, which will contain next/previous links when applicable.
    Paginator是一个只能的分页形式,他将返回数据库中有多少数据的总数。他将在返回的meta数据中添加一个paginator并包含上一页和下一页的链接。
    Resource
    A resource is an object which acts as a wrapper for generic data. A resource will have a transformer attached, for when it is eventually transformed ready to be serialized and output.
    资源是一个对象,它充当通用数据的包装器。 资源将连接一个变换器,因为它最终被转换为准备好被序列化和输出。
  • Serializer
    Serializer以某种方式构建转换后的数据。 API有很多输出结构,两种流行的结构是HAL and JSON-API. Twitter and Facebook output data differently to each other, and Google does it differently too. Serializers let you switch between various output formats with minimal effect on your Transformers.
    Serializer以某种方式构建转换后的数据。 API有很多输出结构,两种流行的结构是HAL and JSON-API.Serializer以某种方式构建转换后的数据。 Twitter和Facebook的输出数据彼此不同,谷歌也有不同的做法。Serrializer用对Transformer影响最小的方式在不同的输出格式之间切换.
  • Transformer
    Transformers are classes, or anonymous functions, which are responsible for taking one instance of the resource data and converting it to a basic array. This process is done to obfuscate your data store, avoiding Object-relational impedance mismatch and allowing you to even glue various elements together from different data stores if you wish. The data is taken from these complex data store(s) and made into a format that is more manageable, and ready to be Serialized.
    TransFormer可以是一些类或者匿名函数,可以从resource实例中取得数据并转换为基本数组。这个过程是为了混淆你的数据存储,避免 Object-relational impedance mismatch ,并且如果你愿意甚至允许你将从不同数据存储中的 各种元素粘合在一起。 数据是从这些复杂的数据存储中获取的,并制作成更易于管理的格式,并且可以进行序列化。

Resources

Resources are objects that represent data, and have knowledge of a “Transformer”, which is an object or callback that will know how to output the data.
资源是表示数据的对象,并且具有“变换器”的知识,“变换器”是知道如何输出数据的对象或回调。

Two types of resource exist:

  • League\Fractal\Resource\Item - A singular resource, probably one entry in a data store,单条资源,可能是数据存储中的一个条目
  • League\Fractal\Resource\Collection - A collection of resources,一个资源的集合

The Item and Collection constructors will take any kind of data you wish to send it as the first argument, and then a “transformer” as the second argument.
'ItemCollection`构造函数会将任何想要发送的数据作为第一个参数,然后使用“变换器”作为第二个参数。

These examples use callback transformers instead of creating classes, purely for demonstrative purposes.
这些示例使用回调变换器而不是创建类,纯粹用于演示目的。

Item Example

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 Example

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,
        ]
    ];
});

In this example $books is an array of Acme\Model\Book instances, or a collection class that implemented ArrayIterator.

4.Serializers

A Serializer structures your Transformed data in certain ways. There are many output structures for APIs, two popular ones beingHAL and JSON-API. Twitter and Facebook output data differently to each other, and Google does it differently too. Most of the differences between these serializers are how data is namespaced.
Serializer 以某种方式构建您的转换数据。 API有许多输出结构,两个流行的结构是HAL and JSON-API. Twitter和Facebook输出的数据彼此不同,谷歌的做法也不同。 这些序列化程序之间的大部分区别在于数据如何命名空间。

Serializer classes let you switch between various output formats with minimal effect on your Transformers.
Serializer类可以让您在各种输出格式之间切换,而对Transformer的影响最小。

A very basic usage of Fractal will look like this, as has been seen in other sections:

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());

// Some sort of ORM call
$book = Book::find(1);

// Make a resource out of the data and
$resource = new Item($book, new BookTransformer(), 'book');

// Run all transformers
$manager->createData($resource)->toArray();

// Outputs:
// [
//     'data' => [
//         'id' => 'Foo',
//         'title' => 'Foo',
//         'year' => 1991,
//     ],
// ];

What is new here is the $manager->setSerializer(new DataArraySerializer()); part. DataArraySerializer is the name of the default serializer in Fractal, but there are more.

4.1 DataArraySerializer

This serializer is not to everyone’s tastes, because it adds a 'data' namespace to the output:
这个串行器并不符合每个人的口味,因为它为输出添加了一个“data”命名空间:

// Item
[
    'data' => [
        'foo' => 'bar'
    ],
];

// Collection
[
    'data' => [
        [
            'foo' => 'bar'
        ]
    ],
];

This is handy because it allows space for meta data (like pagination, or totals) in both Items and Collections.
这很方便,因为它允许在项目和集合中都有元数据空间(如分页或总计)。

// Item with Meta
[
    'data' => [
        'foo' => 'bar'
    ],
    'meta' => [
        ...
    ]
];

// Collection with Meta
[
    'data' => [
        [
            'foo' => 'bar'
        ]
    ],
    'meta' => [
        ...
    ]
];

This fits in nicely for meta and included resources, using the 'data' namespace. This means meta data can be added for those included resources too.
这很适合meta和包含的资源,使用data命名空间。 这意味着元数据也可以添加到这些包含的资源中。

// Item with included resource using meta
[
    'data' => [
        'foo' => 'bar'
        'comments' => [
            'data' => [
                ...
            ],
            'meta' => [
                ...
            ]
        ]
    ],
];

4.2 ArraySerializer

Sometimes people want to remove that 'data' namespace for items, and that can be done using the ArraySerializer. This is mostly the same, other than that namespace for items. Collections keep the 'data' namespace to avoid confusing JSON when meta data is added.
有时候人们想要删除那些用于项目的'data'命名空间,并且可以使用ArraySerializer来完成。 除了该项目的命名空间之外,这大部分是相同的。 集合保留data命名空间以避免在添加元数据时与JSON混淆。

use League\Fractal\Serializer\ArraySerializer;
$manager->setSerializer(new ArraySerializer());
// Item
[
    'foo' => 'bar'
];

// Collection
[
    'data' => [
        'foo' => 'bar'
    ]
];

Meta data is fine for items, but gets a little confusing for collections:
元数据适用于items,但对于collections有点混淆:

// Item with Meta
[
    'foo' => 'bar'
    'meta' => [
        ...
    ]
];

// Collection with Meta
[
    [
        'foo' => 'bar'
    ]
    'meta' => [
        ...
    ]
];

Adding a named key to what is otherwise just a list confuses JSON:

{“0”:{“foo”:”bar”},”meta”:{}}

That "0" is there because you cannot mix index keys and non-indexed keys without JSON deciding to make it a structure (object) instead of a list (array).

This is why ArraySerialzier is not recommended, but if you are not using meta data then… carry on.
这就是为什么不推荐ArraySerialzier的原因,但是如果您没有使用元数据,那么...继续。

4.3 JsonApiSerializer

This is a representation of the JSON-API standard (v1.0). It implements the most common features such as

  • Primary Data
  • Resource Objects
  • Resource Identifier Objects
  • Compound Documents
  • Meta Information
  • Links
  • Relationships
  • Inclusion of Related Resources

Features that are not yet included

  • Sparse Fieldsets
  • Sorting
  • Pagination
  • Filtering

As Fractal is a library to output data structures, the serializer can only transform the content of your HTTP response. Therefore, the following has to be implemented by you
由于Fractal是输出数据结构的库,因此序列化程序只能转换HTTP响应的内容。 因此,您必须实现以下操作

  • Content Negotiation
  • HTTP Response Codes
  • Error Objects
    For more information please refer to the official JSON API specification.

JSON API requires a Resource Key for your resources, as well as an id on every object.
JSON API需要Resource Key用于资源,就好像每个对象上的id

use League\Fractal\Serializer\JsonApiSerializer;
$manager->setSerializer(new JsonApiSerializer());

// Important, notice the Resource Key in the third parameter:
$resource = new Item($book, new JsonApiBookTransformer(), 'books');
$resource = new Collection($books, new JsonApiBookTransformer(), 'books');

The resource key is used to give it a named namespace:

// Item
[
    'data' => [
        'type' => 'books',
        'id' => 1,
        'attributes' => [
            'foo' => 'bar'
        ],
    ],
];

// Collection
[
    'data' => [
        [
            'type' => 'books',
            'id' => 1,
            'attributes' => [
                'foo' => 'bar'
            ],
        ]
    ],
];

Just like DataArraySerializer, this works nicely for meta data:

// Item with Meta
[
    'data' => [
        'type' => 'books',
        'id' => 1,
        'attributes' => [
            'foo' => 'bar'
        ]
    ],
    'meta' => [
        ...
    ]
];

// Collection with Meta
[
    'data' => [
        [
            'type' => 'books',
            'id' => 1,
            'attributes' => [
                'foo' => 'bar'
            ]
        ]
    ],
    'meta' => [
        ...
    ]
];

Adding a resource to an item response would look like this:

// 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'
            ]
        ]
    ]
];

If you want to enable links support, just set a baseUrl on your serializer

use League\Fractal\Serializer\JsonApiSerializer;
$baseUrl = 'http://example.com';
$manager->setSerializer(new JsonApiSerializer($baseUrl));

The same resource as above will look like this

// 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'
            ]
        ]
    ]
];

4.4 Custom Serializers

You can make your own Serializers by implementing SerializerAbstract.
你可以通过实现SerializerAbstract来创建一个自己的Serializer

use Acme\Serializer\CustomSerializer;
$manager->setSerializer(new CustomSerializer());

The structure of serializers will change at some point, to allow items and collections to be handled differently and to improve side-loading logic. Keep an eye on the change log, but do not be afraid to make one.
序列化器的结构将在某些时候发生变化,以允许以不同方式处理项目和集合并改进侧载逻辑。 密切关注更改日志,但不要害怕创建一个。

5 Transformers

In the Resources section the examples show off callbacks for transformers, but these are of limited use:

 (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,
            ]
        ]
    ];
});

These can be handy in some situations, but most data will need to be transformed multiple times and in multiple locations, so creating classes to do this work can save code duplication.
这些在某些情况下可能很方便,但大多数数据需要在多个位置进行多次转换,因此创建类来完成这项工作可以节省代码重复。

5.1 Classes for Transformers

To reuse transformers (recommended) classes can be defined, instantiated and passed in place of the callback.
要重用变换器,可以定义,实例化和传递类来代替回调。

These classes must extend League\Fractal\TransformerAbstract and contain at the very least a method with the name transform().

The method declaration can take mixed input, just like the callbacks:

 (int) $book->id,
            'title'   => $book->title,
            'year'    => (int) $book->yr,
            'links'   => [
                [
                    'rel' => 'self',
                    'uri' => '/books/'.$book->id,
                ]
            ],
        ];
    }
}

Once the Transformer class is defined, it can be passed as an instance in the resource constructor.

5.2 Including Data

Your transformer at this point is mainly just giving you a method to handle array conversion from your data source (or whatever your model is returning) to a simple array. Including data in an intelligent way can be tricky as data can have all sorts of relationships. Many developers try to find a perfect balance between not making too many HTTP requests and not downloading more data than they need to, so flexibility is also important.
transformer主要是为您提供一种方法来处理从数据源(或者你的model返回的)到简单数据的转换。
由于数据可以具有各种关系,因此以智能方式Including Data可能会非常棘手。许多开发人员试图找到一个完美的平衡点,不需要做太多的HTTP请求,也不需要下载比他们需要的更多的数据,
所以灵活性也很重要。

Sticking with the book example, the BookTransformer, we might want to normalize our database and take the two author_* fields out and put them in their own table. This include can be optional to reduce the size of the JSON response and is defined like so:
还是使用书籍做示例“BookTransformer”,我们可能想要对数据库进行规范化,并将两个author_ *字段取出并放入自己的表中。 这包括可以是可选的,以减少JSON响应的大小,并定义如下:

 (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);
    }
}

These includes will be available but can never be requested unless the Manager::parseIncludes()method is called:
除非调用Manager :: parseIncludes()方法,否则这些includes将可用,但永远不会被请求:

parseIncludes($_GET['include']);
}

With this set, include can do some great stuff. If a client application were to call the URL /books?include=author then they would see author data in the response.
有了这套,include 可以做一些伟大的事情。 如果客户端应用程序调用URL/ books?include = author,那么他们会在响应中看到作者数据。

These includes can be nested with dot notation too, to include resources within other resources.
这些 includes也可以用点符号嵌套,以包含其他资源中的资源。

E.g: /books?include=author,publishers.somethingelse

Note: publishers will also be included with somethingelse nested under it. This is shorthand for publishers,publishers.somethingelse.

This can be done to a limit of 10 levels. To increase or decrease the level of embedding here, use the Manager::setRecursionLimit(5) method with any number you like, to strip it to that many levels. Maybe 4 or 5 would be a smart number, depending on the API.
这可以达到10个级别的限制。 要在这里增加或减少嵌入的级别,可以使用Manager :: setRecursionLimit(5)方法以及任何你喜欢的数字来将它剥离到许多级别。 也许4或5将是一个聪明的数字,取决于API。

5.3 Default Includes

Just like with optional includes, default includes are defined in a property on the transformer:
就像optional一样,default includes定义在一个transformer的属性中。

author;

        return $this->item($author, new AuthorTransformer);
    }
}

This will look identical in output as if the user requested ?include=author.
这在输出中看起来是一样的,就好像用户请求'?include = author一样。

5.4 Excluding Includes

The Manager::parseExcludes() method is available for odd situations where a default include should be omitted from a single response.
Manager::parseExcludes()方法对单数情况生效,其中应从单个响应中省略默认包含。

parseExcludes('author');

The same dot notation seen for Manager::parseIncludes() can be used here.

Only the mostly deeply nested resource from the exclude path will be omitted.
只会省略排除路径中大部分深度嵌套的资源。

To omit both the default author include on the BookTransformer and a default editor include on the nested AuthorTransformer, author.editor,author would need to be passed, since author.editor alone will omit only the editor resource from the respone.
要省略BookTransformer上的默认包含的author和嵌套在AuthorTransformer中的默认editorauthor.editor,author需要传递,因为author.editor单独会省略 只有来自respone的editor资源。

Parsed exclusions have the final say whether or not an include will be seen in the response data. This means they can also be used to omit an available include requested in Manager::parseIncludes().
解析的排除项最终决定是否在响应数据中看到包含。 这意味着它们也可以用于省略Manager :: parseIncludes()中请求的可用包含。

5.5 Include Parameters

When including other resources, syntax can be used to provide extra parameters to the include methods. These parameters are constructed in the URL, ?include=comments:limit(5|1):order(created_at|desc).
包含其他资源时,可以使用语法为include方法提供额外的参数。 这些参数可以在URL中构造?include=comments:limit(5|1):order(created_at|desc).

This syntax will be parsed and made available through a League\Fractal\ParamBag object, passed into the include method as the second argument.
此语法将通过 League\Fractal\ParamBag 对象进行解析并提供,并作为第二个参数传递给include方法。

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);
    }

Parameters have a name, then multiple values which are always returned as an array, even if there is only one. They are accessed by the get() method, but array access is also an option, so $params->get('limit') and $params['limit'] do the same thing.
参数有一个名称,然后是多个值,它们总是作为数组返回,即使只有一个。 它们可以通过get()方法访问,但是数组访问也是一个选项,所以$ params-> get('limit')$ params['limit']做同样的事情。

5.6 Eager-Loading vs Lazy-Loading

The above examples happen to be using the lazy-loading functionality of an ORM for $book->author. Lazy-Loading can be notoriously slow, as each time one item is transformered, it would have to go off and find other data leading to a huge number of SQL requests.
上面的例子使用ORM的延迟加载功能来表示$book->author。 懒惰加载可能非常慢,因为每次转换一个项目时,它都必须关闭并找到导致大量SQL请求的其他数据。

Eager-Loading could easily be used by inspecting the value of $_GET['include'], and using that to produce a list of relationships to eager-load with an ORM.
通过检查$ _GET ['include']的值,可以很容易地使用Eager-Loading,并使用它来生成与ORM一起急切加载的关系列表。

6 Pagination

When working with a large data set it obviously makes sense to offer pagination options to the endpoint, otherwise that data can get very slow. To avoid writing your own pagination output into every endpoint, Fractal provides you with two solutions:
使用大型数据集时,为端点提供分页选项显然是有意义的,否则数据会变得很慢。 为避免将自己的分页输出写入每个端点,Fractal为您提供了两种解决方案:

  • Paginator
  • Cursor

6.1 Using Paginators

Paginators offer more information about your result-set including total, and have next/previous links which will only show if there is more data available. This intelligence comes at the cost of having to count the number of entries in a database on each call.
Paginators提供有关结果集的更多信息,包括总数,并有下一个/上一个链接,只有在有更多可用数据时才会显示。 这种情报的代价是必须在每次呼叫时计算数据库中的条目数。

For some data sets this might not be an issue, but for some it certainly will. If pure speed is an issue, consider using Cursors instead.
对于某些数据集,这可能不是问题,但对某些人来说肯定会。 如果纯速度是一个问题,请考虑使用游标。

Paginator objects are created, and must implement League\Fractal\Pagination\PaginatorInterface and its specified methods. The instantiated object must then be passed to the League\Fractal\Resource\Collection::setPaginator() method.
Paginator对象被创建,并且必须实现League\Fractal\Pagination\PaginatorInterface 及其指定的方法。 然后必须将实例化的对象传递给League\Fractal\Resource\Collection::setPaginator()方法。

Fractal currently ships with the following adapters:
Fractal目前附带以下适配器:

  • Laravel’s illuminate/pagination package as League\Fractal\Pagination\IlluminatePaginatorAdapter
  • The pagerfanta/pagerfanta package as League\Fractal\Pagination\PagerfantaPaginatorAdapter
  • Zend Framework’s zendframework/zend-paginator package as League\Fractal\Pagination\ZendFrameworkPaginatorAdapter

6.2 Laravel Pagination

As an example, you can use Laravel’s Eloquent or Query Builder method paginate() to achieve the following:

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));

6.3 Symfony Pagination

Below is an example of pagination using the Pagerfanter Paginator with a collection of objects obtained from Doctrine.

$doctrineAdapter = new DoctrineCollectionAdapter($allItems);
$paginator = new Pagerfanta($doctrineAdapter);
$filteredResults = $paginator->getCurrentPageResults();

$paginatorAdapter = new PagerfantaPaginatorAdapter($paginator, function(int $page) use (Request $request, RouterInterface $router) {
    $route = $request->attributes->get('_route');
    $inputParams = $request->attributes->get('_route_params');
    $newParams = array_merge($inputParams, $request->query->all());
    $newParams['page'] = $page;
    return $router->generate($route, $newParams, 0);
});
$resource = new Collection($filteredResults, new BookTransformer);
$resource->setPaginator($paginatorAdapter);

6.4 Including existing query string values in pagination links

In the example above, previous and next pages will be provided simply with ?page=# ignoring all other existing query strings. To include all query string values automatically in these links we can replace the last line above with:

use Acme\Model\Book;

$year = Input::get('year');
$paginator = Book::where('year', '=', $year)->paginate(20);

$queryParams = array_diff_key($_GET, array_flip(['page']));
$paginator->appends($queryParams);

$paginatorAdapter = new IlluminatePaginatorAdapter($paginator);
$resource->setPaginator($paginatorAdapter);

6.5 Using Cursors

When we have large sets of data and running a SELECT COUNT(*) FROM whatever isn’t really an option, we need a proper way of fetching results. One of the approaches is to use cursors that will indicate to your backend where to start fetching results. You can set a new cursor on your collections using the League\Fractal\Resource\Collection::setCursor() method.

The cursor must implement League\Fractal\Pagination\CursorInterface and its specified methods.

Fractal currently ships with a very basic adapter: League\Fractal\Pagination\Cursor. It’s really easy to use:

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);

These examples are for Laravel’s illuminate\database package, but you can do it however you like. The cursor also happens to be constructed from the id field, but it could just as easily be an offset number. Whatever is picked to represent a cursor, maybe consider using base64_encode() and base64_decode() on the values to make sure API users do not try and do anything too clever with them. They just need to pass the cursor to the new URL, not do any maths.

6.6 Example Cursor Usage

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
        }
    }
}

On the next request, we move the cursor forward.

  • Set cursor to next from the last response
  • Set previous to current from the last response
  • limit is optional * You can set it to count from the previous request to maintain the same limit
    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
        }
    }
}

你可能感兴趣的:([PHP] Fractal)