Reinventing the wheel

This code was developed due to specific constraints I wanted in a template engine, and the desire to use it in projects that unfortunately still require PHP4. It is currently pre-release code, but should work in PHP4.2 and up.

The first specification of the project was to ensure valid XHTML. I haven't yet set a DTD (for those who need full on validation), but the templates should be editable in WYSIWYG editors such as Dreamweaver without causing issues.

<!-- example template structure -->
<html> 
<head>
  <title>{$title}</title>
</head>
<body>
<h1>{$title}</h1>

<tpl:optional src="description">
  <p>Text that will only show up if "description"
    is set</p>
</tpl:optional>
<tpl:default src "description">
  <p>Default text that shows up if "description"
    is not set</p>
</tpl:default>

<div class="{$some_class}">
  <tpl:list src="list_data">
    <p>
      <tpl:item>
         - this is row # {$num} from "list_data" 
      </tpl:item>
    </p>
  <tpl:default />
    <p>No list data found!</p>
  </tpl:list>
</div>

<tpl:include src="footer.php" />

You can see the template tags are valid XHTML, and allow for basic conditionals, variable substitutions, iteration of data, and inclusion of other files.

The usage of the engine is as follows:

  $obj =& new Template();
  $obj->load('mytemplate.php');
  $obj->attach($data);
  $obj->display();

As you can see, the usage is pretty simple. There is only one real caveat, in that $data should be an array of key/value pairs. Value can be an associative array of Record objects (a base implementation is included in utility.php).

Record objects allow for overloading to take place, and provide an easy environment to decorate data coming out of a database.

  $query = "SELECT id, name FROM users WHERE id = 1";
  $result = $db->get_array_of_data($query);
  $user =& new UserDecorator($result);
  
  class UserDecorator extends Record {

    function UserDecorator($data) {
        parent::Record($data);
    }

    function get_email() {
    //  we can extend the User dataset here
        return "noemail@nodomain.com";
    }

  }

Our template engine can now ask the user for it's email address using the Record's _get('email') method at runtime. If we implemented a method in our decorator that required another database query, we can sleep well knowing that query will only be called if the template actually requests the data, instead of pushing all data to the template, knowing some of it will not be utilized.

Some caveats:

  • The template language has no validation at the moment, so tags have to be pretty carefully placed. I'd like to add some simple validation in the real release.
  • There is no support for recursive includes at the moment. It isn't a feature that I've needed very often if at all. I may include this at some point.
  • There is a terrible, messy hack in it, see if you can find it! This should be fixed shortly (hint: has to do with the creation of directories for cached files)
  • There is no pagination support, but that's definitely something that will be added.