Richard Parnaby-King

Web Developer – PHP, Zend Framework and Actionscript 3

Posted on | | 3 Comments

Routing is the action of taking the requested url and pointing the application to the correct module, controller, and action so that the request can be correctly processed. There are a number of ways to implement this using the Zend Framework, the most common of which is to create a routing object in the application bootstrap file. An alternative, which this blog post explores, is to store all possible urls in your database, with the corresponding default route stored alongside it.

The default route in a Zend Framework application is structured ‘module/controller/action/parameters’, such that visiting the url example.com/products/index/view/id/99 would internally redirecte to the module named products, the index controller, running the view action, passing in the parameter id with a value of 99. As you can see this is hardly a user-friendly url, which is why many developers create alternate routes for their applications – known as ‘pretty-urls’. For example, any url beginning with ‘product’ should be redirected to the products module, with the rest of the url passed in as a parameter – e.g. example.com/products/my-fancy-socks-with-pockets.html. Below is an example of how this can be implemented in the bootstrap file:


<?php
/*
 /application/modules/products/bootstrap.php
*/
public function _initRoutes() {
  $this->bootstrap('frontController');

  /* @var $frontcontroller Zend_Controller_Front */
  $frontcontroller = $this->getResource('frontController');

  $router = $frontcontroller->getRouter();

  //add route
  $router = $frontcontroller->getRouter();
  $router->addRoute(
    'product', new Zend_Controller_Router_Route_Regex('products/([a-zA-Z0-9-]+).html',
      array(
        'module' => 'products',
        'controller' => 'index',
        'action' => 'product',
      ),
      array('product' => 1),
      'products/%s.html'
    )
  );
}

In the index controller of our products module we would then load the product based on the url.

Instead of adding all possible routes on a per module basis (e.g. blog, categories, products, pages, admin area, forum, etc) we instead use a plugin that maps the request to the default route. The benefits of this approach are:

  • We can remove the prefix in the url (e.g. get rid of that annoying /products/) allowing our urls to be even nicer-looking and remember-able for humans.
  • By centralising the routing operations we do not need to duplicate the code on a per-module basis, i.e. each module does not need to look in a different table to locate during the dispatch process to determine if the page/product/whatever exists before deciding to return a 404 or not
  • It is possible to have multiple URLs pointing to the same resource (useful if, for example, you have a blog post in multiple categories)

The Code

<?php
/**
 * Routing plugin
 *
 * Checks the database for slug and converts it into module/controller/action/param structure.
 * If no route found, return 404
 *
 * LICENSE: Some license information
 *
 * @copyright    2013 Richard Parnaby-King
 * @since        File available 0.0.1
 */

/**
 * Checks the database for slug and converts it into module/controller/action/param structure.
 * If no route found, return 404
 * @copyright    2013 Richard Parnaby-King
 * @since        File available 0.0.1
 */
  class RPK_Controller_Plugin_Routing extends Zend_Controller_Plugin_Abstract
  {
    /**
     * Get requested url, check the db for a route that matches, and set the new route.
     * @param Zend_Controller_Request_Abstract
     * @return Zend_Controller_Request_Abstract
     */
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
      //get requested route, check db, return new route.
      $routes = new Default_Model_Routes();
      $route = $routes->getRedirect($request->getRequestUri());
      
      //no route found - set to return 404
      if(count($route) === 0)
      {
        throw new Zend_Controller_Action_Exception('This page does not exist', 404);
      }
      
      //set new route
      $request->setRequestUri($route->redirect);
      $request->setDispatched(false);
      
      //Not required. Do a return anyway
      return $request;
    }
  }
<?php
/**
 * Routes table
 *
 * @copyright    2013 Richard Parnaby-King
 * @since        File available 0.0.1
 */
  class Default_Model_Routes extends Zend_Db_Table_Abstract
  {
    protected $_name = 'routes';
    
    /**
     * Return the module/controller/action/params for the current slug
     * @param String $slug
     * @return Zend_Db_Table_Row_Abstract OR Boolean(FALSE) if not record found
     */
    public function getRedirect($slug)
    {
      return $this->fetchRow(
                $this->select()
                     ->where('slug = ?', $slug)
             );
    }
  }

The columns in the routes table are ‘id’, ‘slug’, and ‘redirect’. The slug column holds the pretty url (e.g. some-category/somepage.html) while the redirect column holds the application path, including parameters (e.g. products/index/view/id/314)

There are a few hooks that registered plugins can latch onto during various stages of the application execution process (in order):

  • routeStartup
  • routeShutdown
  • dispatchLoopStartup
  • preDispatch
  • postDispatch
  • dispatchLoopShutdown

More information about each can be found in the documentation on the official Zend Framework website.

This plugin hooks onto the routeStartup call, passing the request url (e.g. some-category/somepage.html) into a table object which checks if the route exists. If a route has not been found, then we call another plugin – Zend_Controller_Plugin_ErrorHandler – telling it we have found a 404. This error handler routes to /default/error/error (default module, error controller, error action) which sets the 404 header and displays an unpleasant not found message – I recommend you edit the view script to something a bit more pleasing :) If a route is found, then have the request point to the default-route-structured-redirect and continue the dispatch process. The application will then load the correct module/controller/action with the correct parameters.

Posted By:

Comments

  • ABOUT

    Having three years of programming experience in PHP, Zend Framework and ActionScript3, I have a very strong working knowledge of object orientated programming.

    I am a PC Gamer! Playing FPS, RTS, RPG and the occasional MMO since 1996 I have a huge number of a variety of fast-paced games.

  • Recent Posts

  • Categories

  • RSS SUBSCRIBE TO OUR FEED

  •