Skip to content

La creazione di un controller

Il nostro progetto del guestbook è già attivo sui server di produzione, ma abbiamo imbrogliato un po'. Il progetto non ha ancora nessuna pagina. L'homepage risulta essere una noiosa pagina di errore 404. Sistemiamo le cose.

Quando arriva una richiesta HTTP come per la homepage (http://localhost:8000/), Symfony cerca di trovare una rotta che corrisponda al percorso della richiesta (/ in questo caso). Una rotta è il collegamento tra il percorso della richiesta e una funzione callable di PHP che crea la risposta HTTP per quella richiesta.

Queste funzioni "callable" sono chiamate "controller". In Symfony, la maggior parte dei controller è implementata come classe PHP. Possiamo creare una classe di questo tipo in modo manuale ma, siccome ci piace andare veloci, vediamo come Symfony ci può aiutare.

Essere pigri con MakerBundle

Per generare dei controller senza sforzo, possiamo usare il pacchetto symfony/maker-bundle, il quale è stato installato come parte del pacchetto webapp.

MakerBundle ci aiuta a generare un sacco di classi diverse. Lo useremo molto spesso in questo libro. Ogni "generatore" è definito in un comando e tutti i comandi fanno parte del namespace dei comandi make.

Il comando list della console di Symfony elenca tutti i comandi disponibili sotto un dato namespace; possiamo usarlo per scoprire tutti i generatori forniti da MakerBundle:

1
$ symfony console list make

Scegliere un formato per la configurazione

Prima di creare il primo controller del progetto, dobbiamo decidere quali formati di configurazione vogliamo utilizzare. Symfony supporta nativamente YAML, XML, PHP e `Attributi PHP`.

Per la configurazione relativa ai pacchetti, YAML è la scelta migliore. Questo è il formato utilizzato per la cartella config/. Spesso, quando si installa un nuovo pacchetto, la ricetta del pacchetto stesso aggiungerà un nuovo file con estensione .yaml a questa cartella.

Per la configurazione relativa al codice PHP, gli attributi sono una scelta migliore poiché sono definite nel codice stesso. Prendiamo in esame questo esempio: quando una richiesta arriva all'applicazione, la configurazione deve dire a Symfony quale specifico controller (una classe PHP) dovrà gestirla. Se utilizzassimo un formato di configurazione tra YAML, XML e PHP, due file sarebbero coinvolti (il file di configurazione e il file del controller PHP). Utilizzando gli attributi, la configurazione sarà inclusa direttamente nella classe del controller.

Come fare a sapere il nome del pacchetto che ci serve installare per una determinata funzionalità? Il più delle volte non c'è bisogno di saperlo, infatti Symfony ci dirà il nome del pacchetto da installare attraverso dei messaggi d'errore. Lanciare il comando symfony console make:message senza aver installato il pacchetto messenger, per esempio, avrebbe sollevato un'eccezione contenente un suggerimento riguardo a cosa installare.

Generare un controller

Creiamo il nostro primo controller tramite il comando make:controller:

1
$ symfony console make:controller ConferenceController

Il comando crea una classe ConferenceController nella cartella src/Controller/. La classe generata sarà composta da codice predefinito e pronto per essere messo a punto:

src/Controller/ConferenceController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Controller;

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

class ConferenceController extends AbstractController
{
    #[Route('/conference', name: 'app_conference')]
    public function index(): Response
    {
        return $this->render('conference/index.html.twig', [
            'controller_name' => 'ConferenceController',
        ]);
    }
}

L'attributo #[Route('/conference', name: 'app_conference')] è ciò che rende il metodo index() un controller (la configurazione è assieme al codice che configura).

Quando si visita /conference nel browser, il controller viene eseguito e una risposta viene restituita.

Modifichiamo la rotta per farla corrispondere alla homepage:

1
2
3
4
5
6
7
8
9
10
11
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -8,7 +8,7 @@ use Symfony\Component\Routing\Attribute\Route;

 class ConferenceController extends AbstractController
 {
-    #[Route('/conference', name: 'app_conference')]
+    #[Route('/', name: 'homepage')]
     public function index(): Response
     {
         return $this->render('conference/index.html.twig', [

Il parametro name della rotta sarà utile qualora volessimo fare riferimento alla homepage all'interno del codice. Invece di scrivere direttamente il percorso / potremo utilizzare il nome della rotta.

Al posto della pagina predefinita, restituiamo una semplice pagina HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -11,8 +11,13 @@ class ConferenceController extends AbstractController
     #[Route('/', name: 'homepage')]
     public function index(): Response
     {
-        return $this->render('conference/index.html.twig', [
-            'controller_name' => 'ConferenceController',
-        ]);
+        return new Response(<<<EOF
+            <html>
+                <body>
+                    <img src="/images/under-construction.gif" />
+                </body>
+            </html>
+            EOF
+        );
     }
 }

Aggiorniamo il browser:

/

La responsabilità principale di un controller è quella di restituire una Response HTTP per la richiesta.

Siccome la rimanente parte del capitolo riguarda codice che non vorremo memorizzare, facciamo commit dei cambiamenti ora:

1
2
$ git add .
$ git commit -m'Add the index controller'

Aggiunta di un "easter egg"

Per dimostrare come una risposta possa sfruttare le informazioni provenienti dalla richiesta, aggiungiamo un piccolo easter egg (contenuto nascosto). Ogni volta che la homepage contiene una query string come ?hello=Fabien, aggiungiamo del testo per salutare la persona:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,17 +3,24 @@
 namespace App\Controller;

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;

 class ConferenceController extends AbstractController
 {
     #[Route('/', name: 'homepage')]
-    public function index(): Response
+    public function index(Request $request): Response
     {
+        $greet = '';
+        if ($name = $request->query->get('hello')) {
+            $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
+        }
+
         return new Response(<<<EOF
             <html>
                 <body>
+                    $greet
                     <img src="/images/under-construction.gif" />
                 </body>
             </html>

Symfony espone i dati della richiesta attraverso un oggetto Request. Quando Symfony rileva un parametro del controller con questo tipo, passa automaticamente la richiesta attraverso questo oggetto: possiamo utilizzarlo per ottenere l'elemento name dalla query string e aggiungere un <h1> al titolo della pagina.

Proviamo a visitare in un browser il percorso / e poi /?hello=Fabien per vedere la differenza.

Note

Nota: la chiamata a htmlspecialchars() serve per evitare problemi di XSS (cross-site scripting). Questa cosa sarà fatta in modo automatico quando utilizzeremo un sistema appropriato per i template.

Avremmo anche potuto rendere il nome parte dell'URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -9,11 +9,11 @@ use Symfony\Component\Routing\Attribute\Route;

 class ConferenceController extends AbstractController
 {
-    #[Route('/', name: 'homepage')]
-    public function index(Request $request): Response
+    #[Route('/hello/{name}', name: 'homepage')]
+    public function index(string $name = ''): Response
     {
         $greet = '';
-        if ($name = $request->query->get('hello')) {
+        if ($name) {
             $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
         }

La parte {name} della rotta è un parametro di rotta dinamico: funziona come segnaposto. Possiamo dunque visitare /hello e poi /hello/Fabien attraverso il browser per ottenere lo stesso risultato di prima. Possiamo inoltre ottenere il valore del parametro {{name}} aggiungendo una variabile al controller con lo stesso nome (quindi, $name).

Annulliamo le modifiche che abbiamo appena fatto:

1
$ git checkout src/Controller/ConferenceController.php
1
2
$ git reset HEAD src/Controller/ConferenceController.php
$ git checkout src/Controller/ConferenceController.php

Variabili di debug

La funzione dump() di Symfony è un ottimo aiuto per il debug. È sempre disponibile e ci consente di eseguire il dump di variabili complesse in una forma bella e interattiva.

Cambiamo momentaneamente il file src/Controller/ConferenceController.php per eseguire il dump dell'oggetto Request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,14 +3,17 @@
 namespace App\Controller;

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;

 class ConferenceController extends AbstractController
 {
     #[Route('/', name: 'homepage')]
-    public function index(): Response
+    public function index(Request $request): Response
     {
+        dump($request);
+
         return new Response(<<<EOF
             <html>
                 <body>

Quando ricarichiamo la pagina sarà possibile notare la nuova icona "target" nella barra degli strumenti; ci consentirà di inspezionare il dump. È possibile cliccare su di essa per accedere a una pagina dove la navigazione sarà più semplice:

/

Annulliamo le modifiche che abbiamo appena fatto:

1
$ git checkout src/Controller/ConferenceController.php
1
2
$ git reset HEAD src/Controller/ConferenceController.php
$ git checkout src/Controller/ConferenceController.php
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version