Advanced Routing

The recommended route for a resource is a Zend\Mvc\Router\Http\Segment route, with an identifier:

'route' => '/resource[/:id]'

This works great for standalone resources, but poses a problem for hierarchical resources. As an example, if you had a “users” resource, but then had “addresses” that were managed as part of the user, the following route definition poses a problem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
'users' => array(
    'type' => 'Segment',
    'options' => array(
        'route' => '/users[/:id]',
        'controller' => 'UserResourceController',
    ),
    'may_terminate' => true,
    'child_routes' => array(
        'addresses' => array(
            'type' => 'Segment',
            'options' => array(
                'route' => '/addresses[/:id]',
                'controller' => 'UserAddressResourceController',
            ),
        ),
    ),
),

Spot the problem? Both the parent and child have an “id” segment, which means there is a conflict. Let’s refactor this a bit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
'users' => array(
    'type' => 'Segment',
    'options' => array(
        'route' => '/users[/:user_id]',
        'controller' => 'UserResourceController',
    ),
    'may_terminate' => true,
    'child_routes' => array(
        'type' => 'Segment',
        'options' => array(
            'route' => '/addresses[/:address_id]',
            'controller' => 'UserAddressResourceController',
        ),
    ),
),

Now we have a new problem, or rather, two new problems: by default, the ResourceController uses “id” as the identifier, and this same identifier name is used to generate URIs. How can we change that?

First, the ResourceController allows you to define the identifier name for the specific resource being exposed. You can do this via the setIdentifierName() method, but more commonly, you’ll handle it via the identifier_name configuration parameter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
'phlyrestfully' => array(
    'resources' => array(
        'UserResourceController' => array(
            // ...
            'identifier_name' => 'user_id',
            // ...
        ),
        'UserAddressResourceController' => array(
            // ...
            'identifier_name' => 'address_id',
            // ...
        ),
    ),
),

If you are rendering child resources as part of a resource, however, you need to hint to the renderer about where to look for an identifier.

There are several mechanisms for this: the getIdFromResource and createLink events of the PhlyRestfully\Plugin\HalLinks plugin; or a metadata map.

The HalLinks events are as followed, and triggered by the methods specified:

Event name Method triggering event Parameters
createLink createLink
  • route *
  • id
  • resource
  • params *
getIdFromResource getIdFromResource
  • resource *

Let’s dive into each of the specific events.

Note

In general, you shouldn’t need to tie into the events listed on this page very often. The recommended way to customize URL generation for resources is to instead use a metadata map.

getIdFromResource event

The getIdFromResource event is only indirectly related to routing. Its purpose is to retrieve the identifier for a given resource so that a “self” relational link may be generated; that is its sole purpose.

The event receives exactly one argument, the resource for which the identifier is needed. A default listener is attached, at priority 1, that uses the following algorithm:

  • If the resource is an array, and an “id” key exists, it returns that value.
  • If the resource is an object and has a public “id” property, it returns that value.
  • If the resource is an object, and has a public getId() method, it returns the value returned by that method.

In all other cases, it returns a boolean false, which generally results in an exception or other error.

This is where you, the developer come in: you can write a listener for this event in order to return the identifier yourself.

As an example, let’s consider the original example, where we have “user” and “address” resources. If these are of specific types, we could write listeners like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$sharedEvents->attach('PhlyRestfully\Plugin\HalLinks', 'getIdFromResource', function ($e) {
    $resource = $e->getParam('resource');
    if (!$resource instanceof User) {
        return;
    }
    return $resource->user_id;
}, 100);

$sharedEvents->attach('PhlyRestfully\Plugin\HalLinks', 'getIdFromResource', function ($e) {
    $resource = $e->getParam('resource');
    if (!$resource instanceof UserAddress) {
        return;
    }
    return $resource->address_id;
}, 100);

Since writing listeners like these gets old quickly, I recommend using a metadata map instead.