Talk by
@codekipple Carl Hughes
Illustration by
@laurasuzanneillustration
Template engine for PHP
Built by Sensio Labs the company responsible for Symfony
Stand alone component
Can and is being used in many different framewoks and applications
Separates backend code (PHP) from the presentation layer (HTML).
Provides concise syntax for doing common template logic.
HTML more readable, PHP is cumbersome, when mixed with HTML it makes both the PHP and the HTML harder to read.
Template orientated syntax with shortcuts for common patterns
Can be used to create reusable templates/patterns.
Security, automatic output escaping can be enabled
Because it's really fun.
<?php echo $var ?>
<?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>
{{ var }}
{{ var|escape }}
{{ var|e }} {# shortcut to escape a variable #}
PHP way
Twig way 😍
escaping
<?php if ($posts): ?>
<?php foreach ($posts as $post): ?>
<?php echo $post['title']; ?>
<?php endforeach; ?>
<?php else: ?>
<p>Sorry, no posts matched your criteria.<p>
<?php endif; ?>
{% for post in posts %}
{{ post.title }}
{% else %}
<p>Sorry, no posts matched your criteria.</p>
{% endfor %}
Looping
PHP way
Twig way 😍
There's a plugin for that
Not all Hero's wear capes
Jared Novack started the Timber plugin
Plugin handles of the integration between
Twig and Wordpress plus adds a host of extra
WordPress specific functionality to Twig.
Based on the Underscores theme _s
"Because WordPress is awesome, but the_loop isn't."
- https://github.com/timber/timber
PHP files no longer contain any HTML.
No more the_loop!
All example code can be found here
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<!-- header meta -->
</head>
<body class="{{ body_class }}">
<header class="page-head">
<div class="logo" role="banner">
<a href="<?php bloginfo( 'url' ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a>
</div>
</header>
<main role="main">
</main>
<footer class="page-foot">
<div class="u-constrict u-clearfix">
<div class="footer-text">
<p>Copyright <?php echo date('Y'); ?></p>
</div>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>
header.php
page.php
<?php get_header(); ?>
<div class="main-content">
<!-- page content -->
</div>
<?php get_footer(); ?>
footer.php
No more header.php of footer.php!*
Everything goes into the base template
Define blocks whose content can be:-
Replaced
Appended to
Prepended to
* With the caveat that some plugins need header.php and footer.php but this can be dealt with, see files in timber starter theme https://github.com/timber/starter-theme
Twig has template inheritance, all templates that extend the base template can remove or redefine the blocks within it.
<!doctype html>
<html {{ site.language_attributes }}>
<head>{% include 'partials/head.twig' %}</head>
<body class="{{ body_class }}" data-template="base.twig">
{% block header %}
<header class="page-head">
<div class="logo" role="banner"><a href="{{site.url}}" rel="home">{{site.name}}</a></div>
</header>
{% endblock %}
{% block main %}
<main role="main">
<div class="main-content">
{% block content %}
Sorry, no content
{% endblock %}
</div>
</main>
{% endblock %}
{% block footer %}
<footer class="page-foot">
{% block footer_content %}
<div class="footer-text">
<p>Copyright {{ "now"|date('Y') }}</p>
</div>
{% endblock %}
</footer>
{{ function('wp_footer') }}
{% endblock %}
</body>
</html>
templates/base.twig
templates/page.twig
{% extends "base.twig" %}
{% block content %}
<p>Page content</p>
{% endblock %}
templates/base.twig
Replace content
Append to contents
Prepend to contents
Remove block
{% block sidebar %}
<p>Content</p>
{% endblock %}
{% block sidebar %}
{{ parent() }}
<p>content</p>
{% endblock %}
{% block sidebar %}
<p>content</p>
{{ parent() }}
{% endblock %}
{% block sidebar false %}
Check block exists
{% if block("sidebar") is defined %}
<p>Content</p>
{% endif %}
<?php get_header(); ?>
<div class="main-content">
<?php if ( have_posts() ) : ?>
<h2>Latest posts</h2>
<?php while ( have_posts() ) : the_post(); ?>
<h2><a href="<?php echo esc_url( get_permalink() ) ?>"><?php the_title(); ?></a></h2>
<?php the_content() ?>
<?php endwhile; ?>
<?php endif; ?>
</div>
<?php get_footer(); ?>
page.php
Uses the_loop, looks compact but if we start adding custom content other than the latest posts (e.g. a custom post type) the template can get quite verbose.
<?php
// get global context
$context = Timber::get_context();
// Gets post retrieved from WordPress and
// add them to $context array
$context['posts'] = Timber::get_posts();
// render the template and pass the $context
Timber::render( array( 'index.twig' ), $context );
The responsibility of index.php becomes to fill the $context with all the data the template needs.
This is going to return an object with a lot of the common things we need across the site (navs, current theme, theme url, site name...)
index.php
{% extends "base.twig" %}
{% block content %}
<h2>Latest posts</h2>
{% for post in posts %}
<article class="post post-{{post.post_type}}" id="post-{{post.ID}}">
<h2><a href="{{ post.link }}">{{ post.title }}</a></h2>
<p>{{ post.get_preview }}</p>
</article>
{% endfor %}
{% endblock %}
index.twig
Ok out theme now has a homepage displaying the latest posts.
</main>
<footer class="page-foot">
<div class="footer-text"><p>Copyright <?php echo date('Y'); ?></p></div>
<?php
$tweets = get_tweets();
if ($tweets && is_home()): ?>
<div class="tweets">
<h3>Tweets</h3>
<ul>
<?php foreach ($tweets as $tweet): ?>
<li class="tweet">
<a href="<?php echo $tweet['link']; ?>">
<img src="<?php echo $tweet['avi']; ?>" alt="" />
</a>
<?php echo $tweet['tweet']; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</footer>
<?php wp_footer(); ?>
</body>
</html>
footer.php
<?php
$context = Timber::get_context();
$context['posts'] = Timber::get_posts();
$context['tweets'] = get_tweets();
Timber::render( array( 'index.twig' ), $context );
index.php
{% extends "base.twig" %}
{% block content %}
<h2>Latest posts</h2>
{% for post in posts %}
<article class="post post-{{post.post_type}}" id="post-{{post.ID}}">
<h2><a href="{{ post.link }}">{{ post.title }}</a></h2>
<p>{{ post.get_preview }}</p>
</article>
{% endfor %}
{% endblock %}
{% block footer_content %}
{{ parent() }}
{% if tweets %}
<div class="tweets">
<h3>Tweets</h3>
<ul>
{% for post in posts %}
<li class="tweet">
<a href="{{ tweet.link }}">
<img src="tweet.avi" alt="" />
</a>
{{ tweet.text }}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}
index.twig
<?php $widgets = get_widgets(); ?>
<section class="homepage-widgets">
<h2>Widgets</h2>
<?php $i =0; foreach ($widgets as $widget): $i++; ?>
<div class="widget">
<h3><?php echo $widget['title']; ?></h3>
<p><?php echo $widget['body']; ?></p>
</div>
<?php endforeach; ?>
</section>
<?php $widgets = get_widgets(); ?>
<section class="page-widgets">
<?php $i =0; foreach ($widgets as $widget): $i++; ?>
<div class="widget">
<h3><?php echo $widget['title']; ?></h3>
<p><?php echo $widget['body']; ?></p>
</div>
<?php endforeach; ?>
</section>
index.php
page.php
Image the widget component was more complicated 😐
Now imagine it was included in 6 different page templates 😞
<div class="homepage-widgets">
<h2>Widgets</h2>
{% for widget in widgets %}
{% include 'partials/widget.twig' with {
'title': widget.title,
'body': widget.body
} %}
{% endfor %}
</div>
<div class="widget">
<h3>{{ title }}</h3>
<p>{{ body }}</p>
</div>
$context['tweets'] = get_tweets();
index.twig
index.php
partials/widget.twig
One widget component template that is used everywhere.
Timber plugin looks for Twig templates in child theme first.
Works as you would expect.
Twig - Uses Twig via Timber includes styleguide via Spress.
Child - Child of Twig theme
Normal - Same design as the Twig theme but no Twig.