Lauri Eskola (lauriii)
Twitter: @laurii1
Twitter: @laurii1
Flexible way to output markup
Laziness is the first step towards efficiency.
<?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)
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 %}
And theme functions will be deprecated in 8.0.x
<?php
/**
* Implements hook_theme().
*/
function sandwich_theme() {
return [
'sandwich' => [
'variables' => [
'attributes' => [],
'name' => '',
'bread' => '',
'cheese' => '',
'veggies' => [],
'protein' => '',
'condiments' => [],
],
],
];
}
<?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']],
];
}
<?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'),
],
];
}
}
node.html.twig
node--article.html.twig
<?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
<?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';
}
<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>
{{ 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');
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
6
Functions in Twig are like PHP functions
{{ attach_library('color/drupal.color') }}
Example
{# 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);
How to create a Twig extension
<?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);
}
}
services:
trim_string.twig.trimstring:
class: Drupal\trim_string\TwigExtension\TrimString
tags:
- { name: twig.extension }
trim_string.services.yml
{% set variable = 'kittens are cool in the winter' %}
{{ variable|trim_string(16) }}
kittens are cool
Returns
https://github.com/lauriii/trim-string
Rule #1
<?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
{{ 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
{{ 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
{{ 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
<?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:
<?php
use Drupal\Component\Render\FormattableMarkup;
new FormattableMarkup('<a href=":url"></a>', [
':url' => 'javascript:alert(String.fromCharCode(88,83,83))'
]);
This is safe:
<?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:
Which means using any custom templating engine or Theme functions are not autoescaped
https://www.drupal.org/node/2575199
Twitter: @laurii1