Drupal 8 The Backend of Frontend

Lauri Eskola (lauriii)

Twitter: @laurii1

Lauri Eskola

  • Drupal Theme System co-maintainer
  • Working for Druid
  • I love kittens (added 68 of them to Drupal 8 core and counting...)
  • ... and I like to break Bartik.

Twitter: @laurii1

Drupal Theme System

Flexible way to output markup

Drupal 8 ships with simplified theme layer

Drupal 8 - Be lazy!

Laziness is the first step towards efficiency.

 Patrick Bennett

Drupal 7 Template process layer

<?php

function template_process_html(&$variables) {
  // Render page_top and page_bottom into top level variables.
  $variables['page_top'] = drupal_render($variables['page']['page_top']);
  $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);
  // Place the rendered HTML for the page body into a top level variable.
  $variables['page']              = $variables['page']['#children'];
  $variables['page_bottom'] .= drupal_get_js('footer');

  $variables['head']    = drupal_get_html_head();
  $variables['css']     = drupal_add_css();
  $variables['styles']  = drupal_get_css();
  $variables['scripts'] = drupal_get_js();
}

Wastes resources if something doesn't get used inside the template (or theme function)

Template process layer has been removed.

https://www.drupal.org/node/2038981

Everything is a render array!

Drupal 8

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

  $variables['another_list'] = array(
    '#theme' => 'item_list',
    '#items' => $items,
  );

Drupal 7

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

  {{ list }}
  <?php

  print $list;
  print render($another_list);
<?php

function theme_item_list($variables) {
  $items = $variables['items'];
  $title = $variables['title'];
  $type = $variables['type'];
  $attributes = $variables['attributes'];

  // Only output the list container and title, if there are any list items.
  // Check to see whether the block title exists before adding a header.
  // Empty headers are not semantic and present accessibility challenges.
  $output = '<div class="item-list">';
  if (isset($title) && $title !== '') {
    $output .= '<h3>' . $title . '</h3>';
  }

  if (!empty($items)) {
    $output .= "<$type" . drupal_attributes($attributes) . '>';
    $num_items = count($items);
    $i = 0;
    foreach ($items as $item) {
      $attributes = array();
      $children = array();
      $data = '';
      $i++;
      if (is_array($item)) {
        foreach ($item as $key => $value) {
          if ($key == 'data') {
            $data = $value;
          }
          elseif ($key == 'children') {
            $children = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $data = $item;
      }
      if (count($children) > 0) {
        // Render nested list.
        $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
      }
      if ($i == 1) {
        $attributes['class'][] = 'first';
      }
      if ($i == $num_items) {
        $attributes['class'][] = 'last';
      }
      $output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n";
    }
    $output .= "</$type>";
  }
  $output .= '</div>';
  return $output;
}
  
  {%- if items or empty -%}
    <div class="item-list">
      {%- if title is not empty -%}
        <h3>{{ title }}</h3>
      {%- endif -%}
      {%- if items -%}
        <{{ list_type }}{{ attributes }}>
          {%- for item in items -%}
            <li{{ item.attributes }}>{{ item.value }}</li>
          {%- endfor -%}
        </{{ list_type }}>
      {%- else -%}
        {{- empty -}}
      {%- endif -%}
    </div>
  {%- endif %}

All 154 Theme functions have been converted to templates

And theme functions will be deprecated in 8.0.x

Drupal 8 Theme System pipeline

Code examples:

Drupal 8 Theme System pipeline

hook_theme()

  
  <?php

  /**
   * Implements hook_theme().
   */
  function sandwich_theme() {
    return [
      'sandwich' => [
        'variables' => [
          'attributes' => [],
          'name' => '',
          'bread' => '',
          'cheese' => '',
          'veggies' => [],
          'protein' => '',
          'condiments' => [],
        ],
      ],
    ];
  }

Build your render array

  
  <?php

  public function build() {
    return [
      '#theme' => 'sandwich',
      '#name' => $this->t('Chickado'),
      '#attributes' => [
        'id' => 'best-sandwich',
        'class' => ['menu--left', 'clearfix'],
      ],
      '#bread' => $this->t('Sourdough'),
      '#cheese' => $this->t('Gruyère'),
      '#veggies' => [
        $this->t('Avocado'),
        $this->t('Red onion'),
      ],
      '#protein' => $this->t('Chicken'),
      '#condiments' => [
        $this->t('Mayo'),
        $this->t('Dijon'),
      ],
      '#attached' => ['library' => ['sandwich/flavour']],
    ];
  }

Build your render array with render element

  
  <?php

  public function build() {
    return [
      '#type' => 'sandwich',
      '#name' => $this->t('Chickado'),
      '#attributes' => [
        'id' => 'best-sandwich',
        'class' => ['menu--left', 'clearfix'],
      ],
      '#bread' => $this->t('Sourdough'),
      '#cheese' => $this->t('Gruyère'),
      '#veggies' => [
        $this->t('Avocado'),
        $this->t('Red onion'),
      ],
      '#protein' => $this->t('Chicken'),
    ];
  }
<?php

/**
 * Provides a render element for displaying some delicious sandwiches.
 *
 * @RenderElement("sandwich")
 */
class Sandwich extends RenderElement {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    return [
      '#theme' => 'sandwich',
      '#attached' => [
        'library' => [
          'sandwich/flavour'
        ],
      ],
      '#bread' => $this->t('Sourdough'),
      '#condiments' => [
        $this->t('Mayo'),
        $this->t('Dijon'),
      ],
    ];
  }
}

Drupal 8 Theme System pipeline

Theme suggestions

node.html.twig

node--article.html.twig

hook_theme_suggestion and hook_theme_suggestion_alter

  
  <?php

  /**
   * Implements hook_theme_suggestions_sandwich().
   */
  function sandwich_theme_suggestions_sandwich($variables) {
    return 'sandwich__' . strtolower($variables['name']);
  }
  
  /**
   * Implements hook_theme_suggestions_sandwich_alter().
   */
  function sandwich_theme_suggestions_sandwich_alter(&$suggestions, $variables) {
  }

sandwich--chickado.html.twig

sandwich--yummy.html.twig

Drupal 8 Theme System pipeline

template_preprocess and 

hook_preprocess

  
  <?php

  /**
   * Implements template_preprocess_sandwich().
   */
  function template_preprocess_sandwich(&$variables) {
    $variables['name'] = 'Kitten';
  }
  
  /**
   * Implements hook_preprocess_sandwich().
   */
  function sandwich_preprocess_sandwich(&$variables) {
    $variables['name'] = 'Llama';
  }
  
  /**
   * Implements hook_preprocess_sandwich__chickado().
   */
  function sandwich_preprocess_sandwich__chickado(&$variables) {
    $variables['name'] = 'Flamingo';
  }

Drupal 8 Theme System pipeline

sandwich.html.twig


  <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>Veggies:</strong>
      <ul>
        {% for veg in veggies %}
          <li>{{ veg }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  
    {% if condiments %}
      <strong>Accoutrement:</strong>
      <ul>
        {% for condiment in condiments %}
          <li>{{ condiment }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  </section>

Download the code:

Twig - what you really need to know.

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');
              

Twig filters

Meant to manipulate a variable. Takes the first parameter from the variable before "|".


  {% set text = 'Kitten' %}
  {# Print variable using lenght filter. #}
  {{ text|length }}

Example

Returns

  
  5

Twig functions

Functions in Twig are like PHP functions


 {{ attach_library('color/drupal.color') }}

Example

Print what you want, when you want

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

Drupal 7

Drupal 8

 

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

Sometimes what Twig has built inside might not be enough...

How to create a Twig extension

1. Create a Twig extension class


<?php
  /**
   * A class providing my own Twig extension.
   */
  class TrimString extends \Twig_Extension {
    /**
     * {@inheritdoc}
     */
    public function getFilters() {
      return [
        new \Twig_SimpleFilter('trim_string', [$this, 'trimString']),
      ];
    }

    public function trimString($string, $length = 10) {
      return substr($string, 0, $length);
    }
  }
              

2. Tell Twig about your class


  services:
    trim_string.twig.trimstring:
      class: Drupal\trim_string\TwigExtension\TrimString
      tags:
        - { name: twig.extension }
              

trim_string.services.yml

3. Profit!


  {% set variable = 'kittens are cool in the winter' %}
  {{ variable|trim_string(16) }}

  kittens are cool

Returns

Example:

https://github.com/lauriii/trim-string

Autoescape

Markup should live inside a Twig template. Not in PHP!

Rule #1

What is escaping?


  <?php

  use Drupal\Component\Utility\Html

  print Html::escape('Some escaped <em>text</em>');

Prints markup in HTML


 Some escaped &lt;em&gt;text&lt;/em&gt;

Some escaped <em>text</em>

Prints in the browser

What is Autoescaping?


  {{ text }}

Prints markup in HTML


 Some escaped &lt;em&gt;Kittens&lt;/em&gt;

Some escaped <em>Kittens</em>

  
  <?php

  function bartik_preprocess_page(&$variables) {
    $variables['text'] = '<em>Kittens</em>';
  }

Prints in the browser

How to avoid autoescaping?


  {{ text }}

Prints markup in HTML


 Some escaped <em>Kittens</em>

Some escaped kittens

  
  <?php

  function bartik_preprocess_page(&$variables) {
    $variables['text']['#markup'] = '<em>Kittens</em>';
  }

Prints in the browser

How to avoid autoescaping?


  {{ text }}

Prints markup in HTML


  Some escaped <em>Kittens</em>

Some escaped kittens


  <?php
  
  use Drupal\Component\Render\FormattableMarkup;
  
  function bartik_preprocess_page(&$variables) {
    $variables['text'] = new FormattableMarkup('<em>@txt</em>', ['@txt' => 'Kittens']);
  }

Prints in the browser

But it has also its caveats...

When autoescaped strings are safe?

Whenever the escaped string is being printed in HTML node.

Attributes are NOT HTML!


  <?php
  
  use Drupal\Component\Render\FormattableMarkup;
  
  new FormattableMarkup('<em@txt></em>', ['@txt' => 'Kittens']);

  new FormattableMarkup('<a href="@url"></a>', ['@url' => 'http://kittens.com']);
  new FormattableMarkup('<a href="@url"></a>', [
    '@url' => 'javascript:alert(String.fromCharCode(88,83,83))'
  ]);

These are all dangerous:

New placeholder for URLs

  <?php
  
  use Drupal\Component\Render\FormattableMarkup;

  new FormattableMarkup('<a href=":url"></a>', [
    ':url' => 'javascript:alert(String.fromCharCode(88,83,83))'
  ]);

This is safe:

Array keys

  <?php
  
  use Drupal\Core\StringTranslation\TranslatableMarkup;

  // These two do the same thing.
  $text = new TranslatableMarkup('Translate me');
  $text = t('Translate me');
  $array[$text] = $text;
  <?php
  
  use Drupal\Core\StringTranslation\TranslatableMarkup;

  $text = new TranslatableMarkup('Translate me');
  $array[(string) $text] = $text;

This will fatal:

This works:

Autoescaping is only enabled  for Twig templates

Which means using any custom templating engine or Theme functions are not autoescaped

That's why PHPTemplate was overtaken by the amazing Nyan Cat templating engine.

https://www.drupal.org/node/2575199

Questions?

Twitter: @laurii1

Made with Slides.com