Relational RESTful controllers routes

2.0 version
Maintained

Relational RESTful controllers routes

Sometimes it's better to place subresource actions in their own controller, especially when you have more than 2 subresource actions.

Resource collection

In this case, you must first specify resource relations in special rest YML or XML collection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# src/Acme/HelloBundle/Resources/config/users_routes.yml
users:
    type:     rest
    host:     hostname.example.com
    resource: Acme\HelloBundle\Controller\UsersController

comments:
    type:     rest
    host:     hostname.example.com
    parent:   users
    resource: Acme\HelloBundle\Controller\CommentsController
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- src/Acme/HelloBundle/Resources/config/users_routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://friendsofsymfony.github.com/schema/rest"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://friendsofsymfony.github.com/schema/rest https://raw.github.com/FriendsOfSymfony/FOSRestBundle/master/Resources/config/schema/routing/rest_routing-1.0.xsd">

    <import id="users" type="rest" resource="Acme\HelloBundle\Controller\UsersController" host="hostname.example.com" />
    <import type="rest" parent="users" resource="Acme\HelloBundle\Controller\CommentsController"  host="hostname.example.com" />
</routes>

Notice parent: users option in the second case. This option specifies that the comments resource is child of the users resource.

It is also necessary to add type: rest to the routing.yml file:

1
2
3
4
5
# app/config/routing.yml
acme_hello:
    type: rest
    host:   hostname.example.com
    resource: "@AcmeHelloBundle/Resources/config/users_routes.yml"

In this case, your UsersController MUST always have a single resource get... action:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php

namespace AppBundle\Controller;

class UsersController extends Controller
{
    public function getUserAction($slug)
    {} // "get_user"   [GET] /users/{slug}

    // ...
}

It's used to determine the parent collection name. Controller name itself is not used in routes auto-generation process and can be any name you like.

Define child resource controller

CommentsController actions now will looks like:

 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
<?php

namespace AppBundle\Controller;

class CommentsController extends Controller
{
    public function postCommentVoteAction($slug, $id)
    {} // "post_user_comment_vote" [POST] /users/{slug}/comments/{id}/vote

    public function getCommentsAction($slug)
    {} // "get_user_comments"   [GET] /users/{slug}/comments

    public function getCommentAction($slug, $id)
    {} // "get_user_comment"    [GET] /users/{slug}/comments/{id}

    public function deleteCommentAction($slug, $id)
    {} // "delete_user_comment" [DELETE] /users/{slug}/comments/{id}

    public function newCommentsAction($slug)
    {} // "new_user_comments"   [GET] /users/{slug}/comments/new

    public function editCommentAction($slug, $id)
    {} // "edit_user_comment"   [GET] /users/{slug}/comments/{id}/edit

    public function removeCommentAction($slug, $id)
    {} // "remove_user_comment" [GET] /users/{slug}/comments/{id}/remove
}

Notice, we got rid of the User part in action names. That is because the RestBundle routing already knows, that CommentsController::... is child resources of UsersController::getUser() resource.

Include resource collections in application routing

Last step is mapping of your collection routes into the application routing.yml:

1
2
3
4
# app/config/routing.yml
users:
    type:     rest
    resource: "@AcmeHelloBundle/Resources/config/users_routes.yml"

That's all. Note that it's important to use the type: rest param when including your application's routing file. Without it, rest routes will still work but resource collections will fail. If you get an exception that contains "routing loader does not support given key: ``parent``", then you are most likely missing the type: rest param in your application level routes include.

Routes naming

RestBundle uses REST paths to generate route name. This means, that URL:

1
[POST] /users/{slug}/comments/{id}/vote

will become the route with the name post_user_comment_vote.

For further examples, see comments of controllers in the code above.

Naming collisions

Sometimes, routes auto-naming will lead to route names collisions, so RestBundle route collections provides a name_prefix (name-prefix for xml and @NamePrefix for annotations) parameter (you can use name_prefix only in a file loaded by the rest loader.):

1
2
3
4
5
# app/config/routing.yml
users:
    type: rest  # Required for ``RestYamlLoader`` to process imported routes
    prefix: /api
    resource: "@AcmeHelloBundle/Resources/config/users_routes.yml"
1
2
3
4
5
# src/Acme/HelloBundle/Resources/config/users_routes.yml
comments:
    type:         rest
    resource:     "@AcmeHelloBundle/Controller/CommentsController"
    name_prefix:  api_ # Our precious parameter

With this configuration, route name would become:

1
api_vote_user_comment

Say NO to name collisions!

That was it!

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.