Vous êtes sur la page 1sur 14

Module Framework - partie 1 - année 2021-22

Yoann DIEUDONNÉ & Stéphane DEVISMES - Université de Picardie

Symfony 4.4 : MVC, routage, contrôleur,


template, entité, ...
FrameWork
• un cadre de travail : une boite à outils pour augmenter la productivité de
programmation
• avantages :
◦ aide/oblige à bien organiser le code
• donc à bien l'écrire
• facile de répartir le travail pour une équipe
• facile de maintenir le code
• utilisation de briques toutes faites
• existence d'une communauté de développeurs
◦ inconvénients :
• apprentissage long
• risque au changement de version
• frameworks Php : Zend framework, CakePhP, Laravel … Symfony
• autre frameworks web : Django (Python), Spring (Java), Express (Javascript) ...

Créer des pages d'accueil


pour la gestion des salles de Tps

Le traitement des requêtes se fait globalement ainsi :

• Créer la route :
ajouter à config/routes.yaml :
salle_tp_accueil:
path: /accueil
controller: App\Controller\SalleController::accueil
◦ c’est du Yaml :
• l'indentation se fait avec des espaces (mais le même nombre d’espace
exactement !) et surtout pas avec des tabulations !!
◦ Le nom de la route est salle_tp_accueil
▪ son path spécifie que l'URL aura pour chemin /accueil
▪ le contrôleur qui traitera sera : SalleController
▪ et la méthode/fonction à exécuter sera accueil

• Créer le contrôleur src/Controller/SalleController.php et la méthode accueil


<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;

class SalleController {
public function accueil() {
return new Response("ici l'accueil !");
}
}
◦ Tester http://localhost:8000/accueil
◦ puis changer la réponse :
return new Response("<html><body><h1>Salles :</h1> <p>Voici quelques
informations concernant les salles<br \>blablabla blablabla blablabla blablabla
blablabla blablabla blablabla blablabla ....</p></body></html>");
▪ et re-tester http://localhost:8000/accueil
• Ajout de packages/bundles :
◦ la version 4 de Symfony est "minimaliste" : elle installe le minimum de composants.
Le développeur peut ajouter des packages/bundles supplémentaires. Cela se fait à
l'aide de "recipes" (recettes) utilisant Flex, donc composer.
▪ Les "recipes" peuvent être "official" ou "contrib" cad proposée par la communauté
▪ Ajoutons symfony/debug https://packagist.org/packages/symfony/debug
composer require --dev debug
• par dépendance, il ajoute aussi le composant symfony/twig-bundle : le moteur
de template TWIG
▪ et re-tester http://localhost:8000/accueil

• …. le profiler, pour déboguer, s’affiche en bas de la page

• Créer un template
◦ Twig est un moteur de template qui simplifie le travail d'affichage de la vue :
il évite les fastidieux appels PHP : <?php echo $a; ?>
◦ créer le fichier templates/salle/accueil.html.twig
<!DOCTYPE html>
<html>
<head>
<title>Accueil</title>
</head>
<body>
<h1>Salles :</h1>
<p> Voici quelques informations concernant les salles<br \>
En particulier, la salle {{ numero }} <br \>
blablabla blablabla blablabla blablabla blablabla blablabla blablabla
blablabla ....
</p>
</body>
</html>
• Le contrôleur fait appel au moteur de rendu de template
et a besoin pour cela d’hériter d’AbstractController
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class SalleController extends AbstractController {


public function accueil() {
$nombre = rand(1,84);
return $this->render('salle/accueil.html.twig',
array('numero' => $nombre)); // ou ['numero'=>$nombre]);
}
}
▪ Tester http://localhost:8000/accueil
Salles :
Voici quelques informations concernant les salles
En particulier, la salle 34
blablabla blablabla blablabla blablabla blablabla blablabla blablabla blablabla ....

• Ajoutons une action « afficher une salle » avec en paramètre son numéro :
◦ éditer SalleController.php :
▪ créer l’action afficher et ajouter un paramètre à l'action
public function afficher($numero) {
return $this->render('salle/afficher.html.twig',
array( 'numero' => $numero ));
}
◦ ajouter une route à config/routes.yaml :
salle_tp_afficher:
path: /afficher/{numero}
controller: App\Controller\SalleController::afficher
◦ ajouter un template
▪ créer le fichier templates/salle/afficher.html.twig
<!DOCTYPE html>
<html>
<head>
<title>Salle</title>
</head>
<body>
<h1>Salle {{ numero }}</h1>
<p> Voici quelques informations concernant la salle numéro : {{ numero }}<br \>
blablabla {{ numero }} blablabla {{ numero }} blablabla {{ numero }}
blablabla {{ numero }} blablabla {{ numero }} ....
</p>
</body>
</html>
◦ Tester http://localhost:8000/afficher/999
Salle 999
Voici quelques informations concernant la salle de numéro : 999
blablabla 999 blablabla 999 blablabla 999 blablabla 999 blablabla 999 ....
• Ajoutons une image dans le répertoire public/images
et, dans le template, le code suivant ;
<body>
<img src="/images/logo-UPJV.png" />
<h1>Salle {{ numero }}</h1>
◦ Tester http://localhost:8000/afficher/999

Pensez à sauvegarder ! → chap1page4.zip

Exercices 1.1
• Avant de changer de projet, arrêter le serveur
$ symfony server:stop
• Créer un projet guideMichelin
• Puis créer une page d'accueil affichant « Bonjour : Guide Michelin .. trouvez un
restaurant » de routing /accueil

Architecture du framework
Il fonctionne selon l'architecture MVC modèle vue contrôleur.
Structure simplifiée d’un projet symfony :
 config/
les fichiers de configuration générale
 routes.yaml les routes
 ...
 bin/
les exécutables
 console.php utilitaire de développement
 src/
votre code Php !
Les contrôleurs, au sens MVC, analysent et traitent la requête, utilisent si nécessaire le modèle,
préparent la réponse : SalleControlleur.php, ….
Chaque contrôleur possède une ou plusieurs méthodes/fonctions
 templates/
les templates, les vues , au sens MVC, affichent la réponse
 var/
les fichiers créés :
 cache/
Pour un traitement accéléré, Symfony met beaucoup de fichiers en cache : cela pose
quelques fois des Pbs !
 log/ les logs de, notamment, vos erreurs
 vendor/
répertoire des librairies chargées par composer
 symfony/
 doctrine/
 twig/
 ...
 public/
répertoire racine du projet
 index.php qui appelle le app/AppKernel.php avec la requête en cours
 les fichiers public (images, CSS, javascript…)
La gestion du cache :
si Pb de modification non prise en compte

Symfony est rapide grâce à sa mise en cache des pages déjà calculées. Mais, quelques fois,
un changement peut ne pas être pris en compte :

• En mode dev, normalement à chaque changement, Symfony recalcule une bonne partie
du cache. Si des dépendances ne sont pas prises en compte, tapez la commande :
symfony console cache:clear
• Si elle "crache" des erreurs, ou si ça ne suffit pas, supprimez le contenu de
app/cache

Routing

• Module de Symfony basée sur un ensemble de règles qui associent une URL à une
action (≈ l’URL Rewriting).

• Essayer http://localhost:8000/afficher/124 : çà marche !


• Essayer http://localhost:8000/reafficher /124
◦ quand aucune route ne correspond, l'erreur 404 est renvoyée :
No route found for "GET /salleTp/reafficherr/124"
404 Not Found -
• La liste des routes est affichée dans le profiler : → menu Routing
l’ordre de résolution est respecté !

• Définition du routing :
◦ (++) Symfony propose 4 techniques de définition des règles : les annotations, le
YAML, XML et PHP
Attention : ne pas mélanger les techniques !
▪ Par fichier de configuration YAML du bundle :
donc config/routes.yaml
// déjà programmée !
salle_tp_afficher:
path: /afficher/{numero}
controller: App\Controller\SalleController::afficher
▪ Par annotation dans les contrôleurs :
• Pour cela, lancer la "recipe" :
// ne pas executer !
composer require annotations

// ne pas programmer
class SalleController extends Controller {
...
/**
* @Route("/afficher/{numero}", name="salle_tp_afficher")
*/
public function afficher($numero) {
return $this->render('salle/afficher.html.twig', array(
'numero' => $numero ));
}
}
▪ Par fichier de configuration XML routing.xml :
// ne pas programmer !
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="salle_tp_accueil" path="/accueil" controller="App\Controller\
SalleController::accueil" />
<route id="salle_tp_afficher" path="/afficher/{numero}" controller="App\Controller\
SalleController::afficher" />
</routes>

• Paramètres et contraintes des routes :


◦ Affiner la route en ajoutant une contrainte sur le paramètre :
les contraintes sont exprimées sous forme d'expressions régulières
salle_tp_afficher:
path: /afficher/{numero}
controller: App\Controller\SalleController::afficher
requirements:
numero: \d{1,2}
▪ essayer http://127.0.0.1:8000/afficher/abc
No route found for "GET /afficher/abc"
404 Not Found -
▪ essayer http://127.0.0.1:8000/afficher/12
▪ essayer http://127.0.0.1:8000/afficher/1234
◦ Exemple à plusieurs paramètres : /bienafficher /A/23
// ne pas programmer !
salle_tp_bienafficher :
path: /bienafficher/{batiment}/{numero}
controller: App\Controller\SalleController::bien afficher
requirements:
batiment: A|B|C|D
numero: \d{1,2}

◦ Exemple (à paramètre facultatif : /bienafficher /salle/A


// ne pas programmer !
salle_tp_bienafficher :
path: /bienafficher/{batiment}/{numero}
controller: App\Controller\SalleController::bien afficher
defaults :
numero : 0
requirements:
batiment: A|B|C|D
numero: \d{1,2}

◦ Exemple à paramètre format :


ce paramètre spécifique détermine le Content-type de la réponse
// ne pas programmer !
salle_tp_liste:
path: /liste/{document}.{_format}
controller: App\Controller\SalleController::liste
requirements:
_format: xml|json
• L'import de routes :
◦ permet d’importer les routes de différents fichiers
◦ route = préfixe de l’import + route importée
▪ Copier le fichier des routes en config/routes/salle/routes.yaml
salle_tp_accueil:
path: /accueil
controller: App\Controller\SalleController::accueil

salle_tp_afficher:
path: /afficher/{numero}
controller: App\Controller\SalleController::afficher
requirements:
numero: \d{1,2}
▪ Changer config/routes.yaml ainsi :
salle_tp :
resource: routes/salle/routes.yaml
prefix: /salle
Utiliser des préfixes distincts pour chaque contrôleur ou bundle afin d’éviter les
conflits de résolution de route !
▪ Essayer http://localhost:8000/salle/afficher/24 : çà marche !

• Générer des URLs :


◦ dans un contrôleur :
Modifier les méthodes du contrôleur SalleController.php :
Le contrôleur fait une réponse HTTP de redirection sur une url
L’url est générée à partir du nom de sa route.
public function dix() {
$url = $this->generateUrl('salle_tp_afficher', array('numero' => 10));
return $this->redirect($url);
}
• ou plus rapidement :
public function dix() {
return $this->redirectToRoute('salle_tp_afficher', array('numero' => 10));
}
▪ Ajoutez le routing salle_tp_dix au fichier config/routes/salle/routes.yaml :
salle_tp_dix:
path: /dix
controller: App\Controller\SalleController::dix
◦ Essayer http://localhost:8000/salle/dix : çà « redirectionne » !

◦ dans un template :
Modifier le fichier twig templates/salle/afficher.html.twig:
la fonction path construit l'url à partir d'un nom de route
...
<p>lien vers <a href="{{ path('salle_tp_afficher', { 'numero': numero +
1 }) }}">
la suivante</a></p>
<p>lien vers <a href="{{ path('salle_tp_accueil') }}">l'accueil</a></p>
</body>
</html>
▪ Le résultat donne :

lien vers la suivante
lien vers l'accueil
• Générer une erreur/exception :
◦ Par conséquent, une réponse HTTP 404 : ce n'est pas une route mais une
fonctionnalité du contrôleur !
◦ Modifier la méthode afficher du contrôleur SalleController.php :
public function afficher($numero) {
if ($numero > 50)
throw $this->createNotFoundException('C\'est trop !');
else
return $this->render('salle/afficher.html.twig',
array('numero' => $numero));
}
▪ Essayer http://localhost:8000/salle/afficher/51 :

◦ (++) Il est préférable que l’utilisateur soit informé et renvoyé vers l’accueil de
l’application : une solution simple est de surcharger les templates d’erreur et
d’exception

Pensez à sauvegarder ! → chap1page8.zip

Exercices 1.2
• modifier la configuration générale du routing pour que toutes les routes commencent
par /guideMichelin. En particulier, la page d’accueil sera dorénavant accessible via
guideMichelin/accueil.
• Ajouter un lien dans la page d’accueil vers une page menu qui comportera la phrase
« Bienvenue dans le menu du Guide Michelin » de routing /guideMichelin/menu. La page
menu comportera un lien pour revenir à la page d’accueil.
• Apporter les modifications nécessaires pour que sur la page d’accueil s’affiche le nom
donné en paramètre de la route correspondant à l’accueil. Exemple :
/guideMichelin/accueil/Didier affichera la page d’accueil avec la mention « Bonjour
Didier : Guide Michelin ...trouvez un restaurant ». Si le paramètre nom n’est pas
renseigné, la page d’accueil devra simplement avoir le même affichage qu’avant.

Entité
• installation de la librairie Doctrine pour Symfony afin d’accéder simplement aux bases de
données et des outils de création de contrôleurs, entités, ..
composer require symfony/orm-pack
composer require --dev symfony/maker-bundle

• configuration pour la base de données :


◦ Renseignez le fichier général .env :
▪ Soit l’utilisateur «upjv» de mot de passe «upjv2021» et sa base de données
nommée «upjv», accès par serveur local

DATABASE_URL=mysql://upjv:upjv2021@127.0.0.1:3306/upjv
...
• Une entité (entity) est un objet agrégeant des données
qui sont facilement extraites ou fournies à une table de base de données.
Le modèle du MVC Symfony est basé sur l'utilisation d'entités grâce à Doctrine : cet
ORM gère les actions sur l'objet entité en les traduisant en opération sur la base de
données sous-jacente.

◦ Générons, interactivement en console, une entité qui représente une salle avec 3
champs : bâtiment de type string, étage et numéro de type entier.
L'entité se nommera … Salle dans notre bundle.

symfony console make:entity

Class name of the entity to create or update (e.g. AgreeablePizza):


> Salle
created: src/Entity/Salle.php
created: src/Repository/SalleRepository.php

New property name (press <return> to stop adding fields):


> batiment
Field type (enter ? to see all types) [string]:
>
Field length [255]:
> 1
Can this field be null in the database (nullable) (yes/no) [no]:
>

Add another property? Enter the property name (or press <return> to stop adding fields):
> etage
Field type (enter ? to see all types) [string]:
> smallint
Can this field be null in the database (nullable) (yes/no) [no]:
>

Add another property? Enter the property name (or press <return> to stop adding fields):
> numero
Field type (enter ? to see all types) [string]:
> smallint
Can this field be null in the database (nullable) (yes/no) [no]:
>

Add another property? Enter the property name (or press <return> to stop adding fields):
>
Success!

◦ Voici un résumé du fichier entité généré src/Entity/Salle.php :


L'objet a ses champs en private et des méthodes getter et setter en fonction des
droits R/W. Le champ id, clé primaire est automatiquement généré : il est en mode R
(uniquement la méthode get !)
<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

class Salle {
private $id;
private $batiment;
private $etage;
private $numero;

public function getId(): ?int {


return $this->id;
}

public function setBatiment(string $batiment): self {


$this->batiment = $batiment;

return $this;
}

public function getBatiment(): ?string {


return $this->batiment;
}

...
}

◦ Utilisons tout de suite notre entité dans la méthode treize() du contrôleur


SalleController.php :
L'entité Salle est instanciée puis renseignée et … utilisée.
use App\Entity\Salle;

public function treize() {
$salle = new Salle;
$salle->setBatiment('D');
$salle->setEtage(1);
$salle->setNumero(13);
return $this->render('salle/treize.html.twig',
array('salle' => $salle));
}
▪ Créez templates/salle/treize.html.twig :
<!DOCTYPE html>
<html>
<body>
<ul>
<li>batiment {{ salle.batiment }}</li>
<li>etage {{ salle.etage }}</li>
<li>numero {{ salle.numero }}</li>
</ul>
</body>
</html>
▪ Ajoutez des routings
salle_tp_treize:
path: /treize
controller: App\Controller\SalleController::treize

salle_tp_quatorze:
path: /quatorze
controller: App\Controller\SalleController::quatorze
▪ Essayer http://localhost:8000/salle/treize :
• batiment D
• etage 1
• numero 13

• Il est possible/souhaitable d'ajouter des méthodes à l'entité :


souvent, elles constituent la logique métier du modèle
• Ajoutez au fichier entité Entity/Salle.php :
public function __toString() {
return $this->getBatiment().'-'.$this->getEtage().'.'.$this->getNumero();
}
◦ Rmq : la fonction est préfixée par __ c'est une fonction magique (!) de Php
• Modifions le contrôleur Salle.php :
public function quatorze() {
$salle = new Salle;
$salle->setBatiment('D');
$salle->setEtage(1);
$salle->setNumero(14);
return $this->render('salle/quatorze.html.twig',
array('designation' => $salle->__toString()));
//ou seulement $salle
}
• Créez le template quatorze.html.twig :
<!DOCTYPE html>
<html>
<body>
<h1>Salle {{ designation }} </h1>
</body>
</html>
▪ Essayer http://localhost:8000/salle/quatorze :
Salle D-1.13

Pensez à sauvegarder ! → chap1page11.zip

ORM Object-Relational Mapper

• L'ORM permet de manipuler les


données du Modèle en assurant la
correspondance (mapping) entre les
entités et les tables de la base de
données.

Doctrine2 est l'ORM utilisé par


Symfony : http://www.doctrine-
project.org/

• Voici les annotations de l'entité Salle dans le fichier src/Entity/Salle.php


<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity(repositoryClass="App\Repository\SalleRepository")
*/
class Salle
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;

/**
* @ORM\Column(type="string", length=1)
*/
private $batiment;

/**
* @ORM\Column(type="smallint")
*/
private $etage;

/**
* @ORM\Column(type="smallint")
*/
private $numero;
...
}
▪ L'annotation Table permet de définir un nom pour la table qui sera créée en base
de donnée : cette annotation est facultative.
▪ L'annotation Entity permet d'indiquer que l'objet est une entité (qui devra donc être
éventuellement persistée en BD).
▪ L'annotation Column s'applique sur un attribut de la classe : elle permet d'indiquer
les propriétés que devra avoir la colonne de la table correspondant à l'attribut.
• Preparez la migration de la base de données :
symfony console make:migration
Le fichier généré contient la bonne séquence SQL de création de la table Salle dans la
base de données.
◦ Mettez à jour la base de données :
symfony console doctrine:migrations:migrate

WARNING! You are about to execute a database migration that could result in schema
changes and data loss. Are you sure you wish to continue? (y/n)y
Migrating up to 20190705165125 from 0

-> CREATE TABLE salle (id INT AUTO_INCREMENT NOT NULL, batiment VARCHAR(1) NOT
NULL, etage SMALLINT NOT NULL, numero SMALLINT NOT NULL, PRIMARY KEY(id)) DEFAULT
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB
◦ Avec Phpmyadmin, vérifiez la nouvelle structure de la base.
Puis ajoutez quelques salles dans la table ….
INSERT INTO `salle` (`batiment`, `etage`, `numero`) VALUES
('B', 1, 23), ('B', 1, 22), ('D', 1, 12);

• Récupérer une entité salle depuis la BD avec un EntityRepository :


Récupérons une ligne de la table salle dans une entité Salle
◦ Ajouter la fonction voir() du contrôleur SalleController.php :
public function voir($id) {
$salle = $this->getDoctrine()->getRepository(Salle::class)->find($id);
if(!$salle)
throw $this->createNotFoundException('Salle[id='.$id.'] inexistante');
return $this->render('salle/voir.html.twig',
array('salle' => $salle));
}
Le "repository" est un dépositaire chargé de récupérer l'entité depuis la BD ;

La méthode find() récupère la ligne de la table selon la clé passée et hydrate
l'entité : cad récupère les informations et les retourne sous forme d'objet.
▪ modifier le routing :
salle_tp_voir:
path: /voir/{id}
controller: App\Controller\SalleController::voir
requirements:
numero: \d+
▪ Copiez templates/salle/treize.html.twig pour obtenir voir.html.twig
▪ tester http://localhost:8000/salle/voir/1 :
• batiment B
• etage 1
• numero 23

• Enregistrer une entité salle dans la BD :

• Ajoutons une route :


salle_tp_ajouter:
path: /ajouter/{batiment}-{etage}.{numero}
controller: App\Controller\SalleController::ajouter
requirements:
batiment: A|B|C|D
etage: \d
numero: \d{1,2}
• Ajoutons la fonction ajouter au contrôleur Salle.php :
...
public function ajouter($batiment, $etage, $numero) {
$entityManager = $this->getDoctrine()->getManager();
$salle = new Salle;
$salle->setBatiment($batiment);
$salle->setEtage($etage);
$salle->setNumero($numero);
$entityManager->persist($salle);
$entityManager->flush();
return $this->redirectToRoute('salle_tp_voir',
array('id' => $salle->getId()));
}
•La méthode persist() indique à Doctrine que l'entité "salle" sera persistante :
toutes ses modifications apportées seront à reporter dans la base au moment
de la « sauvegarde ».
• La méthode flush() réalise la sauvegarde des entités persistantes dans la base
: l'entité salle voit alors son attribut id affecté.
▪ Tester http://localhost:8000/salle/ajouter/C-0.02
• batiment C
• etage 0
• numero 2

• Le Profiler trace aussi les requêtes SQL :


indispensable pour vérifier que Doctrine réalise les bonnes requêtes SQL
◦ En cliquant sur le profiler : DB queries,
vous obtenez la requête SQL du SELECT d'affichage de l'action voir,

◦ Puis cliquer sur Last10 pour obtenir la liste des 10


dernières requêtes

◦ Cliquer sur la requête « salle/ajouter »

◦ Puis sur le menu Doctrine pour obtenir le


requête d’insertion dans la table

Pensez à sauvegarder ! → chap1page13.zip


Exercices 1.3

• Créer une entité Resto contenant un nom, un chef et un nombre d'étoiles


• Créer la table associée dans votre base de données et y ajouter quelques restos avec
Phpmyadmin
• Créer une route et une fonction pour l'Url /guideMichelin/resto/voir/{id} qui affiche ce
resto
• Créer une route et une fonction qui permettent d’ajouter des nouveaux restos

Vous aimerez peut-être aussi