Sam Heuck

View templates in PHP

PHP

When first learning PHP, it is quite easy to shoot yourself in the foot. PHP is forgiving and you can get away with writing some seriously crappy code. Consequently, PHP programmers often follow a similar path as they develop their skills. I started off writing horrendous code that was utterly unmaintainable. Of course I ended up rewriting all of it. Twice. And through that process, one which all programmers are familiar with, I learned some fundamentals of design and architecture. "Separation of Concerns", and "DRY" or "Don't Repeat Yourself" are terms you've no doubt heard, but they are a little abstract. I hope to shed some light on these concepts.

A templating system is one way to significantly improve both separation of concerns and DRY. View templates separate logic and data from design and layout, and provide a set of common functions for creating views. This post will introduce and explore templating techniques. The code in this post is mostly an exercise, because normally, you will be using external libraries and frameworks to do these same things. But writing your own system is a great way to level up your skills, not to mention it will teach you to recognize these techniques in existing systems, which will allow you to debug and troubleshoot more effectively.

We'll take a look at two common yet diametrical approaches to templating: search & replace templates, and include templates - as well as some other nifty tricks.

Think about what goes into a simple page of HTML. First there is a standard structure that never changes - Doctype followed by head with some meta tags, a title, stylesheets and maybe some javascript. Then comes the body tag, and some content. Some elements of this basic structure are the same on every page, and other elements vary. This is the perfect situation to use a template. The template will contain those elements that are static, and then PHP will inject the dynamic elements into the template. This is DRY because we don't have to write the same bits of HTML for every single page in a site. It also separates concerns by allowing content to be completely separate from layout.

Let's start with the code that will actually create and display a page of HTML.

<?php
  $page = new HtmlPage();
  $page
    ->setTitle('Home')
    ->setMeta('keywords', 'some slightly-less-than-random words')
    ->addStylesheet('base.css')
    ->addStylesheet('my_beautiful_styles.css')
    ->addScript('jquery.js')
    ->addScript('awesome_blinky_text.js')
    ->setBodyId('home')
    ->setBodyClass('blue')
    ->addContent($some_content)
    ->addContent($more_content)
    ->render('layout.phtml');
?>

Did you notice this code is...

  • Object oriented
  • Configurable (more on this later)
  • Stringing function calls together (fluent interface)
  • Declarative (stating what to do, not how)

This PHP writes HTML code. Make note of the HTML elements that are being injected: CSS, javascript, titles, ids and content. These are the dynamic elements - different on each page. These dynamic elements could really come from any source, a text file on the local file system, a database, or even a RESTful API.

Once all of the dynamic elements are declared, the page is rendered. Let's take a closer look at the HtmlPage::render() function. It takes a single argument that references a file containing the HTML template for a basic page.

//layout.phtml
<?php echo $this->doctype; ?>;
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <?php echo $this->title; ?>
    <?php echo $this->meta; ?>

    <!-- STYLES -->
    <?php echo $this->stylesheets; ?>

    <!-- SCRIPTS -->
    <?php echo $this->scripts; ?>
  </head>

  <?php echo $this->bodyTag; ?>
  <?php echo $this->content; ?>
</body>
</html>

This HTML template uses PHP to print out the dynamic elements declared by the HtmlPage class. $this refers to a specific instance of HtmlPage. So the HtmlPage class is including the template file, and the template file refers to instance variables of the HtmlPage class. Here's how that works.

<?php
  ...

  /**
    * The render function.
    *
    * Responsible for displaying the page.
    *
    * @param String
    *   - path to template
    */
  public function render($template) {
    ob_start();
    $this->_include($template);
    return print ob_get_clean();
  }


  /**
    * Include wrapper.
    *
    * Using the include keyword, a
    * template is included.
    */
  private function _include() {
    include func_get_arg(0);
  }
?>

HtmlPage::render() creates an output buffer to capture the template content being included. Then the include wrapper function is called with a reference to a template file. This reference is just a string that represents the file path to the template. The call to func_get_arg(0) limits the available scope of the template to the object referenced by $this. This is the heart of the include template technique.

The major advantage to this technique is that there is now one place which defines layout and static elements. The template can be changed or updated, and all other instances of HtmlPage will in turn reflect that change.

So how do the methods generate HTML?

public function __get($key) {
  // Tries to find and call a getter.
  $method = 'get' . ucfirst($key);
  return method_exists($this, $method) ? $this->$method() : null;
}

The function __get() is a magic function that gets called whenever a class variable that has not been defined is referenced. In other words, I can refer to $this->foo in a template, and that instance of HtmlPage will call the __get(foo) function because the foo variable was not explicitly defined in the HtmlPage class.

The implementation of __get() can be customized, as is the case here, to do anything we need. In this example, I use the passed in string to call another function which returns HTML.

// A 'getter' method, called by __get
public function getBodyTag()
{
  return sprintf("<body%s%s>", $this->_bodyId, $this->_bodyClass);
}

In the template, <?php echo $this->bodyTag; ?> is really calling the getter function HtmlPage::getBodyTag() via the magical __get() function. A similar getter function exists for each setter function. HtmlPage::addScript() for instance, allows us to add as many scripts as we like. Each string passed in from HtmlPage::addScript() gets stored in an array, and the getter method iterates through the array, adds the appropriate javascript element tags, and sets the source to the value of the string passed in by addScript(). Each getter will have its own unique implementation depending on the output that is needed. HtmlPage contains the setters and getters necessary for creating HTML pages, but it could easily be extended to add new functions.

Including pieces of content calls for a slightly different templating technique. Individual pieces of content often contain a few dynamic elements - the name of a logged in user for example. Search and replace templating is a technique you can use to inject variables into a piece of content. The addContent() function uses this technique. Its argument is actually an object defined by this class.

<?php
class Partial
{
  private $_html;
  private $_items = array();

  public function __construct($file)
  {
    if (!$this->_html = file_get_contents($file, FILE_USE_INCLUDE_PATH)) {
      throw new Exception("Error Reading File: $file");
    }
  }

  public function set($item, $value)
  {
    $this->_items[$item] = $value;
    return $this;
  }

  public function output()
  {
    if (count($this->_items) > 0 ) {
      foreach ($this->_items as $item => $value) {
          $replace = "[%$item]";
          $this->_html = str_replace($replace, $value, $this->_html);
      }
    }
    return preg_replace('/\[%.+\]/', '', $this->_html);
  }
}
?>

A partial is a small piece of HTML that acts as mini-template. Dynamic elements within the partial have a special syntax that distinguish them from regular HTML. When Partial::output() is called, any named placeholders (the dynamic elements) found in the partial are replaced with values stored and indexed under their corresponding names. We use an associative array for this, and to populate that array, we use the Partial::set() function to pass in an index and a value. The Partial::output() function iterates through this array, replacing any placeholder in the "mini-template" with the value of the corresponding index in the associative array. Any placeholder that doesn't have a value set, gets replaced with an empty string by the reg-ex in the return statement.

Long story short, little chunks of HTML can be appended to our HtmlPage's content variable. If we want to include some dynamic elements in that content, we can use named placeholders. An example is in order.

//index.php
...

$content = new Partial('some_really_cool_content.html');
$content->set('foo', 'bar');

$page->addContent($content);

And here is what "some_really_cool_content.html" might look like:

<h1>This content is SWEEEEEEEEEET!</h1>
<span>[%foo]</span>

[%foo] is the named placeholder and will be replaced by our amazing content: "bar". How very exciting. The data that replaces the placeholders could come from a database somewhere, an RSS feed, really anything. Since we use PHP to add the content and set the dynamic variables, we have the full flexibility of PHP at our disposal. We could even use the output of other partials to replace placeholders in a partial. Of course you don't have to use any placeholders at all; partials can be used to simply include snippets of pure HTML.

When we use <?php echo $this->content ?>, HtmlPage::getContent() will iterate through all partials which have been added, and call each of their output functions in turn - printing out all of the dynamically generated code. The content will appear in FIFO order (first in, first out) - in other words, in the same order each partial was added.

Caveats of search and replace

  • Escaping unsafe strings is slightly trickier, especially with compound partials
  • It's expensive - lots of parsing overhead, reg-ex
  • Performance concerns can be alleviated through cacheing, but this adds complexity
  • Not as well suited for highly dynamic web applications with lots of users

Nifty tricks...

How does HtmlPage::addScript('script.js') know where to find the script.js file?

HtmlPage is configurable.

<?php
...

public function __construct()
{
    $path = realpath(dirname(__FILE__)) . "/HtmlPage.ini";
    if (file_exists($path)) {
        $config = parse_ini_file($path);
        foreach($config as $key => $value) {
            $this->_config[$key] = $value;
        }
    }

    $this->_doctype = empty($this->_config['doctype.default'])
        ? 'XHTML_1_0_STRICT' : $this->_config['doctype.default'];
}

If HtmlPage finds a file called HtmlPage.ini in the same directory in which it resides, it will parse that file and save any configuration settings in an array. For instance, if a doctype has been specified in the config file, it will use it, otherwise, it will use XHTML 1.0 STRICT.

The config file can be used to set base paths to scripts, stylesheets, templates, etc.. It can also specify a default template, which allows you to call HtmlPage::render() without any arguments at all. It will get the template file from the config file. Of course, any argument passed in will override the config file, so switching templates on the fly is still a cinch. The config file is handy for setting a base title and separator for pages too. If you end up extending HtmlPage, you can take advantage of the config file to set sensible defaults for any new functionality. Here's what my config file looks like.

//HtmlPage.ini
[base_paths]
javascript.basepath  = "/public/js/"
css.basepath         = "/public/css/"
templates.directory  = "templates/"

[defaults]
template.default = "layout.phtml"
doctype.default  = "XHTML_1_0_STRICT"
title.prefix     = "Test"
title.separator  = " | "

The fluent interface...
Fluent interfaces are very easy to implement in PHP. All you have to do is return $this; in the function. Since $this is a reference to an object, you can call another function, which also returns a reference to an object, and so on.

Prevent vital class variables from being overridden
Magic functions in PHP are quite useful. Since we have defined several class variables, such as $content, that, if overwritten could potentially erase content from the HTML page, we need a way to reserve these variables so that client code cannot overwrite them. We want HtmlPage to be closed to modification, but open to extension. __set() provides the functionality needed.

//HtmlPage.php
<?php
...

public final function __set($property, $value)
{
    if (method_exists($this, 'get' . ucfirst($property))
        || array_key_exists($property, get_class_vars(get_class($this)))
    ) {
        throw new Exception('Attempted to set a reserved variable');
    }
    $this->$property = $value;
    return $this;
}

Basically, any time a client assigns a class property, __set() checks to see if there is a getter method, or a class variable already defined for that property. If there is, __set() throws an exception. So the only way to add new class properties to HtmlPage is to extend it.

Conclusion

The code outlined here is simply to illustrate some basic techniques for templating. This code is fully functional, and can be used to create web pages, but it's not really meant to be used as a foundational framework. I merely hope that it helps you realize the power of separating those parts of an application that are static into templates, and injecting the dynamic elements into those templates. These techniques lead to more flexible, and maintainable code. Poke around some of the major PHP frameworks like Zend, and you will see some of these basic techniques in action.

Happy coding!