Advanced Rendering

What if you’re not returning an array as a resource from your persistence layer? Somehow, you have to be able to transform it to an associative array so that it can be rendered to JSON easily. There are a variety of ways to do this, obviously; the easiest would be to make your resource implement JsonSerializable. If that’s not an option, though, what other approaches do you have?

In this section, we’ll explore one specific solution that is the most explicit of those available: the renderCollection.resource event of the HalLinks plugin.

The renderResource and renderCollection events

These events are triggered each time you render a resource or collection, even if they are embedded. As such, this allows you to introspect these items and manipulate them prior to extracting them to a representation.

As an example, let’s say we want to inject a “describedby” link into any My\User resource and/or My\Users collection. We can do this as follows:

 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
$sharedEvents->attach(
    'PhlyRestfully\Plugin\HalLinks',
    array('renderResource', 'renderCollection'),
    function ($e) {
        $resource   = $e->getParam('resource', false);
        $collection = $e->getParam('collection', false);
        if (!$resource && !$collection) {
            return;
        }
        if ($resource && !$resource instanceof \My\User) {
            return;
        }
        if ($collection && !$collection instanceof \My\Users) {
            return;
        }
        if ($collection) {
            $resource = $collection;
        }
        $links = $resource->getLinks();
        $links->add(\PhlyRestfully\Link::factory(array(
            'rel' => 'describedby',
            'url' => 'http://example.com/api/help/resources/user',
        )));
    }
);

The above attaches to both events, and then checks if we have a resource or collection we’re interested in; if so, it creates a link and injects it into the composed link collection. This will ensure we have a “describedby” relational link in each one of these rendered resources.

The renderCollection.resource event

This method is triggered as part of the renderCollection() method, once for each resource in the collection. It receives the following parameters:

  • collection, the current collection being iterated
  • resource, the current resource in the iteration
  • route, the resource route defined for the collection; usually, this is the same route as provided to the controller.
  • routeParams, any route params defined for resources within the collection; usually, this is empty.
  • routeOptions, any route options defined for resources within the collection; usually, this is empty.

Let’s consider the following scenario: we’re rendering something like a public status timeline, but the individual status resources in our timeline belong to another route. Additionally, we want to show a subset of information for each individual status when in the public timeline; we don’t need the full status resource.

We’d define a listener:

 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
$sharedEvents->attach('PhlyRestfully\Plugin\HalLinks', 'renderCollection.resource', function ($e) {
    $collection = $e->getParam('collection');
    if (!$collection instanceof PublicTimeline) {
        // nothing to do here
        return;
    }

    $resource = $e->getParam('resource');
    if (!$resource instanceof Status) {
        // nothing to do here
        return;
    }

    $return = array(
        'id'        => $resource->getId(),
        'user'      => $resource->getUser(),
        'timestamp' => $resource->getTimestamp(),
    );

    // Parameters are stored as an ArrayObject, allowing us to change them
    // in situ
    $params = $e->getParams();
    $params['resource']    = $return;
    $params['route']       = 'api/status/by-user';
    $params['routeParams'] = array(
        'id'   => $resource->getId(),
        'user' => $resource->getUser(),
    );
}, 100);

The above extracts three specific fields of the Status object and creates an array representation for them. Additionally, it changes the route used, and sets some route parameters. This information will be used when generating a “self” relational link for the resource, and the newly created array will be used when creating the representation for the resource itself.

This approach gives us maximum customization during the rendering process, but comes at the cost of added boiler plate code. As per the section on routing, I recommend using a metadata map unless you need to dynamically determine route parameters or filter the resource before rendering. Additionally, in many cases hydrators (the subject of the next section) are more than sufficient for the purpose of creating an array representation of your resource.