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 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.
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'],
],
'#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_suggestions and hook_theme_suggestions_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_HOOK and
hook_preprocess_HOOK
<?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 length filter. #}
{{ text|length }}
Example
Returns
6
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_extension
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('<em>Kittens</em>');
Prints markup in HTML
<em>Kittens</em>
<em>Kittens</em>
Prints in the browser
What is Autoescaping?
{{ text }}
Prints markup in HTML
<em>Kittens</em>
<em>Kittens</em>
<?php
function bartik_preprocess_page(&$variables) {
$variables['text'] = '<em>Kittens</em>';
}
Prints in the browser
Why do we need autoescaping?
Why do we need autoescaping?
How to avoid autoescaping?
{{ text }}
Prints markup in HTML
<em>Kittens</em>
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
<em>Kittens</em>
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
Drupal Camp Vienna 2015 - Drupal 8 The Backend of the Frontend
By lauriii
Drupal Camp Vienna 2015 - Drupal 8 The Backend of the Frontend
- 2,215