Alternate resource return values

Typically, you should return first-class objects or arrays from your Resource listeners, and use the hydrators map and metadata map for hinting to PhlyRestfully how to render the various resources.

However, if you need to heavily optimize for performance, or want to customize your resource or collection instances without needing to wire more event listeners, you have another option: return HalResource, HalCollection, or ApiProblem instances directly from your Resource listeners, or the objects they delegate to.

HalResource and HalCollection

PhlyRestfully\HalResource and PhlyRestfully\HalCollection are simply wrappers for the resources and collections you create, and provide the ability to aggregate referential links. Links are aggregated in a PhlyRestfully\LinkCollection as individual PhlyRestfully\Link objects.

HalResource requires that you pass a resource and its identifier in the constructor, and then allows you to aggregate links:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use PhlyRestfully\HalResource;
use PhlyRestfully\Link;

// Create the HAL resource
// Assume $user is an object representing a user we want to
// render; we could have also used an associative array.
$halResource = new HalResource($user, $user->getId());

// Create some links
$selfLink = new Link('self');
$selfLink->setRoute('user', array('user_id' => $user->getId()));

$docsLink = new Link('describedBy');
$docsLink->setRoute('api/help', array('resource' => 'user'));

$links = $halResource->getLinks();
$links->add($selfLink)
     ->add($docsLink);

The above example creates a HalResource instance based on something we plucked from our persistence layer. We then add a couple of links describing “self” and “describedBy” relations, pointing them to specific routes and using specific criteria.

We can do the same for collections. With a collection, we need to specify the object or array representing the collection, and then provide metadata for various properties, such as:

  • The route to use for generating links for the collection, including any extra routing parameters or options.
  • The route to use for generating links for the resources in the collection, including any extra routing parameters or options.
  • The name of the identifier key within the embedded resources.
  • Additional attributes/properties to render as part of the collection. These would be first-class properties, and not embedded resources.
  • The name of the embedded collection (which defaults to “items”).

The following example demonstrates each of these options, as well as the addition of several relational links.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
use PhlyRestfully\HalCollection;
use PhlyRestfully\Link;

// Assume $users is an iterable set of users for seeding the collection.
$collection = new HalCollection($users);

$collection->setCollectionRoute('api/user');
// Assume that we need to specify a version within the URL:
$collection->setCollectionRouteParams(array(
    'version' => 2,
));
// Tell the router to allow query parameters when generating the URI:
$collection->setCollectionRouteOptions(array(
    'query' => true,
));

// Set the resource route, params, and options
$collection->setResourceRoute('api/user');
$collection->setResourceRouteParams(array(
    'version' => 2,
));
$collection->setResourceRouteOptions(array(
    'query' => null, // disable query string params
));

// Set the collection name:
$collection->setCollectionName('users');

// Set some attributes: current page, total number of pages, total items:
$collection->setAttributes(array(
    'page'        => $page, // assume we have this from somewhere else
    'pages_count' => count($users),
    'users_count' => $users->countAllItems(),
));

// Add some links
$selfLink = new Link('self');
$selfLink->setRoute('api/user', array(), array('query' => true));
$docsLink = new Link('describedBy');
$docsLink->setRoute('api/help', array('resource' => 'users'));

$links = $collection->getLinks();
$links->add($selfLink)
    ->add($docsLink);

Using this approach, you can fully customize the HalResource and HalCollection objects, allowing you to set custom links, customize many aspects of output, and more. You could even extend these classes to provide additional behavior, and provide your own HalLinks implementation that renders them differently if desired.

The downside, however, is that it ties your implementation directly to the PhlyRestfully implementation, which may limit some use cases.

ApiProblem

Just as you can return a HalResource or HalCollection, you can also directly return a PhlyRestfully\ApiProblem instance if desired, allowing you to fully craft the return value.

Unlike HalResource and HalCollection, however, ApiProblem does not allow you to set most properties after instantiation, which means you’ll need to ensure you have all your details up front.

The signature of the constructor is:

1
2
3
4
5
6
7
public function __construct(
    $httpStatus,                // HTTP status code used for the response
    $detail,                    // Summary of what happened
    $describedBy = null,        // URI to a description of the problem
    $title = null,              // Generic title for the problem
    array $additional = array() // Additional properties to include in the payload
);

Essentially, you simply instantiate and return an ApiProblem from your listener, and it will be used directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use PhlyRestfully\ApiProblem;

return new ApiProblem(
    418,
    'Exceeded rate limit',
    $urlHelper('api/help', array('resource', 'error_418')),
    "I'm a teapot",
    array(
        'user'  => $user,
        'limit' => '60/hour',
    )
);

And with that, you have a fully customized error response.