Schneller ins WordPress-Theming einsteigen mit Twig und Timber

Mit Twig und Timber das erste eigene WordPress-Theme erstellen
Keine Kommentare

Für WordPress steht eine große Anzahl an fertigen Themes für unterschiedliche Arten von Websites zur Verfügung. Neben dem Themes-Verzeichnis auf www.wordpress.org existieren noch unzählige Markplätze, auf denen Themes zum Kauf angeboten werden. Doch für Projekte, die Wert auf ein eigenes Branding legen, ist ein maßgeschneidertes Theme unvermeidlich.

WordPress hat seine eigenen Standards, um ein Theme umzusetzen. Templates sind üblicherweise so programmiert, dass in ihnen HTML und PHP vermischt sind. Für die Erstellung oder Anpassung eines Templates sind deshalb PHP-Kenntnisse erforderlich – zumindest muss man bereit sein, sich ein wenig in PHP zu vertiefen. Auch werden in WordPress viele PHP-Funktionen in Form sogenannter Templatetags angeboten. Diese Vermischung birgt ein gewisses Risiko in sich. Da PHP auf dem Server ausgeführt wird, können versehentlich Sicherheitslücken entstehen, wenn Daten nicht korrekt validiert oder bereinigt werden.

Eine Möglichkeit, um das Risiko zu minimieren, ist der Einsatz einer Template-Engine. Mit einer solchen kann man nach dem bekannten „Model View Controller“-Muster arbeiten, auf dem viele moderne Frameworks basieren. Ein wichtiger Vorteil dieses Ansatzes ist, dass Programm- und Layoutlogik getrennt werden.

Eine derzeit in der PHP-Welt sehr beliebte Template-Engine ist Twig. Seinen Ursprung hat Twig im Symfony-Framework, doch wird es inzwischen auch von vielen anderen Projekten genutzt. Das bekannteste Beispiel ist wahrscheinlich Drupal 8, aber auch Systeme wie eZ Publish, Bolt, Grav, Craft oder Sculpin setzen es ein. Dass Twig in Zukunft als Standard für WordPress genutzt wird, ist indes eher unwahrscheinlich.

Der Einsatz von Twig bietet einige Vorteile. Man begegnet damit typischen Vorbehalten, die nicht wenige davon abhalten, mit WordPress zu arbeiten: Zum Beispiel ist nicht jeder Frontend-Entwickler oder Webdesigner bereit, viel Zeit in PHP zu investieren. Viele PHP-Entwickler kennen sich mit WordPress zudem nur begrenzt aus, was insbesondere deshalb problematisch ist, da es in WordPress große Abweichungen zu gängigen PHP-Standards gibt. Nicht zuletzt müssen Entwickler, die von anderen Redaktionssystemen kommen, komplett umdenken.

Twig sieht eine klare Trennung zwischen PHP- und HTML-Code vor. Das HTML wird dabei in sogenannte Views ausgelagert. Twig nimmt Daten entgegen und rendert über diese Views das finale HTML. Auch lässt sich so einfacher objektorientiertes PHP schreiben, weil die Programmlogik ohne Layoutcode auskommt. Dies macht es einfacher, getrennt an Frontend- und Backend-Code zu arbeiten.

Für WordPress gibt es verschiedene Plug-ins zur Nutzung von Twig, das bekannteste ist sicherlich Timber. Das liegt wohl daran, dass es für Timber eine gute Dokumentation und auch eine Einführung als Videoreihe gibt. Weiterer Vorteil ist die Unterstützung des beliebten Plug-ins Advanced Custom Fields, sowohl in der freien als auch in der Pro-Variante. Auch kann Timber in Kombination mit dem E-Commerce-Plug-in WooCommerce genutzt werden.

Installation

Um Twig zu nutzen, muss das Timber Plug-in schon installiert sein (Twig selbst wird mit dem Plug-in gleich mitgeliefert). Es gibt zwei Möglichkeiten, Timber zu installieren. Am einfachsten ist es, das Plug-in direkt über das WordPress-Backend hinzuzufügen. Alternativ kann Timber mit dem PHP-Paketmanager Composer installiert werden. Dies setzt natürlich Erfahrung mit Composer voraus. Benötigt wird mindestens PHP 5.3, Timber setzt noch auf Twig 1.x. Inzwischen gibt es zwar schon Twig 2.0, aber dafür wird mindestens PHP 7.0 verlangt. Zusätzlich kann beim Entwickeln auch die Timber Debug Bar von Nutzen sein, um zu sehen, was in WordPress geladen wurde. So sind etwaige Fehler schneller zu finden.

Im Theme kann Timber über composer.json als Abhängigkeit deklariert und über ein composer install installiert werden:

{
"name": "meins/mein-timber-theme",
"type": "wordpress-theme",
"license": "GPL-2.0+",
"require": {
"timber/timber": "^1.5.0"
}
}

Spätestens in der functions.php des aktiven Themes muss der Composer-Autoloader geladen werden. Es ist auch möglich, dies früher im WordPress-Bootstrap-Prozess zu erledigen, zum Beispiel als Must-Use-Plug-in oder in der Konfigurationsdatei wp-config.php. Über die Nutzung der Klasse TimberSite kann die Verknüpfung zwischen WordPress und Twig hergestellt werden. An dieser Stelle werden die unterschiedlichen Theme-Optionen von WordPress aktiviert und konfiguriert (Listing 1).

<?php
require __DIR__ . '/vendor/autoload.php';

class MySite extends TimberSite {
  function __construct() {
    add_theme_support( 'post-formats' );
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'menus' );
    add_theme_support( 'html5', array( 'comment-list', 'comment-form', 'search-form', 'gallery', 'caption' ) );
    parent::__construct();
  }
}

new MySite();

Existierende Themes anpassen

Timber folgt der Templatehierarchie von WordPress. Dadurch können existierende Themes einfach erweitert werden. Im Theme kann in der Aktion after_setup_theme die Timber-Klasse instanziiert werden. Twig steht dann zur Verfügung, ohne dass bestehende Templates beeinträchtigt werden. So können Themes Schritt für Schritt umgebaut werden, es ist also nicht notwendig, sofort komplett auf Twig umzustellen. Die Instanziierung kann auch außerhalb eines Hooks gemacht werden, aber generell vermeide ich diese Methode, um potenziellen Seiteneffekten vorzubeugen.

<?php
require __DIR__ . '/vendor/autoload.php';
add_action( 'after_setup_theme', function() {
new Timber\Timber();
} );

Twig-Syntax

Die Syntax von Twig ist abgeleitet von Jinja2, einer Template-Engine für die Programmiersprache Python. Auch für andere Programmiersprachen gibt es Varianten von Jinja2. Daher wird Twig vielen vertraut vorkommen, die mit anderen Programmiersprachen oder anderen Systemen Erfahrung haben.

Die Twig-Syntax besteht aus geschweiften Klammern in drei Ausprägungen: Daten stehen als Variablen zur Verfügung und werden mit {{ }} ausgegeben. Templatelogik wird mit {% %} aufgerufen, während {# #} für Kommentare genutzt wird. Meistens wird ein Basistemplate definiert, auf dem weiter aufgebaut werden kann. Listing 2 zeigt ein einfaches Template, das die Funktionsweise von Twig demonstriert.

 
{# base.twig #}
<!DOCTYPE html>
<html {{ site.language_attributes }}>
  <head>
    <meta charset="{{ site.charset }}" />
    <title>{{ wp_title }}</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {{ function('wp_head') }}
  </head>
  <body class="{{ body_class }}">
    <header class="site-header">
      {% block header %}
        <a class="site-link" href="{{ site.url }}">{{ site.name }}</a>
      {% endblock %}
    </header>    
    <nav class="site-navigation">
      {% block navigation %}
        {% include "menu.twig" %}
      {% endblock %}
    </nav>
    <div class="site-content">
      {% block content %}
      Keine Inhalte gefunden.
      {% endblock %}
    </div>
    {{ function('wp_footer') }}
  </body>
</html>

Templates bestehen aus reinem HTML mit ein wenig Twig-Syntax. Die Frontend-Entwicklung wird dadurch vereinfacht, weil PHP-Kenntnisse zunächst nicht notwendig sind. Hierdurch wirken Templates übersichtlicher als eine wilde Mischung aus HTML und PHP. Timber sorgt zudem dafür, dass bestimmte WordPress-Daten als Variablen verfügbar sind.

Innerhalb der Templates lassen sich Blöcke definieren, um bestimmte Inhalte zu markieren. Diese können später überschrieben werden. Jedem Block wird ein Platzhalter zugewiesen, wie z. B. für den Hauptinhalt {% block content %}{% endblock %}. Es können mehrere Blöcke definiert und je nach Anwendungsfall überschrieben oder erweitert werden. Twig bietet die Möglichkeit, HTML-Fragmente zu nutzen, um Code modular aufzubauen. Dies geschieht über sogenannte Includes. Zum Beispiel kann so eine Navigationskomponente erstellt werden. Der Vorteil liegt darin, dass die Komponente auch auf sich selbst verweisen kann, um so mehrere Ebenen abzubilden (Listing 3).

{# menu.twig #}
{% if menu %}
  <ul>
  {% for item in menu %}
    <li class="{{ item.classes | join(' ') }}">
    <a href="{{ item.link }}">{{ item.title }}</a>
        {% include "menu.twig" with {'menu': item.get_children} %}
    </li>
  {% endfor %}
  </ul>
{% endif %}

Auch ist es möglich, mit {% if %} {% endif %} Konditionen abzufragen. Im Menübeispiel aus Listing 3 wird kontrolliert, ob ein Menü verfügbar ist und nur dann ausgegeben. In Templates sind auch Funktionen verfügbar. Diese werden unabhängig von Variablen eingesetzt, um Inhalte zu generieren. Timber hat zum Beispiel die Übersetzungsfunktion von WordPress übernommen. Somit können die Gettext-Übersetzungsdateien wie gewohnt verwendet werden:

{{ __('Sorry, no posts.', 'meine-textdomäne') }}

Timber stellt auch die function-Funktion bereit, um PHP-Funktionen direkt aufzurufen. Hier wird ein wenig gemogelt, weil WordPress sehr viele Hilfsfunktionen und Templatetags bietet. Alle WordPress-Funktionen in Twig nachzubauen, wäre zu aufwendig. In base.twig werden die WordPress-Funktionen für den Header bzw. Footer aufgerufen. Hiermit wird die Kompatibilität mit der Standard-WordPress-Funktionalität gewährleistet.

Ein wichtiges Feature von Twig ist die Vererbung von Templates. Dadurch kann jedes Template wiederverwendet werden. Für Seiten könnten separate Templates definiert werden. Dafür wird das Basistemplate referenziert und die betroffen Blöcke wie {% block content %} werden überschrieben. Auf diese Weise ist schnell und einfach eine alternative Variante erstellt, ohne viel Code schreiben zu müssen (Listing 4).

{# page.twig #}
{% extends "base.twig" %}
{% block content %}
<main id="post-{{ post.ID }}" class="{{ post.post_type }}">
  <h2>{{ post.title }}</h2>
  {% if post.thumbnail %}
    <img src="{{ post.thumbnail.src }}" alt="">
  {% endif %}
  <div class="content">
    {{ post.content }}
  </div>
</main>
{% endblock %}

Neben Includes gibt es auch Macros. Diese sind nützlich, um wiederverwendbare HTML-Elemente als Funktion zu verpacken. Macros können nicht auf Templatevariablen zugreifen. Das ist auch der wichtigste Unterschied zu den Includes. Beide können indes für ähnliche Zwecke eingesetzt werden, und man sollte im Einzelfall entscheiden, welcher Ansatz besser geeignet ist. Mehrere Macros können in eine separate Datei ausgelagert werden, etwa für Formulare. Diese müssen im Template importiert werden: {% import „components/forms.twig“ as forms %}. Wenn ein Macro in der gleichen Templatedatei definiert wurde, kann über die Variable _self darauf zugegriffen werden. Ein guter Anwendungsfall für Macros sind rekursive Funktionen. In Listing 5 werden beispielsweise Kommentare und deren Antworten verschachtelt ausgegeben.

{# comments.twig #}
{% macro comment(comment) %}
<li class="comment {{comment.comment_type}}" id="comment-{{ comment.ID }}">
  <div class="comment__author">{{ comment.author.name }}</div>
  <div class="comment__date">{{ comment.date }}</div>
  <div class="comment__content">{{comment.comment_content|wpautop}}</div>

  {% for cmt in comment.children %}
    <ul>
    {{ _self.comment(cmt) }}
    </ul>
  {% endfor %}
</li>
{% endmacro %}

{% if post.comments %}
<section class="comments">
  {% for comment in post.comments %}
    <ul>
    {{ _self.comment(comment) }}
    </ul>
  {% endfor %}
</section>
{% endif %}

Neben Funktionen können in Twig auch Filter genutzt werden. Diese verändern Inhalte, bevor sie ausgegeben werden. Im Beispiel des Kommentar-Macros (Listing 5) wird der Filter wpautop genutzt, der doppelte Zeilenumbrüche in HTML-Paragrafen umwandelt, genau wie die Funktion in WordPress. Es gibt eine ganze Reihe fertige Filter in Twig und in Timber.

PHP

Um Inhalte tatsächlich auszugeben, müssen die Twig-Variablen mit Daten befüllt werden. Hierzu stehen eine Reihe von PHP-Klassen zur Verfügung, die als Schnittstelle zu WordPress fungieren. Der Unterschied zu einem normalen Theme ist die Menge an benötigtem PHP-Code, der mit Twig überschaubar ist. Für Seiten reichen folgende Zeilen:

<?php
/* page.php */
$context = Timber::get_context();
$context['post'] = Timber::get_post();
Timber::render( 'page.twig', $context );

Mit Timber::get_context werden einige globale WordPress-Daten geladen, wie der Titel oder der URL der Website. Die Variable $context ist ein Array und kann mit beliebigen weiteren Daten befüllt werden. Im Seitenbeispiel oben wird mit Timber::get_post der aktuelle Post geladen. Mit Timber::render wird dann das gewünschte Template geladen, und die Daten werden übergeben.

International PHP Conference 2018

Getting Started with PHPUnit

by Sebastian Bergmann (thePHP.cc)

Squash bugs with static analysis

by Dave Liddament (Lamp Bristol)

API Summit 2018

From Bad to Good – OpenID Connect/OAuth

mit Daniel Wagner (VERBUND) und Anton Kalcik (business.software.engineering)

In WordPress-Templates werden Inhalte immer über die WordPress-Loop ausgegeben – auch wenn es sich um eine einzelne Seite oder einen einzelnen Blogpost handelt. Für WordPress-Einsteiger ist das etwas verwirrend. In Twig wird ein Loop nur dann benötigt, wenn tatsächlich eine Liste durchlaufen werden muss, zum Beispiel für die Start- oder Archivseite (Listing 6).

{# archive.twig #}
{% extends "base.twig" %}
{% block content %}
  {% for post in posts %}
  <article>
    <h2><a href="{{ post.link }}">{{ post.title }}</a></h2>
    <div class="excerpt">
    {% if post.excerpt %}
      {{post.excerpt}}
    {% else %}
      {{post.content|excerpt(55)}}
    {% endif %}
    </div>
  </article>
  {% endfor %}
{% endblock %}

Für das Aufrufen von mehreren Posts steht eine separate Methode zur Verfügung:

$context['posts'] = Timber::get_posts();

Sowohl hier als auch bei Timber::get_post besteht die Möglichkeit, die WP_Query-Syntax zu nutzen:

$args = array(
'post_type'      => 'custom_post_type',
'post_status'    => 'publish',
'posts_per_page' => 5,
);
$context['posts'] = Timber::get_posts( $args );

Timber erweitern

In PHP können weitere Daten hinzugefügt werden, um diese global abrufbar zu machen. Ein Navigationsmenü wäre ein typischer Anwendungsfall. Timber stellt hierfür eine separate PHP-Klasse bereit. Über den Timber-Kontext kann so das Menü auf allen Seiten eingeblendet werden.

<?php
function my_twig_context( $data ) {
  $data['menu'] = new TimberMenu();

  return $data;
}
add_filter( 'timber/context', 'my_twig_context' );

Auch können eigene Twig-Filter definiert werden, um zum Beispiel Markdown zu parsen {{ post.content|markdown }}. Hierzu muss der Twig-Filter dem WordPress-Filter timber/twig hinzugefügt werden (Listing 7).

<?php
function my_twig_filters( $twig ) {
  $twig->addFilter(
    new Twig_SimpleFilter(
      'markdown',
      function( $text ) {
        /* https://github.com/erusev/parsedown */
        $Parsedown = new Parsedown();

        return $Parsedown->text( $text );
      } ) );

  return $twig;
}
add_filter( 'timber/twig', 'my_twig_filters' );

Für Twig gibt es fertige Erweiterungen, die zusätzliche Funktionen oder Filter bieten. Zum Beispiel kann ein Textstring über die Erweiterung Twig_Extension_StringLoader als Template genutzt werden: {{ include(template_from_string(„Hallo {{ current_user.name }}“)) }}. Auch Erweiterungen werden über einen WordPress-Filter geladen (Listing 8).

<?php
function my_twig_extensions( $twig ) {
  $twig->addExtension(
    new Twig_Extension_StringLoader()
  );

  return $twig;
}
add_filter( 'timber/twig', 'my_twig_extensions' );

Fazit

Mit Twig wird der Einstieg in das WordPress-Templating vereinfacht, insbesondere, wenn man schon einmal mit Templatesprachen gearbeitet hat. Die Trennung von HTML und PHP ist leichter verdaulich als das normale WordPress-Theming. Falls auch Sie auf den Geschmack gekommen sind und mit Twig und Timber loslegen wollen, gibt es ein Starter-Theme auf GitHub. Empfehlenswert ist auch das Starter-Theme Lumberjack, das modernes PHP inklusive Namespaces nutzt und PHP 5.4 voraussetzt. Beispiele von Webseiten, die mit Timber umgesetzt wurden, finden sich – teilweise auch mit Quellcode – auf der Showcase-Seite.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

X
- Gib Deinen Standort ein -
- or -