HTML CSS JavaScript jQuery PHP Python MySQL

Lightweight Template Engine with PHP

Posted on by David Adams

Lightweight Template Engine with PHP

The template engine class is light, flexible, fast, and secure, it compiles templates to optimized PHP code.

Below I will provide you with the full source code and examples on how you can use the template engine class.

Why do I need a Template Engine?

The template engine keeps your design code away from your application code, this reason alone is good practice and follows many design patterns. Using a template engine is entirely up to you, if you prefer to keep your code clean and tidy then using a template engine is ideal.

If you're working with the MVC pattern then it is a good idea to use a template engine.

Source

Create a new file and name it Template.php and add:

PHP Copy
<?php
class Template {

	static $blocks = array();
	static $cache_path = 'cache/';
	static $cache_enabled = FALSE;

	static function view($file, $data = array()) {
		$cached_file = self::cache($file);
	    extract($data, EXTR_SKIP);
	   	require $cached_file;
	}

	static function cache($file) {
		if (!file_exists(self::$cache_path)) {
		  	mkdir(self::$cache_path, 0744);
		}
	    $cached_file = self::$cache_path . str_replace(array('/', '.html'), array('_', ''), $file . '.php');
	    if (!self::$cache_enabled || !file_exists($cached_file) || filemtime($cached_file) < filemtime($file)) {
			$code = self::includeFiles($file);
			$code = self::compileCode($code);
	        file_put_contents($cached_file, '<?php class_exists(\'' . __CLASS__ . '\') or exit; ?>' . PHP_EOL . $code);
	    }
		return $cached_file;
	}

	static function clearCache() {
		foreach(glob(self::$cache_path . '*') as $file) {
			unlink($file);
		}
	}

	static function compileCode($code) {
		$code = self::compileBlock($code);
		$code = self::compileYield($code);
		$code = self::compileEchos($code);
		$code = self::compileEscapedEchos($code);
		$code = self::compilePHP($code);
		return $code;
	}

	static function includeFiles($file) {
		$code = file_get_contents($file);
		preg_match_all('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', $code, $matches, PREG_SET_ORDER);
		foreach ($matches as $value) {
			$code = str_replace($value[0], self::includeFiles($value[2]), $code);
		}
		$code = preg_replace('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', '', $code);
		return $code;
	}

	static function compilePHP($code) {
		return preg_replace('~\{%\s*(.+?)\s*\%}~is', '<?php $1 ?>', $code);
	}

	static function compileEchos($code) {
		return preg_replace('~\{{\s*(.+?)\s*\}}~is', '<?php echo $1 ?>', $code);
	}

	static function compileEscapedEchos($code) {
		return preg_replace('~\{{{\s*(.+?)\s*\}}}~is', '<?php echo htmlentities($1, ENT_QUOTES, \'UTF-8\') ?>', $code);
	}

	static function compileBlock($code) {
		preg_match_all('/{% ?block ?(.*?) ?%}(.*?){% ?endblock ?%}/is', $code, $matches, PREG_SET_ORDER);
		foreach ($matches as $value) {
			if (!array_key_exists($value[1], self::$blocks)) self::$blocks[$value[1]] = '';
			if (strpos($value[2], '@parent') === false) {
				self::$blocks[$value[1]] = $value[2];
			} else {
				self::$blocks[$value[1]] = str_replace('@parent', self::$blocks[$value[1]], $value[2]);
			}
			$code = str_replace($value[0], '', $code);
		}
		return $code;
	}

	static function compileYield($code) {
		foreach(self::$blocks as $block => $value) {
			$code = preg_replace('/{% ?yield ?' . $block . ' ?%}/', $value, $code);
		}
		$code = preg_replace('/{% ?yield ?(.*?) ?%}/i', '', $code);
		return $code;
	}

}
?>

Remember to update the $cache_enabled and $cache_path variables, the caching is currently disabled for development purposes, you can enable this when your code is production ready.

How To Use

Create a new file and name it index.php and add:

PHP Copy
<?php
include 'Template.php';
Template::view('index.html');
?>

Create a new HTML file and name it layout.html and add:

HTML Copy
<!DOCTYPE html>
<html>
	<head>
		<title>{% yield title %}</title>
        <meta charset="utf-8">
	</head>
	<body>
    {% yield content %}
    </body>
</html>

This is the layout we will use for this example.

Now create the index.html file and add:

HTML Copy
{% extends layout.html %}

{% block title %}Home Page{% endblock %}

{% block content %}
<h1>Home</h1>
<p>Welcome to the home page!</p>
{% endblock %}

Now give it a try, navigate to the index.php file and you should see the output, pretty awesome right?

But what if we want to use variables in our template files? Easy, change the template code in the index.php file to:

PHP Copy
Template::view('about.html', [
    'title' => 'Home Page',
    'colors' => ['red','blue','green']
]);

And then we can use it as so:

HTML Copy
{% extends layout.html %}

{% block title %}{{ $title }}{% endblock %}

{% block content %}
<h1>Home</h1>
<p>Welcome to the home page, list of colors:</p>
<ul>
    {% foreach($colors as $color): %}
    <li>{{ $color }}</li>
    {% endforeach; %}
</ul>
{% endblock %}

What if we want to secure our output? Instead of:

{{ $output }}

Do:

{{{ $output }}}

This will escape the output using the htmlspecialchars function.

Extend blocks:

HTML Copy
{% block content %}
@parent
<p>Extends content block!</p>
{% endblock %}

Include additional template files:

HTML Copy
{% include forms.html %}

If we want to remove all the compiled files we can either delete all the files in the cache directory or execute the following code:

PHP Copy
Template::clearCache();

Conclusion

The template engine is very useful in big projects, it will keep your design code away from your application code, I use this class in a custom MVC framework that I have developed and found it easier to navigate to template files for editing, and others have found the code to be read more clearly.

You're free to use this template class in your projects.

Released under the MIT License.