TSP1C102 | Frameworks pour le web

Symfony

David Annebicque | @DavidAnnebicque

Objectifs

  • Maîtriser les concepts avancés de la programmation orientée objet et utiliser et appréhender les concepts d’un framework MVC.
  • Prérequis :
    • Algorithmique
    • Programmation Orientée Objet
    • Base de données
    • Concepts MVC

Organisation

  • 20 heures de TD
  • 12 heures de TP
  • Notes :
    • 1 évaluation écrite (dernier TD)
    • 1 note pratique (lors du dernier TP)

MVC (rappels)

MVC : Controller

C'est lui qui reçoit l'interaction (la demande/request) du visiteur

Il se charge de récupérer les éléments nécessaires auprès du/des modèle(s)

Il transmets toutes les données nécessaires à la vue

MVC : View

C'est lui qui apporte la réponse (response) au visiteur

Une vue peut être une page web, un fichier pdf, ...

Ne se préoccupe que de l'affiche des informations, n'assure aucun traitement

MVC : Model

C'est lui qui s'occupe de récuperer et préparer les données

Le modèle peut être en lien avec une base de données

Le modèle peut être en lien avec des API

Le modèle prépare les données pour qu'elles soient facilement manipulables par la vue

Notion de framework

En programmation informatique, un framework ou structure logicielle est un ensemble cohérent de composants logiciels structurels, qui sert à créer les fondations ainsi que les grandes lignes de tout ou d’une partie d’un logiciel (architecture). Un framework se distingue d’une simple bibliothèque logicielle principalement par :

Notion de framework

son caractère générique, faiblement spécialisé, contrairement à certaines bibliothèques ; un framework peut à ce titre être constitué de plusieurs bibliothèques chacune spécialisée dans un domaine. Un framework peut néanmoins être spécialisé, sur un langage particulier, une plateforme spécifique, un domaine particulier : reporting, mapping, etc. ;

Notion de framework

le cadre de travail (traduction littérale de l’anglais : framework) qu’il impose de par sa construction même, guidant l’architecture logicielle voire conduisant le développeur à respecter certains patterns ; les bibliothèques le constituant sont alors organisées selon le même paradigme.

Framework Orienté Objet

un framework est typiquement composé de classes mères qui seront dérivées et étendues par héritage en fonction des besoins spécifiques à chaque logiciel qui utilise le framework.

Framework Orienté Objet

le programmeur qui utilise le framework pourra personnaliser les éléments principaux du programme par extension, en utilisant le mécanisme d’héritage : créer des nouvelles classes qui contiennent toutes les fonctionnalités que met en place le framework, et en plus ses fonctionnalités propres, créées par le programmeur en fonction des besoins spécifiques à son programme.

Avantages d'un framework

  • Pour éviter des erreurs dans l’organisation des appels
  • Éviter les appels directs aux commandes PHP
  • Préférer les versions des Frameworks qui apportent leur lot de contrôles.
  • Plus grand portabilité du code
  • Ne pas réinventer la roue
  • La gestion des formulaire, des utilisateurs, ...

Inconvénients d'un framework

  • Apprentissage d’une couche supplémentaire
  • La majorité des fonctionnalités PHP sont redéfinies
  • Généralement apprentissage d’un moteur de template
  • Apprentissage de l’utilisation du framework choisit : ses classes, ses objets, sa logique !

Quelques frameworks PHP


Détails des frameworks

Quelques frameworks PHP

Symfony

Symfony

  • Framework MVC en PHP 5 (V2) et PHP 7 (V3 et V4), libre
  • Développé en 2005 par la société Sensio pour répondre à ses besoins
  • Division de la société Sension en deux entités l’agence Web et l’entreprise qui soutient et maintient Symfony : SensioLabs, dirigée par Fabien Potencier, l’auteur de Symfony
  • Framework français !, De renommée mondiale
  • Premier framework en France et en Europe

Symfony : Avantages

  • Connectable à presque tous les SGBD
  • De nombreux Bundles, contributeurs, utilisateurs
  • Moteur de template puissant et simple
  • Depuis la V4, Symfony est très léger et très rapide => fin annoncé de Sylex

Feuille de route

Symfony V4

Beaucoup plus d'utilisation de standard, et moins de règles "Symfony", pour un apprentissage plus rapide, et une plus grande simplicité.

Beaucoup de mécanismes sont maintenant automatisés (plus besoin d'écrire de configuration)

Skeleton et Flex

Version Skeleton de Symfony : Apporte un framework Symfony très légér, avec le minimum pour faire fonctionner un controller.

La version Skeleton importe Flex (basée sur Composer) qui est un gestionnaire de "recipes", qui permet l'ajout de fonctionnalité à Symfony (gestionnaire de vue, de base de données, d'email, ...) avec un mécanisme d'auto-configuration de ces "bundles".

Skeleton et Flex

Par défaut Symfony version Skeleton ne sais rien faire ! Par contre, il n'embarque pas des dizaines de Bundles dont vous n'aurez peut être jamais besoin (fonctionnement des versions 2 et 3 avec plus de 46 bundles par défaut, contre 10 aujourd'hui).

Grâce à Flex vous installez rapidement le nécessaire pour répondre à votre projet.

Configuration requise

Pour fonctionner Symfony requiert :

Exercice 1

Installer et configurer son environnement

  • Installer un serveur local (si ce n'est pas déjà fait)
  • Installer Git
  • Installer Composer
  • Installer un vraie IDE ! (PhpStorm / NetBeans, ou éventuellement VSCode).

Exercice 1

Utilisateur de Windows

  • Configurer vos variables d'environnement PATH pour avoir PHP, GIT et COMPOSER
  • Installer une vraie console, par exemple : CMDER
  • Testez les commandes suivantes :

                    php -v
                    composer -v
                    git -v
	            

Vous devez vous afficher les numéros de version. Corrigez les messages d'erreurs éventuels.

Exercice 1

Utilisateur de Mac ou Linux

  • Trouvez et modifiez le php.ini associé à la console. Quelques explications ici.
  • Vous devez ajouter la définition du timezone et ajouter la ligne suivante au php.ini date.timezone="Europe/Paris"
  • Testez les commandes suivantes :

                    php -v
                    composer -v
                    git -v
	            

Vous devez vous afficher les numéros de version. Corrigez les messages d'erreurs éventuels.

Exercice 1

Installation de Symfony

Placez-vous, avec la console, dans le répertoire utilisé par votre serveur Web (www, htdocs, public_html, ...)


                    composer create-project symfony/skeleton nomDuProjet
	            

nomDuProjet est à remplacer par le nom qe vous souhaitez

Le téléchargement commence et peut prendre quelques minutes.

Exercice 1

Installation de Symfony

Vérifions que l'installation fonctionne


                    cd nomDuProjet
                    php -S 127.0.0.1:8000 public/index.php
	            

Rendez vous à l'URL : http://localhost:8000/

Exercice 1

Vous devriez avoir la page suivante :

Eco-système de Symfony et Vocabulaire

Symfony nécessite tout un environnement pour fonctionner. On a déjà vu Composer pour gérer les dépendances par exemple.

Symfony implique aussi différents "langages" et utilise un vocabulaire spécifique (souvent repris dans d'autres framewrok).

YAML

Format de structuration de données très utilisé dans Symfony, mais on peut utiliser du JSON, XML ou des classes PHP, les fichiers de config par défaut sont en YAML.

Annotation

Commentaire PHP directement dans les classes utiles (controller, entité) interprété par Symfony pour générer des fichiers de config temporaires; Nous utiliserons pour un soucis de simplification en majorité cette notation.

Entité (eq. du modèle)

Une entité est une classe PHP. Elle peut faire le lien avec une base de données, on y déclare les différentes propriétés accessibles; Symfony utilise par défaut un outil de persistence de données : Doctrine pour lier une entité à une table de base de données.

ORM : Object Relationnal Mapping

Système permettant de se libérer des requêtes pour la base de données. Il se charge de générer les requêtes à effectuer sur les Entités spécifiées.

Repository

Classe PHP qui fait le pont entre une entité et l'ORM, il permet notamment de structurer des requêtes complexes.

Bundle

Sorte de modules Symfony qui peuvent contenir tout et n'importe quoi ; C'est la force de Symfony les modules peuvent fonctionner indépendemment et même sur d'autres structures PHP, autre framework etc.

Cette notion tend à disparaître avec la V4 de Symfony.

Environnements

Symfony propose par défaut 2 environnements : dev et prod qui permettent de donner des configs différentes en fonction de l'environnement de travail ; dev permet une utilisation sans cache avec des outils de dev comme le profiler ; prod lui permet d'utiliser le site sous cache et sans aucun message d'erreurs. De plus on peut configurer les différentes environnements pour par exemple rediriger tous les mails vers toto@titi.com en dev et laisser le fonctionnement normal pour prod ; pratique pour les debugs.

Environnements

Symfony propose également de définir autant d'environnement que nécessaire afin d'avoir différentes configurations. Le changement d'un environnement à un autre se faire en modifiant la ligne suivante dans le fichier ".env" :


                ###> symfony/framework-bundle ###
                APP_ENV=dev
                

Profiler

Le profiler est un outil puissant (et indispensable) pour débuger une application. Par défaut le profiler n'est pas installée. Pour l'ajouter il faut executer la commande suivante :


                composer require profiler --dev
                
                

Profiler

Le profiler est toujours visible en bas de la page en mode développement.

Profiler

Chaque item est cliquable et apporte d'autres précieuses informations.

Architecture de Symfony

Avec la version 4, Symfony à grandement simplifié la structure de ses répertoires et à normalisé les appelations pour coincider avec la pratique de la majorité des framexork.

Symfony à également abandonné la notion de Bundle, qui était nécessaire pour développer son projet.

Architecture de Symfony

Architecture de Symfony

  • bin/ : contient la console
  • config/ : contient les configurations des différents bundles, les routes, la sécurité et les services
  • public/ : c'est le répertoire public et accessible du site. Contient le contrôleur frontal "index.php" et les assets (css, js, images, ...)
  • src/ : contient votre projet (M et C du MVC)

Architecture de Symfony

  • templates/ : contient les vues (V du MVC)
  • var/ : contient les logs, le cache
  • vendor/ : contient les sources de bundles tiers et de Symfony

Architecture de Symfony : config/

Architecture de Symfony : src/

Création d'une première page

Source : Documentation Symfony officielle

Pour créer une page dans Symfony, il faut, au minimum :

  • Une route : pour faire le lien entre une URL et une méthode d'un contrôleur
  • Un contrôleur : qui contient des méthodes, chacune, en générale, associée à une route
  • Une méthode : permet l'execution d'une action précise, généralement en lien avec une route

Création d'une première page


                    // src/Controller/LuckyController.php
                    namespace App\Controller;
                    use Symfony\Component\HttpFoundation\Response;

                    class LuckyController
                    {
                        public function number()
                        {
                            $number = mt_rand(0, 100);
                            return new Response(
                                'Lucky number: '.$number.''
                            );
                        }
                    }
                

Création d'une première page

Ce code est votre premier contrôleur (à déposer dans src/Controller)

Ce contrôleur est composé d'une méthode qui calcul un nombre aléatoire et retourne une réponse qui est du code HTML.

Ce code n'utilise pas directement les vues de Symfony, et ne fonctionne pas, car il n'est pas lié à une route.

Création d'une première page

Il faut définir les routes. Il existe de nombreuses méthodes (yaml, xml, php, annotations)

Pour information voici la syntaxe en YAML, à mettre dans le fichier routing.yaml.


                    # config/routes.yaml

                    # the "app_lucky_number" route name is not important yet
                    app_lucky_number:
                        path: /lucky/number
                        controller: App\Controller\LuckyController::number
                

Création d'une première page

Le site sera accessible à cette adresse (en exécutant le serveur local à Symfony) http://localhost:8000/lucky/number

Nous n'utiliserons pas cette solution, pour des raisons de confort.

Création d'une première page

Nous allons utiliser les annotations, qui permettent une syntaxe plus simple, et une proximité entre la définition de la route et la définition de la méthode.

Pour cela, il faut installer les annotations à Symfony


                    composer require annotations
                

Création d'une première page


                    // src/Controller/LuckyController.php
                    namespace App\Controller;
                    use Symfony\Component\HttpFoundation\Response;
                    use Symfony\Component\Routing\Annotation\Route;


                    class LuckyController
                    {
                        /**
                         * @Route("/lucky/number", name="app_lucky_number")
                         */
                        public function number()
                        {
                            $number = mt_rand(0, 100);
                            return new Response(
                                'Lucky number: '.$number.''
                            );
                        }
                    }
                

Création d'une première page

Cette solution fonctionne, mais écrire tout le code HTML dans la méthode n'est pas très pratique. Nous devons donc écrire des vues. Par défaut, Symfony utilise Twig.

Pour cela, il faut l'installer


                    composer require twig
                

Création d'une première page

Il faut ensuite modifier le contrôleur pour utiliser les vues.


                    // src/Controller/LuckyController.php
                    namespace App\Controller;
                    use Symfony\Component\Routing\Annotation\Route;
                    use Symfony\Bundle\FrameworkBundle\Controller\Controller;

                    class LuckyController extends Controller
                    {
                        /**
                         * @Route("/lucky/number", name="app_lucky_number")
                         */
                        public function number()
                        {
                            $number = mt_rand(0, 100);
                            return $this->render('lucky/number.html.twig', array(
                                'number' => $number,
                            ));
                        }
                    }
                

Création d'une première page

Il faut maintenant écrire la vue.


                    {# templates/lucky/number.html.twig #}
                    <h1>Your lucky number is {{ number }}</h1>
                

Et voilà !

Vous venez de faire votre première page !

Exercice 2

A vous de jouer ! Reprenez l'exemple et écrivez votre première page

Exercice 3

Ajoutez deux routes, et deux méthodes pour avoir 2 pages supplémentaires.

Les routes

La documentation complète est ici :

https://symfony.com/doc/current/routing.html

Quelques éléments de synthèses ci-après.

Les routes

Il est possible d'écrire des routes en PHP, en XML, en YML et en annotations

On va privilégier, comme le recommande Symfony, les annotations.

L'avantage est que les routes sont proches des méthodes associées.

Les routes

Une annotation de route comprend au minimum :

  • Un "path" ou "pattern" qui correspond à l'URL saisie par un visiteur
  • Un nom de route.

                    @Route("/blog", name="blog_list")
                

Les routes

Les instructions doivent obligatoirement être entre " " (ne pas utiliser de ' '). En dehors du pattern, tous les autres paramètres ont un nom (name, methods, requirements...)

L'ordre des routes est important. Dès que Symfony trouve une route qui peut correspondre il arrête de chercher !.

Les routes

Il est facile de passer des paramètres dans une URL. Pour cela il faut ajouter dans le pattern et dans les paramètres de la méthode l'information des paramètres.


                    /**
                     * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
                     */
                    public function list($page = 1)
                

Les routes

  • La partie {page} est le paramètre à ajouter dans l'URL.
  • on retrouve ce paramètre dans les paramètres dans la méthode ($page)
  • La partie requirements est optionnelle (mais vivement recommandée), elle permet de spécifier le type de données attendu pour "page". Cela peut permettre de différencier des routes.

Les routes

Pour appeler une route depuis un contrôleur on va utiliser le name de la route souhaitée.


                    $url = $this->generateUrl(
                            'blog_show',
                            array('slug' => 'my-blog-post')
                        );
                

Permet de créer une URL vers la route nommée 'blog_show' qui prend un paramètre nommé 'slug'.

Les routes

Pour appeler une route depuis un template TWIG.


                    <a href="{{ path('blog_show', {'slug': 'my-blog-post'}) }}">Lien vers l'article de blog.</a>
                

Permet de créer une URL vers la route nommée 'blog_show' qui prend un paramètre nommé 'slug'.

Les controllers

https://symfony.com/doc/current/controller.html

Les Vues

https://symfony.com/doc/current/templating.html

Les Vues


                        <body>
                            <h1><?php echo $page_title ?></h1>
                            <ul id="navigation">
                                <?php foreach ($navigation as $item): ?>
                                <li>
                                        <a href="<?php echo $item->getHref() ?>">
                                            <?php echo $item->getCaption() ?>
                                        </a>
                                    </li>
                                <?php endforeach ?>
                            </ul>
                        </body>
                

Les Vues


                        <body>
                            <h1>{{ page_title }}</h1>

                            <ul id="navigation">
                                {% for item in navigation %}
                                    <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
                                {% endfor %}
                            </ul>
                        </body>
                

Les Vues

Il existe trois types de syntaxe dans Twig

  • {{ ... }} : pour "dire quelque chose", affiche une variable ou le résultat d'une expression.
  • {% ... %} : pour "faire quelque chose"
  • {# ... #} : pour commenter quelque chose.

Les Vues

Il est possible d'appliquer des filtres sur les variables pour effectuer des transformations : Majuscules, minuscules, dates, ...


                    {{ variable|upper }}
                

https://twig.symfony.com/doc/2.x/

Il est possible de cumuler les filtres.

Les Vues : Structures de contrôles

Une boucle


                    {% for i in 1..10 %}
                          
                    {% endfor %}
                

Les Vues : Structures de contrôles

Une boucle


                    <ul>
                        {% for user in users %}
                            <li>{{ user.username }}</li>
                        {% endfor %}
                    </ul>
                

Les Vues : Structures de contrôles

Une boucle


                    <ul>
                        {% for user in users if user.active %}
                            <li>{{ user.username }}</li>
                        {% else %}
                            <li>No users found</li>
                        {% endfor %}
                    </ul>
                

Les Vues : Structures de contrôles

Un test conditionnel


                    {% if condition %}
                    
                    {% elseif condition %}
                    
                    {% else %}
                    
                    {% endif %}
                

Base de données / ORM Doctrine

Il est possible de se connecter à la majorité des systèmes de base de données

Contrairement à vos habitudes, dans Symfony, toute la gestion et la création d'une base de données et de ses tables se fait directement dans le cas, et PAS dans PhpMyAdmin ou en SQL.

Base de données / ORM Doctrine

Nativement, Symfony ne gère aucune base de données, il faut donc commencer par l'installer. Nous utiliserons Doctrine.

Doctrine est un ORM (Object Relationnal Mapper), qui peut être utilisé en dehors de Symfony et avec d'autres framework.

ORM : Object Relationnal Mapper

Un ORM (Object Relation Mapper) permet de gérer manipuler et de récupérer des tables de données de la même façon qu'un objet quelconque, donc en gardant le langage PHP. Plus besoin de requête MySQL, PostgresSQL ou autre.

ORM Doctrine

Doctrine va permettre plusieurs choses :

  • Décrire un objet (une entité dans Symfony), correspondant à une table
  • Décrire les liaisons qui existent entre ces objets
  • Ecrire et executer des requêtes, sous un format indépendant du SQL ou du langage du SGBDR utilisé.

Installation de Doctrine

Installation de Doctrine dans symfony


                    composer require doctrine
	            

Installation de Doctrine

Installation de "maker" qui est un outil d'aide dans la console pour générer du code dans Symfony.


                    composer require maker
	            

Configuration de Doctrine

Configuration de Doctrine pour se connecter à notre base de données. Dans le fichier ".env" (il peut être caché), modifier la ligne avec vos paramètres.


                    # .env

                    # customize this line!
                    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name"
	            

Création de la base de données

Maintenant, et toujours sans aller dans PHPMyAdmin, nous allons créer la base de données avec la commande suivante.


                    php bin/console doctrine:database:create
	            

Création d'une table/entity

Nous allons maintenant créer notre première entity (ou table dans ce cas), et toujours sans aller dans PHPMyAdmin, avec la commande suivante.


                     php bin/console make:entity NomDeEntity
	            

Attention. Une entity n'est pas forcément une table. C'est avance tout une classe PHP. Une entity est une table si elle contient des liens avec l'ORM.

Création d'une table/entity

Nous allons maintenant créer notre première entity (ou table dans ce cas), et toujours sans aller dans PHPMyAdmin, avec la commande suivante.

Création d'une table/entity


// src/Entity/NomDeEntity.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\NomDeEntityRepository")
 */
class NomDeEntity
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;
    // add your own fields
}
                

Création d'une table/entity

Pour ajouter des champs, il faut déclarer de nouvelles propriétés dans notre classe, en leur associant le "mapping doctrine" (les annotations commencant par @ORM).

Tous les types disponibles sont décrits dans la documentation de doctrine.

Création d'une table/entity


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

    /**
     * @ORM\Column(type="decimal", scale=2, nullable=true)
     */
    private $price;
                

Création d'une table/entity

Une fois les champs (propriétés) de votre table tous décrits, il faut générer les getters et les setters (l'utilisation de votre IDE est recommandée).

Ensuite il faut executer deux commandes. La première pour "calculer" les différences entre la version précédente et la nouvelle version de vos entités.


                    php bin/console doctrine:migrations:diff
	            

Cette instruction va créer un fichier contenant les modifications à apporter sur la base de données.

Création d'une table/entity

Il faut maintenant appliquer les modifications trouvées sur la base de données pour qu'elle soit cohérente avec les entités.


                    php bin/console doctrine:migrations:migrate
	            

Ajouter des champs dans notre entity

Il est possible d'ajouter des champs après ces étapes dans ce cas il faut refaire le processus suivant :

  • Ajout des propriétés dans nos entités.
  • Génération des getters et des setters.
  • Calcul du fichier de migration
  • Mise à jour de la base de données.

Il est possible de calculer plusieurs migrations sans mettre à jour directement la base de données, simplement pour avoir les fichiers de migration pour chaque modification.

Utilisation de l'ORM

Une fois la base de données mise en place on va pouvoir insérer, modifier, supprimer et récupérer des informations de la base de données sans saisir de requêtes via des méthodes en initialisant l'entité créée.

Insertions dans la base de données

Le code ci-après va permettre d'ajouter une nouvelle entrée dans notre base de données. On suppose que Product est une entitée liée à notre ORM.

Ce morceau de code est à mettre dans une méthode d'un controller.

Insertions dans la base de données


1. // you can fetch the EntityManager via $this->getDoctrine()
2. $entityManager = $this->getDoctrine()->getManager();

3. $product = new Product();
4. $product->setName('Keyboard');
5. $product->setPrice(19.99);
6. $product->setDescription('Ergonomic and stylish!');

7. // tell Doctrine you want to (eventually) save the Product (no queries yet)
8. $entityManager->persist($product);

9. // actually executes the queries (i.e. the INSERT query)
10. $entityManager->flush();
	            

Quelques explications

  • $this->getDoctrine()->getManager() récupére le manger d'entité de doctrine. C'est l'objet le plus important de doctrine qui permet de gérer les objets, et permet la sauvegarde, la récupération des données sous forme d'objets depuis la base de données.
  • Lignes 3 à 6 permettent de créer un nouvel objet, au sens de la POO PHP.

Quelques explications

  • persist($product) permet de sauvegarder les données du nouvel objet dans le manager de doctrine. Cette instruction n'écrit rien dans la base de données
  • flush() permet d'éxecuter tout ce qui est en mémoire de doctrine. Par exemple ici, va éxecuter le fait d'ajouter un nouvel objet dans la base de données..

Récupérer des données

Doctrine intégre des méthodes pour executer rapidement les requêtes de type "select" les plus courantes.


$product = $this->getDoctrine()
        ->getRepository(Product::class)
        ->find($id);

if (!$product) {
    throw $this->createNotFoundException(
        'No product found for id '.$id
    );
}
                

Cette requète permet de récupérer un produit spécifique en fonction de son id.

Récupérer des données

Ci-dessous toutes les possibilités offertes par Doctrine directement.


                $repository = $this->getDoctrine()->getRepository(Product::class);

                // look for a single Product by its primary key (usually "id")
                $product = $repository->find($id);

                // look for a single Product by name
                $product = $repository->findOneBy(['name' => 'Keyboard']);
                // or find by name and price
                $product = $repository->findOneBy([
                'name' => 'Keyboard',
                'price' => 19.99,
                ]);

                // look for multiple Product objects matching the name, ordered by price
                $products = $repository->findBy(
                ['name' => 'Keyboard'],
                ['price' => 'ASC']
                );

                // look for *all* Product objects
                $products = $repository->findAll();
                

Récupérer des données

  • find($id) : permet de récupérer un seul objet en fonction de son id.
  • findAll() : récupére tous les objets d'une table
  • FindBy(array(...)) : permet de récupérer des objets en fonction de critères (where en SQL), les critères sont associés avec un AND
  • FindOneBy(array(...)) : récupère un objet unique en fonction de critères.

Mise à jour d'un objet

La mise à jour est en fait une combinaison d'une requête pour récupérer un objet précis, et d'un enregistrement dans la base de données.


                    1. $entityManager = $this->getDoctrine()->getManager();
                    2. $product = $entityManager->getRepository(Product::class)->find($id);

                    3. if (!$product) {
                    4.     throw $this->createNotFoundException(
                    5.       'No product found for id '.$id
                    6.    );
                    7. }

                    8. $product->setName('New product name!');
                    9. $entityManager->flush();
                

Mise à jour d'un objet

Explication des lignes

  • Ligne 1 à 7, récupération de l'objet selon son ID, et on s'assure que l'objet existe (la requête retourne un résultat)
  • Ligne 8 , mise à jour de l'objet en passant par les setters.
  • Ligne 9 , sauvegarde de l'objet dans la base de données. Pas besoin de persister, puisque l'objet est déjà associé à Doctrine avec la requête.

Utilisation du ParamConverter

Symfony propose une méthode rapide lorsque l'on souhaite récupérer un objet d'une base de données en fonction d'une route. Par exemple afficher les informations d'un produit en particulier ou le modifier.


/**
 * @Route("/product/{id}", name="product_show")
 */
public function showAction(Product $product)
{
    // use the Product!
    // ...
}
                

Utilisation du ParamConverter

L'utilisation du ParamConverter de Symfony, consiste donc à définir le type du paramètre de la méthode.

Dans l'exemple précédent, la route attend un "id" (un numéro de produit). La méthode va convertir cet id en une requète de type "find($id)" pour retourner un Product dans la variable $product. Tout ce mécanisme est automatique.

Si aucun produit ne correspond, la page 404 est renvoyée.

Suppression d'un objet


$entityManager->remove($product);
$entityManager->flush();
                

Après avoir récupérer l'objet à supprimer, on demande à l'entityManger de le supprimer de doctrine (remove), puis de mettre à jour la base de données.

Autres requêtes

Si vous avez besoin d'autres requêtes que celles disponibles, il est bien sûr possible de les écrire. Pour cela il faudra ajouter des méthodes dans le fichier "Repository" associé à votre entité et écrire la requête en utilisant le langage de requête de Doctrine (DQL).

Nous n'aborderons pas cette partie dans ce cours. Mais vous trouverez des éléments Sur la documentation officielle de Doctrine et celle de Symfony

Relations entre entités et ORM

Généralement les entités sont liées entre-elles. Et nous avons la notion de clé étrangère qui peut apparaître dans nos tables. Bien sûr il est possible de gérer cela avec Doctrine.

Pour ce faire, il va falloir expliquer à Doctrine, les liens qui existent entre nos entités. Et Doctrine, se chargera de créer les clés étrangères où cela est nécessaire.

Relations entre entités et ORM

Il existe les relations suivantes dans doctrine :

  • OneToMany (et son inverse ManyToOne)
  • ManyToMany
  • OneToOne

Il existe également une notion très importante dans ces relations : propriétaire et inverse. Une relation entre deux entités a toujours un propriétaire et un inverse.

Relations entre entités et ORM

Propriétaire : L'entité dite propriétaire contient la référence à l'autre entité et est gérée par défaut par Doctrine ; Aucune recherche particulière n'est à faire pour récupérer la relation propriétaire. (Commentaire vers Article, commentaire contiendra la relation vers article avec un champs article_id par exemple et on pourra récupérer la relation de la sorte : $com->article_id->title ).

L'inverse est la relation inverse. Il faudrait faire une recherche un peu plus complexe pour récupérer les relations (Article vers Commentaires avec un where par exemple).

Relations entre entités et ORM

Enfin, une relation peut être unidirectionnelle ou bidirectionnelle. Les relations bidirectionnelles peuvent être gérées automatiquement par Symfony modifiant un peu les entités inverses avec inversedBy et mappedBy.

Dans le cas d'une relation bidirectionnelle, il faut aussi explicité la relation dans l'entité inverse. La relation bidirectionnelle permet de faciliter la recherche d'élement en partant de l'inverse (Article vers Commentaires).

Relation 1..n (OneToMany) et n..1 (ManyToOne)

La relation 1..n définit une dépendance multiple entre 2 entités de sorte que la première peut être liée à plusieurs entités

Prennons l'exemple des étudiants et des absences. Un étudiant peut avoir plusieurs (many) absences, mais une absence n'est associée qu'a un (one) seul étudiant.

Cette relation peut donc se résumer à : plusieurs (many) absences pour un (one) étudiant, ou de manière équivalent un (one) étudiant pour plusieurs (many) absences.

Relation 1..n (OneToMany) et n..1 (ManyToOne)

On se place prioritairement du coté du Many, et on doit écrire la relation ManyToOne. Elle est obligatoire pour définir la relation précédente.



                class Absence
                {
                // ...

                /**
                * @ORM\ManyToOne(targetEntity="App\Entity\Etudiant")
                * @ORM\JoinColumn(nullable=true)
                */
                private etudiant;
                ...
                }
                

Relation 1..n (OneToMany) et n..1 (ManyToOne)

Le code précédent est le minimum pour définir une relation.

On dit dans ce cas que Absence est propriétaire de la relation (toujours du coté du Many).

La relation décrite précédemment est unidirectionnelle. Pour la rendre bidirectionnelle il faut décrire la relation dans l'entité etudiant (avec la relation inverse OneToMany).

Relation 1..n (OneToMany) et n..1 (ManyToOne)

Le code de Etudiant devient



                class Absence
                {
                // ...

                /**
                * @ORM\ManyToOne(targetEntity="App\Entity\Etudiant", inversedBy="absences")
                * @ORM\JoinColumn(nullable=true)
                */
                private $etudiant;
                ...
                }
                

Relation 1..n (OneToMany) et n..1 (ManyToOne)

Le code de etudiant sera :



                class Etudiant
                {
                    // ...

                    /**
                     * @ORM\OneToMany(targetEntity="App\Entity\Absence", mappedBy="etudiant")
                     */
                    private $absences;
                    ...

                    public function __construct()
                    {
                        $this->absences = new ArrayCollection();
                    }

                    /**
                     * @return Collection|Absence[]
                     */
                    public function getAbsences()
                    {
                        return $this->absences;
                    }
                }
                

Relation 1..n (OneToMany) et n..1 (ManyToOne)

La relation OneToMany n'est pas obligatoire. Elle permet juste d'inverser la relation, et de rendre la manipulation plus simple.

Dans ce cas, on fait apparaître un tableau contenant toutes les objets associés à cette relation (many).

Relation 1..n (OneToMany) et n..1 (ManyToOne)

Il faut bien sûr penser à générer les getters et setters, le fichier de migration et mettre à jour la base de données après l'ajout de ces relations.

relation n..n (ManyToMany)

Le fonctionnement est assez similaire à une relation ManyToOne/OneToMany, sauf que cette relation est forcément bidirectionnelle. Il faut décrire le comportement dans les deux entités et choisir, selon la logique désirée, qui sera la relation propriétaire (inversedBy) de la relation inverse (mappedBy).

Cette relation va créer une nouvelle table, contenant les deux clés étrangères.

Exercice 4

Mettre en place les entités pour gérer la structure du jeu.

Attention : on utilisera le type "text" pour les parties contenants des tableaux. Doctrine autorise le type json_array (simplement "json" sur les dernières versions), mais celui-ci pose quelques problèmes avec les anciennes versions de MySQL (il faut au minimum la 5.7).

Exercice 5

Remplir la base de données avec les objectifs et les cartes.

Ecrire la méthode d'un contrôleur permettant de créer une partie (et donc distribuer les cartes)

Exercice 6

A partir d'une partie, écrire la méthode d'un contrôleur permettant d'afficher le plateau.

La sécurité dans Symfony

Symfony gère la sécurité selon deux niveaux :

  • L'authentification : Savoir qui vous êtes et quels droits vous avez
  • L'autorisation (Firewall) : Savoir où vous pouvez aller et qui peut entrer

Documentation sur la sécurité

La sécurité dans Symfony

Il a longtemps été recommandé/pratique (à tord ?) de passer par des Bundles pour gérer la sécurité (FOSUserBundle étant le plus connu).

Aujourd'hui la gestion de la sécurité est devenu d'une grande simplicité. Une simple entity avec quelques méthodes imposées est suffisante.

Qui plus est le retard de FOSUserBundle à passer sur la V4 de Symfony, a vu se développer les tutoriels pour gérer sa sécurité.

Installer le nécessaire

Comme d'habitude, maintenant, il faut installer le nécessaire


                    composer require security
	            

Ceci va installer le nécessaire et créer un fichier security.yaml dans le répertoire config.

Le fichier security.yaml


# config/packages/security.yaml
security:
    providers:
        in_memory: { memory: ~ }
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: ~
            

Le fichier security.yaml

La partie "providers" : détermine comment sont gérés vos utilisateurs (base de données, en clair, ...) et l'encodage des mots de passe.

La partie "firewall" : détermine comment s'authentifie vos utilisateurs (formulaire, OAuth, ...)

La partie "access_control" : détermine qui peut accéder à quelles pages

Vérifier les autorisations

Il est ensuite possible de vérifier dans ses contrôleurs ou vues, les autorisations :

  • $this->getUser(); permet de récuperer l'utilisateur connecté
  • is_granted('NO_DU_ROLE') : permet de vérifer les droits dans une vue
  • isGranted('ROLE_ADMIN') ou $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); permettent de vérifier dans un contrôleur.

Mise en place d'une connexion

Un exemple de tutoriel

Projet

Individuel.

Réaliser un forum, en vous basant sur symfony, votre forum doit intégrer les fonctionnalités suivantes :

  • Inscription, connexion, déconnexion
  • Création de message dans les catégories du forum
  • Possibilité de répondre à un message
  • Administration : Création des catégories et sous-catégories

Projet

  • Il y aura 3 niveaux d'accès : Administrateur, modérateur (possibilité de supprimer ou déplacer un message), membre (possibilité de créer un message)
  • Un message pourra intégrer des fichiers

Projet

L'esthétique du forum n'est pas prise en compte. L'usage d'une librairie CSS ou d'un template est suffisant.

Vous veillerez à l'ergonomie et à la lisibilité.

David Annebicque | IUT De Troyes | DUT MMI | 2018