Just a Few Lines

That's all it takes, usually...

Web Application Elements: Templates

September 30th, 2009 | ,

One of the things PHP does really well is templates. Actually, every PHP file is a template. This is rather unique feature, considering other platforms, like Java, .NET or Ruby. With PHP as a platform, the language itself is usually good enough for a template solution.

There is, however, one feature (provided by some template engines), that makes managing complex sites easier.This feature is usually called “layouts”. By layout I mean something like master pages for ASP, Sitemesh for Java, or Django templates for Python.

This article presents a simple way to achieve similar functionality in PHP.

Features

The functionality we’re looking for:

  • layout support, or template inheritance (to use Django terminology)
  • easy way to define variables containing HTML
  • default values for variables

And all this in less than 50 lines of code!

Implementation overview

Everything is wrapped in just one, easy to use class:

  1. Create new Template instance
  2. set name of the template to execute
  3. execute() and print the output

Full source code with examples is available for download, so I won’t paste it here. I’ll just explain some implementation details you may find interesting.

The execute() method includes the template, then checks if it defined a parent template. If so, the parent is included. If that parent also defined a parent, it is included as well, etc.

The only tricky part here is that the output from files included in execute() is buffered and stored in a special variable (called $body). If given template defines a parent template, that parent gets access to all variables available to the child, plus the output generated by the child – the $body.

The following piece of code explains the idea:

// execute the main template (probably in a controller if it's MVC)
echo Template::make('template1')->execute();

// which uses...
// template1.php
<?
	$this->extend('layout_main');
	$this->var_set($var1, 'template1 says hi');

// Everything returned below will be available in the
// extended template (layout_main) via $body
?>
This is cool!

// which uses...
// layout_main.php
<?
	$this->var_use($var1, 'default value for var1');
?>
<html>
	<body>
	<h1><?= $var1 ?></h1> // template1 says hi
	<?= $body ?> // This is cool!
	</body>
</html>

echo at the top prints

<html>
	<body>
	<h1>template1 says hi</h1>
	This is cool!
	</body>
</html>

Method chaining

Many Template methods return $this, so we can say:

echo Template::make()
	->name('template_x')
	->extend('layout_y')		// default layout
	->execute();

Very simple way to shorten the code.

Variable setting

There are two ways of setting variables for templates. First one is very simple:

// declaration
$tpl->var_set($greeting, 'Good morning');

// usage
echo "$greeting, stranger!";

var_set() method exploits one of the weird features of PHP, explained by the following example:

function f1($p) {}
f1($x); // Notice: Undefined variable: x

function f2(&$p) {}
f2($y); // Works

// Next two lines are equivalent to the line 5
$y = null;
f2($y);

The other way to define variables is a bit more involved:

// declaration
<? $this->var_begin($menu) ?>
	<ul>
		<li>uno</li>
		<li>dos</li>
	</ul>
<? $this->var_end() ?>

// usage
echo "The Spanish menu: $menu";

The trick here is to store the reference to the variable passed by reference. This allows var_end() method to assign a value to the variable passed to var_begin(). For those of you with C++ background, it works like Type*&amp; parameters in function parameters.

Of course, output buffering is used to grab everything between var_begin() and var_end().

Complex defaults

The function var_print() is a little helper that lets us to do this:

// some layout's markup...

// if the extending template didn't define custom menu,
// show the default one
<? if (!$this->var_print($menu)): ?>
	Default menu:
	<ul>
		<li>one</li>
		<li>two</li>
	</ul>
<? endif; ?>

// more markup...

This code simply prints the $menu variable if it’s defined. Otherwise it prints the default markup.

Wrapping up

Solution presented in this article is rather basic. It could be extended in many ways (with support for nested variables for example), but even this simple implementation provides enough functionality to build complex layouts.

Source code includes the Template class and usage examples.

So, that’s it for the first part. In the second installment of the series we’ll create the smallest usable dependency injection container :)

P.S. The main idea for this blog is to keep it simple. So, even if presented code may not always be very straight forward (although it should be), at least I’ll try to keep it short. This way it will be physically impossible to create anything too complicated :)



No comments yet