Drupal 8 Theme System – hook_theme() to Twig template – Scott Reeves



Drupal 8 Theme System – hook_theme() to Twig template – Scott Reeves

1 0


ThemeSystemAustin2014

Slides for DrupalCon Austin 2014 presentation - Drupal 8 Theme System: hook_theme() to Twig template

On Github DrupalTwig / ThemeSystemAustin2014

Drupal 8 Theme System

hook_theme() to Twig template

Scott Reeves & Joël Pittet

Scott Reeves

@Cottser
  • Developer at Digital Echidna
  • Drupal 8 theme system co-maintainer
  • Drupal core mentor
  • Likes beans

Joël Pittet

  • Building Websites since 2001ish
  • Front and Back End Developer
  • Drupal 8 Core Theme System Maintainer
  • And obviously love perogies
@joelpittet

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:

  • Site builders
  • Themers
  • Developers
  • DevOps

Drupal 8 theme layer changes

Template process layer

…is gone!

5:00 - 8:00

  • The main reason for the process layer was to flatten strings into HTML.
  • In Drupal 8 we prefer "lazy" rendering things as late as possible, which usually gets called from the template.

Theme functions

…are being converted to Twig templates.

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.

Theme suggestion hooks

Drupal 7:

<?php
/**
 * 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:

<?php
/**
 * 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.

Goodbye theme(), hello render arrays

Drupal 7:

<?php
$variables['list'] = theme('item_list', array(
  'items' => $items,
));

Drupal 8:

<?php
$variables['list'] = array(
  '#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.

Oh yeah, and Twig!

16:00 - 18:00

  • Twig is a flexible, fast, and secure template engine for PHP.
  • The Twig engine and initial integration code was committed during BADCamp in November 2012.
  • The conversions of .tpl.php to .html.twig files were committed during DrupalCon Portland in May 2013 and Twig is now the default template engine for Drupal 8.

settings.php:

$settings['twig_debug'] = TRUE;

Example output:

<!-- THEME DEBUG -->
<!-- CALL: _theme('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' -->
					

18:00 - 20:00

Sandwiches.

https://github.com/DrupalTwig/sandwich

20:00 - 21:00

We were hungry when we came up with this example.

Define using hook_theme()

<?php
/**
 * Implements hook_theme().
 */
function sandwich_theme() {
  return array(
    'sandwich' => array(
      'variables' => array(
        'attributes' => array(),
        'name' => '',
        'bread' => '',
        'cheese' => '',
        'veggies' => array(),
        'protein' => '',
        'condiments' => array(),
      ),
      'template' => 'sandwich',
    ),
  );
}
						

Build your render array

<?php
/**
 * Builds a sandwich.
 */
public function build() {
  return array(
    '#theme' => 'sandwich',
    '#name' => $this->t('Chickado'),
    '#attributes' => array(
      'id' => 'best-sandwich',
      'style' => 'float: left;',
      'class' => array('left', 'clearfix'),
    ),
    '#bread' => $this->t('Sourdough'),
    '#cheese' => $this->t('Gruyère'),
    '#veggies' => array($this->t('Avocado'), $this->t('Red onion'), $this->t('Romain')),
    '#protein' => $this->t('Chicken'),
    '#condiments' => array($this->t('Mayo'), $this->t('Dijon')),
  );
}
						

Pass in variables using #-prefixed keys.

- 24:00

Markup your Twig template

<section{{ attributes }}>
  <h2>{{ name }}</h2>
  {% if bread %}
    <p><strong>Bread:</strong> {{ bread }}</p>
  {% endif %}

  {% if protein %}
    <p><strong>Protein:</strong> {{ protein }}</p>
  {% endif %}

  {% if cheese %}
    <p><strong>Cheese:</strong> {{ cheese }}</p>
  {% endif %}

  {% if veggies %}
    <strong>Vegetables:</strong>
    <ul>
      {% for veg in veggies %}
        <li>{{ veg }}</li>
      {% endfor %}
    </ul>
  {% endif %}

  {% if condiments %}
    <strong>Condiments:</strong>
    <ul>
      {% for condiment in condiments %}
        <li>{{ condiment }}</li>
      {% endfor %}
    </ul>
  {% endif %}
</section>
						

Voilà!

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.

Overview of rendering flow

drupal_render() #pre_render _theme() Theme suggestion hooks Preprocess functions Template is rendered #post_render

28:00 - 33: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.

Twig magic

{{ sandwich.cheese }}

// Array key.
$sandwich['cheese'];
// Object property.
$sandwich->cheese;
// Object method.
$sandwich->cheese();
// Object get method convention.
$sandwich->getCheese();
// Object is method convention.
$sandwich->isCheese();
// Object dynamic object property is set and get property.
$sandwich->__isset('cheese');
$sandwich->__get('cheese');

33:00 - 35:00

  • checks if foo as an array and bar a valid element;
  • if not, and if foo is an object, checks that bar is a valid property;
  • if not, and if foo is an object, checks that bar is a valid method (even if bar is the constructor - uses __construct() instead);
  • if not, and if foo is an object, checks that getBar is a valid method;
  • if not, and if foo is an object, checks that isBar is a valid method;
  • if not, returns a null value.

Attributes

All the attributes:
<div{{ attributes }}>
Splitting out the class attribute:
<div class="myclass {{ attributes.class }}"{{ attributes|without('class') }}>

35:00 - 38:00

Thanks!

@Cottser

@joelpittet

drupaltwig.org

IRC: #drupal-twig

Twitter: #drupaltwig