Child ResourcesΒΆ

Resources often do not exist in isolation. Besides some resources embedding others, in some cases, a resource exists only as a result of another resource existing – in other words, within a hierarchical or tree structure. Such resources are often given the name “child resources.”

In the advanced routing chapter, we looked at one such example, with a user and addresses.

 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[/:user_id]',
        'controller' => 'UserResourceController',
    ),
    'may_terminate' => true,
    'child_routes' => array(
        'addresses' => array(
            'type' => 'Segment',
            'options' => array(
                'route' => '/addresses[/:address_id]',
                'controller' => 'UserAddressResourceController',
            ),
        ),
    ),
),

In that chapter, I looked at how to tie into various events in order to alter routing parameters, which would ensure that the relational URLs were generated correctly. I also noted that there’s a better approach: metadata maps. Let’s look at such a solution now.

First, let’s make some assumptions:

  • Users are of type User, and can be hydrated using the ClassMethods hydrator.
  • Individual addresses are of type UserAddress, and can by hydrated using the ObjectProperty hydrator.
  • Address collections have their own type, UserAddresses.
  • The users collection is called “users”
  • The address collection is called “addresses”
  • The class UserListener listens on Resource events for users.
  • The class UserAddressListener listens on Resource events for addresses.

Now, let’s create some resource controllers, using configuration as noted in the chapter on resource controllers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
return array(
    // ...
    'phlyrestfully' => array(
        'resources' => array(
            'UserResourceController' => array(
                'listener'                => 'UserListener',
                'collection_name'         => 'users',
                'collection_http_options' => array('get', 'post'),
                'resource_http_options'   => array('get', 'patch', 'put', 'delete'),
                'page_size'               => 30,
            ),
            'UserAddressResourceController' => array(
                'listener'                => 'UserAddressListener',
                'collection_name'         => 'addresses',
                'collection_http_options' => array('get', 'post'),
                'resource_http_options'   => array('get', 'patch', 'put', 'delete'),
            ),
        ),
    ),
);

Now we have controllers that can respond properly. Let’s now configure the metadata and hydrator maps for our resources.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
return array(
    // ...
    'phlyrestfully' => array(
        // ...
        'metadata_map' => array(
            'User' => array(
                'hydrator'        => 'ClassMethods',
                'identifier_name' => 'user_id',
                'route'           => 'users',
            ),
            'UserAddress' => array(
                'hydrator'        => 'ObjectProperty',
                'identifier_name' => 'address_id',
                'route'           => 'users/addresses',
            ),
            'UserAddresses' => array(
                'identifier_name' => 'address_id',
                'route'           => 'users/addresses',
                'is_collection'   => true,
                'route_options'   => array('query' => true),
            ),
        ),
    ),
);

Now, when we render a User, if it composes a UserAddresses object, that object will be rendered as an embedded collection, and each resource inside it will be rendered using the appropriate route and identifier.