On Github DrupalTwig / ThemeSystemDrupalNorth2015
1:30 - 5:00
Now we can hit 'B' and gauge the personas in the audience. Ask people to raise their hand if they are:
…is gone!
5:00 - 8:00
…are being converted to templates…(and other formats).
Only 11 remaining!
8:00 - 11:00
Theme functions are so Drupal 7 (effectively deprecated, people will yell at you) Markup belongs in templates, not in PHP strings. Your IDE/Text Editor will reward you by syntax highlighting markup and much less string concatenation and quote escaping is needed. We have 11 left to convert, with an RC deadline. We converted or removed around 130 already.Drupal 7:
/** * Implements hook_preprocess_HOOK() for node templates. */ function MYTHEME_preprocess_node(&$variables) { $variables['theme_hook_suggestions'][] = 'node__' . 'my_first_suggestion'; $variables['theme_hook_suggestions'][] = 'node__' . 'my_second_more_specific_suggestion'; }
Drupal 8:
/** * Implements hook_theme_suggestions_HOOK_alter() for node templates. */ function MYTHEME_theme_suggestions_node_alter(array &$suggestions, array $variables) { $suggestions[] = 'node__' . 'my_first_suggestion'; $suggestions[] = 'node__' . 'my_second_suggestion'; }
11:00 - 14:00
In Drupal 7 you could manipulate theme suggestions during preprocess, but by this point it might already be too late, or you've already done unnecessary preprocessing. Drupal 8 splits out the manipulation of theme hook (template) suggestions into separate hooks. You can see template suggestions in action when we talk about Twig debug in a few minutes.$variables['list'] = theme('item_list', array( 'items' => $items, ));
$variables['list'] = [ '#theme' => 'item_list', '#items' => $items, ];
14:00 - 16:00
Instead of the list variable being a flat string of HTML, it's a render array that gets passed to the template and rendered from there.
<div{{ attributes }}>
<div {{ attributes }}>
<div✖{{ attributes }}>
<div class="myclass {{ attributes.class }}"{{ attributes|without('class') }}>
16:00 - 19:00
Attributes contain the attributes and also know how to print them properly. If you are printing the entire set of attributes, don't prepend a space.
<div{{ attributes.addClass('hello').removeClass('goodbye') }}>
{% if attributes.hasClass('field-label-inline') %} {# Do something special here. #} {% endif %}
19:00 - 21:00
<div{{ attributes.setAttribute('id', 'eye-d') }}>
<div{{ attributes.removeAttribute('id') }}>
21:00 - 21:30
// We hide the comments and links // to print them later. hide($content['comments']); hide($content['links']); print render($content); // Render calls show() on the element. print render($content['links']); // To get back links with the content. show($content['links']); // Prints content with links yet // without comments :( print render($content);
{# Print without comments and links #} {{ content|without('comments', 'links') }} {# Print only links #} {{ content.links }} {# Print everything without comment! #} {{ content|without('comments') }} {# Print everything YAY :) #} {{ content }}
21:30 - 23:00
Did you notice we snuck that in? 😉
23:00 - 25:00
services.yml:
parameters: twig.config: debug: true
Example output:
<!-- THEME DEBUG --> <!-- THEME HOOK: 'block' --> <!-- FILE NAME SUGGESTIONS: * block--bartik-powered.html.twig * block--system-powered-by-block.html.twig * block--system.html.twig x block.html.twig --> <!-- BEGIN OUTPUT from 'core/modules/block/templates/block.html.twig' --> <div class="block block-system contextual-region" id="block-bartik-powered" role="complementary"> <div data-contextual-id="block:block=bartik_powered:"></div> <div class="content"> <span>Powered by <a href="http://drupal.org">Drupal</a></span> </div> </div> <!-- END OUTPUT from 'core/modules/block/templates/block.html.twig' -->
25:00 - 27:00
settings.php:
$conf['theme_debug'] = TRUE;
Example output:
<!-- THEME DEBUG --> <!-- CALL: theme('block') --> <!-- FILE NAME SUGGESTIONS: * block--system--powered-by.tpl.php * block--system.tpl.php * block--footer.tpl.php x block.tpl.php --> <!-- BEGIN OUTPUT from 'modules/block/block.tpl.php' --> <div id="block-system-powered-by" class="block block-system"> <div class="content"> <span>Powered by <a href="https://www.drupal.org">Drupal</a></span> </div> </div> <!-- END OUTPUT from 'modules/block/block.tpl.php' -->
27:00 - 28:00
https://github.com/DrupalTwig/sandwich
28:00 - 29:00
We were hungry when we came up with this example.
/** * Implements hook_theme(). */ function sandwich_theme() { return [ 'sandwich' => [ 'variables' => [ 'attributes' => [], 'name' => '', 'bread' => '', 'cheese' => '', 'veggies' => [], 'protein' => '', 'condiments' => [], ], ], ]; }
/** * Builds a sandwich. */ public function build() { return [ '#theme' => 'sandwich', '#name' => $this->t('Chickado'), '#attributes' => [ 'id' => 'best-sandwich', 'style' => 'float: left;', 'class' => ['left', 'clearfix'], ], '#bread' => $this->t('Sourdough'), '#cheese' => $this->t('Gruyère'), '#veggies' => [$this->t('Avocado'), $this->t('Red onion'), $this->t('Romaine')], '#protein' => $this->t('Chicken'), '#condiments' => [$this->t('Mayo'), $this->t('Dijon')], ]; }
Pass in variables using #-prefixed keys.
- 31:00
<section{{ attributes }}> <h2>{{ name }}</h2> {% if bread %} <p><strong>Bread:</strong> {{ bread }}</p> {% endif %} <strong>Vegetables:</strong> <ul> {% for veg in veggies %} <li>{{ veg }}</li> {% endfor %} </ul> {% if condiments %} <strong>Condiments:</strong> <ul> {% for condiment in condiments %} <li>{{ condiment }}</li> {% endfor %} </ul> {% endif %} </section>
That's the bare minimum for most practical use cases. But what if you need to do more, or if you're altering themeable output that someone else has defined? Let's dig a bit deeper.
Don't make a template for condiments. Real life example: toolbar.
33:00 - 37:00
drupal_render() is called on the render array. Any defined #pre_render callbacks are called. drupal_render() calls the now internal _theme() function.
After _theme() does its thing, #post_render callbacks can massage the resulting string.
37:00 - 38:00
Overall pretty much the same thing, just drupal_render() and theme() got moved to service classes.
{{ sandwich.cheese }}
// Array key. $sandwich['cheese']; // Object property. $sandwich->cheese; // Also works for magic get (provided you implement magic isset). $sandwich->__isset('cheese'); && $sandwich->__get('cheese'); // Object method. $sandwich->cheese(); // Object get method convention. $sandwich->getCheese(); // Object is method convention. $sandwich->isCheese(); // Method doesn't exist/dynamic method. $sandwich->__call('cheese');
38:00 - 40:00
Show Kint demo.
$user->field_first_name = "<script>alert('XSS')</script>";
BAD <?php print $user->field_first_name; ?>
GOOD <?php print check_plain($user->field_first_name); ?>
GOOD {{ user.field_first_name }}
BAD {{ user.field_first_name|raw }}
@Cottser
IRC: #drupal-twig
Twitter: #drupaltwig