Passo 6: La creazione di un controller

5.0 version
Maintained

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:

1
$ symfony composer req maker --dev

Poiché MakerBundle è utile solo in fase di sviluppo, non dimentichiamoci di aggiungere il parametro --dev per evitare che venga abilitato in produzione.

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 annotazioni.

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, le annotazioni 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 le annotazioni, la configurazione sarà inclusa direttamente nella classe del controller.

Per gestire le annotazioni dobbiamo aggiungere un’altra dipendenza:

1
$ symfony composer req annotations

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 make:controller senza aver installato il pacchetto annotations, 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
17
18
namespace App\Controller;

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

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

L’annotazione @Route("/conference", name="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:

patch_file
 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\Annotation\Route;
 class ConferenceController extends AbstractController
 {
     /**
-     * @Route("/conference", name="conference")
+     * @Route("/", name="homepage")
      */
     public function index()
     {

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:

patch_file
 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
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,6 +3,7 @@
 namespace App\Controller;

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

 class ConferenceController extends AbstractController
@@ -12,8 +13,13 @@ class ConferenceController extends AbstractController
      */
     public function index()
     {
-        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.

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
29
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -3,6 +3,7 @@
 namespace App\Controller;

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

@@ -11,11 +12,17 @@ class ConferenceController extends AbstractController
     /**
      * @Route("/", name="homepage")
      */
-    public function index()
+    public function index(Request $request)
     {
+        $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.

Nota

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
17
18
19
20
21
22
23
24
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -9,13 +9,19 @@ use Symfony\Component\Routing\Annotation\Route;
 class ConferenceController extends AbstractController
 {
     /**
-     * @Route("/", name="homepage")
+     * @Route("/hello/{name}", name="homepage")
      */
-    public function index()
+    public function index(string $name = '')
     {
+        $greet = '';
+        if ($name) {
+            $greet = sprintf('<h1>Hello %s!</h1>', htmlspecialchars($name));
+        }
+
         return new Response(<<<EOF
 <html>
     <body>
+        $greet
         <img src="/images/under-construction.gif" />
     </body>
 </html>

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).


This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.