Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Mastering Twig (DrupalCon Barcelona 2015)

6.251 Aufrufe

Veröffentlicht am

Twig is the template engine used by Drupal 8 and other modern PHP applications. Twig's expressiveness, consistency and secure-by-default policy are still unparalleled among PHP's template engines.

In this session you'll learn advanced techniques, tips and tricks useful for real-world applications and uncommonly used features that will allow you to master Twig.

Veröffentlicht in: Technologie

Mastering Twig (DrupalCon Barcelona 2015)

  1. 1. Javier EguiluzSeptember 22, 2015 Twig TRACK ROOM DATE SPEAKER Symfony 115 Mastering
  2. 2. License of this presentation creativecommons.org/licenses/by-nc-sa/3.0
  3. 3. ABOUT ME
  4. 4. Javier Eguiluz Symfony Evangelist
  5. 5. ABOUT THIS TALK
  6. 6. 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.
  7. 7. DRUPAL & TWIG
  8. 8. Fast Easy to learn Documented Concise Full featured Extensible Tested Useful errors Secure Main Twig features
  9. 9. My favorite feature Consistent
  10. 10. 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!
  11. 11. «Using Twig templates is the best decision Drupal ever made»
  12. 12. DRUPAL 8 TWIG Built-in Drupal templates use ~30% of the available Twig features.
  13. 13. AGENDA
  14. 14. Defensive programming White spaces Debug Escaping Reusing templates Dates Dynamic templates Cool features Variables Agenda
  15. 15. ACCESSING VARIABLES
  16. 16. WHY IS THIS IMPORTANT?? Because you can easily improve the performance of your site/app.
  17. 17. 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
  18. 18. Accessing complex variables ! <nav>{{ content.links }}</nav> core/themes/bartik/templates/comment.html.twig
  19. 19. Accessing complex variables ! <nav>{{ content.links }}</nav> core/themes/bartik/templates/comment.html.twig
  20. 20. 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.
  21. 21. And what about performance?
  22. 22. 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.
  23. 23. 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 EXPECTED
 PERFORMANCE INCREASE 15%
  24. 24. 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
  25. 25. 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') }}
  26. 26. DEFENSIVE PROGRAMMING
  27. 27. WHY IS THIS IMPORTANT?? Because sooner or later errors will happen. What matters is how you deal with them.
  28. 28. Dealing with undefined/empty variables is empty defaultis defined is null {% if %}
  29. 29. The two recommended safeguards {% if variable %} ... {% endif %} ! ! Hi {{ variable|default('user') }}
  30. 30. 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
  31. 31. 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
  32. 32. 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.
  33. 33. 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).
  34. 34. Other safeguards available {% if variable is null %} ... {% endif %} ! ! {% if variable is empty %} ... {% endif %} {% if variable is not empty %} ... {% endif %}
  35. 35. Be ready when iterating empty collections {% for item in collection %} ... {% else %} There are no items. {% endfor %}
  36. 36. Filter values before using them in the loop {% for item in collection if item.published %} ... {% else %} There are no items. {% endfor %}
  37. 37. Avoid missing templates {{ include('menu.twig') }} This will always work because our theme will provide this template.
  38. 38. 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') }}
  39. 39. 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
  40. 40. 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.
  41. 41. Ignore missing templates {{ include('template.twig', ignore_missing = true) }} ! {{ source('template.twig', ignore_missing = true) }} ! {% embed 'template.twig' ignore missing %} ... {% endembed %}
  42. 42. 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
  43. 43. 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.
  44. 44. Declare filters as deprecated new Twig_SimpleFilter('old_filter', ..., array( 'deprecated' => true, 'alternative' => 'new_filter' ));
  45. 45. Declare filters as deprecated new Twig_SimpleFilter('old_filter', ..., array( 'deprecated' => true, 'alternative' => 'new_filter' )); These deprecations notices are not displayed or logged anywhere on Drupal yet. NOTE
  46. 46. Avoid missing blocks {% if 'title' is block %} <title>{{ block('title') }}<title> {% endif %}
  47. 47. 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 %}
  48. 48. WHITE SPACES
  49. 49. WHY IS THIS IMPORTANT?? Because it will make your templates more readable and it will save you time.
  50. 50. <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
  51. 51. <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
  52. 52. <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
  53. 53. <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
  54. 54. <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
  55. 55. <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
  56. 56. 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>
  57. 57. Please, don't waste your time dealing with white spaces.
  58. 58. ! Twig templates should be readable HTML pages should not !
  59. 59. ! Twig templates should be readable HTML pages should not this is where you work everyday ! browsers get a minimized and compressed HTML mess
  60. 60. Sometimes you should add white spaces…
  61. 61. White spaces around HTML attributes <h2{{ title_attributes }}>{{ label }}</h2> core/modules/block/templates/block.html.twig
  62. 62. White spaces around HTML attributes <h2{{ title_attributes }}>{{ label }}</h2> core/modules/block/templates/block.html.twig
  63. 63. 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>
  64. 64. Add white spaces to separate Twig & HTML <h2 {{ title_attributes }}>{{ label }}</h2> ! ! <h2 class="..."> ... </h2> <h2 > ... </h2>
  65. 65. Add white spaces to separate Twig & HTML <h2 {{ title_attributes }}>{{ label }}</h2> ! ! <h2 class="..."> ... </h2> <h2 > ... </h2> white space when the attributes are empty
  66. 66. 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
  67. 67. 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
  68. 68. 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
  69. 69. Hiding HTML code inside Twig strings <div id="site-name"{{ hide_name ? ' class="hidden"' }}> ! ! ! ! ! <div id="site-name"> <div id="site-name" class="hidden"> WARNING HTML attributes defined in Twig strings are easy to overlook core/themes/bartik/templates/maintenance-page.html.twig
  70. 70. Hiding HTML code inside Twig strings <div id="site-name"{{ hide_name ? ' class="hidden"' }}> ! ! ! ! ! <div id="site-name"> <div id="site-name" class="hidden"> WARNING HTML attributes defined in Twig strings are easy to overlook core/themes/bartik/templates/maintenance-page.html.twig DANGER If you miss this single white space, the page design breaks
  71. 71. 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
  72. 72. 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
  73. 73. DEBUG
  74. 74. WHY IS THIS IMPORTANT?? Because it will save you a lot of time while developing your templates.
  75. 75. Configure Twig behavior # sites/default/services.yml parameters: twig.config: debug: true auto_reload: null cache: true
  76. 76. 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
  77. 77. 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"> ! <!-- ... -->
  78. 78. 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=/
  79. 79. 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' -->
  80. 80. 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…
  81. 81. 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…
  82. 82. Which variables are passed to the template? Built-in templates include comments with the full list of variables passed to the Twig template.
  83. 83. Easier way to introspect all variables <pre> {{ dump() }} </pre>
  84. 84. Easier way to introspect all variables <pre> {{ dump() }} </pre> It dumps the contents of all the variables defined in the template.
  85. 85. It's better to dump just the variables you need <pre> {{ dump(label, title_attributes) }} </pre>
  86. 86. It's better to dump just the variables you need <pre> {{ dump(label, title_attributes) }} </pre> It dumps only the given variables
  87. 87. CAUTION! Don't forget to rebuild your cache after changing the config files and templates.
  88. 88. drupalconsole.com
  89. 89. drupalconsole.com $ drupal cache:rebuild $ drupal c:r
  90. 90. ESCAPING
  91. 91. WHY IS THIS IMPORTANT?? Because it can prevent you a lot of security-related problems.
  92. 92. CAUTION! Drupal has replaced the default Twig escaping filter by their own.
  93. 93. By default, contents are escaped for HTML Hi {{ content }}! $content = '<strong>John</strong>';
  94. 94. 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>!
  95. 95. The "raw" filter prevents the escaping Hi {{ content|raw }}! $content = '<strong>John</strong>';
  96. 96. The "raw" filter prevents the escaping Hi {{ content|raw }}! $content = '<strong>John</strong>'; What you expect… Hi John! What you get… Hi John!
  97. 97. What if contents are used in URLs or JS? <a href="...?param={{ value }}"></a> ! ! ! <script> var variable = "{{ content }}"; </script>
  98. 98. What if contents are used in URLs or JS? <a href="...?param={{ value }}"></a> ! ! ! <script> var variable = "{{ content }}"; </script> WRONG HTML ESCAPING
  99. 99. Applying different escaping strategies <a href="...?param={{ value|e('url') }}"></a> ! ! ! <script> var variable = "{{ content|e('js') }}"; </script>
  100. 100. Escaping strategies available in Twig {{ content|e('html') }} {{ content|e('js') }} {{ content|e('css') }} {{ content|e('url') }} {{ content|e('html_attr') }}
  101. 101. REUSING TEMPLATES
  102. 102. WHY IS THIS IMPORTANT?? Because it allows you to avoid repeating code and it makes your themes easier to maintain.
  103. 103. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  104. 104. How often are these alternatives used {% embed %} {% extends %} include( ) {% set %} {% use %} macro( ) Always Sometimes Rarely
  105. 105. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  106. 106. Use {% extends %} to share layouts
  107. 107. Use {% extends %} to share layouts 1 layout with the common design elements
  108. 108. Use {% extends %} to share layouts 1 layout with the common design elements + 4 simple pages which only define their contents
  109. 109. layout.twig <!DOCTYPE html> <html> <head> <title> {% block title %}ACME website{% endblock %} </title> </head> <body> <div class="container"> {% block content %}{% endblock %} </div> </body> </html>
  110. 110. layout.twig <!DOCTYPE html> <html> <head> <title> {% block title %}ACME website{% endblock %} </title> </head> <body> <div class="container"> {% block content %}{% endblock %} </div> </body> </html>
  111. 111. Other templates can reuse this layout {% extends 'layout.twig' %} ! {% block title %}Community{% endblock %} {% block content %} <div> ... </div> {% endblock %}
  112. 112. 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.twig extends layout.twig extends schedule.twig
  113. 113. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  114. 114. 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
  115. 115. When should you use include( ) • To reuse large fragments of code, such as sidebars, navigation menus, etc.
  116. 116. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  117. 117. 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
  118. 118. 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 %}
  119. 119. 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>
  120. 120. 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)
  121. 121. 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.
  122. 122. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  123. 123. Grid-based design embed allows to reuse inner page structures (e.g. the 3-column grid)
  124. 124. Grid-based design embed allows to reuse inner page structures (e.g. the 3-column grid)
  125. 125. You can't use "include" to solve this problem {{ include('common/grid_3.twig') }}
  126. 126. 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)
  127. 127. You can't use "extends" to solve this problem {% extends 'common/grid_3.twig' %} ! {% block column1 %} ... {% endblock %} {% block column2 %} ... {% endblock %} {% block column3 %} ... {% endblock %}
  128. 128. 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
  129. 129. 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>
  130. 130. Reuse the three-column grid template {% embed 'common/grid_3.twig' %} {% block column1 %} ... contents ... {% endblock %} ! {% block column2 %} ... contents ... {% endblock %} ! {% block column3 %} ... contents ... {% endblock %} {% endembed %}
  131. 131. When should you use {% embed %} • To reuse page structures across different templates (e.g. grids)
  132. 132. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  133. 133. Reusing fragments with {% set %} {% set navigation %} <a href="...">Previous</a> ... ... <a href="...">Next</a> {% endset %}
  134. 134. Reusing fragments with {% set %} {% set navigation %} <a href="...">Previous</a> ... ... <a href="...">Next</a> {% endset %} {{ navigation }} {{ navigation }}
  135. 135. 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.
  136. 136. Lots of different ways to reuse templates {% embed %} {% extends %}include() {% set %} {% use %}macro()
  137. 137. When should you use {% use %} • This is too advanced and for very specific use cases. • You should probably never use it when creating themes.
  138. 138. DYNAMIC TEMPLATES
  139. 139. WHY IS THIS IMPORTANT?? Because Drupal allows to create sites with very advanced needs.
  140. 140. Templates created on-the-fly {% set code = 'Hi {{ name }}' %} {% set template = template_from_string(code) %} ! {{ include(template) }}
  141. 141. 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
  142. 142. Templates created on-the-fly {{ include(template_from_string( 'Hi {{ name }}' )) }}
  143. 143. 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) }}
  144. 144. 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.
  145. 145. Imagine a site which allows this customization Section 1 Section 2 Default design
  146. 146. 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.
  147. 147. 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.
  148. 148. Twig Sandbox in practice {% sandbox %} {{ include(section.name ~ '/sidebar.twig') }} {% endsandbox %} ! ! {{ include(section.name ~ '/sidebar.twig', sandboxed = true) }}
  149. 149. 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.
  150. 150. 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 );
  151. 151. DATES
  152. 152. WHY IS THIS IMPORTANT?? Because dealing with dates is not easy and Twig can perform a lot of operations on dates.
  153. 153. Timezones support {{ 'now'|date(timezone='Asia/Tokyo') }} ! {{ 'now'|date(timezone=user.timezone) }}
  154. 154. Comparing dates {% if event.startsAt > date('now') %} Buy tickets {% endif %}
  155. 155. Comparing dates {% if event.startsAt > date('now') %} Buy tickets {% endif %} NOTE This is the date( ) function, not the date filter
  156. 156. 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 }}
  157. 157. COOL FEATURES
  158. 158. These are some of the features that put Twig years ahead of PHP
  159. 159. Useful filters for collections {{ user.friends|first }} {{ event.sessions|last }}
  160. 160. Useful tests for strings {% if url starts with 'https://' %} {% endif %} ! {% if file_path ends with '.pdf' %} {% endif %} ! {% if phone matches '/^[d.]+$/' %} {% endif %}
  161. 161. REJECTED BY PHP Useful tests for strings {% if url starts with 'https://' %} {% endif %} ! {% if file_path ends with '.pdf' %} {% endif %} ! {% if phone matches '/^[d.]+$/' %} {% endif %}
  162. 162. The "in" operator {% if password in username %} BAD PASSWORD {% endif %} ! {% if method in ['GET', 'POST'] %} ... {% endif %}
  163. 163. The "in" operator {% if password in username %} BAD PASSWORD {% endif %} ! {% if method in ['GET', 'POST'] %} ... {% endif %} REJECTED BY PHP
  164. 164. Named parameters {{ content|convert_encoding('UTF-8', 'iso-2022-jp') }}
  165. 165. Named parameters {{ content|convert_encoding('UTF-8', 'iso-2022-jp') }} which is the original charset and which one the target charset?
  166. 166. 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' ) }}
  167. 167. 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' ) }}
  168. 168. 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' ) }}
  169. 169. Named parameters {{ include('template.html', {}, true, true) }} ! ! ! {{ include('template.html', ignore_missing = true) }} template variables with_context ignore_missing
  170. 170. It's common to do things in batches 1 Image gallery 2 3 4 5 6
  171. 171. 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>
  172. 172. 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 %}
  173. 173. 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
  174. 174. 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 %}
  175. 175. 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 %}
  176. 176. Short ternary operator $result = $condition ? 'is true';
  177. 177. Short ternary operator $result = $condition ? 'is true'; ERROR Parse error: syntax error, unexpected ';' on line 1
  178. 178. Short ternary operator $result = $condition ? 'is true'; ERROR Parse error: syntax error, unexpected ';' on line 1 {{ condition ? 'is true' }}
  179. 179. 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' }}
  180. 180. Short ternary operator <li class="{{ condition ? 'selected' }}"> ... </li> ! <li class="{{ condition ? 'selected' : '' }}"> ... </li>
  181. 181. Short ternary operator <li class="{{ condition ? 'selected' }}"> ... </li> ! <li class="{{ condition ? 'selected' : '' }}"> ... </li> always use this
  182. 182. 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
  183. 183. 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
  184. 184. The "loop" magic variable Everyone needs an $i variable inside the for loop. So Twig provides you this and other useful variables.
  185. 185. The "loop" variable exists only inside the "for" {% for ... in collection %} {{ loop.index }} {{ loop.index0 }} {{ loop.first }} {{ loop.last }} {{ loop.length }} {% endfor %}
  186. 186. 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
  187. 187. {{ product.photo|image(400, 150, 0.9) }} The problem with filter arguments
  188. 188. {{ product.photo|image(400, 150, 0.9) }} The problem with filter arguments What if I need to define more arguments?
  189. 189. {{ 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?
  190. 190. {{ 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
  191. 191. Defining a filter with lots or arguments $filter = new Twig_SimpleFilter('image', function ( $path, $width, $height, $opacity ) { $path = ... $width = ... $height = ... $opacity = ... });
  192. 192. $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
  193. 193. $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)
  194. 194. $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)
  195. 195. TO SUM UP
  196. 196. «Using Twig templates is the best decision Drupal ever made»
  197. 197. Drupal 8 templates are safe, concise, modern and consistent. Twig
  198. 198. Drupal 8 templates are safe, concise, modern and consistent.
  199. 199. Drupal 8 themes
  200. 200. REFERENCES
  201. 201. References • Official Twig documentation
 twig.sensiolabs.org/documentation
 • Twig in Drupal 8
 drupal.org/theme-guide/8/twig
  202. 202. CONTACT
  203. 203. Contact info • javier.eguiluz@sensiolabs.com • github.com/javiereguiluz • linkedin.com/in/javiereguiluz ! ! !

×