Programmation PHP avec Symfony/Contrôleur

Principe

modifier

Les contrôleurs Symfony sont les classes qui définissent les opérations à réaliser quand on visite les pages du sites[1] : elles transforment une requête HTTP en réponse (JSON, XML (dont HTML), etc.).

Par convention, leurs noms se terminent par Controller, les noms de leurs méthodes se terminent par "Action", et les URL qui provoquent leurs exécutions sont définies dans leurs annotations. L'exemple suivant affiche un texte quand on visite l'adresse "/" ou "/helloWorld" :

class HelloWorldController extends AbstractController
{
    #[Route(path: '/', name: 'helloWorld')]
    #[Route(path: '/helloWorld', name: 'helloWorld')]
    public function indexAction(Request $request): Response
    {
        return new Response('Hello World!');
    }
}

NB : en PHP < 8, remplacer l'attribut par une annotation :

   /**
    * @Route("/", name="helloWorld")
    * @Route("/helloWorld")
    */

Retours

modifier

Ces méthodes peuvent déboucher sur plusieurs actions :

  • Response() : affiche un texte, et facultativement un code HTTP en deuxième paramètre (ex : erreur 404).
    • JsonResponse() ou $this->json() : affiche du JSON.
    • RedirectResponse() : renvoie vers une autre adresse. Si elle se trouve dans la même application, on peut aussi utiliser le $this->forward() hérité du contrôleur abstrait.
    • BinaryFileResponse() : renvoie un fichier à télécharger (à partir de son chemin).
  • $this->redirect('mon_url') : redirige à une autre adresse.
  • $this->redirectToRoute('nom_de_la_route'); : redirige vers une route du site par son nom.
  • $this->generateUrl('app_mon_chemin', []); : redirige vers une URL relative (ajouter UrlGeneratorInterface::ABSOLUTE_URL en paramètre 3 pour l'absolue, car il est à UrlGeneratorInterface::ABSOLUTE_PATH par défaut dans SF3).
  • $this->container->get('router')->generate('app_mon_chemin', ['paramètre' => 'mon_paramètre']);.
  • $this->render() : affiche une page à partir d'un template, par exemple HTML ou Twig.
 On peut changer les options d'encodage en JSON ainsi :
    $response = new JsonResponse();
    $response->setEncodingOptions(JSON_UNESCAPED_UNICODE);
    $response->setData($data);

    return $response;

Requêtes

modifier

L'objet Request est à préférer à la variable superglobale $_REQUEST, car il fournit une sécurité et des méthodes de manipulation. Ex :

  • $request->getMethod() : la méthode HTTP utilisée.
  • $request->query : les arguments $_GET (query param).
  • $request->request : les arguments $_POST (lui préférer $request->getContent()).
  • $request->files : les fichiers $_FILES (dans un itérable FileBag).

ParamConverter

modifier

On peut injecter un ID dans l'URL ou la requête pour le CRUD d'une entité, mais grâce au paramConverter on peut aussi injecter directement l'entité. Ex :

#[Route('/my_entity/{id}', methods: ['GET'])]
public function getProduct(MyEntity $myEntity): JsonResponse
{
    return new JsonResponse($myEntity);
}

 

Avant Symfony 6.2 cela fonctionne avec un composer require sensio/framework-extra-bundle.

Flashbag

modifier

On peut aussi ajouter un bandeau de message temporaire en en-tête via :

$this->addflash('success', 'mon_message');

Le Twig peut les récupérer ensuite avec[2] :

 {% for flashMessage in app.session.flashbag.get('success') %}
    {{ flashMessage }}
 {% endfor %}

En effet, ils sont stockés dans un Flashbag : un objet de session.

De plus, il en existe plusieurs types (chacun avec une couleur) : success, notice, info, warning, error.

 

Le fait de lire les flash (au moins depuis les Twig avec app.flashes) vide leur tableau.

Accès aux paramètres et services

modifier

Les contrôleurs étendent la classe abstraite Symfony\Bundle\FrameworkBundle\Controller\AbstractController. Cela leur permettait entre autres dans Symfony 2, de récupérer les services et paramètres ainsi :

dump($this->get('session'));
dump($this->getParameter('kernel.project_dir'));

Depuis Symfony 4, il faut injecter le service service_container pour accéder à la liste des services publiques (public: true en YAML), mais la bonne pratique est d'injecter uniquement les services nécessaires dans le constructeur[3][4].

Les paramètres sont ceux des fichiers .yml du dossier "config", mais plusieurs autres paramètres sont fournis par Symfony :

bin/console debug:container --parameters
  • kernel.debug : renvoie vrai si le site est en préprod et faux en prod.
  • kernel.project_dir : dossier racine (qui contient bin/, config/, src/, var/, vendor/).
  • kernel.build_dir.
  • kernel.cache_dir.
  • kernel.logs_dir.
  • kernel.root_dir : deprecated en SF5.3. Chemin du site dans le système de fichier.
  • kernel.bundles : liste JSON des bundles chargés.

Routing

modifier

Par exemple pour créer une nouvelle page sur l'URL :

http://localhost:8000/test

Installer le routage :

composer require sensio/framework-extra-bundle
composer require symfony/routing

Par défaut, la page renvoie l'exception No route found for "GET /test". Pour la créer, il faut d'abord générer un fichier contrôleur (rôle MVC), qui fera le lien entre les URL, les données (modèle) et les pages (vue).

Les URL définies dans l'attribut (ou l'annotation) "route" d'une méthode exécuteront cette dernière :

<?php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class TestController extends AbstractController
{
    #[route('/test/{numero}', name: 'test', requirements: ['id' => '\d*'], methods: ['GET', 'POST'], priority: -1)]
    public function HelloWorldAction(int $numero = 0)
    {
        return new Response('Hello World! '.$numero);
    }
}

NB : en PHP < 8, remplacer l'attribut par une annotation :

   /**
    * @Route("/test/{numero}", name="test", requirements={"id"="\d*"}, methods={"GET|POST"}, priority=-1)
    */

Autres exemples de prérequis :

  • requirements={"id"="fr|en"}
  • requirements={"id"="MaClasse::MA_CONSTANTE1|MaClasse::MA_CONSTANTE2"}
  • requirements={"id"="(?!api/doc|_profiler).*"}
 On peut placer plusieurs attributs ou annotations "route" sur une même méthode. Il est alors possible de changer son comportement selon la route avec if ($request->get('_route') === 'test').

Pour créer des alias, c'est-à-dire plusieurs autres URL pointant vers la page ci-dessus, on peut l'ajouter dans les annotations des contrôleurs, ou bien dans config/routes.yaml (anciennement app\config\routing.yml sur Symfony < 4) :

test:
    path:      /test/{numero}
    defaults:  { _controller: AppBundle:Test:HelloWorld }

À présent http://localhost:8000/test/1 ou http://localhost:8000/test/2 affichent "Hello World!".

 

  • Une fois le YAML sauvegardé, l'URL fournie en annotation (/test) ne fonctionne plus.
  • S'il y a des annotations précédant @Route dans le même bloc, cela peut inhiber son fonctionnement.

Redirection vers la dernière page visitée

modifier

Une astuce pour rediriger l'utilisateur vers la dernière page qu'il avait visité :

$router = $this->get('router');
$lastPage = $request->getSession()->get('last_view_page');
$parameterLastPage = $router->match($lastPage);
$routeLastPage = $parameterLastPage['_route'];
unset($parameterLastPage['_route']); // Pour ne pas la voir dans l'URL finale
return $this->redirect(
   $this->generateUrl($routeLastPage, $parameterLastPage)
);

Annotations.yaml

modifier

Ce fichier permet de définir des groupes de contrôleurs, dont les routes sont préfixées. Ex :

back_controllers:
    resource: ../../src/Controller/BackOffice
    type: annotation
    prefix: admin

front_controllers:
    resource: ../../src/Controller/FrontOffice
    type: annotation
    prefix: api

 

Dans le cas où les contrôleurs ont des contrôles d'accès différents dans security.yaml, il est impératif de les préfixer ainsi pour éviter toute collision des gardiens.

 security.yaml utilise les voteurs : des classes qui écoutent des évènements pour vérifier les permissions de l'utilisateur logué[5].

Paramètres spéciaux

modifier

Il existe quatre paramètres spéciaux que l'on peut placer dans routes.yaml ou en argument des méthodes des contrôleurs[6] :

  • _controller : contrôleur appelé par le chemin.
  • _ format : format de requête (ex : html, xml).
  • _fragment : partie de l'URL après "#".
  • _locale : langue de la requête (code ISO, ex : fr, en).

Exemple :

#[Route('/controller_route', requirements: ['_locale' => 'en|fr'])]
class MyController extends AbstractController

Pour commencer à créer des pages plus complexes, il suffit de remplacer :

 return new Response('Hello World!');

par une vue issue d'un moteur de template. Celui de Symfony est Twig :

 return $this->render('helloWorld.html.twig');

Pour installer les bibliothèques JavaScript qui agiront sur ces pages, se positionner dans /public. Exemple :

cd public/
sudo apt-get install npm
npm install --save jquery
npm install --save bootstrap

Ensuite il suffit de les appeler dans /templates/helloWorld.html.twig pour pouvoir les utiliser :

<link rel="stylesheet" href="{{ asset('node_modules/bootstrap/dist/css/bootstrap.min.css') }}">

<script type="text/javascript" src="{{ asset('node_modules/jquery/dist/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('node_modules/bootstrap/dist/js/bootstrap.min.js') }}"></script>

Modèle

modifier

Pour gérer le modèle du MVC, c'est-à-dire la structure des données stockées, l'ORM officiel de Symfony se nomme Doctrine.

Par défaut, ses classes sont :

  • src/Entity : les entités, reflets des tables.
  • src/Repository : les requêtes SELECT SQL (ou find MongoDB).

Tester un contrôleur

modifier
Pour plus de détails voir : Programmation PHP avec Symfony/HttpClient#Tests.

Références

modifier