Date post: | 15-Apr-2017 |
Category: |
Technology |
Upload: | javier-eguiluz |
View: | 5,105 times |
Download: | 2 times |
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