Serverside Webscripting [JLW322]

08.templates

Introducing Templates

Typical PHP page

  • Follows a certain structure
    • Logic at the top
    • Display at the bottom
<?php

	// define our vars (fixed or via calculations)
	$title = 'testpage';
	$userName = 'bramus';
	$weatherToday = 'cloudy';

?><!doctype html>
<html>
<head>
  <title><?php echo htmlentities($title); ?></title>
</head>
<body>
  Hi there <?php echo htmlentities($userName); ?>, the weather today is <?php echo htmlentities($weatherToday); ?>.
</body>
</html>

Introducing Templates

  • Split out the HTML from our PHP logic:
    1. Put all the HTML into a separate file and provide named placeholders where to show variables
    2. In the remaining PHP code, keep the logic in place. Add code to load in the template and assign values to the template variables
  • Naming conventions:
    • Give the template file a .tpl extension
    • Put variables between {{ and }} in the template, e.g. {{ firstName }}

Basic Execution

<html>
<head>
  <title>{{ title }}</title>
</head>
<body>
  Hi there {{ user }}, the weather today is {{ weather }}.
</body>
</html>
<?php

$title = 'testpage';
$userName = 'bramus';
$weatherToday = 'cloudy';

$tplContent = file_get_contents(__DIR__ . '/templates/main.tpl');

$tplContent = str_replace('{{ title }}', htmlentities($title), $tplContent);
$tplContent = str_replace('{{ user }}', htmlentities($userName), $tplContent);
$tplContent = str_replace('{{ weather }}', htmlentities($weatherToday), $tplContent);

echo $tplContent;

OO Execution (1)

  • Let's put this logic into a (very basic) class
    <?php
    
    class Template {
    
    	private $content;
    
    	public function __construct($template = null) {
    		if($template) $this->loadTemplate($template);
    	}
    
    	public function loadTemplate($template) {
    		$this->content = file_get_contents($template);
    	}
    
    	public function render($data) {
    		foreach ($data as $key => $value) {
    			$this->content = str_replace(
    				'{{ '. $key .' }}', htmlentities($value),
    				$this->content
    			);
    		}
    		return $this->content;
    	}
    }
    
    // EOF
Note: this class doesn't do any checking yet!

OO Execution (2)

  • Class usage:
    <?php
    
    	// includes & requires
    	require_once __DIR__ . '/includes/classes/template.php';
    
    	// define our vars (fixed or via calculations)
    	$title = 'testpage';
    	$userName = 'bramus';
    	$weatherToday = 'cloudy';
    
    	// load template
    	$tpl = new Template();
    	$tpl->loadTemplate(__DIR__ . '/templates/main.tpl');
    
    	// render template with our data
    	// @note The template class with automatically prevent XSS for us :-)
    	echo $tpl->render(array(
    		'title' => $title,
    		'user' => $userName,
    		'weather' => $weatherToday
    	));
    
    // EOF

Cool! What now?

  • We've successfully separated the PHP logic and rendering of our HTML.
  • The Template class is very basic. Possible improvements:
    • Support if structures in our templates
    • Support for and foreach structures in our templates
    • Variable modifiers (filters) within our template (think ucfirst)
  • We could implement this ourselves …

Introducing Twig

Twig

  • PHP Templating Library
  • Why Twig and not X?
    • Modern code, Template oriented syntax, Fast, Supports caching, Template Inheritance, Flexible, Extensible, …
    • Twig prevents XSS as it escapes variables by default

Obtaining Twig

  1. Download Twig from https://github.com/twigphp/Twig/tags
  2. Place the Twig folder from the .zip into the includes/ folder of your project
    Twig on disk
Note: In Webscripting2 we'll learn how to include Twig using Composer

Using Twig into your project

  1. Instead of manually including each class that ships with Twig, just include the Twig autoloader.
    require_once __DIR__ . '/includes/Twig/Autoloader.php';
    Twig_Autoloader::register();
  2. Bootstrap Twig, pointing it to the templates/ folder
    $loader = new Twig_Loader_Filesystem(__DIR__ . '/templates');
    $twig = new Twig_Environment($loader);
  3. Load in your template and render it (see next slide)
    • We'll rename our .tpl files to .twig
Note: In Webscripting2 we'll talk about autoloaders and PSR-0 in detail

Your first Twig Template

  • The syntax of our .tpl .twig file hasn't changed
    <html>
    <head>
      <title>{{ title }}</title>
    </head>
    <body>
      Hi there {{ user }}, the weather today is {{ weather }}.
    </body>
    </html>
  • The PHP code is quite similar
    // Assuming we've already included and bootstrapped Twig
    
    $tpl = $twig->loadTemplate('main.twig');
    echo $tpl->render(array(
    	'title' => $title,
    	'user' => $userName,
    	'weather' => $weatherToday
    ));

Exploring Twig

Template Comments

  • Wrap template comments between {# and #} inside your .twig file.
  • Example: {# I am a template comment! #}
  • Template comments won't be rendered, not even in HTML comments.

Variable Filters (1)

  • Use a | followed by a filter in the template to modify a parameter before it is displayed
  • Filters can be combined: {{ firstname|upper|reverse }}
  • Filters can accept arguments: {{ firstname|slice(0,4) }}
  • Default filter applied: escape (aliased to e)
    • Escapes the parameter to prevent XSS
    • Use raw filter to show a variable unaltered

Variable Filters (2)

  • Example:
    <p>The value of <em>title</em> is {{ title }}</p>
    <p>{{ tagline|replace({'like':'love', 'Twig':'you'}) }}</p>
    <p>Today: {{ curdate|date("d/m/Y") }}</p>
    <p>{{ name|upper|reverse }}</p>
    echo $tpl->render(array(
    	'title' => 'The title <script>alert("XSS?");</script>',
    	'tagline' => 'I like Twig',
    	'curdate' => new DateTime(),
    	'name' => 'Bramus'
    ));

Full list of filters: http://twig.sensiolabs.org/doc/filters/index.html

Associative Arrays

  • Twig plays nice with associative arrays
    // define our vars (fixed or via calculations)
    $greeting = 'Good afternoon';
    $user = array(
    	'firstname' => 'Bramus',
    	'lastname' => 'Van Damme',
    	'city' => 'Vinkt'
    );
    
    // load template
    $tpl = $twig->loadTemplate('main.twig');
    
    // render template with our data
    echo $tpl->render(array(
    	'greeting' => $greeting,
    	'user' => $user
    ));
    {{ greeting }} {{ user.firstname }} {{ user.lastname }}.
    How's the weather in {{ user.city }}?

Multi-Dimensional Assoc. Arrays (1)

  • Twig also plays nice with multi-dimensional assoc. arrays …
    $lecturers = array(
    	array(
    		'name' => 'Rogier van der Linde', 'city' => 'Ghent',
    		'courses' => array('Webtechnology', 'Webdesign & Usability', 'Webscripting 1', 'Webprogramming')
    	), array(
    		'name' => 'Kevin Picalausa', 'city' => 'Ghent',
    		'courses' => array('Webscripting 2', 'Webprogramming')
    	), array(
    		'name' => 'Davy De Winne', 'city' => 'Schellebelle',
    		'courses' => array('Webtechnology', 'Webdesign & Usability', 'Webscripting 2')
    	), array(
    		'name' => 'Joske Vermeulen'
    	)
    );
    
    echo $tpl->render(array(
    	'name' => 'Bramus Van Damme',
    	'colleagues' => $lecturers
    ));

Multi-Dimensional Assoc. Arrays (2)

  • … just use for and if tags in your template
    {% for colleague in colleagues %}
    <h3>
    	{{ colleague.name }}
    	{% if colleague.city %}<em>({{ colleague.city }})</em>{% endif %}
    </h3>
    <div>
    	{% if colleague.courses %}
    	<p>You might know him from:</p>
    	<ul>
    		{% for course in colleague.courses %}
    		<li>{{ course }}</li>
    		{% endfor %}
    	</ul>
    	{% else %}
    	<p>(He's not teaching any web courses)</p>
    	{% endif %}
    </div>
    {% endfor %}
Beware: tags must be wrapped between {% and %}

More on if

  • The if tag in Twig works just as any other if
    {% if username == 'bramus' %} YOLO {% endif %}
    {% if user in ['bramus', 'rogier'] %} YOLO {% endif %}
  • elseif is also supported
    {% if blogpost.visibility == 'password' %}
        <p>Blogpost is password protected</p>
    {% elseif blogpost.visibility == 'link' %}
        <p>Blogpost is public for those who have the link</p>
    {% else %}
        <p>Blogpost is public for all</p>
    {% endif %}

More Array goodiness (1)

  • Looping the array $courses using for-else-endfor
    echo $tpl->render(array(
    	'courses' => array(
    		'JPW235' => 'Webscripting1',
    		'JPW213' => 'Webscripting2',
    		'JPW218' => 'Web & Mobile',
    	)
    );
    <ul>
    {% for val in courses %}
    	<li>{{ val }}</li>
    {% else %}
    	<li>(array is empty)</li>
    {% endfor %}
    </ul>

  • Iterating the keys / keys & values:
    <ul>
    {% for k in courses|keys %}
    	<li>key: {{ k }}</li>
    {% else %}
    	<li>(no items in the array)</li>
    {% endfor %}
    </ul>
    <ul>
    {% for key, val in courses %}
    	<li>{{ key }} = {{ val }}</li>
    {% else %}
    	<li>(no items in the array)</li>
    {% endfor %}
    </ul>

More Array goodiness (2)

  • Use the loop variable inside iterations:
    <ul>
    {% for val in courses %}
    	<li>
    		{{ val }}
    		<ul>
    			<li><code>loop.index</code>: {{ loop.index }}</li>
    			<li><code>loop.index0</code>: {{ loop.index0 }}</li>
    			<li><code>loop.revindex</code>: {{ loop.revindex }}</li>
    			<li><code>loop.revindex0</code>: {{ loop.revindex0 }}</li>
    			<li><code>loop.first</code>: {{ loop.first }}</li>
    			<li><code>loop.last</code>: {{ loop.last }}</li>
    			<li><code>loop.length</code>: {{ loop.length }}</li>
    		</ul>
    	</li>
    {% endfor %}
    </ul>
  • It's also possible to create your own sequences
    <p>{% for i in 0..10 %}{{ i }}{% endfor %}</p>
    <p>{% for letter in 'a'..'z' %}{{ letter }}{% endfor %}</p>

Getting Practical

Configuring Twig

  • Twig can be configured during its bootstrapping phase
  • Example: enable caching
    $loader = new Twig_Loader_Filesystem(__DIR__ . '/templates');
    $twig = new Twig_Environment($loader, array(
    	'cache' => __DIR__ . '/cache',
    	'auto_reload' => true // set to false on production
    ));
    • When caching is enabled, Twig will save compiled PHP code of the template for faster use (example)

Template Inheritance (1)

  • Twig supports template inheritance
  • Use case: Have a parent layout.twig and extend it per page in your project

Template Inheritance (2)

  1. Define a template as usual, with some blocks in
    <h1>{{ pageTitle }}</h1>
    <main>
    	{% block pageContent %}
    	<p>This is the pageContent</p>
    	{% endblock %}
    </main>
    {% block pageFooter %}
    <footer>This is the footer</footer>
    {% endblock %}
  2. Define a second template named main.twig
    • Make it extend the first template (layout.twig) using extends
    • If necessary, overwrite some blocks from the first template.
    {% extends 'layout.twig' %}
    
    {% block pageContent %}
    	<p>Lorem ipsum dolor sit amet</p>
    {% endblock %}
    

Template Inheritance (3)

  1. In PHP, load up the second template and display it.
    • The result is a merge between the first and second template
    $tpl = $twig->loadTemplate('main.twig');
    echo $tpl->render(array(
    	'pageTitle' => 'Template Inheritance'
    ));

Template Inheritance (4)

  • Override variables of the parent template from within the child template using set in Twig
    {% extends 'layout.twig' %}
    
    {% set pageTitle = pageTitle|replace({'e': 'a'}) ~ ' (Manipulated)' %}
  • Output the parent block using parent()
    {% extends 'layout.twig' %}
    
    {% block pageStyle %}
    	{{ parent() }}
    	footer { text-align: center; padding-top: 2em; }
    {% endblock %}
Note: Child templates must always overwrite blocks/variables from the parent template

Including other templates

  • It's also possible to include other templates, using include. Principle is the same as PHP's include()
    {% include 'sidebar.twig' %}

Want more?

Want more?

Questions?

Code summary

A code-only summary of this chapter is available at 08.templates.summary.html

Note: not all information from these slides can be found in this summary!

Sources

ikdoeict.be