« Back to blog

Drupal-style Path Aliases in Lithium

In Drupal, node urls are formatted as node/nid where nid is the unique numeric id of the node. The path module provides the very useful ability of creating custom aliases for system paths (e.g. my-custom-page maps to node/7).  Other modules extend this this functionality to automatically generate clean aliases based on node content.

In one of my late night Lithium R&D sessions, I decided to see how similar functionality could be developed using Lithium, one of the first frameworks to take advantage of PHP 5.3.  I decided to package the functionality into a library so it can easily be added to my other Lithium apps.  However, you can place this code directly within your app.

The first step is to create a paths collection in MongoDB.  If you're using an SQL database, create a paths table with system and alias varchar fields.

The Path model provides a method to set or delete an alias.  It requires the system path (i.e. controller/action/args) and has optional parameters for alias and delete.  If the alias is empty or the delete flag is present, any existing alias for the system path is deleted.  If the alias is not empty, a new Path containing the system path and alias is created.

The Path model also provides a method to resolve an alias to its corresponding system path.  If a path exists for the given alias, it's corresponding system path is returned.  If no path is found, the alias is returned.

li3_paths/models/Path.php:

<?php

namespace li3_paths\models;

/**
 * `Path` is a model that allows system paths to be aliased and accessed by
 * "clean" urls.  A path consists of an alias and its corresponding system path.
 */
class Path extends \lithium\data\Model {
    
    /**
     * Resolves a url to it's corresponding system path
     *
     * @param string $alias The path to be resolved (e.g. my-custom-url)
     * @return string The system path if one was found, the original path if not
     */
    public static function resolve($alias) {
        return ($path = static::findByAlias($alias)) ? $path->system : $alias;
    }
    
    /**
     * Removes and/or creates a new path alias
     *
     * @param string $system The system path to be (un)aliased (e.g. posts/view/1)
     * @param string $alias The clean url to be used to access the system url
     * @param boolean $delete True to remove any existing aliases to the given system path
     * @return boolean The result of the operation
     */
    public static function alias($system, $alias = '', $delete = true) {
        
        $system = trim($system);
        $alias = trim($alias);
        
        if($delete || empty($alias)) {
            $path = static::findBySystem($system);
            if($path) {
                static::delete($path);
            }
        }

        if(!empty($alias)) {
            $path = static::create(compact('alias', 'system'));
            return static::save($path);
        } else {
            return true;
        }
    }
}

?>

The model is complete.  We now have enough functionality to get and set path aliases and determine system paths from aliases.

Now, it's time for some Lithium magic!  Without hacking the core, we can filter the Dispatcher::run() method and to resolve the custom alias before the url is processed...

In li3_paths/config/bootstrap.php:

<?php


use \lithium\action\Dispatcher;
use \li3_paths\models\Path;


Dispatcher::applyFilter('run', function($self, $params, $chain) {
    $params['request']->url = Path::resolve($params['request']->url);
    return $chain->next($self, $params, $chain);
});


?>