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'),
// ...
),
),
),
);
|