Collections and Pagination¶
In most use cases, you’ll not want to return a collection containing every resource in that collection; this will quickly get untenable as the number of resources in that collection grows. This means you’ll want to paginate your collections somehow, returning a limited set of resources at a time, delimited by some offset in the URI (usually via query string).
Additionally, to follow the Richardson Maturity Model properly, you will likely want to include relational links indicating the next and previous pages (if any), and likely the first and last as well (so that those traversing the collection know when to stop).
This gets tedious very quickly.
Fortunately, PhlyRestfully can automate the process for you, assuming you are
willing to use Zend\Paginator to help do some of the heavy lifting.
Paginators¶
ZendPaginator is a general purpose component for paginating collections of data. It requires only that you specify the number of items per page of data, and the current page.
The integration within PhlyRestfully for Zend\Paginator uses a “page” query
string variable to indicate the current page. You set the page size during
configuration:
1 2 3 4 5 6 7 8 9 10 11 | return array(
'phlyrestfully' => array(
'resources' => array(
'Paste\ApiController' => array(
// ...
'page_size' => 10, // items per page of data
// ...
),
),
),
);
|
All you need to do, then, is return a Zend\Paginator\Paginator instance from
your resource listener (or an extension of that class), and PhlyRestfully will
then generate appropriate relational links.
For example, if we consider the walkthrough example, if
our onFetchAll() method were to return a Paginator instance, the
collection included 3000 records, we’d set the page size to 10, and the request
indicated page 17, our response would include the following links:
{
"_links": {
"self": {
"href": "http://example.org/api/paste?page=17
},
"prev": {
"href": "http://example.org/api/paste?page=16
},
"next": {
"href": "http://example.org/api/paste?page=18
},
"first": {
"href": "http://example.org/api/paste
},
"last": {
"href": "http://example.org/api/paste?page=300
}
},
// ...
}
Again, this functionality is built-in to PhlyRestfully; all you need to do is
return a Paginator instance, and set the page_size configuration for
your resource controller.
Manual collection links¶
If you do not want to use a Paginator for whatever reason, you can always
listen on one of the controller events that returns a collection, and manipulate
the returned HalCollection from there. The events of interest are:
- getList.post
- replaceList.post
In each case, you can retrieve the HalCollection instance via the
collection parameter:
$collection = $e->getParam('collection');
From there, you will need to retrieve the collection’s LinkCollection, via
the getLinks() method, and manually inject Link instances. The following
creates a “prev” relational link based on some calculated offset.
$sharedEvents->attach('Paste\ApiController', 'getList.post', function ($e) {
$collection = $e->getParam('collection');
// ... calculate $someOffset ...
$links = $collection->getLinks();
$prev = new \PhlyRestfully\Link('prev');
$prev->setRoute(
'paste/api',
array(),
array('query' => array('offset' => $someOffset))
);
$links->add($prev);
});
This method could be extrapolated to add additional route parameters or options as well.
With these events, you have the ability to customize as needed. In most cases, however, if you can use paginators, do.
Query parameter white listing¶
Often when dealing with collections, you will use query string parameters to allow such actions as sorting, filtering, and grouping. However, by default, those query string parameters will not be used when generating links. This is by design, as the relational links in your resources typically should not change based on query string parameters.
However, if you want to retain them, you can.
As noted a number of times, the ResourceController exposes a number of
events, and you can tie into those events in order to alter behavior. One method
that the HalCollection class exposes is setCollectionRouteOptions(),
which allows you to set, among other things, query string parameters to use
during URL generation. As an example, consider this listener:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $allowedQueryParams = array('order', 'sort');
$sharedEvents->attach('Paste\ApiController', 'getList.post', function ($e) use ($allowedQueryParams) {
$request = $e->getTarget()->getRequest();
$params = array();
foreach ($request->getQuery() as $key => $value) {
if (in_array($key, $allowedQueryParams)) {
$params[$key] = $value;
}
}
if (empty($params)) {
return;
}
$collection = $e->getParam('collection');
$collection->setCollectionRouteOptions(array(
'query' => $params,
));
});
|
The above is a very common pattern; so common, in fact, that we’ve automated it.
You can whitelist query string parameters to use in URL generation for
collections using the collection_query_whitelist configuration parameter for
your resource controller:
1 2 3 4 5 6 7 8 9 10 11 | return array(
'phlyrestfully' => array(
'resources' => array(
'Paste\ApiController' => array(
// ...
'collection_query_whitelist' => array('order', 'sort'),
// ...
),
),
),
);
|