Stap 6: Een controller aanmaken

5.0 version
Maintained

Een controller aanmaken

Ons gastenboek-project is al live op productieservers, maar we hebben een beetje vals gespeeld. Het project heeft nog geen webpagina’s. De homepage is een saaie 404-foutpagina. Laten we dat oplossen.

Wanneer er een HTTP-request binnenkomt, zoals voor de homepage ( http://localhost:8000/ ), probeert Symfony een route te vinden die overeenkomt met het aanvraagpad ( / hier). Een route is de link tussen het aanvraagpad en een PHP callable , een functie die de HTTP-response voor deze aanroep creëert.

Deze callables worden “controllers” genoemd. In Symfony zijn de meeste controllers geïmplementeerd als PHP-classes. Je kunt zo’n class handmatig maken, maar omdat we graag tempo maken, laten we eens kijken hoe Symfony ons kan helpen.

Lui zijn met de Maker Bundle

Om moeiteloos controllers te genereren, kunnen we de symfony/maker-bundle-package gebruiken:

1
$ symfony composer req maker --dev

Vergeet niet de --dev-vlag toe te voegen om te voorkomen dat de package in de productieomgeving wordt ingeschakeld, aangezien de maker bundle alleen nuttig is tijdens de ontwikkeling.

De maker bundle helpt je om veel verschillende classes te genereren. We zullen dit dan ook constant gebruiken in dit boek. Elke “generator” wordt gedefinieerd in een commando en alle commando’s maken deel uit van de make command namespace.

Het ingebouwde list-commando van de Symfony Console geeft een overzicht van alle commando’s die beschikbaar zijn onder een bepaalde namespace; gebruik het om alle generatoren te ontdekken die door de maker bundle worden aangeleverd:

1
$ symfony console list make

Het kiezen van een configuratie-indeling

Voordat we de eerste controller van het project aanmaken, moeten we eerst beslissen welke configuratie-indeling we willen gebruiken. Symfony ondersteunt standaard YAML, XML, PHP en annotaties.

Voor configuratie met betrekking tot packages is YAML de beste keuze. Dit is het formaat dat in de config/-directory wordt gebruikt. Wanneer je een nieuwe package installeert, zal het recipe van de package vaak een nieuw .yaml-bestand toevoegen aan die map.

Voor configuratie met betrekking tot PHP-code zijn annotaties een betere keuze omdat ze bij de code zijn gedefinieerd. Ik zal het uitleggen met een voorbeeld. Wanneer een request binnenkomt, moet een bepaalde configuratie Symfony vertellen dat het request path door een specifieke controller (een PHP-class) moet worden afgehandeld. Bij het gebruik van YAML-, XML- of PHP-configuratie-indeling gaat het om twee bestanden (het configuratiebestand en het PHP controller bestand). Bij het gebruik van annotaties wordt de configuratie direct in de controller class gedaan.

Om annotaties te beheren, moeten we nog een dependency toevoegen:

1
$ symfony composer req annotations

Je vraagt je misschien af hoe je de packagenaam kunt raden die je voor een functionaliteit moet installeren? Meestal hoef je het niet te weten. In veel gevallen verwijst Symfony naar het te installeren package in zijn foutmeldingen. Wanneer je symfony make:controller uitvoert zonder bijvoorbeeld het annotations-package zal er een foutmelding optreden met daarin een hint over het installeren van het juiste package.

Een controller genereren

Maak jouw eerste controller aan via het make:controller-commando:

1
$ symfony console make:controller ConferenceController

Het commando creëert een ConferenceController-class in de src/Controller/-map. De gegenereerde class bestaat uit wat boilerplate code die klaar is om te worden uitgebreid:

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',
        ]);
    }
}

De @Route("/conference", name="conference")-annotatie is wat de index()-methode tot een controller maakt (de configuratie staat bij de code die dit configureert).

Wanneer je in een browser /conference bezoekt , wordt de controller uitgevoerd en wordt er een response teruggestuurd.

Pas de route aan zodat deze overeenkomt met de 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()
     {

De route name is handig als we in de code naar de homepage willen verwijzen. In plaats van het / pad hard te coderen, gebruiken we de naam van de route.

Laten we een eenvoudige HTML-pagina terugsturen, in plaats van de standaard gerenderde pagina:

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
+        );
     }
 }

Vernieuw de browser:

De hoofdverantwoordelijkheid van een controller is het terugsturen van een HTTP Response voor het verzoek.

Een easter egg toevoegen

Om aan te tonen hoe een response kan profiteren van de informatie uit het verzoek, laten we een kleine Easter egg toevoegen. Wanneer de homepage een query string bevat zoals ?hello=Fabien, voegen we wat tekst toe om de persoon te begroeten:

 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 maakt de gegevens van het verzoek beschikbaar via een Request object. Als Symfony een controller-argument ziet met deze class als typehint, weet het automatisch dat het dit argument aan jou door moet geven. We kunnen dit gebruiken om het name item uit de query string te halen en het aan een <h1> titel toe te voegen.

Bezoek dan / in een browser en vervolgens /?hello=Fabien om het verschil te zien.

Notitie

Let op de call htmlspecialchars() om XSS-kwetsbaarheden te vermijden. Dit is iets dat automatisch voor ons zal worden gedaan wanneer we overschakelen naar een goede template engine.

We hadden ook de naam onderdeel kunnen maken van de 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>

Het {name} deel van de route is een dynamische routeparameter - het werkt als een wildcard. Je kan nu /hello in een browser bezoeken en daarna /hello/Fabien om hetzelfde resultaat te krijgen. Je kan de waarde van de {name} parameter verkrijgen door een controllerparameter met dezelfde naam toe te voegen. Dus $name.


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