Filed under: drupal

Using Lithium in Drupal 7

Let me start off by saying that the Lithium framework is awesome.  It allows the programmer to write very elegant code, something that cannot be said about many of the other PHP frameworks out there.  Also, it's very loosely-coupled, and many of it's components can be used separately and integrated with other applications.

Lithium prides iteself upon being easy to integrate into other systems.  Let's put that to the test in Drupal 7.  I am planning on a "full blown" Lithium module for Drupal that will allow for other modules to easily hook into the bootstrap process.  The code below is meant to show how easy it is to use Lithium within a Drupal 7 application.

First, download the lithium core library and place it in /sites/all/libraries.

Then, create a custom module and include the following lines at the top of it:

define('LITHIUM_LIBRARY_PATH', './sites/all/libraries');
require_once(LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php');

Next, implement hook_init() and bootstrap the Lithium core library and your custom module library:

function mymodule_init() {
   \lithium\core\Libraries::add('lithium');
   \lithium\core\Libraries::add('mymodule', array('path' => __DIR__));
   // Now, you can add a database connection or any other bootstrap code. 
}

That's it!  You can now use classes from the Lithium core library and your own custom library in your custom modules and themes.  Here's a simple example:

function mymodule_node_view($node, $view_mode, $langcode) {
   echo \lithium\util\String::insert('Now viewing node {:nid}', array('nid' => $node->nid)); 
   // If you setup a connection and created a model at /sites/all/modules/mymodule/models/Foo.php, you can do this:
   $foo = \mymodule\models\Foo::findById(1);          
}

Nodes and Terms in Drupal 6

I often find myself writing custom chunks of code in Drupal to build taxonomy term arguments consisting of terms shared by the current node.  This is useful in generating blocks of content based on shared tags.  I finally decided to expand that code into a pretty useful function.  The function accepts node(s), term id(s), and some configuration options and returns either an array of term objects or a concatenated string of term ids.  I think it's pretty flexible for such a short amount of code, comments aside.  Maybe someone else will find it useful!

/**
* Gets terms for a node filtered by certain vocabularies.
*
* Example(s):
* 
* Get an array of terms from node 1 from vocabularies 2 and 3:
* mymodule_terms(1,array(2,3));
*
* Get terms common to nodes 1, 2, and 3 from vocabulary 7
* in a string separated by a '+':
* mymodule_terms(array(1,2,3), 7, array('intersection'=>true, 'implode'=>'+'));
*
* @param mixed $node An array of nids or node objects or a single nid or node objects
* @param mixed $vid An array of vids or single vid by which to filter, false to ignore 
* @param mixed $config Additional configuration parameters including implode and intersection
* @return mixed A hash of $tid => $term pairs or a string of tids separated by the contents of $config[implode]
*/
function mymodule_terms($node, $vid = false, $config = array()) {
    
    $defaults = array('implode' => false, 'intersection' => false);
    $config += $defaults;
    
    $tids = array();
    $count = array();
    
    /**
    * Convert single nodes or vids into arrays
    */
    if(!is_array($node)) $node = array($node);
    if(!is_array($vid) && $vid != false) $vid = array($vid);
    
    foreach($node as $n) {

        /**
        * Load the node if only a nid is provided
        */
        if(!is_object($n)) $n = node_load($n);
        
        /**
        * Iterate over all terms, adding any that match the specified vids
        * to the result set
        */
        foreach($n->taxonomy as $tid => $term) {
            if(!$vid || in_array($term->vid, $vid)) {
                $tids[$tid] = $term;
                $count[$tid] = (isset($count[$tid])) ? $count[$tid] + 1 : 1;
            }
        }
    }
    
    /**
    * If we only want tids that all passed nodes share, strip out
    * any that aren't present for each node
    */
    if(count($node) > 1 && $config['intersection']) {
        foreach($tids as $tid=>$term) {
            if($count[$tid] != count($node)) unset($tids[$tid]);
        }
    }
    
    /**
    * Return either the array or a string of tids.
    */
    return ($config['implode'] !== false) ? implode($config['implode'], array_keys($tids)) : $tids;
}

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);
});


?>