Get Twig.
Use Twig.
Smile.
Talk by
@codekipple Carl Hughes
Illustration by
@laurasuzanneillustration
What is Twig? 🤷
-
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
What is a template engine? 🤷♂️
-
Separates backend code (PHP) from the presentation layer (HTML).
-
Provides concise syntax for doing common template logic.
Why use one? 🙋🏿
-
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.
Code examples
<?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
Code examples
<?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 😍
OK! but how to use it with WordPress? 🙋🏽
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.
- https://en-gb.wordpress.org/plugins/timber-library/
Timber starter Theme
-
Based on the Underscores theme _s
How does it affect theming? 🙋🏿♂️
"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!
"
- Me
"
Let's create a theme
All example code can be found here
Structure HTML - PHP way
<!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
Structure HTML - Twig way
-
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.
Structure HTML - Twig way 😍
<!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
Structure HTML - Twig way 😍
templates/page.twig
{% extends "base.twig" %}
{% block content %}
<p>Page content</p>
{% endblock %}
Working with {% blocks %}
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 %}
index.php - Old way
<?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.
index.php - Twig way
<?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
index.php - Twig way 😍
{% 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
-
index.twig extends the base twig template
- Adds latest post to the content block
Theme progress
Ok out theme now has a homepage displaying the latest posts.
Lets add some Tweets
-
Appear in footer
- Only on homepage
Tweets - PHP way
</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
Tweets - Twig way
<?php
$context = Timber::get_context();
$context['posts'] = Timber::get_posts();
$context['tweets'] = get_tweets();
Timber::render( array( 'index.twig' ), $context );
index.php
-
Get tweet data
- Add it to the $context array so it gets passed along to the index.twig template
Tweets - Twig way 😍
{% 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
Theme progress
-
We will add them to the index and page template.
-
Structure of each widget will be the same.
- Content of widgets will be different.
Lets add widgets
Widgets - PHP way
<?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>
- The widget components HTML is repeated.
index.php
page.php
Image the widget component was more complicated 😐
Now imagine it was included in 6 different page templates 😞
Widgets - Twig way 😍
<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.
- Twig include statement similar to the PHP include statement
Theme progress
Styleguide
-
If we have reusable Twig templates why not add a styleguide that runs off the same templates.
- Spress is a static site generator that has Twig template support
Styleguide
- When changing widget.twig both the site and styleguide are updated.
What about child themes? 🙋🏼♂️
-
Timber plugin looks for Twig templates in child theme first.
-
Works as you would expect.
Example themes
Twig - Uses Twig via Timber includes styleguide via Spress.
Child - Child of Twig theme
Normal - Same design as the Twig theme but no Twig.
Thanks.
Any questions?
Get Twig. Use Twig. Smile
By codekipple
Get Twig. Use Twig. Smile
- 4,012