Tuesday, September 24, 2013

Bringing PHP Templates to MojoMotor

Recently, one of my clients added a site that leverages MojoMotor (MM) to the list of assets that I now help maintain and improve. MM has an almost Perch like philosophy about being a CMS: it's all about being as lightweight as possible. And to the credit folks behind MM, they have managed to put together a lean system.

MM uses a sort of tag based markup language to power its templates. While handy for simple tasks, I vastly prefer using PHP as a template language, rather than something ad hoc. Luckily, MojoMotor is easily extendable and in about 40 lines of code, I had a full PHP based template engine running within MojoMotor.

Now, a MM layout can contain code like:

 <html>
   <head>
     {mojo:snippet:apply path="assets/js" src="foo.js"}
     ...

Which invokes snippets/assets/js.php and sets $src to the value foo.js. Within that file, I may have:

<?
  /*
   * Render a javascript include
   */
  $xcache = calculate_our_cache_busting_value();
  $should_mini = should_mini_files();
  $src = $should_mini ? "/assets/mini/$src" : "/assets/full/$src";
?>
<script src="<?= $src ?>?xcache=<?=$xcache?>"
           type="text/javascript">

Using this tiny function, it's possible to invoke one snippet (PHP template) from within another, making for easy abstractions.

To implement this in MM, I created the following directory structure:

system/mojomotor/third_party/snippet/index.html   (an empty file)
system/mojomotor/third_party/snippet/libraries/
system/mojomotor/third_party/snippet/libraries/snippet.php

Inside of snippet.php is this hunk of PHP code:

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/* CHANGE ME: At the top level of the source tree.  */
define('SNIPPET_BASE_DIR', dirname(__FILE__) . '/../../../../../snippets');

/**
 * Snippets are small chunks of HTML code that are
 * imported and evaluated on the fly.
 */
class Snippet {

  public function apply($template_data) {
    $_path = $template_data['parameters']['path'];
    unset($template_data['parameters']['path']);

    if(!$_path) {
      return "Snippet error: no path param";
    }
    if(!file_exists(SNIPPET_BASE_DIR . "/$_path.php")) {
      return "Snippet error: not found: $_path";
    }

    $body = $template_data['tag_contents'];  
    extract($template_data['parameters']);

    ob_start();
    require(SNIPPET_BASE_DIR . "/$_path.php");
    $content = ob_get_clean();

    return $content;
  }
}
?>

I'm sure there are optimizations to made (add caching of snippets, perhaps?), but as a quick and easy way to add a lot of power (and abstraction) to your templates, this approach is hard to beat.

No comments:

Post a Comment