Search code examples
cakephptreeroutesslugcakephp-2.2

SEO routes for nested categories with paginator and articles


Human readable URLs with nested categories like /category/subcategory/n-subcategories/article. I'm using CakePHP 2.2.3 and can't find a proper solution for a routing problem. Using 2 Tables:

  • articles (could also be products or posts or...)
    • have just a normal "single" view
    • an article belongs to one category
  • categories
    • nested (Tree behaviour with n-levels)
    • one category can have many articles
    • category-view lists all articles, that are related to this category
    • category view uses paginator for showing article lists

A very common example I guess. But how do I have to define the router now, to get URL-paths with the nested categories like this:

/categoryname1                                         (showing category view)
/categoryname1/articlename1                            (showing article view)
/categoryname2/articlename2                            (showing article view)
/categoryname2/subcategoryname1                        (showing category view)
/categoryname2/subcategoryname2/articlename4           (showing article view)
/n-categoryname/././...n-subcategoryname               (showing category view)
/n-categoryname/././...n-subcategoryname/n-articlename         (article view)

I tried to make all routes fix in the routes.php, but that is not very comfortable and I think there should be a dynamic solution.

I also tried to automatically generate all routes out of category- and article-alias and save them in a separate "routes" database table - it worked, but I don't think it's really necessary to define hunderets of single routes?!

I also tried just to define all the categories fix in router, like

Router::connect(
  '/any-category-name',
  array('controller' => 'categories', 'action' => 'view', 1)
);

and then for the articles

Router::connect(
  '/any-category-name/:slug',
  array('controller' => 'articles', 'action' => 'view'),
  array('pass' => array('slug'))
);

But with this method, all articles are available in all categories, which isn't a good solution. And I thought about using

Router::connect(
  '/:slug', ...

but I don't know how to go on, because there are two different controllers and two different views possible (also I don't know if Pagination will still work in this case and what will happen, if I also want to use more controllers/actions in the installation).

I think it shouldn't be so difficult to get nested urls with two controllers (categories and articles) in Cake?! Thanks for any helpful advise!


Solution

  • I think you have to check for two things:

    1) Check the number of categories in the url and
    2) Check if the last parameter is a category or an article

    Handle both checks within a (dynamic) route may be very difficult. I would suggest to create just one route for all these requests and do the checks for 1) and 2) in a controller.

    The route may be something like this:

    Router::connect(
      '/*',
      array('controller' => 'outputs', 'action' => 'index')
    );
    

    I called the controller for this route OutputController because this will be the controller that handles the output for all these urls.

    class OutputController extends AppController
    {
      public $uses = array('Article', 'Category');
    
      public function index()
      {
        // Get n parameters from url (1)
        $args = func_get_args();
        $last_arg = $args[count($args) - 1];
    
        // Check if this is an article (2)
        $article = $this->Article->find('first', array(
          'conditions' => array('Article.slug' => $last_arg
        ));
        if (!empty($article)) {
          $this->set('article', $article);
          $this->render('article');
        }
    
        // Check if this is an category (2)
        $category = $this->Category->find('first', array(
          'conditions' => array('Category.slug' => $last_arg
        ));
        if (!empty($category)) {
          $this->set('category', $category);
          $this->render('category');
        }
    
        // Page not found
        if (empty($article) and empty($category)) {
          throw new NotFoundException();
        }
      }
    
      // ...
    

    To display an article, the view 'Output/article.ctp' is used. For a category, CakePHP renders 'Output/category.ctp'. In addition you can use the parameters in $args to fetch all the necessary data for your (sub-) categories.