Nell’articolo precedente abbiamo visto come creare nuove pagine, introducendo, tra gli altri, il template engine Twig. In questa puntata approfondiremo il discorso e scopriremo le funzionalità che gli permettono di essere apprezzato così tanto.
Può interessare che, dietro Twig, si celi niente meno che la stessa combriccola malvagia che ha creato Symfony, pur non essendo strettamente legato al framework. Infatti può essere usato in un qualsiasi progetto PHP e non è obbligatorio nemmeno usarlo in Symfony, ma credo sia rarissimo trovare progetti con impostazione fullstack che non lo abbiano adottato. Voglio rassicurarvi: io lo trovo molto pratico e intuitivo e i primi passi si fanno in fretta. Potete trovare la documentazione qui.
Non vorrei essere banale, ma è bene sapere che Twig è PHP sotto mentite spoglie: si tratta sempre di Server Side Rendering. La sintassi è diversa, certo, ma il suo cuore pulsa PHP. Vuol dire che una pagina Twig viene prima “tradotta in PHP” e solo allora viene costruito l’HTML da mandare al client, nella risposta HTTP. Come potete immaginare, questo vuol dire che c’è uno sforzo computazionale maggiore, ma l’overhead in realtà è minimo e ne vale assolutamente la pena, considerando l’agile sintassi dai costrutti template-oriented e alcune peculiarità, che vedremo di seguito.
Solo una raccomandazione: per aiutarvi a comprendere meglio e più velocemente, vi consiglio di esaminare sempre il codice sorgente prodotto (dal browser: click tasto destro > Visualizza codice sorgente).
Per installare Twig basta digitare:
composer require twig
Si comincia!
Tag e definizione di una variabile
Potete vedere il contenuto di un file .twig come semplice HTML. Ciò di cui il motore Twig si fa effettivamente carico è quello che si trova tra i tag di apertura e chiusura. Insomma, proprio come succede in PHP puro: il documento è HTML e solo ciò che si trova tra i tag <?php … ?> viene poi processato.
I tag di apertura e chiusura, in Twig possono essere fondamentalmente di due tipi:
- {% … %} corrispondente a <?php … ?> e contiene un elemento di logica, magari la definizione di una variabile;
- {{ … }} corrisponde a <?= … ?> (introdotto in PHP 7 e corrisponde a sua volta a <?php echo … ?>) e serve a stampare a video il contenuto della variabile;
Facciamo un esempio. Usiamo la direttiva set per definire una variabile e poi stampiamola.
{% set site_name = "ZombieProcess" %}
Impariamo Symfony su {{ site_name }}
Il codice PHP corrispondente è:
<?php $site_name = "ZombieProcess"; ?>
Impariamo Symfony su <?= $site_name ?>
Il risultato prodotto è la stringa Impariamo Symfony su ZombieProcess. Analogamente a quanto accade in puro PHP, set è un direttiva di assegnazione. Perciò, se decidete di modificare il valore della variabile, dovrete riutilizzare la direttiva.
Cicli e condizioni
Il costrutto if funziona in maniera analoga a quello di PHP, in riferimento soprattutto alla valutazione booleana di un’espressione. La sintassi, però, è più verbosa, con gli operatori && e || che vengono sostituiti con and e or. Direi che non mi soffermo oltre, dato che la documentazione ufficiale è già molto chiara e ricca di esempi.
Del ciclo for esistono più varianti, una più comoda delle altre a seconda delle esigenze. Vediamole e poi le commentiamo:
{# Questo è un commento #}
{# Esempio N.1 #}
Lista degli impiegati:<br>
{% for employee in employees %}
{{ employee.name }}
{% else %}
Nessun impiegato trovato
{% endfor %}
{# Esempio N.2 #}
Alfabeto:<br>
{% for letter in 'a'..'z' %}
<p>{{ letter }}</p>
{% endfor %}
Nel primo esempio, ipotizziamo di aver già creato una Action da cui passiamo un array di nome employees, ossia una lista di impiegati. È interessante sapere che gli elementi di questo array possono essere oggetti oppure array associativi, ma per Twig non cambia nulla: è agnostico alla modalità di accesso, perciò, se si tratta di un oggetto, ne preleverà la proprietà name, mentre, se si tratta di un array associativo, preleverà l’elemento con chiave name. Ritorneremo sul concetto nei prossimi articoli, ma questa caratteristica di flessibilità è grandiosa, perché permette di concentrarsi sul templating vero e proprio, piuttosto che costringere ad adattarsi a ciò che arriva dal Controller.
Riprendendo il nostro esempio, possiamo dire che il primo for corrisponda ad un foreach di PHP. Avrete notato, però, la presenza di un else, a cui si accede quando l’array employees è vuoto. Questo ci permette di non dover mettere un if per fare un check preliminare: è un’altra caratteristica template-oriented.
Nel secondo caso, invece, elenchiamo le lettere dell’alfabeto, attraverso la notazione dei due punti (come in Pascal 😀 ), con cui definiamo un range. Questi costrutti possono essere molto più sofisticati: date un’occhiata alla doc per una panoramica completa.
Avrete notato, poi, la presenza di commenti, compresi tra i tag {# … #}. Vediamo se siete bravi. Visto che nei file .twig c’è anche HTML, qual è la differenza tra il fare un commento in Twig e farne uno in HTML? Ricordate, Twig funziona server side, i commenti non vengono renderizzati. Questo vale a dire che, se visualizzate il codice sorgente dal browser, il commento Twig non sarà visibile, mentre quello HTML sì. Perciò fate attenzione ed evitate di commentare del codice Twig con HTML: tenete il codice al sicuro. Ma tenete anche a mente che per un commento HTML l’overhead computativo è minore (per quanto possa essere irrisorio).
Filtri e funzioni
In Twig sono ampiamente utilizzati i filtri, che vengono applicati attraverso il pipe, cioè il simbolino |, e servono ad applicare una trasformazione. Vediamo alcuni esempi:
Visualizziamo il numero di impiegati, ossia la grandezza dell’array:
{{ employees|length }}
Visualizziamo il nome dell’impiegato in maiuscolo:
{{ employee.name|upper }}
Visualizziamo il giorno del compleanno dell’impiegato, a partire dalla sua data di nascita:
{{ employee.bornOn|date('d/m') }}
Nell’ultimo caso, bornOn è un oggetto di tipo \DateTime, che, passato nel filtro date con argomento ‘d/m’, viene restituito come stringa nel formato giorno/mese.
Le funzioni sono più facili da comprendere. Un filtro è una trasformazione applicata a qualcosa, mentre la funzione può essere una cosa a sè stante. La funzione più usata in Twig è quella per richiamare le rotte, path, che abbiamo già conosciuto nell’articolo precedente. La cosa più interessante da sapere in questo momento è che possiamo definire filtri e funzioni customizzati e tra qualche articolo vedremo come fare, auando avremo già introdotto un altro paio di concetti.
Finora abbiamo grattato in superficie, abbiamo visto che esistono molti elementi che rendono il templating agevole, ma ora veniamo alla roba kick-ass. Vediamo quali sono le caratteristiche più importanti, quelle che davvero ci fanno capire perché Twig è così apprezzato.
Estensione
Negli esempi precedenti ho omesso per semplicità la struttura HTML della pagina, ma in un caso reale deve esserci, ovviamente. Definiamo quindi tre Action (se serve un aiutino, trovate qui il codice), ognuna delle quali renderizza uno dei seguenti template (allocati nella directory /templates):
{# symfony_page.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>Symfony 4</title>
</head>
<body>
<h1>Framework Symfony 4</h1>
<p>
Symfony 4 è un framework backend
</p>
</body>
</html>
{# vue_page.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<h1>Framework Vue</h1>
<p>
Vue è un framework frontend
</p>
</body>
</html>
Un po’ ripetitivo, non trovate? Entrambi definiscono la struttura della pagine, eppure, se ci fate caso, gli elementi sono pressocché gli stessi: c’è un tag title, c’è un titolo H1 e c’è un contenuto principale, che è un testo tra tag <p>. In questo esempio la struttura è anche piuttosto semplice, ma in un sito reale cominciano ad esserci moltissimi metatag, header di vario genere, script, poi l’intestazione, il footer, il menù. Tutti elementi che si ripetono in molte (se non tutte) le pagine. In puro PHP avremmo dovuto utilizzare potenzialmente molti include, ma come comodità non è il massimo. Twig, ideato proprio per facilitare il modo di costruire i template, gode di una proprietà fondamentale: l’estensione. Per usarla creeremo un file /templates/base.html.twig contenente la struttura della pagina (diciamo tutti gli elementi che si ripetono), mentre i template sopra creati si limiteranno ad “estendere” il nuovo, piazzando il contenuto ad hoc al posto giusto:
{# base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Zombie Process{% endblock %}</title>
</head>
<body>
<h1>{% block main_title %}{% endblock %}</h1>
{% block content %}{% endblock %}
</body>
</html>
{# symfony_page.html.twig #}
{% extends "base.html.twig" %}
{% block title %}Symfony 4{% endblock %}
{% block main_title %}Framework Symfony 4{% endblock %}
{% block content %}
<p>
Symfony 4 è un framework backend
</p>
{% endblock %}
{# vue_page.html.twig #}
{% extends "base.html.twig" %}
{% block title %}Vue{% endblock %}
{% block main_title %}Framework Vue{% endblock %}
{% block content %}
<p>
Vue è un framework frontend
</p>
{% endblock %}
I due template iniziali ereditano la struttura di base.html.twig pari pari, attraverso la direttiva extends. Il principio con cui viene definito un contenuto customizzato, avviene grazie alla definizione dei blocchi, chiamati arbitrariamente title, main_title e content. Il template genitore definisce dove piazzare il contenuto, mentre il template figlio definisce con quale contenuto sovrascrivere il blocco.
Nel template figlio, qualsiasi cosa sarà definita in un blocco. Se un blocco non viene sovrascritto, allora rimarrà valido quanto definito nel template genitore. Ad esempio, il blocco title di base.html.twig, che definisce il nome della scheda del browser, contiene Zombie Process. Se uno dei template figli, non avesse ridefinito il blocco title, il nome della scheda non sarebbe stato ancora Zombie Process. Provate voi stessi, è più facile capirlo vedendo, che leggendo.
Insomma, grazie alla proprietà di estensione, si viene a creare una gerarchia di template, che ci agevolerà enormemente il lavoro.
Include
Un’altra direttiva largamente usata è include, che serve ad “inglobare” un template in un altro. Ad esempio, e se volessimo inserire un menù, ma solo in alcuni di tutti i template che potremmo aver creato? Potremmo creare un file /templates/menu.html.twig, in cui definire il menù e poi utilizzare la direttiva include nei template interessati:
{# menu.html.twig #}
<ul>
<li><a href="{{ path('home') }}">Torna alla home</a></li>
<li><a href="{{ path('who_we_are') }}">Chi siamo</a></li>
<li><a href="{{ path('contact_us') }}">Contattaci</a></li>
</ul>
{# symfony_page.html.twig #}
{% extends "base.html.twig" %}
{% block title %}Symfony 4{% endblock %}
{% block main_title %}Framework Symfony 4{% endblock %}
{% block content %}
{% include "menu.html.twig" %}
<p>
Symfony 4 è un framework backend
</p>
{% endblock %}
In questa puntata ho riassunto quelli che sono i costrutti e le caratteristiche che uso di più. Twig, però, ha molto di più da offrire, perciò non esitate a sfogliare la documentazione per fugare ogni dubbio. Nel prossimo articolo conosceremo Doctrine, il componente più usato per interfacciarsi ad un database.