Get Twig.

Use Twig.

Smile.

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

Timber starter Theme

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

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