Mapping request parameters to action method parameters

Recently I have been looking into ASP.NET MVC implementation and I have to admit it is pretty neat. One thing I liked in particular about it is that they map request parameters (the arguments usually passed in the URL) directly to the method parameters. In Zend Framework it is required to use the getParam method on the request object

$this->_request->getParam('name', 'default');

Being a developer I decided to add same feature to Zend Framework as well. Basically my solution uses reflection API to discover parameters, type hints and optional default values.

Here is a simple example of this in action

/**
 * Use default value:
 *   URL: index/view
 *   OUPUT: int(1)
 * Pass page value (Rewrite)
 *   URL: index/view/page/2
 *   OUTPUT: int(2)
 * Classic method (GET)
 *   URL: index/view/?page=3
 *   OUTPUT: int(3)
 * Pass invalid (string) value
 *   URL: index/list/page/invalid
 *   OUTPUT: int(0)
 *
 * @param int
 */
public function indexAction($page = 1)
{
    Zend_Debug::dump($page);
    die;
}

This highlights couple of aspects. First you can provide type hinting in phpdoc comment and it will automatically cast argument to the given type. This may seem as a tedious extra writing, but it does encourage to use method documentation. However due to way Zend Framework reflection API works the @param in the comments must be in exactly same order as parameters in the method. Names are not matched!

Some would argue that this syntactic sugar decreases performance due to extra work involved and Reflection uses. I agree with this –it does incur some overhead. However considering that Zend Framework requires a pretty complicated boilerplate code to actually reach your action this here is miniscule by comparison. Besides cashing can be used to speed things up.

Here is another more advanced example

/**
 * index/info/
 * index/info/tags/one/tags/two
 * index/info/msg/msg-only
 * index/info/date/2009-10-03
 * index/info/msg/RandomOrder/date/2009-10-03
 *
 * @param array $tags
 * @param Zend_Date $date
 * @param string $msg
 */
public function infoAction(array $tags = null, Zend_Date $date = null, $msg = 'hello')
{
    Zend_Debug::dump($tags);
    if (!is_null($date)) echo $date->get(Zend_Date::DATETIME_FULL);
    else echo "no date passed\n";
    Zend_Debug::dump($msg);
    die;
}

You can easily pass arrays and objects and mapper will automatically do appropriate casting and initialization. The order in which arguments appear in the URL is not important and any of them can be missing in which case default value will be used. When using class or array type hint then phpdoc comments are not really necessary however due to reflection API limitation parameters must appear in the same order. Since msg is last and we want it to be a string we need to add $tags and $date beforehand.

However be vary when instantiating classes directly in this manner. Since the input is not validated you can easily get hard to track errors or exceptions.

By default missing parameters (even if method prototype doesn’t have a default value) will be silently ignored and a default value assigned (empty array, null or an object instance). However you can optionally specify a required variable and have the mapper throw an error if parameter doesn’t exist.

/**
 * @require_page Exception
 * @param int
 */
public function testAction($page)
{
    Zend_Debug::dump($page);
    die;
}

Simply add @require_<param here name> [and optionally exception class name to throw] into the phpdoc. With this in place if $page is not passed mapper will throw an exception.

To use this download the package below and simply extend your class from ZendX_Controller_Action

Download it from here: Action.zip

15 Responses to “Mapping request parameters to action method parameters” RSS

  1. Jordon says:

    This is pretty neat.

    Thanks.

  2. David Caunt says:

    Very neat – any idea what kind of overhead is incurred?

  3. [...] post includes a snippet of sample code, but you can learn more about the script here. Share and [...]

  4. umpirsky says:

    This should be default behaviour. Did you think about writing proposal and include it into zf?

    What about performances?

    • This may be an answer: performances will be lower than doing nothing (sic!).
      This is a choice: make developper’s life better or server’s life better…

      I have chosen both as far it is possible. I put reflexion informations into cache :)

      Wilfried

  5. Ben says:

    Nice idea, will try that in some of my future projects. Thanks for this post!

  6. Richard Noya says:

    Thanks, very intuitive. Excellent solution to use PHP doc. Once worked with it, it indeed should become default behaviour.

  7. Hi,

    I haven’t implemented your mapping because stg was missing to me: you propose get on requests but not set.
    I will try to use the ‘&’ notation to promote set params I need… If this works correctly, our code will be greatly cleaned!

    Thx again for your sample code,
    Wilfried

  8. Robert says:

    Hi,

    this looks really great and makes my colleague happy :-) But one question: Have you tried to get your changes include into the “official” Framework?

    Regards

    Robert

  9. This solution is not very secure. It allows the user to set values of any variable in your function, because the call_user_func_array function doesn’t check for variables that are not parameters of the function.

    • Albeva says:

      This is not true. Please look more closely at the source. Only arguments present in the method signature are mapped. Secondly lookup call_user_func_array in php documentation. It only calls the method and does not assign any values to variables defined within the method. Only in the signature.

      Or did I misunderstand you?

  10. Why would ZF link this from homepage, but NOT put it in their incubator SVN? It frustrates me I have to copy paste this file everytime I am setting up something on ZF I wasnt to use it with. They should add this to the incubator so we can get it with an SVN external.

    • The Zend Framework community doesn’t just add random code from around the Web into the framework. Albeva would have to create a proposal if he would like to get something like this looked at by the community. I will say that it is unlikely for 2.0 given that there is an effort to make the MVC dispatch process faster and more streamlined.

Leave a Reply