Not too long ago you've all heard me saying Bye, bye Friendly URL’s. Now, after a lot of feedback and some more thoughts on it I came to what I find a nice compromise between REST'ness, SEO'ness, and FRIENDLY'ness.
For those of you who didn't read the old post, the basic problem was that it's very nice and convenient to have full control over your url's. So instead of having /posts/view/1 you might prefer /a-cool-thing or /post/a-cool-thing or something else, more friendly to both human's and search engines. However, the problem you get, is that you easily break bookmarks & links to your sites when you change the title of the post. And yet another issue is that this method requires all title fields to be unique. Now you can work around all of those, but what I decided in the Bye, bye Friendly URL’s-article was that it is too much of a hassle and you loose several advantages like the ease of building web api's.
So for my current project I started to rethink the issue again and remembered the idea brought up by tomo (and Peter Goodman). Instead of using the post title to identify a post in the url, just use it as 'decoration' together with the real id. So for example you could use urls like this in your application: /post/1/my-cool-post. Your action will only check the first parameter (1) in order to identify the post, so if the title ever changes, the link will still work, or to say it differently /post/1* will all lead to the same page.
What I didn't like about this structure was that for both, search engines, and people it could look like "my-cool-post" is a sub-item of /post/1/ which isn't the case. So a couple days ago I came up with a syntax I really like a lot:
/post/1:my-post-title
Maybe it's just personal preference. But to me it has all the good stuff:
- When the title changes, all other urls (/post/1:*) will remain usable
- The hierarchy is correct: "my-post-title" is the "1"st "post"
- It's REST'ful: /post/1 instead of /posts/view/1 (which is RPC)
- ... and, I think it just looks very pretty ; )
So however you may want to structure your url's, here is how to accomblish this stuff in CakePHP. The first thing is a route that will redirect your /post/* url's to PostsController::show();. My example is from CakePHP 1.2's Router, however it should work just the same with the old one:
Router::
connect('/post/*',
array('controller' =>
'posts',
'action' =>
'show'));
In your PostsController::show() action you'll just need a tiny bit of logic to split the id and post title (which I call url_suffix since I have an additional field for it in my project to gain more control over it):
function show
($id)
{
@
list($id,
$url_suffix) =
preg_split('/[:]/',
$id,
2);
$post =
$this->
Post->
findById($id);
$this->
set('post',
$post);
}
Ok and for those of you who want to get even fancier here is an improved version:
function show
($id)
{
@
list($id,
$url_suffix) =
preg_split('/[:]/',
$id,
2);
$post =
$this->
Post->
findById($id);
if ($url_suffix!==
$post['Post']['url_suffix'])
{
$this->
redirect('/post/'.
$post['Post']['id'].
':'.
$post['Post']['url_suffix'],
301);
exit;
}
$this->
set('post',
$post);
}
The difference between this function and the first one is that the function checks if the $url_prefix that was in the user request is the same one as currently assigned to our post. In case it has changed (and our user is using an old bookmark for example), we redirect him to a page with the proper url_suffix attached to the url. This is not all that important for normal users, but it's very efficent for telling search engines that your page has moved.
Oh and I'll post a function that'll convert "This is my post" type of titles into url ones like "this-is-my-post". It's not all that difficult, but I want to make sure it takes care of a lot of eventualities (Umlaute/Utf8 chars, double spaces, ...).
--Felix Geisendörfer aka the_undefined