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



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

0 0


ThemeSystemBadCamp2014


On Github DrupalTwig / ThemeSystemBadCamp2014

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
  • Other!

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…(and other formats).

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 20 left to convert.

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.

Attributes

All the attributes:
<div{{ attributes }}>
Please don't do this, you will end up with yucky whitespace:
<div {{ attributes }}>
<div✖{{ attributes }}>
Splitting out the class attribute:
<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.

Attributes cont'd

Class manipulation:
<div{{ attributes.addClass('hello').removeClass('goodbye') }}>
Testing:
{% if attributes.hasClass('field-label-inline') %}
  {# Do something special here. #}
{% endif %}

19:00 - 21:00

Print what you want, when you want

Drupal 7:

<?php
// We hide the comments and links now so that we can render them later.
hide($content['comments']);
hide($content['links']);
print render($content);
?>

Drupal 8:

{# We give you what you ask for. #}
{{ content|without('comments', 'links') }}

21:00 - 23:00

Oh yeah, and Twig!

23:00 - 25: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.

services.yml:

parameters:
  twig.config:
    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' -->
						

25:00 - 27:00

Drupal 7.33!

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

Sandwiches.

https://github.com/DrupalTwig/sandwich

28:00 - 29: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' is the default in Drupal 8!
      'template' => 'sandwich',
    ),
  );
}
						
<?php
/**
 * Implements hook_theme().
 */
function sandwich_theme() {
  return [
    'sandwich' => [
      'variables' => [
        'attributes' => [],
        'name' => '',
        'bread' => '',
        'cheese' => '',
        'veggies' => [],
        'protein' => '',
        'condiments' => [],
      ],
    ],
  ];
}
						

Build your render array

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

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

33:00 - 38: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;
// 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

  • checks if sandwich is an array and cheese a valid element;
  • if not, and if sandwich is an object, checks that cheese is a valid property;
  • if not, and if sandwich is an object, checks that cheese is a valid method;
  • if not, and if sandwich is an object, checks that getBar is a valid method;
  • if not, and if sandwich is an object, checks that isBar is a valid method;
  • if not, returns a null value.

Thanks!

@Cottser

@joelpittet

drupaltwig.org

IRC: #drupal-twig

Twitter: #drupaltwig

Questions?