You are viewing revision #1 of this wiki article.
This is the latest version of this article.
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.
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.
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.
@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.
Is
useStrictParsing
really needed?Qiang, can you please explain, why do you force
useStrictParsing
to be set toTRUE
? 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 forCUrlManager.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
andurlSuffix
.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.
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
but it seems like it should be in with the controllers.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.