Okay, this will require some setup:
I'm working on a method of using nice post title "slugs" in the URL's of my cakePHP powered blog.
For example: /blog/post-title-here
instead of /blog/view_post/123
.
Since I'm obviously not going to write a new method for every post, I'm trying to be slick and use CakePHP callbacks to emulate the behavior of PHP 5's __call()
magic method. For those who do not know, CakePHP's dispatcher checks to see if a method exists and throws a cakePHP error before __call()
can be invoked in the controller.
What I've done so far:
In the interest of full disclosure ('cause I have no Idea why I'm having a problem) I've got two routes:
Router::connect('/blog/:action/*', array('controller' => 'blog_posts'));
Router::connect('/blog/*', array('controller' => 'blog_posts'));
These set up an alias for the BlogPostsController so that my url doesn't look like /blog_posts/action
Then in the BlogPostsController:
public function beforeFilter() {
parent::beforeFilter();
if (!in_array($this->params['action'], $this->methods)) {
$this->setAction('single_post', $this->params['action']);
}
}
public function single_post($slug = NULL) {
$post = $this->BlogPost->get_post_by_slug($slug);
$this->set('post', $post);
//$this->render('single_post');
}
The beforeFilter
catches actions that do not exist and passes them to my single_post
method. single_post
grabs the data from the model, and sets a variable $post
for the view.
There's also an index
method that displays the 10 most recent posts.
Here's the confounding part:
You'll notice that there is a $this->render
method that is commented-out above.
- When I do not call
$this->render('single_post')
, the view renders once, but the$post
variable is not set. - When I do call
$this->render('single_post')
, The view renders with the$post
variable set, and then renders again with it not set. So in effect I get two full l开发者_如何学JAVAayouts, one after the other, in the same document. One with the content, and one without.
I've tried using a method named single_post
and a method named __single_post
and both have the same problem. I would prefer the end result to be a method named __single_post
so that it cannot be accessed directly with the url /blog/single_post
.
Also
I've not yet coded error handling for when the post does not exist (so that when people type random things in the url they don't get the single_post view). I plan on doing that after I figure out this problem.
This doesn't explicitly answer your question, but I'd just forego the whole complexity by solving the problem using only routes:
// Whitelist other public actions in BlogPostsController first,
// so they're not caught by the catch-all slug rule.
// This whitelists BlogPostsController::other() and ::actions(), so
// the URLs /blog/other/foo and /blog/actions/bar still work.
Router::connect('/blog/:action/*',
array('controller' => 'blog_posts'),
array('action' => 'other|actions'));
// Connect all URLs not matching the above, like /blog/my-frist-post,
// to BlogPostsController::single_post($slug). Optionally use RegEx to
// filter slug format.
Router::connect('/blog/:slug',
array('controller' => 'blog_posts', 'action' => 'single_post'),
array('pass' => array('slug') /*, 'slug' => 'regex for slug' */));
Note that the above routes depend on a bug fix only recently, as of the time of this writing, incorporated into Cake (see http://cakephp.lighthouseapp.com/projects/42648/tickets/1197-routing-error-when-using-regex-on-action). See the edit history of this post for a more compatible solution.
As for the single_post
method being accessible directly: I won't. Since the /blog/:slug
route catches all URLs that start with /blog/
, it'll catch /blog/single_post
and invoke BlogPostsController::single_post('single_post')
. You will then try to find a post with the slug "single_post", which probably won't exist. In that case, you can throw a 404 error:
function single_post($slug) {
$post = $this->BlogPost->get_post_by_slug($slug);
if (!$post) {
$this->cakeError('error404');
}
// business as usual here
}
Error handling: done.
精彩评论