Mastering Twig (DrupalCon Barcelona 2015)

Preview:

Citation preview

Javier EguiluzSeptember 22, 2015

TwigTRACK ROOM DATE SPEAKERSymfony 115

Mastering

License of this presentation

creativecommons.org/licenses/by-nc-sa/3.0

ABOUT ME

Javier EguiluzSymfony Evangelist

ABOUT THIS TALK

We won't talk about Twig basics.

We won't provide all the low-level details.

How can I create a theme? Which is

the syntax of Twig?

Read the excellent Twig documentation to get

those details.

DRUPAL & TWIG

Fast Easy to learn

Documented Concise Full featured

Extensible Tested Useful errors

Secure

Main Twig features

My favorite feature

Consistent

My favorite feature

Twig defines a very small set of features…

… which are enough to create any template

Consistent

same syntax and behavior since day one!

easy to learn!

«Using Twig templatesis the best decisionDrupal ever made»

DRUPAL 8

TWIG

Built-in Drupal templates use ~30% of the

available Twig features.

AGENDA

Defensive programming White spaces

Debug Escaping Reusing templates

Dates Dynamic templates

Cool features

Variables

Agenda

ACCESSING VARIABLES

WHY IS THIS IMPORTANT??

Because you can easily improve the performance of your site/app.

Accessing simple variables!

<p class="comment__author">{{ author }}</p>

<p class="comment__time">{{ created }}</p>

<p class="comment__permalink">{{ permalink }}</p>

core/themes/bartik/templates/comment.html.twig

Accessing complex variables!

<nav>{{ content.links }}</nav>

core/themes/bartik/templates/comment.html.twig

Accessing complex variables!

<nav>{{ content.links }}</nav>

core/themes/bartik/templates/comment.html.twig

This is how Twig resolves complex variables

<nav>{{ content.links }}</nav> !

$content['links']

$content->links

$content->links()

$content->getLinks()

$content->isLinks()

null

Twig tries all these alternatives and uses the first one that exists.

And what about performance?

Resolving variables is quite expensive

<nav>{{ content.links }}</nav> !

$content['links']

$content->links

$content->links()

$content->getLinks()

$content->isLinks()

null

Resolving a variable is the most expensive Twig task, specially for very complex templates.

Improving Twig performance• Twig provides a PHP extension. • This extension only implements

the variable resolving logic. • See twig.sensiolabs.org/doc/

installation.html#installing-the-c-extension

EXPECTEDPERFORMANCE

INCREASE

15%

Some Drupal variables names are special

!

$variables['site_slogan']['#markup'] = ... !

{{ site_slogan.#markup }}

core/themes/bartik/bartik.theme

This doesn't work because of the # character

Some Drupal variables names are special

!

$variables['site_slogan']['#markup'] = ... !

{{ site_slogan.#markup }}

core/themes/bartik/bartik.theme

This doesn't work because of the # character

{{ site_slogan['#markup'] }}

{{ attribute(site_slogan, '#markup') }}

DEFENSIVE PROGRAMMING

WHY IS THIS IMPORTANT??

Because sooner or later errors will happen. What matters is how you deal with them.

Dealing with undefined/empty variables

is empty defaultis defined

is null {% if %}

The two recommended safeguards

{% if variable %} ... {% endif %} !

!

Hi {{ variable|default('user') }}

The two recommended safeguards

{% if variable %} ... {% endif %} !

!

Hi {{ variable|default('user') }}

It checks that variable is not null or empty or zero !

ONLY works if variable is defined

The two recommended safeguards

{% if variable %} ... {% endif %} !

!

Hi {{ variable|default('user') }}

It checks that variable is not null or empty or zero !

ONLY works if variable is defined

It checks that variable is not null, empty or undefined !

It ALWAYS works as expected

Combining both safeguards

{% if variable|default('user') %} ... {% endif %} It doesn't matter if the variable is not

defined, because the expression will always have a default value.

Checking that the variable is defined

{% if variable is defined %} ... {% endif %} A good practice when the rendered

template cannot be sure about the variables passed from the code. !

In Drupal 8 this problem should not happen (the variable list is strict).

Other safeguards available

{% if variable is null %} ... {% endif %} !

!

{% if variable is empty %} ... {% endif %} {% if variable is not empty %} ... {% endif %}

Be ready when iterating empty collections

{% for item in collection %}

...

{% else %}

There are no items.

{% endfor %}

Filter values before using them in the loop

{% for item in collection if item.published %}

...

{% else %}

There are no items.

{% endfor %}

Avoid missing templates

{{ include('menu.twig') }}

This will always work because our theme will provide this template.

Avoid missing templates

{{ include('menu.twig') }}

This will always work because our theme will provide this template.

Templates with dynamic paths are very prone to error

{{ include('users/' ~ user.name ~ '/bio.twig') }}

Define fallback templates

{{ include([

'users/' ~ user.name ~ '/bio.twig',

'users/' ~ user.name ~ '/default.twig',

'common/user_bio.twig'

]) }}Twig includes the first template that exists

Avoid missing templates• Sometimes it's not possible to provide fallback

templates. • Moreover, in some cases, it's better to ignore the

missing template instead of displaying an error to the user.

Ignore missing templates{{ include('template.twig', ignore_missing = true) }} !

{{ source('template.twig', ignore_missing = true) }} !

{% embed 'template.twig' ignore missing %} ... {% endembed %}

Ignore missing templates{{ include('template.twig', ignore_missing = true) }} !

{{ source('template.twig', ignore_missing = true) }} !

{% embed 'template.twig' ignore missing %} ... {% endembed %} NOTE

no underscore here

Twig filters defined by Drupal 8{{ value|t }} {{ value|trans }} {{ value|passthrough }} {{ value|placeholder }} {{ value|drupal_escape }} {{ value|safe_join }} {{ value|without }} {{ value|clean_class }} {{ value|clean_id }} {{ value|render }}

It's common for a long-standing and complex project to add and remove filters. !

If Drupal removes a filter used by your templates, your site/app will break.

Declare filters as deprecatednew Twig_SimpleFilter('old_filter', ..., array(

'deprecated' => true,

'alternative' => 'new_filter'

));

Declare filters as deprecatednew Twig_SimpleFilter('old_filter', ..., array(

'deprecated' => true,

'alternative' => 'new_filter'

));

These deprecations notices are not displayed or logged anywhere on Drupal yet.

NOTE

Avoid missing blocks

{% if 'title' is block %}

<title>{{ block('title') }}<title>

{% endif %}

This feature is not available yet. It will be included in the upcoming 1.23 version of Twig.

NOTE

Avoid missing blocks

{% if 'title' is block %}

<title>{{ block('title') }}<title>

{% endif %}

WHITE SPACES

WHY IS THIS IMPORTANT??

Because it will make your templates more readable and it will save you time.

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

The "problem" of white spaces

Twig template HTML page

<ul> {% for i in 1..3 %} <li>{{ i }}</li> {% endfor %} </ul>

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

The "problem" of white spaces

Twig template HTML page

<ul> {% for i in 1..3 %} <li>{{ i }}</li> {% endfor %} </ul>

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

The "problem" of white spaces

Twig template HTML page

<ul> {% for i in 1..3 %} <li>{{ i }}</li> {% endfor %} </ul>

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

The "problem" of white spaces

Twig template HTML page

<ul> {% for i in 1..3 %} <li>{{ i }}</li> {% endfor %} </ul>

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

The "problem" of white spaces

Twig template HTML page

<ul> {% for i in 1..3 %} <li>{{ i }}</li> {% endfor %} </ul>

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

The "problem" of white spaces

Twig template HTML page

Removing white spaces

<ul> {%- for i in 1..3 -%} <li>{{ i }}</li> {%- endfor -%} </ul>

<ul> {% spaceless %} {% for i in 1..3 %} <li>{{ i }}</li> {% endfor %} {% endspaceless %} </ul>

Please, don't waste your time dealing with

white spaces.

!

Twig templates should be readable

HTML pages should not!

!

Twig templates should be readable

HTML pages should not

this is where you work everyday

!

browsers get a minimized and compressed HTML mess

Sometimes you should add white spaces…

White spaces around HTML attributes

<h2{{ title_attributes }}>{{ label }}</h2> core/modules/block/templates/block.html.twig

White spaces around HTML attributes

<h2{{ title_attributes }}>{{ label }}</h2> core/modules/block/templates/block.html.twig

White spaces around HTML attributes

<h2{{ title_attributes }}>{{ label }}</h2> core/modules/block/templates/block.html.twig

no white space when the attributes are empty

<h2 class="..."> ... </h2>

<h2> ... </h2>

Add white spaces to separate Twig & HTML

<h2 {{ title_attributes }}>{{ label }}</h2> !

!

<h2 class="..."> ... </h2>

<h2 > ... </h2>

Add white spaces to separate Twig & HTML

<h2 {{ title_attributes }}>{{ label }}</h2> !

!

<h2 class="..."> ... </h2>

<h2 > ... </h2>

white space when the attributes are empty

Add white spaces to separate Twig & HTML

<h2 {{ title_attributes }}>{{ label }}</h2> !

!

<h2 class="..."> ... </h2>

<h2 > ... </h2>

white space when the attributes are empty

IT DOES NOT MATTER

Twig template is more readable

HTML code with white spaces is still valid

Hiding HTML code inside Twig strings<div id="site-name"{{ hide_name ? ' class="hidden"' }}> !

!

!

!

!

<div id="site-name">

<div id="site-name" class="hidden">

core/themes/bartik/templates/maintenance-page.html.twig

Hiding HTML code inside Twig strings<div id="site-name"{{ hide_name ? ' class="hidden"' }}> !

!

!

!

!

<div id="site-name">

<div id="site-name" class="hidden">

core/themes/bartik/templates/maintenance-page.html.twig

Hiding HTML code inside Twig strings<div id="site-name"{{ hide_name ? ' class="hidden"' }}> !

!

!

!

!

<div id="site-name">

<div id="site-name" class="hidden">

WARNINGHTML attributes

defined in Twig strings are easy to overlook

core/themes/bartik/templates/maintenance-page.html.twig

Hiding HTML code inside Twig strings<div id="site-name"{{ hide_name ? ' class="hidden"' }}> !

!

!

!

!

<div id="site-name">

<div id="site-name" class="hidden">

WARNINGHTML attributes

defined in Twig strings are easy to overlook

core/themes/bartik/templates/maintenance-page.html.twig

DANGERIf you miss this single white space, the page

design breaks

Don't hide HTML code inside Twig strings<div id="site-name" class="{{ hide_name ? 'hidden' }}"> !

!

!

!

!

<div id="site-name" class="">

<div id="site-name" class="hidden">

HTML & Twig are decoupled

A single white space won't break the page

Don't hide HTML code inside Twig strings<div id="site-name" class="{{ hide_name ? 'hidden' }}"> !

!

!

!

!

<div id="site-name" class="">

<div id="site-name" class="hidden">Valid HTML code

(tested with the W3C validator)

HTML & Twig are decoupled

A single white space won't break the page

DEBUG

WHY IS THIS IMPORTANT??

Because it will save you a lot of time while developing your templates.

Configure Twig behavior

# sites/default/services.yml parameters: twig.config: debug: true auto_reload: null cache: true

Configure Twig behavior

# sites/default/services.yml parameters: twig.config: debug: true auto_reload: null cache: true

Include debug information in the rendered HTML contents

In production server, always set it to false

HTML content of a rendered Drupal template

<div id="block-bartik-login" class="contextual-region block block-user block-user-login-block" role="form"> <h2>User login</h2> <div data-contextual-id="block:block=bartik_login:langcode=en"></div> <div class="content"> !<form class="user-login-form" data-drupal-selector="user-login-form" action="/node?destination=/node" method="post" id="user-login-form" accept-charset="UTF-8"> !<!-- ... -->

HTML content when Twig debug is enabled

<!-- THEME DEBUG --> <!-- THEME HOOK: 'block' --> <!-- FILE NAME SUGGESTIONS: * block--bartik-login.html.twig * block--user-login-block.html.twig * block--user.html.twig x block.html.twig --> <!-- BEGIN OUTPUT from 'core/themes/bartik/templates/block.html.twig' --> <div id="block-bartik-login" class="contextual-region block block-user block-user-login-block" role="form"> <h2>User login</h2> <div data-contextual-id="block:block=bartik_login:langcode=en"></div> <div class="content"> !<!-- THEME DEBUG --> <!-- THEME HOOK: 'form' --> <!-- BEGIN OUTPUT from 'core/themes/classy/templates/form/form.html.twig' --> <form class="user-login-form" data-drupal-selector="user-login-form" action="/node?destination=/node" method="post" id="user-login-form" accept-charset="UTF-8">

How to override the current template<!-- THEME DEBUG -->

<!-- THEME HOOK: 'block' -->

<!-- FILE NAME SUGGESTIONS:

* block--bartik-login.html.twig

* block--user-login-block.html.twig

* block--user.html.twig

x block.html.twig

-->

<!-- BEGIN OUTPUT from 'core/themes/bartik/templates/block.html.twig' -->

How to override the current template<!-- THEME DEBUG -->

<!-- THEME HOOK: 'block' -->

<!-- FILE NAME SUGGESTIONS:

* block--bartik-login.html.twig

* block--user-login-block.html.twig

* block--user.html.twig

x block.html.twig

-->

<!-- BEGIN OUTPUT from 'core/themes/bartik/templates/block.html.twig' -->

Drupal tried to use all these templates…

How to override the current template<!-- THEME DEBUG -->

<!-- THEME HOOK: 'block' -->

<!-- FILE NAME SUGGESTIONS:

* block--bartik-login.html.twig

* block--user-login-block.html.twig

* block--user.html.twig

x block.html.twig

-->

<!-- BEGIN OUTPUT from 'core/themes/bartik/templates/block.html.twig' -->

…before deciding to use this template.

Drupal tried to use all these templates…

Which variables are passed to the template?

Built-in templates include comments with the full list of variables passed to the Twig template.

Easier way to introspect all variables

<pre>

{{ dump() }}

</pre>

Easier way to introspect all variables

<pre>

{{ dump() }}

</pre>

It dumps the contents of all the variables

defined in the template.

It's better to dump just the variables you need

<pre> {{ dump(label, title_attributes) }}

</pre>

It's better to dump just the variables you need

<pre> {{ dump(label, title_attributes) }}

</pre>

It dumps only the given variables

CAUTION!

Don't forget to rebuild your cache after changing the config files and templates.

drupalconsole.com

drupalconsole.com

$ drupal cache:rebuild $ drupal c:r

ESCAPING

WHY IS THIS IMPORTANT??

Because it can prevent you a lot of security-related problems.

CAUTION!

Drupal has replaced the default Twig escaping filter by their own.

By default, contents are escaped for HTML

Hi {{ content }}!

$content = '<strong>John</strong>';

By default, contents are escaped for HTML

Hi {{ content }}!

$content = '<strong>John</strong>';

What you expect…

Hi John!

What you get…

Hi <strong>John </strong>!

The "raw" filter prevents the escaping

Hi {{ content|raw }}!

$content = '<strong>John</strong>';

The "raw" filter prevents the escaping

Hi {{ content|raw }}!

$content = '<strong>John</strong>';

What you expect…

Hi John!

What you get…

Hi John!

What if contents are used in URLs or JS?

<a href="...?param={{ value }}"></a> !

!

!

<script> var variable = "{{ content }}"; </script>

What if contents are used in URLs or JS?

<a href="...?param={{ value }}"></a> !

!

!

<script> var variable = "{{ content }}"; </script>

WRONG HTML ESCAPING

Applying different escaping strategies

<a href="...?param={{ value|e('url') }}"></a> !

!

!

<script> var variable = "{{ content|e('js') }}"; </script>

Escaping strategies available in Twig

{{ content|e('html') }}

{{ content|e('js') }}

{{ content|e('css') }}

{{ content|e('url') }}

{{ content|e('html_attr') }}

REUSING TEMPLATES

WHY IS THIS IMPORTANT??

Because it allows you to avoid repeating code and it makes your themes easier to maintain.

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

How often are these alternatives used

{% embed %}

{% extends %} include( )

{% set %} {% use %}

macro( )

Always

Sometimes

Rarely

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

Use {% extends %} to share layouts

Use {% extends %} to share layouts

1 layout with the common design elements

Use {% extends %} to share layouts

1 layout with the common design elements

+

4 simple pages which only define their contents

layout.twig<!DOCTYPE html> <html> <head> <title> {% block title %}ACME website{% endblock %} </title> </head> <body> <div class="container"> {% block content %}{% endblock %} </div> </body> </html>

layout.twig<!DOCTYPE html> <html> <head> <title> {% block title %}ACME website{% endblock %} </title> </head> <body> <div class="container"> {% block content %}{% endblock %} </div> </body> </html>

Other templates can reuse this layout{% extends 'layout.twig' %}

!

{% block title %}Community{% endblock %}

{% block content %}

<div> ... </div>

{% endblock %}

When should you use {% extends %}• To create the layout of your theme. • If your site/app is very complex, create two

inheritance levels (base layout and section layouts).

layout.twig schedule.twig training.twigextends layout.twig extends schedule.twig

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

Reusing templates with include( )!

{{ include('listing.twig') }} !

!

<div> {% for item in items %} <h2>{{ item.title }}</h2> <p>{{ item.content }}</p> {% endfor %} </div>

blog/index.twig

blog/listing.twig

When should you use include( )• To reuse large fragments of code, such as sidebars,

navigation menus, etc.

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

Repetitive HTML fragments

<div class="form-group">

<label for="{{ id }}">{{ label }}</label>

<input type="{{ type }}" class="form-control"

id="{{ id }}">

</div> Repeating the same HTML code for all the form fields

is cumbersome

Reusing fragments with macro( )

{% macro form_field(id, label, type="text") %}

<div class="form-group">

<label for="{{ id }}">{{ label }}</label>

<input type="{{ type }}" class="form-control"

id="{{ id }}">

</div>

{% endmacro %}

Using "macros" inside templates{% import _self as macro %}

!

<form>

{{ macro.form_field('first_name', 'First Name') }}

{{ macro.form_field('last_name', 'Last Name') }}

{{ macro.form_field('email', 'Email', 'email') }}

...

</form>

Using "macros" inside templates{% import _self as macro %}

!

<form>

{{ macro.form_field('first_name', 'First Name') }}

{{ macro.form_field('last_name', 'Last Name') }}

{{ macro.form_field('email', 'Email', 'email') }}

...

</form>

Before using a macro, you must "import" them (they can be

defined in a different template)

When should you use macro( )• To reuse short fragments of code, usually in the

same template (e.g. listings, grids, forms, etc.) • If your site/app is very complex, store all the macros

in a single file and reuse it from any other template.

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

Grid-based design

embed allows to reuse inner page structures (e.g. the 3-column grid)

Grid-based design

embed allows to reuse inner page structures (e.g. the 3-column grid)

You can't use "include" to solve this problem

{{ include('common/grid_3.twig') }}

You can't use "include" to solve this problem

{{ include('common/grid_3.twig') }}

you can't change the included contents (you include both the

structure and the content)

You can't use "extends" to solve this problem

{% extends 'common/grid_3.twig' %}

!

{% block column1 %} ... {% endblock %}

{% block column2 %} ... {% endblock %}

{% block column3 %} ... {% endblock %}

You can't use "extends" to solve this problem

{% extends 'common/grid_3.twig' %}

!

{% block column1 %} ... {% endblock %}

{% block column2 %} ... {% endblock %}

{% block column3 %} ... {% endblock %}you can't make the whole structure

of the page (grid 2, grid 3, etc.) because you can't extend from

multiple templates at the same time

Define a three-column grid template<div class="row"> <div class="col-md-4"> {% block column1 %}{% endblock %} </div> !

<div class="col-md-4"> {% block column2 %}{% endblock %} </div> !

<div class="col-md-4"> {% block column3 %}{% endblock %} </div> </div>

Reuse the three-column grid template{% embed 'common/grid_3.twig' %} {% block column1 %} ... contents ... {% endblock %} !

{% block column2 %} ... contents ... {% endblock %} !

{% block column3 %} ... contents ... {% endblock %} {% endembed %}

When should you use {% embed %}• To reuse page structures across different templates

(e.g. grids)

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

Reusing fragments with {% set %}

{% set navigation %} <a href="...">Previous</a> ... ... <a href="...">Next</a> {% endset %}

Reusing fragments with {% set %}

{% set navigation %} <a href="...">Previous</a> ... ... <a href="...">Next</a> {% endset %} {{ navigation }}

{{ navigation }}

When should you use {% set %}• To reuse short fragments of code inside a template

(if those fragments are configurable, use a macro). • It's like an internal include() made from inside the

template itself.

Lots of different ways to reuse templates

{% embed %} {% extends %}include()

{% set %} {% use %}macro()

When should you use {% use %}• This is too advanced and for very specific use

cases. • You should probably never use it when creating

themes.

DYNAMIC TEMPLATES

WHY IS THIS IMPORTANT??

Because Drupal allows to create sites with very advanced needs.

Templates created on-the-fly

{% set code = 'Hi {{ name }}' %}

{% set template = template_from_string(code) %}

!

{{ include(template) }}

Templates created on-the-fly

{% set code = 'Hi {{ name }}' %}

{% set template = template_from_string(code) %}

!

{{ include(template) }}

{% extends template %}

{% embed template %} It works here too

Templates created on-the-fly

{{ include(template_from_string(

'Hi {{ name }}'

)) }}

Templates created and modified on-the-fly

{% set code = 'Hi {{ name }}' %}

{% set code = code|replace({ 'Hi': 'Bye' }) %}

!

{% set template = template_from_string(code) %}

!

{{ include(template) }}

Getting the source of any template

{{ source('core/modules/block/templates/block.html.twig') }}

It gets the source of the given template without actually rendering it.

Imagine a site which allows this customization

Section 1

Section 2

Default design

Customizable site• Users can provide their own Twig snippets to

customize the design and content of some sections. • Problem: even if customization is restricted to a

group of controlled users (e.g. "editors") you can't trust those templates.

Twig Sandbox• It's used to render "untrusted templates". • It restricts the Twig features that can be used by the

template. • Useful for letting users create their own templates

and maintain the application safe.

Twig Sandbox in practice{% sandbox %}

{{ include(section.name ~ '/sidebar.twig') }}

{% endsandbox %}

!

!

{{ include(section.name ~ '/sidebar.twig', sandboxed = true) }}

Twig Sandbox in practice$policy = new Twig_Sandbox_SecurityPolicy( $tags, $filters, $methods, $properties, $functions ); !

$sandbox = new Twig_Extension_Sandbox($policy); $twig->addExtension($sandbox);

Policy is defined as a white-list of allowed tags, filters, etc.

Twig Sandbox policy sample$properties = array( 'label', 'configuration' => array('label', 'module'), 'block' => array('module'), 'attributes', ); !

$policy = new Twig_Sandbox_SecurityPolicy( $tags, $filters, $methods, $properties, $functions );

DATES

WHY IS THIS IMPORTANT??

Because dealing with dates is not easy and Twig can perform a lot of operations on dates.

Timezones support

{{ 'now'|date(timezone='Asia/Tokyo') }} !

{{ 'now'|date(timezone=user.timezone) }}

Comparing dates

{% if event.startsAt > date('now') %}

Buy tickets

{% endif %}

Comparing dates

{% if event.startsAt > date('now') %}

Buy tickets

{% endif %} NOTE This is the date( )

function, not the date filter

Modifying dates semantically

Early Bird ends at {{ event.startsAt|date_modify('-15 days')|date }} !

Confirm your sign up before {{ user.createdAt|date_modify('+48 hours')|date }}

COOL FEATURES

These are some of the features that put Twig years ahead of PHP

Useful filters for collections

{{ user.friends|first }}

{{ event.sessions|last }}

Useful tests for strings

{% if url starts with 'https://' %}

{% endif %}

!

{% if file_path ends with '.pdf' %}

{% endif %}

!

{% if phone matches '/^[\\d\\.]+$/' %}

{% endif %}

REJECTED BY

PHPUseful tests for strings

{% if url starts with 'https://' %}

{% endif %}

!

{% if file_path ends with '.pdf' %}

{% endif %}

!

{% if phone matches '/^[\\d\\.]+$/' %}

{% endif %}

The "in" operator

{% if password in username %}

BAD PASSWORD

{% endif %}

!

{% if method in ['GET', 'POST'] %}

...

{% endif %}

The "in" operator

{% if password in username %}

BAD PASSWORD

{% endif %}

!

{% if method in ['GET', 'POST'] %}

...

{% endif %}

REJECTED BY

PHP

Named parameters{{ content|convert_encoding('UTF-8', 'iso-2022-jp') }}

Named parameters{{ content|convert_encoding('UTF-8', 'iso-2022-jp') }}

which is the original charset and which one the target charset?

Named parameters{{ content|convert_encoding('UTF-8', 'iso-2022-jp') }}

which is the original charset and which one the target charset?

{{ content|convert_encoding( from = 'UTF-8', to = 'iso-2022-jp' ) }}

Named parameters{{ content|convert_encoding('UTF-8', 'iso-2022-jp') }}

which is the original charset and which one the target charset?

{{ content|convert_encoding( from = 'UTF-8', to = 'iso-2022-jp' ) }}

{{ content|convert_encoding( to = 'UTF-8', from = 'iso-2022-jp' ) }}

Named parameters{{ content|convert_encoding('UTF-8', 'iso-2022-jp') }}

PROPOSED FOR

PHP

which is the original charset and which one the target charset?

{{ content|convert_encoding( from = 'UTF-8', to = 'iso-2022-jp' ) }}

{{ content|convert_encoding( to = 'UTF-8', from = 'iso-2022-jp' ) }}

Named parameters{{ include('template.html', {}, true, true) }} !

!

!

{{ include('template.html', ignore_missing = true) }}

template variables

with_context

ignore_missing

It's common to do things in batches

1

Image gallery

2 3

4 5 6

The HTML of the image gallery<div class="row">

<div class="image"> ... </div>

<div class="image"> ... </div>

<div class="image"> ... </div>

</div>

!

<div class="row">

<div class="image"> ... </div>

<div class="image"> ... </div>

<div class="image"> ... </div>

</div>

The template without the "batch" filter{% for i, image in images %}

{% if i is divisible by(3) %} <div class="row"> {% endif %}

!

<div class="image">

<img src="" alt="" >

<p>...</p>

</div>

!

{% if i is divisible by(3) %} </div> {% endif %}

{% endfor %}

The template without the "batch" filter{% for i, image in images %}

{% if i is divisible by(3) %} <div class="row"> {% endif %}

!

<div class="image">

<img src="" alt="" >

<p>...</p>

</div>

!

{% if i is divisible by(3) %} </div> {% endif %}

{% endfor %}

UGLY CODE

The template with the "batch" filter{% for row in images|batch(3) %} <div class="row"> !

{% for image in row %} <div class="image"> <img src="" alt="" > <p>...</p> </div> {% endfor %} !

</div> {% endfor %}

The template with the "batch" filter{% for row in images|batch(3) %} <div class="row"> !

{% for image in row %} <div class="image"> <img src="" alt="" > <p>...</p> </div> {% endfor %} !

</div> {% endfor %}

Short ternary operator

$result = $condition ? 'is true';

Short ternary operator

$result = $condition ? 'is true';

ERROR Parse error: syntax error, unexpected ';' on line 1

Short ternary operator

$result = $condition ? 'is true';

ERROR Parse error: syntax error, unexpected ';' on line 1

{{ condition ? 'is true' }}

Short ternary operator

$result = $condition ? 'is true';

ERROR Parse error: syntax error, unexpected ';' on line 1

OK This works perfectly on Twig

{{ condition ? 'is true' }}

Short ternary operator

<li class="{{ condition ? 'selected' }}">

...

</li>

!

<li class="{{ condition ? 'selected' : '' }}"> ...

</li>

Short ternary operator

<li class="{{ condition ? 'selected' }}">

...

</li>

!

<li class="{{ condition ? 'selected' : '' }}"> ...

</li>

always use this

Short slice syntax

!

!

!

{{ user.friends[0:3] }}

{{ user.friends[:-3] }}

It combines array_slice, mb_substr and substr PHP functions.

get first three friends

get last three friends

Short slice syntax

{{ '0123456789'[0:] }} {# 0123456789 #}

{{ '0123456789'[1:] }} {# 123456789 #}

{{ '0123456789'[20:] }} {# (empty) #}

{{ '0123456789'[-5:] }} {# 56789 #}

{{ '0123456789'[-1:] }} {# 9 #}

{{ '0123456789'[1:5] }} {# 12345 #}

{{ '0123456789'[1:-5] }} {# 1234 #}

OUTPUT

The "loop" magic variable

Everyone needs an $i variable inside the for loop. So Twig provides you this and other useful variables.

The "loop" variable exists only inside the "for"

{% for ... in collection %}

{{ loop.index }}

{{ loop.index0 }}

{{ loop.first }}

{{ loop.last }}

{{ loop.length }}

{% endfor %}

The "loop" variable exists only inside the "for"

{% for ... in collection %}

{{ loop.index }}

{{ loop.index0 }}

{{ loop.first }}

{{ loop.last }}

{{ loop.length }}

{% endfor %}

1, 2, 3, 4, 5, ...

0, 1, 2, 3, 4, ...

true, false, false, ...

..., false, false, true

5

{{ product.photo|image(400, 150, 0.9) }}

The problem with filter arguments

{{ product.photo|image(400, 150, 0.9) }}

The problem with filter arguments

What if I need to define more arguments?

{{ product.photo|image(400, 150, 0.9) }}

{{ product.photo|image( width = 400, height = 150, opacity = 0.9 ) }}

The problem with filter arguments

What if I need to define more arguments?

{{ product.photo|image(400, 150, 0.9) }}

{{ product.photo|image( width = 400, height = 150, opacity = 0.9 ) }}

The problem with filter arguments

What if I need to define more arguments?

this is a valid solution for Twig, but the underlying PHP code is still very complex

Defining a filter with lots or arguments$filter = new Twig_SimpleFilter('image', function (

$path, $width, $height, $opacity

) {

$path = ...

$width = ...

$height = ...

$opacity = ...

});

$filter = new Twig_SimpleFilter('image', function (

$path, $options = array()

) {

$path = ...

$width = $options['width'];

$height = $options['height'];

$opacity = $options['opacity'];

}, array('is_variadic' => true));

Defining a variadic filter

$filter = new Twig_SimpleFilter('image', function (

$path, $options = array()

) {

$path = ...

$width = $options['width'];

$height = $options['height'];

$opacity = $options['opacity'];

}, array('is_variadic' => true));

Defining a variadic filter

a single variadic parameter holds any number of passed parameters (unlimited)

$filter = new Twig_SimpleFilter('image', function (

$path, $options = array()

) {

$path = ...

$width = $options['width'];

$height = $options['height'];

$opacity = $options['opacity'];

}, array('is_variadic' => true));

ACCEPTED BY

PHP

Defining a variadic filter

a single variadic parameter holds any number of passed parameters (unlimited)

TO SUM UP

«Using Twig templates is the best decision Drupal ever made»

Drupal 8 templates are safe, concise, modern

and consistent.

Twig

Drupal 8 templates are safe, concise, modern

and consistent.

Drupal 8 themes

REFERENCES

References• Official Twig documentation

twig.sensiolabs.org/documentation

• Twig in Drupal 8drupal.org/theme-guide/8/twig

CONTACT

Contact info• javier.eguiluz@sensiolabs.com • github.com/javiereguiluz • linkedin.com/in/javiereguiluz

!

!

!