Add request method requirement to routing in symfony 1.1
Doing rails-like RESTful resources is not natively possible with symfony, but with a bit of tweaking you can get closer to it.
The problem
Thanks to routing requirements, you can restrict the cases where a route matches a URL.
show_article:
url: /article/:id
params: { module: article, action: show }
requirements: { id: \d+ }
What I sometimes miss, is the ability to add a HTTP request method requirement, i.e. making a route match only if the request is GET, or POST, or DELETE. This is really useful when designing RESTful resources, where a single external URL can match more than one resource, depending on the request method:
show_article:
url: /article/:id
params: { module: article, action: show }
requirements: { id: \d+, sf_method: get }
update_article:
url: /article/:id
params: { module: article, action: update }
requirements: { id: \d+, sf_method: post }
delete_article:
url: /article/:id
params: { module: article, action: delete }
requirements: { id: \d+, sf_method: delete }
The solution
In order to allow symfony to do that, you need to modify two symfony classes: sfWebRequest and sfPatternRouting. As both of these classes are controlled by a factory in symfony 1.1, I recommend creating two custom classes, one for the request and one for the routing. Download these two classes here, and place them under the application lib/ folder. Now all you need is to edit your factories.yml to use these classes instead of the default ones:
all:
request:
class: myWebRequest
routing:
class: myPatternRouting
Clear the cache, and now you can add method requirements to your routing rules, as explained above.
In order to create a link to a route with a requirement other than sf_method: get, you must pass the sf_method parameter to url_for() explicitly (don't worry, it will not end up in the external URL):
What's next
Note that this is just a proof-of-concept, and to be perfectly RESTful, link_to() should generate a form doing a POST request when sf_method=post is passed. Also, the routing configuration handler could be modified to transform a single RESTful rule:
article:
restful:
base_url: /articles
module: article
identifier: id
Into a list of normal rules:
# display a list of articles
list_article:
url: /articles
params: { module: article, action: index }
requirements: { sf_method: get }
# display a single article
show_article:
url: /articles/:id
params: { module: article, action: show }
requirements: { id: \d+, sf_method: get }
# display an empty form for a new article
new_article:
url: /articles/new
params: { module: article, action: new }
requirements: { sf_method: get }
# handle the submission of a new article form
create_article:
url: /articles/new
params: { module: article, action: create }
requirements: { sf_method: post }
# display a form to edit an existing article
edit_article:
url: /articles/:id/edit
params: { module: article, action: edit }
requirements: { id: \d+, sf_method: get }
update_article:
url: /articles/:id/edit
params: { module: article, action: update }
requirements: { id: \d+, sf_method: post }
delete_article:
url: /articles/:id
params: { module: article, action: delete }
requirements: { id: \d+, sf_method: delete }
I leave that to your sagacity.
Possibly related posts (automatically generated):
Hi François,
nice post. I'm a former Symfony developer myself but still prefere Rails. Not that Symfony is a bad framework, but I just prefere ruby above PHP.
Putting the REST idea into Symfony would be a nice start, and your post here handles it very well.
But what about nested resources? That may get very complex, and how does Symhony handles this? I'm finishing a complex application now, with a lot of complex nested resources, and even in Rails it was a pain in the *** to get everything working properly with good data validation.
Using database transaction locks is one option, but you still have to provide good validation responses where errors may accure.
But I'm sure this is a good start for Symfony to go REST.
Nice work