Templates

If you look at the last version of the contact form, you have probably noticed that it is becoming quite a convoluted mess of PHP and HTML (and yet it is still a very simple form). The solution to this problem is to use (HTML) templates.

The advantage of using templates is that they simplify both the PHP and HTML code and that they protect your code against Cross Site Scripting vulnerabilities. The templates disadvantage is that you need to learn another language. HTML templates are called templates, because they are HTML pages with placeholders. The actual value of the placeholders is supplied when the template is rendered (which usually means – sent to a web browser). Only then the template becomes a valid HTML page.

Templating Engines

Template engine is a library which processes a HTML page with macros (snippets of a template language) and produces a valid HTML page. There are many templating engines available for PHP, some of the popular engines are: Smarty, Latte, Twig. All of them (and many others) can be used as parts of their respective frameworks or standalone. In the following examples I will stick to using the standalone Latte templating engine. The choice is rather arbitrary, as all templating engines work in a very similar way, have almost the same features and they even have a somewhat similar syntax.

Getting Started with Latte

First you need to obtain Latte, either by using the Composer tool or simply by downloading the library from Github:

Screenshot -- Download Latte

You should get latte-master and copy the contents of the src folder to your script. Now create a PHP script like this:

<?php

require 'latte.php';

$latte = new Latte\Engine();
$templateVariables['pageTitle'] = 'Template engine sample';
$latte->render('template-1.latte', $templateVariables);

And a template file template-1.latte like this:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>{$pageTitle}</title>
	</head>
	<body>
		<h1>Title: {$pageTitle}</h1>
		<p>This page is generated by Latte.</p>		
	</body>
</html>

Latte templating engine evolves like any other software. Newer version can have trouble to run in older PHP environment. Check if your Latte versions supports your PHP version. In case that you need older release of Latte, simply click releases tab on GitHub and and download older one. Latte 3.x requires PHP 7.x to run, if you do not have one, download older version, e.g. 2.4.x which runs with PHP 5.4.

Now, let’s see what has happened. The template looks like a standard HTML document (with the file extension latte), there are some additional features however. In the template above I used {$pageTitle} which prints the contents of a template variable $pageTitle.

Now for the PHP script – the first statement require 'latte.php'; instructs PHP to include the file latte.php in the same directory as your script. The latte.php is part of the Latte Templating engine and makes sure that the library is available in your script. Next we create an instance of the Engine class: $latte = new Latte\Engine();. Note that the class Engine is in a namespace Latte\. Next we create an associative array $templateVariables with value 'Template engine sample' assigned to the pageTitle key. On the last line, we call the render function of the Latte engine and pass to it the filename of the template (template-1.latte) and the array of $templateVariables.

As you can see, the variables used and available in the template must be passed via an associative array to the render method.

Macros

Template engine offers plenty of macros which simplify generation of the HTML code. In the above example, I used the {$variable} macro. Let’s see some more examples:

<?php

require 'latte.php';

$latte = new Latte\Engine;

$tplVars['flintstones'] = [
	'father' => 'Fred',
	'mother' => 'Wilma',
	'child' => 'Pebbles',
];
$tplVars['rubbles'] = [
	'father' => 'Barney',
	'mother' => 'Betty',
	'child' => 'Bamm-Bamm',
];
$tplVars['pageTitle'] = 'Flintstones';
$tplVars['showBold'] = true;

$latte->render('template-2.latte', $tplVars);

Template file template-2.latte:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>{$pageTitle}</title>
	</head>
	<body>
		<h1>Title: {$pageTitle}</h1>
		<h2>Flintstones</h2>
		<ul>
		{foreach $flintstones as $role => $name}
			<li>{$role|capitalize} is
				{if $showBold}
					<strong>{$name}</strong>
				{else}
					{$name}
				{/if}
			</li>
		{/foreach}
		</ul>
		<h2>Rubbles</h2>
		<ul>
			<li n:foreach="$rubbles as $role => $name">{$role|capitalize} is 
				<strong n:tag-if="$showBold">{$name}</strong>
			</li>
		</ul>
	</body>
</html>

There are two options how the macros can be written in latte. In the template above, the flintstones array is printed using the longer syntax {foreach}{/foreach} and {if}{/if} which is more similar to the PHP syntax. The rubbles array is printed using the shorter syntax n:foreach and n:if, which disturbs the flow of the HTML code much less.

The statement {$role|capitalize} applies the built-in Latte capitalize filter.

In the PHP code, I need to define all the variables: $flintstones, $rubbles, $pageTitle and $showBold in an associative array. I have taken the liberty to shorten the name of the variable $templateVariables to just $tplVars.

You can put comments in the templates as well:

{* this is a comment *}

Contrary to comments in HTML, comments in templates will not be contained in the resulting page. If you find passing variables between a PHP script and a template confusing, have a look at the following schema.

Schematic of template variables

Task – Contact form

Let’s convert the contact form we did in the previous chapter.

<?php

require 'latte.php';

$latte = new Latte\Engine();
$currentUser = [
    'first_name' => 'John',
    'last_name' => 'Doe',
    'email' => 'john.doe@example.com',
    'birth_year' => 1996,
];
/*
// Not Logged User
$currentUser = [
    'first_name' => '',
    'last_name' => '',
    'email' => '',
    'birth_year' => '',
];
*/
if ($currentUser['first_name']) {
    $tplVars['message'] = "Hello,\nI'd like to know more about your product <ProductName>\n\nBest Regards,\n" . 
        $currentUser['first_name'] . ' ' . $currentUser['lastName'];
} else {
    $tplVars['message'] = "Hello,\nI'd like to know more about your product <ProductName>\n\nBest Regards,\n<YourName>";
}

$tplVars['rows'] = 10;
$tplVars['cols'] = 50;
$tplVars['pageTitle'] = "Contact form";
$tplVars['currentUser'] = $currentUser;
$tplVars['years'] = [];
for ($year = 1916; $year < date('Y'); $year++) {
    $tplVars['years'][] = $year;
}

$latte->render('form-5.latte', $tplVars);

Template file form-5.latte:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>{$pageTitle}</title>
    </head>
    <body>
        <h1>{$pageTitle}</h1>
        <h2 n:if="$currentUser['first_name']">Hello {$currentUser['first_name']} {$currentUser['last_name']}</h2>
    	<form method="post" action="http://odinuv.cz/form_test.php">
    		<ul>
    			<li>
    				<label>First name:
    					<input type="text" name="first_name" value="{$currentUser['first_name']}">
    				</label>
    			</li>
                <li>
                    <label>Last name:
                        <input type="text" name="last_name" value="{$currentUser['last_name']}">
                    </label>
                </li>
                <li>
                    <label>E-mail address:
                        <input type="email" name="email" value="{$currentUser['email']}">
                    </label>
                </li>
                <li>
                    <label>Year of birth:
                        <select name="birth_year">
                        {foreach $years as $year}
                            <option n:attr="selected => $currentUser['birth_year'] == $year" value="{$year}">{$year}</option>
                        {/foreach}
                        </select>
                    </label>
                </li>
    			<li>
    				<label>Message for us:
    					<textarea name="message" cols="{$cols}" rows="{$rows}">{$message}</textarea>
    				</label>
    			</li>
    		</ul>
    		<button type="submit" name="contact" value="contact">Contact Us</button>
    	</form>
	</body>
</html>

I have simplified the condition if ($templateVariables['currentUser']['firstName'] != '') to if ($templateVariables['currentUser']['firstName']) because the automatic boolean conversion allows us to do it.

You need to convert the entities &< and &gt; in the message back to the characters < and >. Now Latte does this conversion automatically for you.

Task – Person Form

Using templates, create a form like the one below. Assume that you have a variable $person which contains the default values for the form inputs. The person variable should be an associative array with the keys id, first_name, last_name, nickname, birth_day, height.

// This is not a solution, only a hint, how the PHP script should start
// Existing user
$person = [
    'id' => 123,
    'first_name' => 'John',
    'last_name' => 'Doe',
    'nickname' => 'johnd',
    'birth_day' => '1996-01-23',
    'height' => 173,
];
/*
// New user
$person = [
    'id' => null
    'first_name' => '',
    'last_name' => '',
    'nickname' => '',
    'birth_day' => null,
    'height' => null,
];
*/
<?php

require 'latte.php';

$latte = new Latte\Engine();
// Existing user
$person = [
    'id' => 123,
    'first_name' => 'John',
    'last_name' => 'Doe',
    'nickname' => 'johnd',
    'birth_day' => '1996-01-23',
    'height' => 173,
];
/*
// New user
$person = [
    'id' => null
    'first_name' => '',
    'last_name' => '',
    'nickname' => '',
    'birth_day' => null,
    'height' => null,
];
*/
if ($person['id']) {
    $tplVars['pageTitle'] = "Edit person";
} else {
    $tplVars['pageTitle'] = "Add new person";
}
$tplVars['person'] = $person;

$latte->render('person-form.latte', $tplVars);
    

Template file person-form.latte:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>{$pageTitle}</title>
    </head>
    <body>
        <h1>{$pageTitle}</h1>
        {if $person['id']}
            <h2>Update person {$person['first_name']} {$person['last_name']}</h2>
        {else}
            <h2>Create a new person</h2>
        {/if}
    	<form method="post" action="http://odinuv.cz/form_test.php">
    		<ul>
    			<li>
    				<label>First name:
    					<input type="text" name="first_name" value="{$person['first_name']}">
    				</label>
    			</li>
                <li>
                    <label>Last name:
                        <input type="text" name="last_name" value="{$person['last_name']}">
                    </label>
                </li>
                <li>
                    <label>Nickname:
                        <input type="text" name="nickname" value="{$person['nickname']}">
                    </label>
                </li>
                <li>
                    <label>Date of Birth:
                        <input type="date" name="birth_day" value="{$person['birth_day']}">
                    </label>
                </li>
    			<li><label>Height:
                        <input type="number" name="height" value="{$person['height']}">
                    </label>
    			</li>
    		</ul>
            {if $person['id']}
                <button type="submit" name="submit" value="update">Update</button>
            {else}
                <button type="submit" name="submit" value="create">Create new</button>
            {/if}
    	</form>
	</body>
</html>

Summary

Using a template engine requires you to learn its macro language. However it does lead to a cleaner and safer HTML and PHP code. You don’t need to struggle so much with using proper quotes. When using templates, don’t forget that the variables defined inside a template are only those passed in an array to the render method! Variables from the PHP script are not available in the template automatically. Also keep in mind that while a HTML Template is very similar to a HTML page, it cannot be interpreted by the web browser, only the corresponding template engine is capable of processing it and producing a valid HTML page.

Now you should be familiar with the principle of a PHP template engine and you should be aware of the benefits of using a template engine. You should be able to use basic macros for inserting variables in a template and working with conditionals and loops in Latte templates (either syntax).

New Concepts and Terms

  • Templates
  • Latte
  • Macros
  • Template Variables

Control question

  • Why use templates?
  • Are templates slower than plain PHP (using echo)?
  • Can you define yor own macro of filter?
  • What is the interface between template and PHP code?
  • How does a template obtain its variables?
  • What is the result of template rendering?