Yii 1.1: Hyphenation of routes in URL management

23 followers

In this article, we introduce an approach that allows automatic hyphenation of the route part in URLs.

As we know, Yii uses the URL manager to support URL creation and parsing. However, the default implementation does not deal well with the route that has mixed cases. For example, given a route /createAccount, the URL manager would generate the following URL by default:

/user/createAccount

For SEO purists, this is not very pretty. They would want something like /user/create-account for better readability. To achieve this goal, we can add the following rule when configuring the URL manager:

'user/create-account' => 'user/createAccount'

This is fine but not perfect, because it requires us to specify a similar rule for every route that has mixed cases. To avoid this trouble and also to improve the performance, we can extend CUrlManager as follows,

class UrlManager extends CUrlManager
{
    public $showScriptName = false;
    public $appendParams = false;
    public $useStrictParsing = true;
    public $urlSuffix = '/';
 
    public function createUrl($route, $params = array(), $ampersand = '&')
    {
        $route = preg_replace_callback('/(?<![A-Z])[A-Z]/', function($matches) {
            return '-' . lcfirst($matches[0]);
        }, $route);
        return parent::createUrl($route, $params, $ampersand);
    }
 
    public function parseUrl($request)
    {
        $route = parent::parseUrl($request);
        return lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $route))));
    }
}

In the above, we define a new class UrlManager which extends from CUrlManager. We mainly override the createUrl() and parseUrl() methods to perform the hyphenation of routes. We also override the default values of several properties of CUrlManager so that the URLs are even more SEO friendly.

Now we need to make some minor changes to the application configuration:

return array(
    // ....
    'components' => array(
        'urlManager' => array(
            'class' => 'UrlManager',
            'rules' => array(
                // ....
                '<controller:[\w\-]+>/<action:[\w\-]+>' => '<controller>/<action>',         
            ),
        ),
    ),
);

In the above, we mainly specify the class of urlManager to be our new class UrlManager. We also change the rule a little bit so that it can match the hyphens (-) in the URLs (the default setting only matches word characters which don't include hyphens).

With these code in place, for the route user/createAccount we would obtain URL /user/create-account/. The ending slash is because we set urlSuffix to be / in UrlManager.

Note: Because the above code uses anonymous function and lcfirst(), it requires PHP 5.3 or above.

Total 6 comments

#17000 report it
Don Khan at 2014/04/21 07:50pm
Where to put it?

Since I can't find an answer anywhere this is probably a silly, self-evident question that I should know already, but...where should I put this class?

I've got it in

/models

but it seems like it should be in with the controllers.

#16999 report it
Don Khan at 2014/04/21 07:50pm
Where to put it?

Since I can't find an answer anywhere this is probably a silly, self-evident question that I should know already, but...where should I put this class?

I've got it in [code]/models[/code] but it seems like it should be in with the controllers.

#12845 report it
Trejder at 2013/04/15 02:40pm
Is `useStrictParsing` really needed?

Qiang, can you please explain, why do you force useStrictParsing to be set to TRUE? If this is enabled, it causes problems with default controllers and/or actions not being executed correctly, as described in here.

Setting this parameter to FALSE (default value for CUrlManager.useStrictParsing) seems to be solving the problem and solution from your article also seems to be working fine without it. So, I think it is not required / not necessary here.

Also, tell us if using urlSuffix is obligatory? Or is it just an addition. I'm not using it in my rules, so I disabled it and solultion also seems to be working fine without it.

EDIT: After testing this for a few days, I can say that presented solution works like a charm without useStrictParsing and urlSuffix.

#12830 report it
Trejder at 2013/04/15 03:30am
@Boaz

Using method mentioned in this article is prefered one, as in OOP manner.

But, if someone would prefer your solution, here is CController.missingAction description to read about.

#10262 report it
Boaz at 2012/10/15 05:40pm
Another approach

Another approach can be to add a 'missingAction' method in the controller class where you need to have hyphen/dash using action names. This method is called whenever Yii doesn't find an action with the name it parsed.

Sample method:

public function missingAction($action_id) {
    /**
     * Support dash separated action ids: convert whatever-action-id to actionWhateverActionId method name, check if it exists and if it does - run it.
     */
    $action_id = explode('-', $action_id);
    $action_id = array_map('strtolower', $action_id);
    $action_id = array_map('ucfirst', $action_id);
    $action_id = implode('', $action_id);
    if (method_exists($this, 'action' . $action_id) || array_key_exists('action' . $action_id, $this->actions())) {
        $this->setAction($action_id);
        $this->run($action_id);
    }
    else {
        throw new CHttpException(404);
    }
}

Credit - this kind of method (or similar) was suggested in Yii's forums. I don't have the URL for it at the moment.

#10247 report it
yasen at 2012/10/13 05:21pm
RegExp

I've been thinking about the regexp:

$route = preg_replace_callback('/(?<![A-Z])[A-Z]/', function($matches) {
  return '-' . lcfirst($matches[0]);
}, $route);

The negative lookbehind would match a capital letter not preceeded by a capital letter. A class name 'camelXHtmlTest' would turn into 'camel-xHtml-test'. In order to be 'camel-x-html-test', the pattern should become:

'/[A-Z]/'

Am I missing something? Thanks.

Edit: Think I got it... The patterns avoids many hyphens if the class name contains more consecutive capital letters, e.g. "camelXHTMLTest". Conslusion: follow the convention of not using consecutive capital letters, so the class should better be "camelXhtmlTest" and then one may go for the simpler pattern.

Leave a comment

Please to leave your comment.

Write new article
  • Written by: qiang
  • Category: How-tos
  • Yii Version: 1.1
  • Votes: +18
  • Viewed: 14,317 times
  • Created on: Oct 13, 2012
  • Last updated: never
  • Tags: URL