Published by Fabian on 03 May 2008 at 12:26 pm
Creating cleaner, RESTful actions using sfPopulatedRouting
When I was thinking about the MVC separation concerns some people have with symfony I came across something that I noticed in most of my actions that seem like a violation of MVC logic. In almost every action I do a database lookup. Not only is that more a part of the controller, rather than the view (I consider the view made up action and template), but also Java Frameworks can do dependency injection (which works declarative rather than programmatic).
So I was thinking of a User Edit action. It usually looks like
$this->user = UserPeer::retriveByPK(this->getRequestParameter('user')); $this->forward404Unless($this->user);
I would assume that 2/3 of the actions in all symfony applications have similar lines at the beginning. And its not only extra effort to code these two lines every action, you also put code into that somehow fulfills a different purpose. The action should assume that the user to edit is already retrieved.
As a solution for those issues I propose the use of sfPopulatedRouting. It uses the exact configuration as the current sfPatternRouting (standard routing.yml file), but it treats the requirement differently.
Ideally this would be only a slim extension on top of sfPatternRouting, but I had quite a lot of issues extending the class. As I see the implementation anyway just as a proof of concept this is okay and can be fixed later on. Using that new routing your action will look like:
$this->user = this->getRequestParameter('user');
I came up with the idea to recycle the requirements parameters in routing as the meaning does nicely overlap
. Here an example:
edit_user:
url: /edit/:user
param: { module: user, action: edit }
requirements: { user: UserPeer::retrieveByPk }As you see it will use propel pk getter to return the user from the Peer object. Even though the use of slugs is widespread, also many people use the PK in the url. for that you can use the better shortcut:
edit_user:
url: /edit/:user
param: { module: user, action: edit }
requirements: { user: User }Ideally this would detect your ORM and perform the right query.
you can find the prototype sfPopulatedRouting.class.php and a diff file (against sfPatternRouting) here.
(requiring php 5.2.3 due to call_user_func() signature)
Let me know what you think of this, and if you have any good idea on for example auto publish the parameters to the action. Because
$this->user = this->getRequestParameter('user');
doesn’t look like injected. But making it
echo $this->user->getName();
requires modifying code of the action dispatcher. Something like the bad register_globals
Martin on 05 May 2008 at 12:42 am #
Ever heard of the myUser class ? Put your logic there.
Fabian on 05 May 2008 at 8:48 am #
Hi Martin, sure I have heard of the myUser.class.
But what has the myUser to do with injecting the required model objects into the actions.
Are you confused because I have taken user as an example?
dob on 16 May 2008 at 6:00 pm #
Django has the useful notion of generic views for this pattern:
http://www.djangoproject.com/documentation/generic_views/
Might be worth a few minutes of your time to look at their approach.
Fabian on 16 May 2008 at 6:39 pm #
Thanks dob, that looks interesting. Actually not much different from what symfony does, but in a more complicated way