-
Notifications
You must be signed in to change notification settings - Fork 47
Routing
Conventional routing loader. Current loader has 2 drawbacks:
- Extreme verbosity
- Names ambiguity
blog_display:
pattern: /blogs/{id}
defaults: { _controller: App:Blog:show }
requirements: { id: '\d+' }
This could look like a short record, but in reality, for a simple CRUD you'll need 7 of those, forcing you to repeat yourself with parameters or patterns from controller to controller, from project to project.
Even worse than that - there's more important problem here - naming problem. You have 3 different entity names for each route record:
- Route name (
blog_display
) - Action name (
App:Blog:show
) - URL pattern (
/blogs/{id}
)
Though they all represent the same URL<->Controller mapping, they are really loosely coupled, which means that you need to maintain them separately during the whole lifetime of the project. Have you ever asked yourself "how to name routes for this controller?" or "how to name this action?". That's the kind of questions Symfony2 forces you to answer from project to project, again and again. Also, that's the kind of questions that could be answered once and for all with some simple convention.
So let us present you the first big improvement of RadBundle - Conventional Routing Loader. It provides routing configuration support, which lives somewhere in between RoR and Symfony2, but provides you all the flexibility that the standard routing.yml
does.
First thing, Routing Loader is purely based on a couple of conventions. Those conventions are simple rules like:
- Controller name as objects collection presentation (plural)
- Most controllers could be represented with 5 crud basic actions:
-
indexAction
- list items -
newAction
- show form or save new item -
showAction
- show single item -
editAction
- show form or update existing item -
deleteAction
- delete item from collection
-
Following this simple convention, your Symfony2 controller will look like:
namespace App\Controller;
class BlogsController
{
public function indexAction() {}
public function newAction(Request $request) {}
public function showAction($id) {}
public function editAction($id) {}
public function deleteAction($id) {}
}
If you follow this simple convention, conventional routing loader will be able to do most of the job for you. Only thing you will need to do is to add this line to routing.yml
:
App:Blogs: ~
This record will automatically expand into full routing map like that:
route name | URL pattern | HTTP method | controller action |
---|---|---|---|
app_blogs_index | /blogs | GET | App\Controller\BlogsController::indexAction() |
app_blogs_new | /blogs/new | GET | App\Controller\BlogsController::newAction() |
app_blogs_create | /blogs | POST | App\Controller\BlogsController::newAction() |
app_blogs_show | /blogs/{id} | GET | App\Controller\BlogsController::showAction($id) |
app_blogs_edit | /blogs/{id}/edit | GET | App\Controller\BlogsController::editAction($id) |
app_blogs_update | /blogs/{id} | PUT | App\Controller\BlogsController::editAction($id) |
app_blogs_delete | /blogs/{id} | DELETE | App\Controller\BlogsController::deleteAction($id) |
Ok, but what if instead of /blogs
, you want those routes to live inside /admin/blogs
URL?
App:Blogs: /admin/blogs
Will generate this route map:
route name | URL pattern | HTTP method | controller action |
---|---|---|---|
app_blogs_index | /admin/blogs | GET | App\Controller\BlogsController::indexAction() |
app_blogs_new | /admin/blogs/new | GET | App\Controller\BlogsController::newAction() |
app_blogs_create | /admin/blogs | POST | App\Controller\BlogsController::newAction() |
app_blogs_show | /admin/blogs/{id} | GET | App\Controller\BlogsController::showAction($id) |
app_blogs_edit | /admin/blogs/{id}/edit | GET | App\Controller\BlogsController::editAction($id) |
app_blogs_update | /admin/blogs/{id} | PUT | App\Controller\BlogsController::editAction($id) |
app_blogs_delete | /admin/blogs/{id} | DELETE | App\Controller\BlogsController::deleteAction($id) |
What if you don't want a whole CRUD? What if the only thing you need is index
and show
? You can specify which collections or resource actions you want to map:
App:Blogs:
collections: [ index ]
resources: [ show ]
Will generate this routes map:
route name | URL pattern | HTTP method | controller action |
---|---|---|---|
app_blogs_index | /blogs | GET | App\Controller\BlogsController::indexAction() |
app_blogs_show | /blogs/{id} | GET | App\Controller\BlogsController::showAction($id) |
Want custom object action? No problem:
App:Blogs:
collections: [ index ]
resources: [ show, ban ]
Will generate this route map:
route name | URL pattern | HTTP method | controller action |
---|---|---|---|
app_blogs_index | /blogs | GET | App\Controller\BlogsController::indexAction() |
app_blogs_show | /blogs/{id} | GET | App\Controller\BlogsController::showAction($id) |
app_blogs_ban | /blogs/{id}/ban | PUT | App\Controller\BlogsController::banAction($id) |
Don't like default requirements or pattern for some method? You can always fine-tune it manually:
App:Blogs:
collections: [ index ]
resources:
show: ~
ban:
pattern: /ban-this-guy/{id}
requirements: { _method: 'GET' }
Will generate this route map:
route name | URL pattern | HTTP method | controller action |
---|---|---|---|
app_blogs_index | /blogs | GET | App\Controller\BlogsController::indexAction() |
app_blogs_show | /blogs/{id} | GET | App\Controller\BlogsController::showAction($id) |
app_blogs_ban | /blogs/ban-this-guy/{id} | GET | App\Controller\BlogsController::banAction($id) |
You can even add collection-wide requirements to your routes:
App:Blogs:
collections: [ index ]
resources:
requirements: { id: '\d+' }
show: ~
ban:
pattern: /ban-this-guy/{id}
requirements: { _method: 'GET' }
Will add "id should be number" requirement to all resourceful routes.
Ok, but what if you want a whole CRUD collection but at the same time you need to add couple of custom actions? No need to expand your controller route tree. You could just describe specific actions:
App:Blogs: ~
App:Blogs:ban: /blogs/{id}/ban
App:Blogs:unban: /blogs/{id}/unban
That's it, I think. If you have any questions - ask them on IRC (#geekweek12 [at] freenode.net).
# generating routes for Cheese collection based on conventions
App:Cheeses: ~
# custom pattern
App:Cheeses: /good/cheeses
# a bit expanded to specify needed actions
App:Cheeses:
resources: [show, edit]
collections: [index, new]
# a bit more expanded version of specific routes
App:Cheeses:
resources:
show: /cheeses/{id}
# full version of the one above
show:
pattern: /cheeses/{id}
defaults: ~
requirements: ~
edit: /cheeses/{id}/edit
delete: /cheeses/{id}
collections:
index: /cheeses
new: /cheeses/new
# single resource action
App:Cheeses:list: /cheeses/{id}/list
In Symfony 2.2, the _method request parameter support has been disabled by default.
To handle, for example, "PUT" or "DELETE" methods, it is necessary to execute this :
Request::enableHttpMethodParameterOverride();
Suggest : it can be done in the boot method of your bundle.