RSS
 
04. May. 2013

Passed, named or query string params?

04 May

This is a question often asked. It’s also a question I had to find my own answers to – and to redefine those answers over time.

So here are my five cents regarding this topic:

Persistence: Passed params

You use passed params where there is a definite order in your URL regarding those params. So the second one never goes without the first one etc. One example is a blog with date posts and you could just use the year or year/month or even year/month/day to select posts:

/**
 * url: /posts/index/year/month/day/
 */
public function index($year = null, $month = null, $day = null) {}

Other examples: /controller/action/country/city/ or /controller/action/page/paragraph/

So basically, when the order is fixed and you always need all previous params to use a specific param.

Note: you can also access them using $this->request->params['pass'][0...x]. But since you always know the order, its usually easier to directly pass them into your method as variables.

Outdated flexibility: Named params

For me they are still in use in my places – mainly in older projects of mine.

Advantages

They work well with named routing and custom routes. You can easily make named params into passed ones:

// in the routes.php
Router::connect('/specials/offer/:slug', array('controller'=>'products', 'action'=>'view'), array('pass'=>array('slug')));
 
// url array
array('controller' => 'products', 'action' => 'view', 'slug' => 'kitchen-knife')
 
// resulting "pretty" url instead of "/products/view/slug:kitchen-knife/"
/specials/offers/kitchen-knife/

Issues

The encoding of named params can break urls. And, basically, they always violate the HTTP spec. They are cake specific (no other application uses volatile params this way) and no real standard.

Imagine what happens when you pass named params (via search/userinput) that contain special chars like /, &, … That will break the URL. You would need to manually encode and decode them all the time. Quite the pain. Using mod_rewrite you will also have to adjust the amount of encoding/decoding to the amount of htaccess files invoked in apache (as that also triggers the encoding/decoding). That is a known issue with URLs and apache. For me it was a nightmare do work with it properly without the URL being easily breakable.

Working with extensions can be quite the mess, as well:

// url array
array('ext' => 'json', 'foo' => 'bar')
 
// resulting url
/controller/action/foo:bar.json

To sum it up: Migrate them to query strings (see the next paragraph).

Flexibility the right way: Query strings

This is the way to go for 2.x and especially for 3.x.

When to use them

If the order is irrelevant or if they can be combined in different ways. In many cases like pagination not all query strings are needed to get the desired result. So the are usually always optional.

Advantages

They do not have the encoding issues of named params and are pretty much the way all other web apps also use such volatile params. So it is the de facto standard for web apps.

With query strings the short-routed action names (usually all index actions) don’t pop up anymore as long as you don’t use passed params:

/some_controller (index action of SomeController)
/some_controller/index/page:2/... (wrong)
/some_controller?page=2&... (nice)

They also work well with extensions:

// url array
array('ext' => 'json', '?' => array('foo' => 'bar')
 
// resulting url
/controller/action.json?foo=bar

Tip: Since CakePHP 2.3 you can use the convenience method CakeRequest::query() to read the URL query array in an error free manner. Any keys that do not exist will return null:

$foo = $this->request->query('foo');
// returns "bar" in our example - or null if no foo key is found

Nice things you can do with them, e.g. passing defaults for forms

Let’s say you link from articles to directly add a comment: /comments/add?article_id=2&title=Foo

public function add() {
    if ($this->request->is('post')) {
        // save
    } else {
        $this->request->data['Comment'] = $this->request->query;
    }
}

With that single line you are passing the query string params straight into the form as default values, populating the inputs on GET. For each POST it will then ignore those, of course.

You could argue that the same was possible with named params. Well, kind of. They were not capable of using chars that needed to be urlescaped. With query strings you don’t have to even think about it. It all works out of the box with anything you pass along.

Hands on

So how can we get our (new?) 2.x application future-proof and a little bit more 3.x ready?

I use this in my controllers to overwrite “named” as default paramType for the Paginator┬┤:

// in beforeFilter()
$this->paginate['paramType'] = 'querystring';

Note: This works if you do not overwrite the whole paginate array in your methods, but use the same key/value assignments. Alternatively, this is what I do:

// In your controller index actions
$this->paginate = array(
    'conditions' => ...,
    'contain' => ...,
    ...
) + $this->paginate;

This merges the AppController settings (including the paramType) into it if not overwritten.

Furthermore, my Search plugin is also now using query strings due to this config setting:

$config['Search'] = array(
    'Prg' => array(
        'commonProcess'=>array('paramType' => 'querystring', 'filterEmpty' => true),
        'presetForm' =>array('paramType' => 'querystring')
    )
);

All other named param checks in my code i simply switched to if ($this->request->query('foo')) etc. Along with the correct linking, of course:

// old: $this->Html->link('Foo Link', array('action' => 'some', 'foo' => 1))
$this->Html->link('Foo Link', array('action' => 'some', '?' => array('foo' => 1)))

I think that should already cover 99% of all cases. Beware of special route setups, though, that might need some custom handling.

Tip: Using my Tools plugin CommonComponent you can get a warning if you forgot to remove/upgrade any named params to query strings when enabling Configure key 'App.warnAboutNamedParams'. Set it to true and get error log entries regarding those issues. Helped me to find remaining named params.

Upgrading with BC

In plugins I often had this code:

$limit = null;
if (!empty($this->request->params['named']['limit'])) {
    $limit = $this->request->params['named']['limit']
}

You might not be able to change all dependant apps, right away, though. I often add the query strings while keeping it also named-param compatible.

Now, to upgrade this to query strings while having some BC, all that is needed is:

$limit = $this->request->query('limit');
if (!empty($this->request->params['named']['limit'])) {
    $limit = $this->request->params['named']['limit']
}

And once you don’t need BC anymore, you can simply drop the named param stuff:

$limit = $this->request->query('limit');

Mission accomplished.

Last words

As mentioned above my opinion regarding named params changed over time. I was working with them for years from 1.2 up to 2.2. But at some point I saw the disadvantages outweigh the advantages. In 2.x there have been also many improvements regarding query strings so that since 2.3 I always use and recommend query strings for new applications. And for legacy ones I recommend upgrading – if possible.

And for 3.x there will be no named params anymore, anyway. So the sooner we start applying the query strings to apps and plugins, the better.

Also, using the extension routing as mentioned above you don’t really have any other choice, anyway. Passed params like this are not even valid AFAIK:

/some/controller/action.json/bar/y

This is also pretty ugly:

/some/controller/action.json/foo:bar/x:y

The only clean approach here are the query strings appended:

/some/controller/action.json?foo=bar&x=y

Which can easily be read from the controller using CakeRequest::query():

$foo = $this->request->query('foo'); // returns 'bar'
$x = $this->request->query('y'); // returns 'y'

Insight

The 3.x docs state the following:

Named parameters required special handling both in CakePHP as well as any PHP or javascript library that needed to interact with them, as named parameters are not implemented or understood by any library except CakePHP. The additional complexity and code required to support named parameters did not justify their existence, and they have been removed. In their place you should use standard query string parameters or passed arguments

Read more about the usage of the param types above in the documentation.

Passed, named or query string params?
2 votes, 5.00 avg. rating (97% score)
 
3 Comments

Posted by Mark in CakePHP

 

Tags: , , ,

Leave a Reply

Tip:
If you need to post a piece of code use {code type=php}...{/code}.
Allowed types are "php", "mysql", "html", "js", "css".

Please do not escape your post (leave all ", <, > and & as they are!). If you have encoded characters and need to reverse ("decode") it, you can do that here!
 

 
  1. Rob Maurer

    April 22, 2014 at 05:32

    Very grateful to you. Thanks for explaining the reason why query string params are the way to go.

     
  2. Neon1024

    May 1, 2014 at 11:49

    I don't like query params because they make the url look messy. I think this article needs a follow up or amend explaining about creating SEO friendly urls using query strings using rewrite rules.

     
  3. Mark

    May 1, 2014 at 11:53

    I disagree. It can't look any more messy than with named params. Especially if you are an outsider, seeing them for the first time.
    You might let you cloud your judgement by what you are used so far in Cake – but giving the in-validness of named params they are not comparable.
    So it doesn't matter actually if they are nice or whatever. Just don't use em as if they never existed.

    Using query strings usually doesn't involve SEO – so it doesn't necessarily need a follow up. When SEO is relevant you would usually use passed params – or internally make them behave like passed ones via routing.
    Query strings are – as explained above – the way to quickly filter results – and as such don't need any special love IMO.
    Especially in the backend and such. For revelant frontend parts you can probably use some routing approach to map them to a more passed param style.