Skip to content

Latest commit

 

History

History
217 lines (172 loc) · 7.87 KB

advanced-routing.rst

File metadata and controls

217 lines (172 loc) · 7.87 KB

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:

'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:

'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:

'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 <ref/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 <ref/metadata-map>.

The createLink method is currently called only from PhlyRestfully\ResourceController::create(), and is used to generate the Location header. Essentially, what it does is call the url() helper with the passed route, and the serverUrl() helper with that result to generate a fully-qualified URL.

If passed a resource identifier and resource, you can attach to the event the method triggers in order to modifiy the route parameters and/or options when generating the link.

Consider the following scenario: you need to specify an alternate routing parameter to use for the identifier, and you want to use the "user" associated with the resource as a route parameter. Finally, you want to change the route used to generate this particular URI.

The following will do that:

$request = $services->get('Request');
$sharedEvents->attach('PhlyRestfully\Plugin\HalLinks', 'createLink', function ($e) use ($request) {
    $resource = $e->getParam('resource');
    if (!$resource instanceof Paste) {
        // only react for a specific type of resource
        return;
    }

    // The parameters here are an ArrayObject, which means we can simply set
    // the values on it, and the method calling us will use those.
    $params = $e->getParams();

    $params['route'] = 'paste/api/by-user';

    $id   = $e->getParam('id');
    $user = $resource->getUser();
    $params['params']['paste_id'] = $id;
    $params['params']['user_id']  = $user->getId();
}, 100);

The above listener will change the route used to "paste/api/by-user", and ensure that the route parameters "paste_id" and "user_id" are set based on the resource provided.

The above will be called with create is successful. Additionally, you can use the HalLinks plugin from other listeners or your view layer, and call the createLink() method manually -- which will also trigger any listeners.

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:

$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 <ref/metadata-map> instead.

event, resource controller, hal, routing, HalLinks, metadata