Symfony 4: come creare nuove pagine

Finalmente entriamo nel vivo! Nell’ultimo articolo abbiamo visto come Symfony è strutturato e gestisce una richiesta. Sapere cosa succede dietro le quinte è importante e vi tornerà sicuramente utile, ma nella pratica è tutto molto più semplice. D’ora in poi vi consiglio di affiancare il vostro editor (o IDE) preferito e di provare in prima persona.

Il Controller

Quando avete installato Symfony, avrete visto una schermata di benvenuto. Se avete installato anche il profiler, avrete notato quel 404 a sfondo rosso:

Stato del profiler al termine dell’installazione

Lo saprete, si tratta del codice HTTP che indica che non è stata trovata la risorsa corrispondente. Ne consegue che la nostra home, in realtà, non esiste. Per crearla abbiamo bisogno di definire l’Action di un Controller! Dunque, creiamo il file /src/Controller/DefaultController.php, ed inseriamo il seguente codice:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends AbstractController
{
	/**
	 * @Route("/", name="home")
	 */
	public function homepage()
	{
		return new Response('Zombie World');
	}
}

Con poche righe abbiamo creato il Controller DefaultController, che dal punto di vista del codice non è altro che una classe che estende una utility del framework (Symfony\Bundle\FrameworkBundle\Controller\AbstractController), la quale contiene un po’ di magia per far funzionare le cose. Potete vedere un Controller come un contenitore di Action, ossia metodi che in linea di massima corrispondono ad una URL. In questo caso, il metodo homepage() corrisponde alla pagina iniziale del sito. Andiamo col browser su http://127.0.0.1:8000/ et voilà, dovremmo vedere il testo Zombie World sullo schermo.

In un progetto possono esistere più Controller e ognuno può contenere più Action. Con l’esperienza capirete da soli quando definire l’Action in un Controller esistente e quando crearne uno nuovo. In generale dipende dai vincoli strutturali (ad esempio per suddividere le varie sezioni del sito) e funzionali (ad esempio per lavorare meglio sulle rotte e sulla sicurezza) che volete dare.

Request e Response

Nel precedente articolo abbiamo parlato di due oggetti molto importanti nel framework: Request e Response. Ogni volta che visitiamo una pagina, Symfony crea un oggetto Request e individua la Action da eseguire, che ritorna un oggetto Response. Quindi l’Action rappresenta (indicativamente) la prima porzione di codice scritta dall’utente sviluppatore e ritorna sempre un oggetto Response.

Come si può intuire, l’oggetto Request viene costruito a partire dalla richiesta HTTP che è arrivata al server e ne contiene i parametri tipici, come l’URL, la lingua del client, cookie ed header di vario genere.

L’oggetto Response, invece, viene costruito per generare la risposta HTTP da mandare indietro al client, perciò contiene parametri tipici come lo status code (200 per una risorsa trovata, 404 per una non trovata, 301 per un redirect, eccetera), il contenuto l’HTML (o altro formato).

I template

L’argomento dell’oggetto Response non è semplice testo, ma è HTML che il browser dovrà renderizzare. È evidente che inserire tutto il codice client side lì dentro non è proprio il massimo, perciò impareremo ad utilizzare i template.

Sia chiaro, innanzitutto, che la gestione del front-end può avvenire in diversi modi. Quello più semplice, più documentato e più diffuso vede al centro di tutto Twig, un componente che si occupa di gestire il lato presentazionale della webapp attraverso funzionalità decisamente interessanti.

Nella directory templates è già presente il file base.html.twig. Noterete che contiene dell’HTML di base e degli strani costrutti del tipo {% … %}, chiamati blocchi. Inseriamo dopo l’apertura del body HTML una stringa, ad esempio Hello Zombie Process!

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
    	Hello Zombie Process!

        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

Ora bisogna dire alla nostra Action di utilizzare questo template, quindi invece di creare un oggetto di tipo Response, utilizziamo il metodo render di AbstractController (nel ControllerTrait), da cui eredita il nostro Controller:

// src/Controller/DefaultController.php

// Nuova dipendenza in cima al file
use Symfony\Component\HttpFoundation\Request;

/**
 * @Route("/", name="home")
 */
public function homepage(Request $request)
{
	// Esaminiamo il contenuto dell'oggetto Request
	dump($request);

	$response = $this->render('base.html.twig');

	// Esaminiamo il contenuto dell'oggetto Response
	dump($response);

	return $response;
}

Per utilizzare un template è sufficiente un return $this->render('base.html.twig');, dove il metodo render accetta come argomento un path relativo alla directory templates. Nello script ho inserito qualche ulteriore riga per mostrarvi un paio di cosette. Innanzitutto la Action accetta il parametro $request, di tipo Request (deve essere necessariamente type-hinted). Voi non dovete fare nulla: è un oggetto inizializzato e iniettato automaticamente. Con il metodo dump possiamo esaminare il contenuto dell’oggetto Request e dell’oggetto Response, così da renderci conto di cosa sta accadendo. Se avete installato il package debug, potrete navigare gli oggetti attraverso il profiler:

Debugging attraverso il profiler

Le rotte

Avete capito come abbiamo specificato la rotta della pagina? Negli esempi precedenti abbiamo usato le annotation, che vi ricordo, non sono semplici commenti, ma contengono microdata utili al framework. La rotta dell’homepage è stata definita con la direttiva @Route("/", name="home").

Dare un nome ad ogni rotta è importante, perché rende il sistema di routing di Symfony estremamente potente. Provate ad immaginare un sito enorme, con tantissime pagine e con molti link. Immaginate ora che ci sia la necessità di cambiare un indirizzo: è un suicidio pensare di modificarlo manualmente per tutte le occorrenze nell’intero progetto. Con il sistema delle rotte di Symfony questo probema non c’è, perché possiamo associare ad ogni rotta un nome univoco con cui identificarla. Chiaramente, se si tratta di pagine indicizzate, bisogna gestire questa cosa anche dal lato SEO (redirect, segnalazione a Google, eccetera).

Mettiamo insieme quello che abbiamo detto fino ad ora. Aggiungiamo una nuova pagina, quindi definiamo una nuova Action nel nostro DefaultController, e poi creiamo il file /templates/pizza.html.twig:

// src/Controller/DefaultController.php

/**
 * @Route("/pizza/{pizzaname}", name="pizza")
 */
public function pizza($pizzaname = 'Margherita')
{
	return $this->render('pizza.html.twig', [
		'name' => 'Antonio',
		'pizza' => $pizzaname
	]);
}
<!DOCTYPE html>
<html>
	<head>
		<title>pizzeria Zombie Process</title>
	</head>

	<body>
		<p>Ciao, sono {{ name }}! La mia pizza preferita è la {{ pizza }}</p>
		
		<a href="{{ path('home') }}">Torna alla home</a>
	</body>
</html>

Andiamo su http://127.0.0.1:8000/pizza…

Ciao, sono Antonio! La mia pizza preferita è la Margherita
Torna alla home

Forte, no? Con il minimo impegno capirete che il primo argomento del metodo render specifica il template, mentre il secondo è un array utilizzato per passare dati a Twig. Con le doppie parentesi graffe, nel template, facciamo una stampa delle variabili name e pizza. Possiamo vedere che mentre la prima è stata definita nel Controller stesso tramite assegnazione ( ‘name’ => ‘Antonio’), la seconda è passata come valore all’Action ed è definita nella rotta. Praticamente abbiamo definito una query string, per cui il risultato varia in base alla URL che visitiamo:

127.0.0.1:8000/pizza/burrata -> [...] La mia pizza preferita è la burrata
127.0.0.1:8000/pizza/bufala -> [...] La mia pizza preferita è la bufala

Inoltre abbiamo stabilito un valore di default, Margherita, che viene stampato se il parametro della query string è vuoto. Se non specifichiamo un valore di default, il parametro diventa obbligatorio e in sua assenza viene generato un 404.

Riguardo alle rotte, infine, vi faccio notare che abbiamo inserito un link alla home. Invece di inserire il path reale nell’href, abbiamo usato la funzione di Twig path, che riceve come argomento il nome della rotta e ritorna l’indirizzo reale, che verrà inserito nell’HTML.

In questo articolo abbiamo visto piccole pillole di un mondo molto vasto, ma con cui è facile apprezzare la potenza di questo framework. Nel prossimo articolo vedremo più da vicino cosa può offrire un template engine potetente come Twig.