Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
L'ensemble de ce cours se base sur la version 6 de Symfony
Le cours des versions précédentes se trouve : https://cours.davidannebicque.fr/symfony/v/version-4.1/ ou https://cours.davidannebicque.fr/symfony/v/version-5/
Comprendre le concept de MVC (Modèle-Vue-Contrôleur)
Comprendre le fonctionnement d'un framework
Installer et débuter avec le framework Symfony
Durant ce semestre nous aurons pour fil rouge la construction d'un mini forum de discussion. Vous pouvez en voir une demonstration ici : https://forum.davidannebicque.ovh
Ajoutez une entité "Auteur" comprenant les champs suivant :
Nom
Prénom
droit (auteur/administrateur)
Un auteur peut publier des posts, modifier ses posts.
Un auteur avec des droits administrateurs peut modifier tous les posts, et gérer les catégories.
Modifiez l'entité post pour lui ajouter un auteur et sauvegardé l'auteur dans le post (dans un premier temps on fera une liste déroulante des auteurs)
Créer un formulaire pour pouvoir ajouter des auteurs.
Créer l'entité auteur (make:entity)
Ajouter les 4 champs (nom, prénom, email, droit)
Mettre à jour la base de données (bin/console d:s:u -f depuis docker)
Créer un contrôleur ou lancer la commande make:crud sur auteur
Créer ou améliorer le formulaire avec les labels des champs, le mettre en forme en utilisant un thème.
Modifier l'entité post pour faire une relation avec auteur
make:entity sur Post
La relation est de type ManyToOne
Modifier le formulaire (PostType) pour ajouter une liste déroulante avec les auteurs (entity Auteur)
Depuis la version 4, Symfony à grandement simplifié la structure de ses répertoires et à normalisé les appellations pour coïncider avec la pratique de la majorité des framework.
Symfony à également abandonné la notion de Bundle, qui était nécessaire pour développer son projet.
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)
templates/ : contient les vues (V du MVC)
var/ : contient les logs, le cache
vendor/ : contient les sources de bundles tiers et de Symfony
Vous remarques des fichiers au format yaml, le format par défaut pour la configuration de Symfony. Vous pouvez aussi remarquer les répertoires dev, prod et test. Ces répertoires permettent de facilement différencier une configuration pour un environnement de développement, pour le site en ligne, ou encore pour un contexte de test.
Par exemple, lorsque l'on développe, on ne souhaite pas réellement envoyer les mails aux usagers, par contre, on souhaite les visualiser. On peut donc configurer ce comportement dans le répertoire dev, pour le bundle de gestion des emails. Un autre exemple est la gestion des logs. En dev, on ne souhaite pas les conserver, mais les afficher, en production, on ne souhaite surtout pas les afficher, mais les stocker dans des fichiers.
Le concept est donc de définir le comportement global quelque soit l'environnement dans le fichier à la racine de config/packages, et de spécifier un comportement en fonction de l'environnement dans les répertoire dev, prod ou test.
Dans ce répertoire se trouve toute la logique de l'application, les contrôleurs, les modèles, nos classes spécifiques, les services, ...
Controller/ : Tous les contrôleurs de l'application, accessibles par des routes
Entity/ : Les classes spécifiques pour la gestion de l'application, des données et des API
Migrations/ : Contient les fichiers permettant la mise à jour de la base de données pour chaque modification effectuée sur la structure.
Repository/ : Est très généralement lié à une entité, et permet d'ajouter des requêtes spécifiques.
Tous ces points seront détaillés dans les parties suivantes.
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
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. Ce code n'utilise pas directement les vues de Symfony, et ne fonctionne pas (en tout cas il n'est pas possible de l'appeler), car il n'est pas lié à une route.
Nous allons utiliser les Attributes, qui permettent une syntaxe plus simple, et une proximité entre la définition de la route et la définition de la méthode (il existe d'autres façons de faire, qui peuvent avoir leurs avantages et leurs inconvenients et dépendre des pratiques de l'entreprise).
Les Attributes, contrairement aux annotations, ne nécessitent pas de dépendances supplémentaires. En effet les Attributes sont maintenant natifs en PHP 8.0, et sont supportés par Symfony depuis la version 5.1.
Modifions le contrôleur précédent en intégrant directement la route sous forme d'Attribute.
Pour accéder a cette route, il faut maintenant taper l'URL suivante : http://localhost:8000/lucky/number
si vous executez le serveur de développement de Symfony. Sinon, il faut taper l'URL suivante : http://localhost:8000/public/index.php/lucky/number
. La partie locahost:8000 est à adapter en fonction de votre configuration.
Si vous testez cette URL, vous devriez voir affiché le nombre aléatoire. Cependant, regardez attentivement et vous verrez que ce qui est renvoyé n'est pas du HTML, mais du texte brut. C'est parce que nous n'utilisons pas encore les vues de Symfony, et que nous retournons directement une réponse sans plus d'information que cela sur le format retourné.
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
Il faut ensuite modifier le contrôleur pour utiliser les vues.
Il faut maintenant écrire la vue.
Et Voilà !
Symfony propose un outil qui se nomme "maker" qui permet de générer du code pour nous. Le code produit est très générique, ne correspond pas forcément exactement à vos besoin, mais permet d'avoir une première base de travail et une structure pour vos différents fichiers.
Pour installer le maker (si vous n'avez pas installé un projet avec toutes les dépendances --wepabb), en étant dans votre projet :
composer require maker --dev
ou
symfony composer require maker --dev
Ensuite pour utiliser le maker, dans le projet saisir la commande suivante
bin/console make:controller NomDuController
Cette commande va générer un controller nommé "NomDuController" (dans src/controller) et la vue associée dans le repertoire templates/NomDuController, avec une méthode index.
Pour ajouter d'autres méthodes, vous devrez le faire directement dans le controller, le maker ne permet pas d'en ajouter dans un fichier existant.
La présentation :
Découvrir et appréhender un framework PHP web.
PHP
Programmation Orientée Objet
Structure MVC
Base de données
Le concept de programmation MVC consiste à séparer les différentes parties d'un projet web en trois parties distinctes : le modèle, la vue et le contrôleur. Le modèle représente les données et la logique métier, la vue représente l'interface utilisateur et le contrôleur gère les interactions entre le modèle et la vue. Le contrôleur reçoit les demandes de l'utilisateur, récupère les données nécessaires auprès du modèle et les transmet à la vue pour affichage.
L'intérêt d'utiliser le concept de programmation MVC est de séparer les différentes parties d'un projet web en trois parties distinctes, ce qui permet une meilleure organisation et une plus grande facilité de maintenance du code. En séparant la logique métier, l'interface utilisateur et les interactions entre les deux, il est plus facile de faire des modifications sans affecter les autres parties du code. Cela permet également une meilleure collaboration entre les développeurs travaillant sur le même projet.
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.
C'est lui qui apporte la réponse (response/render) 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
C'est lui qui s'occupe de récupérer 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.
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 :
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. ;
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 (modèle de conception) ; les bibliothèques le constituant sont alors organisées selon le même paradigme.
Un framework dit orienté objet 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.
Le développeur qui utilise le framework pourra personnaliser les éléments principaux du framework 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 développeur en fonction des besoins spécifiques à son application.
Framework MVC en PHP 5 (V2), PHP 7 (V3, V4 et V5 ), PHP 8.1 (V6) libre
Développé en 2005 par la société Sensio pour répondre à ses besoins
Division de la société Sensio 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
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
Avec sa version 4 (et suivante), Symfony à pris un virage important par rapport aux précédentes versions, et se rapproche des "standards" de la majorité des framework, mais à aussi grandement optimisé son poids et sa vitesse d'exécution. Dans une grande logique de simplification Symfony à également automatisé de nombreux mécanismes qui auparavant auraient impliqués de nombreuses lignes de configuration.
La version Skeleton de Symfony : Apporte un framework Symfony très léger, avec le minimum pour faire fonctionner un controller.
La version 4 de Symfony introduit Flex qui est un gestionnaire de "recipes" (recettes), 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". Cela permet donc de fournir par défaut un framework très léger, avec une grande facilité pour lui ajouter tous les composants nécessaires, sans en mettre plus que nécessaire.
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.
Avec sa version 5 (et suivante), Symfony continue sa simplification en facilitant l'usage de nombreux composants redéfinis et devenus génériques.
Un serveur Web
PHP 8.1 ou supérieur
Une maîtrise de son système d'exploitation ! (fichiers cachés, variables PATH, php.ini, console...)
Symfony nécessite tout un environnement pour fonctionner. Symfony implique aussi différents "langages" et utilise un vocabulaire spécifique (souvent repris dans d'autres frameworks).
Le logiciel Composer trouve son équivalent pour le front avec npm ou Yarn
Classe PHP qui fait le pont entre une entité et l'ORM, il permet notamment de structurer des requêtes complexes.
Format de structuration de données très utilisé dans Symfony, mais on peut utiliser du XML, du PHP ou encore les annotations (PHP 7) ou les attributs (php 8), les fichiers de configurations par défaut sont en YAML.
Commentaire PHP directement dans les classes utiles (controller, entité, ...) interprété par Symfony pour générer des fichiers de configuration ;
Les Attributes sont la nouvelle version des annotations, intégrés nativement dans les versions 8 et supérieures de PHP. Les Attributes sont une forme de données structurées, qui permet d'ajouter des métadonnées à nos déclarations de classes, propriétés, méthodes, fonctions, paramètres et constantes. Ils vont nous permettre de définir de la configuration au plus près du code, directement sur nos déclarations.
Les routes permettent de faire un lien entre une URL et un contrôleur.
Sorte de modules Symfony qui peuvent contenir tout et n'importe quoi ; C'est la force de Symfony les modules peuvent fonctionner indépendamment et même sur d'autres structures PHP, autre framework etc.
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 avec le cache et sans aucun message d'erreurs.
De plus on peut configurer les différents 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. Le changement d'un environnement à un autre se faire en modifiant la ligne suivante dans le fichier ".env" (ou .env.local) :
Le profiler est un outil puissant (et indispensable) pour débuger une application. Par défaut le profiler n'est pas installé. Pour l'ajouter il faut exécuter la commande suivante :
Le profiler est toujours visible en bas de la page en mode développement.
Le maker est un outil puissant pour générer du code, et des éléments dans notre projet. Par défaut le maker n'est pas installé. Pour l'ajouter il faut exécuter la commande suivante :
Il est assez facile de modifier le maker pour que le code généré corresponde exactement à notre projet et nos attentes.
Dans Symfony la notion de modèle se retrouve sous la forme (entre autre) d'une Entité. Une entité est une classe PHP, qui peut être connectée à une table de votre base de données via l'ORM. Lorsqu'une entité est liée à une table, via l'ORM, il y a en général un fichier "repository" associé. Un repository permet la génération de requêtes simples ou complexes et dont le développeur peut modifier à volonté.
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.
Symfony utilise Doctrine comme ORM dans son système par défaut. Nous allons utiliser Doctrine mais vous pouvez utiliser d'autres systèmes si vous le souhaitez. Doctrine peut-être géré de plusieurs façon : XML, JSON, YAML, PHP et en Annotation nous allons utiliser ce dernier format de données.
Vous êtes libre d'écrire le code qui permet le traitement métiers en dehors des entités et d'avoir votre propre logique d'organisation.
Comme à chaque fois, il est d'abord nécessaire d'installer les bundles nécessaires pour manipuler la base de données avec un ORM. Il vous faut donc exécuter la commande ci_dessous :
On va également installer, si vous ne l'avez pas encore fait, le bundle "maker" qui contient des outils pour générer du code sous Symfony grâce à la console.
Une fois ces deux éléments installés, il faut configurer la connexion à la base de données. Pour ce faire, il faut éditer le fichier .env
à la racine de votre projet, qui doit normalement contenir une ligne d'exemple.
Une fois le fichier à jour avec vos données, vous pouvez créer votre base de données depuis la console.
Les modifications de structure de votre base de données devront être réalisées avec la console pour que Symfony puisse faire le lien entre les tables et l'ORM.
Utilisez la commande make:entity
(qui est dans le bundle maker) pour avoir une série de question vous permettant de créer votre entité avec l'utilisation de l'ORM Doctrine. Vous pouvez créer une nouvelle entité ou modifier (ajouter des champs) une entité déjà existante en saisissant son nom.
Vous allez devoir répondre à une suite de question avec le nom de l'entité (par défaut cela donnera le nom de la table), et les champs à créer. Dans Symfony une entité possède toujours un champs id, qui est la clé primaire et qui est auto-incrémenté. Vous ne devez donc pas l'ajouter dans la console.
Pour la création d'un champs, il vous faudra donner :
son type
sa taille le cas échéant
si ce champs peut être null
s'il doit être unique (index)
Vous pouvez obtenir la liste des types supportés en tapant "?" à la question du type.
Une fois terminé, le fichier d'Entité et le repository associé sont générés.
Exemple dans la console :
Et le code de l'entité généré dans src/Entity/Product.php
:
A ce stade l'entité est créé, mais n'existe pas dans la base de données. Il reste deux étapes à exécuter.
Sans générer de fichier de migration (qui contient toutes les instructions SQL à exécuter sur la base de données, notamment pour le déploiement d'une mise à jour)
La création d'un fichier de migration qui va contenir le code SQL a exécuter en fonction de votre SGBD.
La mise à jour de votre base de données en fonction du fichier précédemment généré.
Si vous consultez votre PHPMyAdmin vous verrez la table apparaître.
Pour modifier des champs vous pouvez éditer directement le code généré dans la partie annotation: nom (par défaut le nom de la variable), taille, type.
Pour ajouter des champs il vous faut relancer la commande make:entity
en remettant le nom de votre entité.
Après chaque modification ou ajout il faut de nouveau générer le fichier de migration et mettre à jour la base de données. Vous pouvez bien sûr modifier ou créer plusieurs entités avant de faire une mise à jour de votre base de données.
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é fraichement créée :
Il existe à la place de $em->persist, $em->remove($post);
qui permettra de faire une suppression.
Configurer votre base de données (dupliquer le .env
en .env.local
et modifier les informations dans le .env.local
)
Si vous utilisez limage docker du cours, la ligne devrait être
Créer la base de données (bin/console doctrine:database:create
)
Créer une entité (bin/console make:entity
), nommée Categorie et ajouter les champs suivants
titre string 150 caractères
ordre int
L'id sera automatiquement ajouté
Créer un nouveau contrôleur nommé "CategorieTestController"
Ajouter une route pour créer un nouvel enregistrement
Créer un objet Categorie et compléter les informations (titre, ordre)
Récupérer la connexion à doctrine ($em = $this->getDoctrine()->getManager()
)
Associer l'instance de Categorie avec l'ORM ($em->persist($categorie))
Enregistrer dans la base de données ($em->flush()
)
Appeler la route et vérifier que cela s'enregistre dans votre base de données
Essayer d'appeler la route plusieurs fois.
Symfony et Doctrine proposes des requêtes prédéfinies, qui répondent aux usages les plus courant.
Si $em
est le manager associé à une entité :
$em->find($id);
on récupère qu'un seul élément de l'entité avec l'id $id
;
$em->findAll();
on récupère toutes les entrées de l'entité concernée
$em->findBy($where, $order, $limit, $offset);
on recherche avec le tableau $where
on tri avec le tableau $order
on récupère $limit
éléments à partir de l'élément $offset
.
$em->findOneBy($where, $order);
on récupère le premier élément respectant le tableau $where
et trié avec le tableau $order
;
$em->findByX($search);
requêtes magiques où X correspond à n'importe quel champs défini dans votre entité
$em->findOneByX($search)
; requêtes magiques où X correspond à n'importe quel champs défini dans votre entité
Par exemple findBySlug('home')
; ou findByTitle('Bonjour);
génèrera des requêtes de recherche automatiquement. Pour les requêtes avec plusieurs éléments il faudra faire une itération (foreach) ou lister les différents éléments.
Exemple
Si aucune requête prédéfinie ne correspond à vos besoin, vous pouvez bien sûr en créer une en passant par le repository.
Vous pouvez également générer vos requêtes manuellement pour avoir une requête complexe et précise directement dans le controller mais idéalement il faudrait le placer dans le repository dédié.
Et l'utiliser dans votre controller
Ce dernier code effectue une création dans la base de données; pour une modification il suffit de modifier l'instanciation de l'entité de la sorte :
ici on récupère le repository de Post et on récupère l'id 1 ; tout le restant du code reste inchangé.
Créer une deuxième entité "Article" avec :
titre string 255
texte text
datePublication datetime
auteur string 255
image string 255
Ajoutez des données depuis phpMyAdmin dans la table Article ( 3 articles) ou avec un nouveau contrôler de test
Modifiez votre contrôleur et la page "/articles" pour afficher tous les articles de votre table, par ordre décroissant (plus récent au plus ancien)
Modifiez votre page d'accueil pour afficher le dernier article publié.
Tout au long des prochaines séances nous allons mettre en place un système de blog simple permettant de poster des articles, d'obtenir des commentaires et de gérer les accès avec des droits différenciés.
Ce projet va évoluer au fur et à mesure des connaissances de Symfony.
Les concepts vont être vus progressivement selon nos besoins pour le projet. Ils ne seront donc jamais décris de manière exhaustive dans ce cours. Vous pouvez avoir l'ensemble des détails dans la documentation officielle de Symfony :
Par ailleurs vous pouvez aussi trouver un exemple complètement décrit de manière progressive dans le livre de Fabien Pontetier :
Une route permet de diriger une url (ou un pattern d'url) vers une méthode de controller appelée Action.
et
Il est possible de décrire des routes selon les formats de fichiers : XML, Classe PHP, en annotation ou avec les Attributes (php8 et +). Pour plus de commodité nous utiliserons les Attributes.
Une route peut être constante : /blog
ou dynamique : /blog/{slug}
. Ici slug englobé de { } devient une variable dynamique qui prend tous les caractères alphanumériques par exemple : /blog/42
, /blog/lorem-ipsum
, /blog/titi-32\_tata
Ces 3 urls correspondent à la méthode ciblée par la route avec une variable slug
différente. Cette variable peut être récupérée par le controller.
Une route est au minimum un chemin (path) et un nom;
Ces variables peuvent être mises par défaut grâce à "defaults"
Ces variables peuvent être soumises à une validation de format via "requirements"
On peut définir un path différent en fonction de la locale
Vous pouvez cumuler plusieurs routes pour une méthode Action
On peut spécifier le moyen d'accès à une route (GET, POST, PUT, ...), avec le paramètre "methods"
Pour pouvoir utiliser les Attributes il faut :
à ajouter après le namespace dans votre Controller.
On définit une variable d'url via des accolades {ma_variable} :
Pour récupérer la variable dans le controller, il suffit de déclarer une variable dans la méthode avec le même nom que la variable d'url.
Pour définir une valeur par défaut, on peut préciser la valeur sur le paramêtre de la méthode :
Si l'utilisateur utilise l'URL /page/
, la variable $page
sera égale à 1.
On peut cumuler plusieurs variables :
On pourrait utiliser un autre séparation que le slash, comme par exemple -, . ou _. La seule condition étant que Symfony puisse être en mesure de différencier les paramètres.
Pour générer une url en PHP on utilise :
Ou sous TWIG on a deux fonctions :
Les routes redirigent vers une méthode d'un contrôleur (une action); un controller Symfony se nomme de la sorte : NomDuController où le suffixe Controller est obligatoire et le nom du fichier et de la classe est en CamelCase.
Les différentes méthodes se nomment de la sorte :
nomDeLaMethode est lui en miniCamelCase
Dans le cas idéal, le controller doit contenir le minimum de code possible (20 lignes maximum selon les standards de Symfony). Il ne doit que faire le lien entre les différents éléments de l'application et une réponse.
Une méthode d'un contrôleur renvoie toujours un type Response ; il existe plusieurs type de Response : JsonResponse, RedirectResponse, HttpResponse, BinaryFileResponse etc ...
La plus utilisée est Response pour l'utiliser on va ajouter dans le "use" suivant :
et dans la méthode action on :
Affiche à l'écran Ma response. Dans l'état cette réponse n'est pas du HTML, car rien n'est précisé dans le retour de la méthode.
Une méthode render() (définie quand la classe AbstractController dont votre controller doit hériter) permet aux méthodes de récupérer une vue et d'afficher le contenu de la vue compilée avec les différentes variables envoyées.
Ici on va récupérer le template présent dans templates/default/index.html.twig pour affecter la variable variable.
Les premières étapes consistent à mettre en place les bases de notre projet :
Une nouvelle installation de Symfony, pour un projet nommé blog
On installera toutes les dépendances en une seule fois pour plus de confort : composer require webapp
dans le dossier blog/
Un contrôleur et la méthode pour la page d'accueil et la route "/"
La vue associée à la page d'accueil
Une page de contact avec une route "/contact" et une vue associée
Testez vos routes et vos vues, ajoutez un contenu fictif simple pour le moment avec uniquement du HTML comme vous savez le faire depuis le S1.
Un template ou une vue est le meilleur moyen d'organiser et de restituer le code HTML à partir de votre application, que vous deviez rendre le code HTML à partir d'un contrôleur ou générer le contenu d'un courrier électronique . Les templates dans Symfony sont créés avec Twig: un moteur de modèle flexible, rapide et sécurisé.
Les vues sont des fichiers HTML qui sont compilés par Symfony pour être envoyés au navigateur. Elles sont stockées dans le dossier templates
et sont généralement organisées par contrôleur.
Un moteur de template permet de limiter les logiques complexes pour réaliser des templates simples à coder. Un moteur de template intègre généralement des fonctionnalités qui sont récurrentes dans le développement "front" et qui permettent de simplifier le code à écrire.
Twig est le moteur de template par défaut de Symfony. Il permet de faire des boucles, des conditions, des inclusions, des héritages, des filtres, des fonctions, etc. Il est très complet et permet de faire des choses très puissantes. Twig est un langage à part entière, mais il est très simple à apprendre et à utiliser. Twig s'intègre dans le code HTML, il sera ensuite interprété par le moteur Symfony pour générer la page HTML finale.
Exemple d'un code que vous pourriez écrire en PHP
Ce même code, écrit avec TWIG serait :
La syntaxe est plus "legére" et moins encombrées des balises PHP. Le code semble donc plus lisible et par conséquent plus facile à maintenir.
La syntaxe Twig est basée sur uniquement trois constructions:
{{ ... }}
, utilisé pour afficher le contenu d'une variable ou le résultat de l'évaluation d'une expression;
{% ... %}
, utilisé pour exécuter une logique, telle qu’une condition ou une boucle;
{# ... #}
, utilisé pour ajouter des commentaires au modèle (contrairement aux commentaires HTML, ces commentaires ne sont pas inclus dans la page rendue).
Vous ne pouvez pas exécuter de code PHP dans les modèles Twig, mais Twig fournit des utilitaires permettant d'exécuter une certaine logique dans les modèles. Par exemple, les filtres modifient le contenu avant le rendu, comme le upper
filtre pour mettre le contenu en majuscule :
{{ title|upper }}
TWIG est très puissant lorsqu'il s'agit de manipuler des variables. Pour lui, si c'est un tableau associatif ou un objet avec des propriétés cela est identique dans la syntaxe.
Par exemple si vous avez le tableau $tab['param1']
vous écrirez en TWIG
Si vous avez un objet $tab
, qui est une instance d'une classe ayant comme propriété $param1
, la syntaxe en TWIG sera :
Si vous avez un objet $tab
, qui est une instance d'une classe ayant comme propriété $param1
, qui est un tableau associatif avec une clé key1
, la syntaxe pourrait être :
La manière dont TWIG inspecte votre code pour trouver la meilleure façon d'interpréter une variable est la suivante :
For convenience's sake foo.bar
does the following things on the PHP layer:
check if foo is an array and bar a valid element;
if not, and if foo is an object, check that bar is a valid property;
if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
if not, and if foo is an object, check that getBar is a valid method;
if not, and if foo is an object, check that isBar is a valid method;
if not, and if foo is an object, check that hasBar is a valid method;
if not, return a null value.
foo['bar'] on the other hand only works with PHP arrays:
check if foo is an array and bar a valid element;
if not, return a null value.
TWIG permet l'héritage de template via un extends
dans les templates enfants :
Dans les templates mère on définit des "block" que l'on vient surcharger dans les templates enfants :
On peut également reprendre le block parent via
On crée donc des templates mère assez flexibles pour pouvoir en hériter et surcharger les différents blocks
En se basant sur les concept d'héritage, définir un menu avec nos deux pages (accueil et contact) dans le fichier base.html.twig et l'hériter dans les deux autres templates.
Pour inclure des images, des fichiers CSS ou JavaScript, il faut utiliser la fonction asset() :
Cette fonction va chercher le fichier dans le dossier public/images/logo.png
Asset n'est pas installé par défaut, il faut donc l'installer :
Vous pouvez utiliser cette fonction pour inclure des fichiers CSS ou JavaScript :
Créer un dossier css dans le dossier public
Créer un fichier style.css dans le dossier css, faites un peu de CSS pour mettre en forme votre page d'accueil
Ajouter une balise link dans le fichier base.html.twig pour inclure le fichier css
Ajouter une balise img dans le fichier index.html.twig de votre template de la page d'accueil pour inclure une image de votre choix
{% %}
permet d'utiliser des logiques tels que :
{% if %} {% else %} {%endif %}
: condition
{% for item in items %}{%endfor}
: foreach
{% set foo='foo' %}
: set des variables
Il est possible d'écrire des tests, la syntaxe est très proche de celle de PHP. Attention toutefois, les opérateurs de comparaison ne s'écrive pas de manière identique.
Le && de PHP s'écrit "and" dans TWIG, et le || de PHP s'écrit "or" dans TWIG.
Il est possible d'avoir des elseif (autant que nécessaire) et un bloc else (1 au maximum)
Il n'existe que la boucle for dans TWIG (elle est un peu l'équivalent d'un foreach).
Le code ci-dessous est une boucle qui varie de 1 à 10.
La boucle ci-dessous est une boucle qui parcours une "collection" users (l'équivalent du foreach). Attention la syntaxe est inversée par rapport au PHP
Dans le cadre d'une boucle TWIG propose une variable nommée loop qui permet d'avoir des informations sur la boucle :
La boucle ci-dessous intègre un test. Ce qui simplifie l'écriture. Elle intègre également un else dans le cas ou la boucle ne ferait aucune itération. Il est possible d'utiliser le else sans le if et réciproquement.
Le code ci-dessus serait équivalent en PHP au code suivant:
Ajouter une page /articles
Construire un tableau PHP contenant 3 articles, contenant chacun un titre, un texte et une date
Passer le tableau à la vue de la page article.
Afficher le tableau en twig
Exemple de tableau PHP
Ajoutez une nouvelle page sur l'url /recherche
Ajouter une vue avec un formulaire avec une zone de saisie et un bouton submit. Le traitement se fera en poste sur l'url /recherche/resultat
La vue résultat affichera le contenu de la zone de texte saisie dans la page précédente.
Il est fréquent que les entités soient liées entre-elles. Et nous avons la notion de clé étrangère qui peut apparaître dans nos tables. Il est possible (voire nécessaire) 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.
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.
Dans une relation entre deux entités, il y a toujours une entité dite propriétaire, et une dite inverse. L'entité propriétaire est celle qui contient la référence à l'autre entité.
Prenons un exemple simple, les commentaires de nos annonces. En SQL pur, vous disposez de la tablecomment et de la tableadvert. Pour créer une relation entre ces deux tables, vous allez mettre naturellement une colonne advert_id dans la tablecomment. La tablecomment est donc propriétaire de la relation, car c'est elle qui contient la colonne de liaison advert_id.
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).
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
Prenons 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.
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.
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).
Le code de Absence devient
Le code de Etudiant sera :
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 ($this->absences
), de type ArrayCollection
contenant tous les objets associés à cette relation (many).
La relation 1..1 est peu utilisée mais permet une flexibilité en terme relationnelle très importante. Elle définit une dépendance unique entre 2 entités :
Utilisateur -> Adresse Produit -> Image Commande -> Facture
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.
La console (make:entity
), nous facilite la création des relations. En créant ou en modifiant l'entité, il est possible d'ajouter le champs contenant la relation. Pour cela le type sera "relation". La console vous demandera de préciser l'entité liée, ainsi que le type de relation. Vous pourrez ensuite selon la relation choisie, préciser la relation inverse, de manière optionnelle ou obligatoire.
Attention, il est d'usage de lancer la console dans l'entité qui porte la relation (propriétaire), ou l'entité qui recevra la clé étrangère.
Comme après chaque modification, il faudra générer le fichier de migration, et appliquer les modifications sur la base de données.
Créer la liaison entre Article
et Categorie
.
Ajoutez deux catégories dans votre base de données et liées vos articles à l'une des catégories.
Modifiez la page "/articles", pour afficher la catégorie de chacun des articles.
Modifier la page "accueil" pour afficher les catégories (et toujours le dernier article publié).
Créez une page "/categorie/..." pour afficher uniquement les articles de la catégorie.
On pourrait judicieusement utiliser un "include" pour avoir la présentation d'un article commune sur toutes les pages
Faire fonctionner la recherche en utilisant les données du formulaire pour trouver et afficher les articles associés.
Modifier le repository de Article pour créer une méthode search($word)
qui recherchera dans le titre et le contenu le mot $word
La gestion des formulaire se fait via plusieurs classes PHP qui permettent entre autre :
La structure et les propriétés du formulaire se gèrent via FormBuilder et peuvent être réutilisées;
On peut créer des classes spécifiques pour chacun de nos formulaires
Permet une gestion des validations simplifiée et une sécurité renforcée
Permet d'hydrater une entité ou un objet rapidement
Gestion de template simple
Pour pouvoir utiliser les formulaires, selon la version d'installation de Symfony, il peut être nécessaire d'installer les packages :
Pour les exemples ci-dessous, on considère l'entité suivante (exemple issue de la documentation Symfony) :
On peut créer un Form de 2 façons différentes :
Cette première solution est rapide à mettre en place, et ne nécessite pas de fichier supplémentaire. Cependant, le formulaire ainsi créé n'est pas réutilisation dans d'autres méthodes.
Cette seconde solution, qui implique des fichiers complémentaires, permet une plus grande souplesse et une meilleure ré-utilisation dans d'autres contextes.
Le fichier ci-dessous permet de créer le même formulaire.
On utilise la classe FormBuilder accessible avec la méthode
dans un contrôleur ; l'argument $task
est l'entité que vous souhaitez hydrater; l'argument n'est pas obligatoire (pour un formulaire de recherche par exemple)
On peut mettre des champs de formulaire par défaut en modifiant l'entité avant de créer le formulaire et de lier le formulaire à l'entité :
Par exemple
Pré-remplira le formulaire avec Ma tâche pré-définie.
Ensuite nous aurons des suites de ->add('nom_du_champs', TypeDeChamps::class, $options);
Par défaut si vous ne mettez que le nom du champs Symfony se chargera de récupérer un type de champs en fonction du type de champs (string, text, boolean, date, ...)
Dans une classe dédiée le createFormBuilder
est déjà instancié il ne vous reste qu'à rajouter les différents add
.
Une fois le formulaire créé et initié il faut renvoyer le tout à TWIG via la méthode :
Soit par exemple
Ensuite nous aurons plusieurs fonctions twig utiles:
{{ form }}
permet d'afficher tout le formulaire
{{ form_start }}
permet de générer la balise <form>
avec les différents attributs
{{ form_end }}
permet de générer la fermeture de <form>
avec les différents champs restants non affichés
{{ form_errors }}
affiche les erreurs éventuelles du formulaire
{{ form_widget(mon formulaire.nomduchamps) }}
affiche le type de champs
{{ form_label(mon formulaire.nomduchamps) }}
affiche le label du champs
{{ form_row(monformulaire.nomduchamps) }}
affiche le form_widget et form_label
{{ form_rest }}
affiche les champs restants non récupéré précédemment (token de vérification par exemple)
Ces fonctions permettent une grande maîtrise de la mise en forme d'un formulaire. Cependant, elle implique de détailler les éléments.
Il est donc possible d'afficher un formulaire en une seule ligne, et le rendu dépendra du paramètrage (ou du template modèle) existant.
la variable_form
correspond à la variable contenant le formulaire envoyée par le contrôleur. Il est enfin possible de préciser un "thème" pour votre formulaire avec la syntaxe :
Le template est un fichier twig qui vient préciser et définir pour chaque élément du formulaire (de manière globale), le rendu en HTML.
Une fois le formulaire créé et affiche via TWIG il faut rajouter un comportement qui va gérer la soumission du formulaire grâce à ces méthodes :
handleRequest($request)
permet d'associer les valeurs input à la classe Form précédemment créé
isSubmitted()
permet de savoir si le formulaire a été envoyé
isValid()
permet de savoir si les données saisies sont valides
Dans la majorité des cas on va tester si :
Ces contraintes ou assert peuvent être gérée de plusieurs façon XML, JSON, YAML, PHP ou en annotation dans notre cas; il faudra utiliser cette ligne tout en haut du contrôleur :
Pour ensuite pouvoir utiliser l'annotation :
Ici on vérifiera que le champs name doit être rempli.
Créer un formulaire directement dans CategorieController qui gèrera la création des Categories
Modifier la page de listing des catégories pour rajouter un lien édition et suppression
Mettre en place les différentes routes pour le "backoffice" de Categorie (ajout / modification / suppression / visualisation)
Déporter le formulaire de gestion de Categorie vers une classe dédiée Form/CategorieType.php
Modifier CategorieController pour utiliser CategorieType
Ajouter une validation au formulaire sur le titre qui ne doit pas dépasser 150 caractères
Rechercher pour mettre en place le template bootstrap pour les Form
Gérer les autres routes : modification et suppression
Ce que nous venons de faire manuellement peut être généré en ligne de commande par Symfony via la commande :
On vous demandera le nom de l'entité précédé du nom de bundle, le chemin pour ce crud et si vous souhaitez avoir les fonction d'édition (ajout/ modification) mettez oui.
On peut également générer seulement les FormType :
Attention si vous modifier une entité les FormType ne sont pas générés automatiquement il faudra rajouter manuellement le champs fraichement créé.
Générer le CRUD de Article
Tester le fonctionnement, comprendre pourquoi ca ne fonctionne pas et corriger
Mettre les liens dans le menu pour aller sur le listing post et listing postcategory; mettre en actif si url courante
Avantages | Inconvénients |
---|
Article comparatifs des 10 frameworks PHP les plus populaires de 2019:
Toutes les informations sur l'évolution du framework :
Une lecture intéressante sur la logique d'évolution du framework Symfony :
Le gestionnaire de dépendance
ou Le nouveau gestionnaire d'installation de Symfony :
Vous pouvez suivre aussi les éléments de la documentation officielle :
est un logiciel gestionnaire de dépendances libre écrit en PHP. Il permet à ses utilisateurs de déclarer et d'installer les bibliothèques dont le projet principal a besoin. Développé, depuis 2011, par Nils Adermann et Jordi Boggiano (qui continuent encore aujourd'hui à le maintenir), il est aujourd'hui en version 2.
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 : pour lier une entité à une table de base de données.
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. Il existe plusieurs ORM, dans Symfony, il s'agit de .
Un moteur de template est un "langage", un "outil" permettant d'écrire les vues (partie visible) de manière efficace et rapide. Symfony utilise par défaut.
est un moteur de rendu de template comme (prestashop) ou (laravel). Twig a cependant été développé pour Symfony à l'origine et peut être utilisé dans d'autres contextes.
Twig est un outil très puissant, mais il implique une courte phase d'apprentissage, car contrairement à d'autres moteurs de template il n'utilise pas une syntaxe PHP. Vous pouvez vous référer à la documentation officielle :
Twig intègre une permettant de répondre aux usages courant. Mais il est très simple d'ajouter nos propres filtres afin de répondre parfaitement à nos besoins.
Tips :
Liste des champs possibles :
Des thèmes par défaut sont proposées : Et la documentation pour créer votre template :
Les validations permettent de gérer des contraintes au niveau du formulaire ; Par exemple pour pourra forcer en PHP que le champs email soit bien un email ou que tel champs ne peut pas dépasser tel nombre de caractères, vous trouverez la liste des contraintes basiques sur site site de symfony :
Pour éviter des erreurs dans l’organisation des appels | Apprentissage d’une couche supplémentair |
Éviter les appels directs aux commandes PHP | La majorité des fonctionnalités PHP sont redéfinies |
Préférer les versions des Frameworks qui apportent leur lot de contrôles. | Généralement apprentissage d’un moteur de template |
Plus grand portabilité du code | Apprentissage de l’utilisation du framework choisit : ses classes, ses objets, sa logique ! |
Ne pas réinventer la roue |
La gestion des formulaire, des utilisateurs, ... |
Variable | Description |
loop.index | The current iteration of the loop. (1 indexed) |
loop.index0 | The current iteration of the loop. (0 indexed) |
loop.revindex | The number of iterations from the end of the loop (1 indexed) |
loop.revindex0 | The number of iterations from the end of the loop (0 indexed) |
loop.first | True if first iteration |
loop.last | True if last iteration |
loop.length | The number of items in the sequence |
loop.parent | The parent context |
Créer un nouveau projet Symfony et installer les dépendances nécessaires.
Pour rappel, en ligne de commande depuis Docker/serveur et dans le repertoire où vous souhaitez mettre votre projet :
symfony new nomDuProjet --webapp
Comme vous utilisez docker, pensez à créer un nouvel alias dans votre fichier de conf (symfony.conf) et redémarrer apache2
En utilisant la notion d'héritage des templates créez un menu avec 3 items :
Accueil (qui mènera vers /)
Articles (qui mènera vers /articles)
Recherche (qui mènera vers /recherche)
Chaque page sera associée à une méthode dans un controller différent.
En utilisant les assets installer Bootstrap.
Sur la page accueil insérez une image de votre choix
Sur la page articles, ajouter des articles dans votre méthode de controller (dans un tableau associatif, ajouter au moins 3 articles).
Affichez ces 3 articles dans votre page articles.
Un article est composé d'un titre, d'un texte (on peut utiliser du loreum ipsun), d'une date de publication (on utilisera un objet datetime), et d'un auteur.
Dans la page recherche, ajoutez simplement un formulaire de recherche comme vous l'auriez fait en BUT1.
Soignez un minimum la mise en page.
Pour la page recherche, on va simuler la récupération de la recherche utilisateur. Pour le moment, on a pas de source de donner pour effectuer la recherche.
Récupéré la recherche de l'utilisateur soumise avec un formulaire en method post ($request peut vous aider), et afficher dans une nouvelle page le mot recherché.
Vous veillerez à ce que le bon item soit "actif" dans le menu.
La documentation officielle : https://symfony.com/doc/current/security.html
Deux notions majeures interviennent dans la conception de sécurité de Symfony :
Authentification : Qui êtes vous ? ; vous pouvez vous authentifier de plusieurs manières (HTTP authentification, certificat, formulaire de login, API, OAuth etc)
Authorization : Avez vous accès à ? ; permet d'autoriser de faire telle ou telle action ou accéder à telle page sans forcément savoir qui vous êtes, utilisateur anonyme par exemple.
Pour fonctionner, il est nécessaire d'ajouter le composant security à votre symfony.
Si vous avez installé le projet avec la version complète (webapp), cette ligne n'est pas nécessaire.
La sécurité dans symfony implique plusieurs éléments :
Le firewall: qui est la porte d'entrée pour le système d'authentification, on définit différents firewall (au minimum 1 seul) qui va permettre de mettre en place le bon système de connexion pour l'url spécifiée via un pattern.
Le provider : qui permet au firewall d'interroger une collection d'utilisateurs/mot de passe ; C'est une sorte de base de tous les utilisateurs avec les mots de passe. Il existe deux type par défaut :
in memory : directement dans le fichier security.yml mais du coup les hash des mots de passes sont disponible dans un fichier
Entity : N'importe quelle entité qui implémente à minima les deux interfaces
Enfin, plusieurs providers peuvent fonctionner en même temps par exemple in_memory et entity voire plusieurs entités simultanément. http://symfony.com/doc/current/security/entity_provider.html
Un encoder : qui permet de générer des hashs/d'encoder des mots de passe ; le plus connu étant MD5 mais vous pouvez utiliser d'autres encoders tels que : sha1, bcrypt ou plaintext (qui n'encode rien c'est le mot de passe en clair) http://symfony.com/doc/current/security/named_encoders.html
Les rôles : qui permettent de définir le niveau d'accès des utilisateurs connectés (authentifiés) et de configurer le firewall en fonction de ces rôles. Les rôles peuvent être hierarchisées afin d'expliquer par exemple qu'un administrateur (ROLE_ADMIN par exemple) et avant tout un utilisateur (ROLE_USER).
Le "guard" ou "authenticator" qui va gérer l'authentification, au travers de "passport". Il va par exemple vérifier que le couple login/mot de passe existe dans l'un des provider.
A partir de la version 4, et avec le composant "maker", la gestion de la sécurité a été grandement facilitée. Là où sur les précédentes versions (la 2 notamment), il était d'usage de passer par un bundle tierce (FOSUserBundle par exemple), aujourd'hui cela n'est plus nécessaire.
Si vous ne disposez pas encore d'une classe permettant la gestion des utilisateurs, il est possible d'en créer une avec la console. Si vous disposez déjà d'une classe utilisateur (ou que vous souhaitez utiliser plusieurs entités, il faudra modifier votre code en implémentant les interfaces UserInterface, PasswordAuthenticatedUserInterface
et en implémentant les méthodes imposées par ces interfaces).
L'instruction ci-dessous permet de lancer la console pour créer la table User.
Symfony va vous poser plusieurs questions afin de configurer les éléments (le nom de l'entité, si vous utilisez doctrine, le champ correspondant au login, et l'encodage du password.
Une fois cette commande exécutée vous avez un fichier d'entité de créé, un repository associé, et le fichier security.yaml (dans config) qui a été mis à jour.
Ce fichier fait le lien avec l'entité User (le provider), le login retenu (ici un email), et l'encodage du mot de passe, par défaut "auto"
Il faut ensuite mettre à jour votre base de données, avec les commandes suivantes:
Une nouvelle fois la console va nous permettre de dégrossir le travail et produire le contrôleur, le fichier de configuration et le formulaire de connexion.
Pour le résultat ci-dessous.
Cette commande, comme indiqué génère plusieurs fichiers :
LoginAuthenticator.php : qui explique comment on authentifie un utilisateur (au travers de passeport)
SecurityController.php : Car ici, nous avons choisi une connexion avec formulaire, ce contrôleur permettra d'afficher la page de login, de récupérer les informations et de gérer la déconnexion
login.html.twig : qui contient le formulaire
Le fichier security.yaml est mis à jour pour faire le lien avec cet authenticator.
Attention !! Il faut modifier la ligne 51 avec une route qui existe dans votre projet
La partie firewall est modifiée pour indiqué quel authenticator utiliser. On pourrait en avoir plusieurs.
Comme indiqué cette commande va créer plusieurs fichiers :
src/Security/LoginAuthenticator.php : qui va contenir la logique de votre authentification. Que faire une fois l'authetification réussie, ou en cas d'échec. Comment récupérer les informations de l'utilisateur.
src/Controller/SecurityController.php : qui va être le contrôleur gérant la partie sécurité et authentification. Par défaut la méthode login pour afficher le formulaire et traiter (avec l'aide de l'authenticator précédent), valider les données. C'est dans ce contraôleur que vous pouvez ajouter la déconnexion, l'enregistrement et le mot de passe perdu par exemple.
templates/security/login.html.twig : la vue contenant le formulaire de connexion, que vous pouvez librement adapter.
Mettre à jour le fichier security.yaml afin qu'il fasse le lien avec les différents éléments de sécurité.
Les étapes suivantes expliques ce qui a été créé.
Quasiment toute la sécurité se joue dans le fichier security.yaml qui fait le lien entre les différents éléments et gère les accès.
Tout est pré-confiuré, vous pouvez bien sûr adapter. L'encodage permet de définir le "format" de cryptage du mot de passe. Par défaut c'est "auto", c'est à dire que selon votre configuration Symfony choisira le niveau le plus élevé possible (bcrypt ou Argon2i)
Le Provider permet de faire le lien avec une source de données contenant les couples login/mot de passe ou des clés d'API... Les providers peuvent être des entités, des données "in_memory", ... Cette partie est configurée a été configurée suite à la création de l'entité User, avec la méthode de connection (login), ici l'email sera utilisé. Il est possible de coupler plusieurs provider tant que l'email est unique sur l'ensemble des sources.
C'est la partie essentielle du process de sécurisation. C'est lui qui permet de dire quand il faut vérifier et authentifier un utilisateur. Le firewall permet de déterminer pour un pattern d'url (une requête (request)), la méthode d'authentification à utiliser (une page de connexion, une clé d'API, une dépendance à un fournisseur OAuth, ...).
L'exemple ci-dessus permet de définir que pour les routes particulières (les assets, le profiler), il n'y a pas de vérification. Pour toutes les autres routes (main), il faudra utiliser le provider contenant nos User et l'authenticator gérant le formulaire de Login. C'est ici que l'on pourrait proposer plusieurs méthodes de connexion en ajoutant les authenticator adaptés.
Symfony propose des exemples pour de nombreuses méthodes d'authentification (login, ldap, json, ...) que vous trouverez sur la documentation officielle
La gestion des roles se fait dans la partie "access_control" du fichier security. Il permet de définir pour chaque pattern d'URL quel rôle peut y accèder. C'est là que l'on sécurise nos différentes parties. Il est donc important de construire et structurer nos URL correctement pour être efficace sur le filtrage. Il faut évidemment veiller à ce que les pages de connexion ne soient pas derrière une page sécurisée...
Exemple:
De cette manière les URL seront automatiquement bloquées si l'utilisateur ne dispose pas du bon rôle. Il est aussi possible de tester ce rôle directement dans un contrôleur ou dans une vue selon les besoins. Voir la documentation pour plus d'éléments sur ce point
Enfin, il est souvent nécessaire de récupérer les informations sur l'utilisateur connecté. Pour cela, dans un contrô leur il est possible d'utiliser directement l'instruction :
Sur la même idée que pour la connexion, il est possible de gérer la déconnexion. Pour cela, dans le fichier security.yaml, il faut définir le path (la route) pour la méthode qui gére la déconnexion, et la cible (une route, optionnelle), une fois la déconnexion réussie.
La méthode dans le contrôleur peut se résumer à :
Cette méthode qui ne retourne rien, permet la déconnexion, et la redirection se fait via le target définit dans security.yaml.
Grâce à la console, il est possible de générer un mot de passe selon l'encodage utilisé par Symfony.
Il est possible d'ajouter des utilisateurs directement dans la base de données (par exemple avec PhpMyAdmin ou en mode console Mysql) avec le mot de passe encodé correctement ou alors en créant un formulaire d'inscription.
On va ajouter PhpMyAdmin à Docker, pour cela dans votre fichier docker-composer.yaml et ajouter les lignes ci-dessous :
Pour vous connecter http://localhost:8082, puis vos identifiants.
Dans la table User ajouter une entrée, avec un mot de passe crypté, et un rôle, qui doit être un tableau, exemple : ["ROLE_ADMIN"]
Attention ! On modifie le docker-compose.yaml MMI pas celui de Symfony !!!
La aussi le maker peut nous aider grandement...
bin/console make:registration
Répondez aux questions, il est nécessaire d'installer un complément si vous voulez vérifier le mail de vos utilisateurs : composer require symfonycasts/verify-email-bundle
Mettre en place une classe User, et créer le formulaire de connexion en suivant la documentation (une commande dans le maker existe).
Ajouter une page qui ne sera accessible qu'a des utilisateurs Admin.
Vous pouvez ajouter une fonction pour récupérer le mot de passe perdu (une commande dans le maker existe...)
Dans Symfony, tous les objets (au sens d'un ensemble de code qui apporte une fonctionnalité : mail, base de données, entitée, ...) sont des services. Dès l'instanciation d'une application symfony, de nombreux services sont lancés et accessible de partout dans l'application. Il est aussi possible de créer ses propres services.
La documentation officielle de Symfony sur les services : https://symfony.com/doc/current/service_container.html
Dans un controller, on peut utiliser un service existant en l'injectant dans la méthode de notre classe "controller". Par exemple, pour envoyer un mail, on peut utiliser le service mailer
:
Dans cet exemple, on injecte le service mailer
(par l'intermédiaire de son interface MailerInterface
) dans la méthode index
de notre controller. On peut ensuite utiliser ce service pour envoyer un mail.
Grâce au mécanisme d'injection de dépendance de Symfony, le service mailer
est automatiquement instancié et injecté dans notre controller. On peut donc l'utiliser dans notre méthode index
. Il n'est pas nécessaire de l'instancier nous même et de le passer à la méthode. Cela fonctionne parce que tout est considéré comme un service dans Symfony.
Il est possible de cumuler des services et des paramètres issus de nos routes. D'ailleurs, vous le faites déjà dans vos controllers. Par exemple, dans le controller ArticleController
, on pourrait avoir :
La méthode index
utilise le service ArticleRepository
pour récupérer tous les articles. La méthode edit
utilise les services Request
et EntityManagerInterface
pour récupérer les données du formulaire et sauvegarder les modifications d'un article, qui est passé en paramètre de la méthode et dans la route.
Notez au passage que la récupération de l'article déclenche aussi un mécanisme particulier qui vient executer une requete SQL pour récupérer l'article à partir de l'id passé en paramètre de la route. C'est le mécanisme de "ParamConverter".
Tout comme nous disposons de nombreux services déjà créés, il est possible de créer ses propres services. Pour cela, il faut créer une classe qui va contenir le code de notre service. Vous pouvez mettre ce fichier dans un repertoire dédié, par exemple src/Service
. Par définition et sauf indication contraire cette classe sera considérée comme un service par Symfony.
Exemple, créons un service qui va nous permettre de récupérer le nombre d'articles dans la base de données :
Dans cet exemple, on a créé une classe ArticleService
qui contient une méthode countArticles
qui va nous permettre de récupérer le nombre d'articles dans la base de données.
Notez que dans ce service, on utilise le service ArticleRepository
qui est injecté dans le constructeur de notre classe.
Pour utiliser ce service, on peut l'injecter dans un controller :
Les services sont très utiles pour plusieurs raisons :
Ils permettent de découper notre code en plusieurs parties. Par exemple, on peut avoir un service qui va nous permettre de récupérer les articles, un autre qui va nous permettre de récupérer les utilisateurs, un autre qui va nous permettre de récupérer les commentaires, etc.
On peut ainsi avoir un service par entité. Cela permet de découper notre code en plusieurs parties et de rendre notre code plus lisible et plus maintenable.
Il n'y a pas de règle pour savoir quand créer un service. Cela dépend de votre code et de vos besoins. Par exemple, si vous avez un controller qui fait trop de choses, vous pouvez créer un service qui va vous permettre de déplacer une partie du code dans ce service.
L'objectif est de garder vos contrôleurs et les méthodes qu'ils contiennent relativement légers. Les services permettent aussi de réduire le code qui serait dupliqué dans plusieurs contrôleurs en mettant à un seul endroit les méthodes qui sont communes à plusieurs contrôleurs.
Pour débuter ce nouveau semestre, nous allons repartir d'un projet Symfony vierge. Pour cela, nous allons utiliser le projet Symfony "skeleton" qui est un projet vierge. Pour créer ce projet, il faut utiliser la commande suivante :
ou
N'oubliez pas de faire le nécessaire du côté de docker pour rendre ce projet accessible depuis votre navigateur sur l'adresse http://localhost:8080/symfonyS4
. (Url à adapter en fonction de votre configuration).
Nous allons installer le bundle MakerBundle
qui va nous permettre de créer des entités, des controllers, des services, etc. Pour cela, il faut lancer la commande suivante :
/!\ Attention, il faut que ces bundles soient installés en mode "dev" et non pas en mode "prod". Pour cela, il faut ajouter le paramètre --dev
à la fin de la commande.
Nous installer également les dépendances suivantes :
Dans un premier temps vous allez créer un contrôleur et une méthode, associée à une route permettant d'envoyer un mail avec avec des informations pré-remplies (n'hésitez pas à reprendre les séances du S3 sur le mailer).
Dans un deuxième temps nous allons utiliser notre propre service de mailer pour gérer l'envoi des mails.
L'intérêt de faire cela est que nous pourrons ainsi définir un ensemble de paramètre par défaut pour nos mails (expéditeur, destinataire, sujet, etc.) et ainsi simplifier l'utilisation de notre service, sans repeter à chaque fois que nécessaire ces paramètres.
L'autre intéret est que nous pourrons ainsi facilement changer les paramètres de notre service de mailer, voire changer de bibliothèque, sans avoir à modifier le code de nos contrôleurs.
Cela évite donc que nos contrôleurs et notre code ne soit trop dépendant des bibliothèques que nous utilisons.
Créer une classe MailerService
dans le dossier src/Service
et ajouter les méthodes suivantes :
le constructeur qui prend en paramètre l'objet MailerInterface
et qui le stocke dans une propriété
sendMail
: qui prend en paramètre le destinataire, le sujet et le corps du mail et qui envoie le mail
Modifier le contrôleur pour utiliser le service de mailer
Pour aller plus loin
Ajoutez une méthode qui permet d'envoyer un email en utilisant "twig"
Quelles sont les propriétés nécessaires ?
Comment modifier le code ?
Mise en place d'une page de contact
Cette commande n'est pas nécessaire si vous avez installé le projet Symfony avec "webapp"
Il faut ensuite configurer le serveur de mail qui sera utilisé.
Pour cela modifier le fichier .env.local
(rappel: on ne modifie jamais le .env
avec vos données personnelles).
Modifier la ligne :
Symfony propose par défaut l'usage du SMTP, mais vous pouvez ajouter d'autres gestionnaires de mail (gmail, mailChimp...). La liste ici : https://symfony.com/doc/current/mailer.html#using-a-3rd-party-transport
Si vous ne disposez pas d'un serveur SMTP, vous pouvez en installer un fictif, via docker, qui permettra de gérer les mails en environnement de développement (notamment ne pas les envoyer réellement aux destinataires, les consulter...).
Dans votre fichier docker-compose.yml ajoutez le service suivant
Ce service ajoute deux choses :
Le serveur smtp sur le port 1025 => Modifier le Mailer_DSN :
un webmail
Ouvrir dans votre navigateur : http://127.0.0.1:8081/#/
Prenons l'exemple de Symfony
Cet exemple ajoute une route (email), qui est associé à une méthode (sendEmail) dans le contrôleur (MailerController).
Modifiez les adresses ligne 16 (l'expéditeur) et ligne 17 (le destinataire).
Vous pourriez ajouter un ensemble d'option (cc, bcc, reply...)
Ligne 22 : C'es le sujet de votre mail
Ligne 23 : C'est votre email au format texte brut (sans HTML donc)
Ligne 24 : C'est le contenu du "body" de votre mail au format HTML
Dans cette construction, le mail est envoyé au format HTML et au format texte, c'est votre outil de mail qui choisira le plus approprié à afficher. Dans l'exemple les contenus sont différents selon le format, ca n'est qu'un exemple bien sûr.
Saisir le code, configurer le serveur de mail, modifier les emails et le texte.
En mode développement, on interdit la distribution des mails réellement (on définit MAILER_DSN=null). Pour voir les mails, on peut donc utiliser le profiler (le dernier icône de la barre).
On peut aussi utiliser un outil type MailTrap, mailDev, ... qui va simuler un SMTP et récupérer l'ensemble des mails envoyés par Symfony.
On peut voir l'ensemble des éléments du mail, le format HTML, Text, brut, l'en-tête...
La solution précédente est fonctionnelle, mais pas très pratique si vous souhaitez faire un long mail, ou un mail complexe, ou encore gérer de nombreuses données.
Il est donc possible de construire un email en utilisant la puissance de Twig et des templates.
Dans ce deuxième exemple on créé un objet TemplatedEmail qui permet de manipuler des templates Twig.
La structure de base ne change pas (destinataires,... sujet).
Ligne 21 : on associe un template (dans le répertoire templates, puis emails/signup.html.twig)
Ligne 24-27 : on passe les paramètres nécessaires à notre vue (comme on le ferait depuis un contrôleur).
Le template de mail pourrait ressembler à :
Dans cet exemple seul le format HTML est proposé. SI l'outil de mail ne supporte pas le format HTML il va afficher un format texte brut que Symfony aussi essayé de construire automatiquement à partir de votre format HTML
Pour ajouter le format texte du mail on peut passer un template spécifique pour le format texte, qui va utiliser les mêmes données fournies par context.
Ce deuxième template (txt) ne devra pas contenir de balises HTML.
Tester ce second envoi d'email en format HTML et texte
Dans Symfony il existe plusieurs façon de gérer les adresses emails. Vous trouverez ci-après quelques exemples.
Exercice :
Créer un formulaire de contact.
Créer un nouveau contrôleur nommé ContactController
Ajouter une méthode index qui va se charger d'afficher un formulaire (adresse de l'expéditeur, sujet, message), vous serez le destinataire par défaut..
Vous pouvez créer le formulaire directement dans le template en HTML ou avec l'objet FormBuilder à votre convenance.
Ajouter une méthode sendMail qui va se charger d'envoyer l'email en fonction des données du formulaire
On utilisera Request pour récupérer les données du formulaire
L'email sera envoyé à vous et en copie à la personne ayant complété le formulaire
Le champ reply sera configuré pour répondre à l'expéditeur
Le mail sera au format texte et HTML
Un template ou une vue est le meilleur moyen d'organiser et de restituer le code HTML à partir de votre application, que vous deviez rendre le code HTML à partir d'un contrôleur ou générer le contenu d'un courrier électronique . Les templates dans Symfony sont créés avec Twig: un moteur de modèle flexible, rapide et sécurisé.
Twig est un moteur de rendu de template comme Smarty (prestashop) ou Blade (laravel). Twig a cependant été développé pour Symfony à l'origine et peut être utilisé dans d'autres contextes.
Un moteur de template permet de limiter les logiques complexes pour réaliser des templates simples à coder. Un moteur de template intègre généralement des fonctionnalités qui sont récurrentes dans le développement "front" et qui permettent de simplifier le code à écrire.
La documentation officielle de Symfony sur les vues et TWIG et La documentation officielle de TWIG
Exemple d'un code que vous pourriez écrire en PHP
Ce même code, écrit avec TWIG serait :
La syntaxe est plus "legére" et moins encombrées des balises PHP. Le code semble donc plus lisible et par conséquent plus facile à maintenir.
La syntaxe Twig est basée sur uniquement trois constructions:
{{ ... }}
, utilisé pour afficher le contenu d'une variable ou le résultat de l'évaluation d'une expression;
{% ... %}
, utilisé pour exécuter une logique, telle qu’une condition ou une boucle;
{# ... #}
, utilisé pour ajouter des commentaires au modèle (contrairement aux commentaires HTML, ces commentaires ne sont pas inclus dans la page rendue).
Vous ne pouvez pas exécuter de code PHP dans les modèles Twig, mais Twig fournit des utilitaires permettant d'exécuter une certaine logique dans les modèles. Par exemple, les filtres modifient le contenu avant le rendu, comme le upper
filtre pour mettre le contenu en majuscule :
{{ title|upper }}
Twig intègre une liste de filtre permettant de répondre aux usages courant. Mais il est très simple d'ajouter nos propres filtres afin de répondre parfaitement à nos besoins.
TWIG est très puissant lorsqu'il s'agit de manipuler des variables. Pour lui, si c'est un tableau associatif ou un objet avec des propriétés cela est identique dans la syntaxe.
Par exemple si vous avez le tableau $tab['param1']
vous écrirez en TWIG
Si vous avez un objet $tab
, qui est une instance d'une classe ayant comme propriété $param1
, la syntaxe en TWIG sera :
Si vous avez un objet $tab
, qui est une instance d'une classe ayant comme propriété $param1
, qui est un tableau associatif avec une clé key1
, la syntaxe pourrait être :
La manière dont TWIG inspecte votre code pour trouver la meilleure façon d'interpréter une variable est la suivante :
For convenience's sake foo.bar
does the following things on the PHP layer:
check if foo is an array and bar a valid element;
if not, and if foo is an object, check that bar is a valid property;
if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
if not, and if foo is an object, check that getBar is a valid method;
if not, and if foo is an object, check that isBar is a valid method;
if not, and if foo is an object, check that hasBar is a valid method;
if not, return a null value.
foo['bar'] on the other hand only works with PHP arrays:
check if foo is an array and bar a valid element;
if not, return a null value.
{% %}
permet d'utiliser des logiques tels que :
{% if %} {% else %} {%endif %}
: condition
{% for item in items %}{%endfor}
: foreach
{% set foo='foo' %}
: set des variables
Il est possible d'écrire des tests, la syntaxe est très proche de celle de PHP. Attention toutefois, les opérateurs de comparaison ne s'écrive pas de manière identique.
Le && de PHP s'écrit "and" dans TWIG, et le || de PHP s'écrit "or" dans TWIG.
Il est possible d'avoir des elseif (autant que nécessaire) et un bloc else (1 au maximum)
Il n'existe que la boucle for dans TWIG (elle est un peu l'équivalent d'un foreach).
Le code ci-dessous est une boucle qui varie de 1 à 10.
La boucle ci-dessous est une boucle qui parcours une "collection" users (l'équivalent du foreach). Attention la syntaxe est inversée par rapport au PHP
Dans le cadre d'une boucle TWIG propose une variable nommée loop qui permet d'avoir des informations sur la boucle :
La boucle ci-dessous intègre un test. Ce qui simplifie l'écriture. Elle intègre également un else dans le cas ou la boucle ne ferait aucune itération. Il est possible d'utiliser le else sans le if et réciproquement.
Le code ci-dessus serait équivalent en PHP au code suivant:
TWIG permet l'héritage de template via un extends
dans les templates enfants :
Dans les templates mère on définit des "block" que l'on vient surcharger dans les templates enfants :
On peut également reprendre le block parent via
On crée donc des templates mère assez flexibles pour pouvoir en hériter et surcharger les différents blocks
Utiliser l'héritage pour mettre le menu dans un seul fichier mais visible sur les 2 pages.
Intégrer bootstrap (CDN)
Pour la page /color afficher le mot de la même couleur dynamiquement ("bleu" en bleu) (en CSS)
Pour la couleur rouge afficher en plus le Message : "Attention risque de virus" en rouge
De la même manière que l'on peut hériter d'un template de base, on peut aussi inclure des "morceaux" de template dans une vue. Toujours dans la perspective de ne jamais multiplier un morceau de code identique dans plusieurs fichiers.
Il est possible d'appliquer des filtres sur les variables pour effectuer des transformations : Majuscules, minuscules, dates, ...
Vous trouverez la liste des filtres sur l'adresse : Liste des filtres de TWIG
Il est possible de cumuler les filtres. Mais il faut faire attention à l'ordre dans lequel on les appliques. Il est aussi possible de créer ses propres filtres.
TWIG propose de gérer facilement les URL de vos images, fichiers CSS ou encore javascript.
Pour cela vous aurez besoin d'ajouter le bundle suivant :
Ensuite, vous pouvez écrire dans TWIG
Par défaut, vos "assets" doivent se trouver dans le répertoire public de Symfony.
Depuis la version 3.4 et 4, Symfony propose de gérer les assets avec WebPack-Encore, qui est une adaptation de Webpack pour Symfony Vous trouverez les éléments sur WebPack-Encore ici : https://symfony.com/doc/current/frontend.html
Modifiez le code précédemment écrit pour manipuler des assets directement dans votre projet. Vous intégrer une image de votre choix et Bootstrap en version locale (supprimer toutes les dépendances aux CDN).
Tout ce qui a été vu et fait dans Symfony en S3 est considéré comme acquis (donc ne pas hésiter à réviser). Quelques rappels seront fait, mais on ne revient pas sur les bases de l'installation, de la manipulation contrôleurs/vue, des entités, de la sécurité.
Approfondir un ensemble de concepts (personnalisation des formulaires, événements, webpack, repository, ...), qui facilitent le développement, permettent de garder un code "propre" et "bien rangé".
Cette ressource sera en lien avec les ressources WR406/WRA406 (développement front) et WR410/WRA410 (API), et la SAE WS401.
L'objectif est de produire un workflow complet d'une application web "moderne".
Séance de TP noté individuelle en fin de ressource.
Nous allons mettre en place le formulaire pour les articles. Dans cette entité nous avons une liaison vers fournisseur. Cette relation est de type 1..n (un article à un fournisseur, un fournisseur peut être dans plusieurs articles).
Générez le formulaire pour article et configurez les champs "simple".
Pour la liaison entre les deux table, il faut un champ de type entity. Le code pourrait être celui ci-dessous :
Vous pouvez définir d'autres options.
N'oubliez pas d'ajouter les use qui vont bien ! (ou utilisez PhpStorm avec le plugin Symfony).
Faite le contrôleur pour afficher et sauvegarder un article.
Il est parfois nécessaire de passer des paramètres au formulaire (donc au fichier xxxType.php), pour par exemple afficher un champ selon des droits, activer ou désactiver une zone, pré-remplir une information ou encore filtrer les résultats d'une liste.
Comme vous pouvez le constater dans la ligne permettant de "créer" le formulaire, il n'est pas possible de passer des paramètres comme vous pourriez le faire avec une méthode ou un constructeur en POO.
Cependant, la méthode createForm
, autorise un troisième paramètre, qui est un tableau et qui va permettre de passer tout ce que l'on souhaite à note fichier de formulaire. Ce troisième paramètre, les options, est contrôler par Symfony (et ce que l'on nomme un OptionResolver(
)
), qui va s'assurer que les paramètres obligatoires sont bien présent, et qu'ils ont les bonnes valeurs. Dans le cas du createForm
, aucun paramètre n'est obligatoire.
Un ensemble de paramètre sont pré-définis (comme action pour modifier le lien de l'action à la validation, attr, ...). En fait ce troisième paramètre fonctionne comme celui des add(...)
pour ajouter des champs (et au passage comme beaucoup de fonctionnalités dans Symfony).
Si on souhaite passer un paramètre qui n'est pas initialement prévu dans l'OptionResolver
, il faut lui expliquer s'il est obligatoire, le type attendu, les valeurs possibles. Au dela de cette contraintes, vous pouvez passer autant de paramètres que nécessaires.
Configurons notre formulaire ArticleType pour recevoir un paramètre correspondant à un code postal que nous pourrons ensuite utiliser pour filtrer la liste des fournisseurs.
Tout d'abord, on modifie l'appel dans le contrôleur :
Le troisième paramètre est un tableau, avec une clé (du texte), et une valeur. Ici la valeur est du texte, mais c'est bien souvent une variable dépendant de notre contexte.
Si vous essayez d'afficher votre formulaire, vous aurez une erreur vous indiquant que 'codePostal' n'est pas autorisé parmi les options définies dans l'OptionResolver.
Notez au passage que vous avez ici la liste de toutes les options connues pour le formulaire.
Nous devons donc ajouter cette possibilité dans notre formulaire, pour cela, nous modifions le fichier ArticleType.php, et plus particulièrement la méthode configureOptions, qui permet de gérer les paramètres autorisés.
Par défaut il contient les éléments ci-dessous
C'est le code minimal, obligatoire pour fonctionner et qui permet de lier une classe à notre formulaire (ligne 4).
Nous allons ajouter entre la ligne 4 et 5, notre nouvelle option, et lui donner une valeur par défaut, le code devient :
Par défaut, on considère qu'il n'y a pas de valeur (null), on pourrait aussi fixer autre chose, cela dépend de votre projet. On pourrait également préciser le type, une plage de valeur possible, ...
Si vous actualisez votre page, vous n'avez plus d'erreurs.
Mais à ce stade, votre nouveau paramètre n'est pas utilisé dans votre code, la valeur que vous avez passé même pas récupérée.
Dans la méthode buildForm
, qui construit le formulaire, nous allons donc récupérer ce paramètre. Pour cela, nous avons le second paramètre $options, qui contient tout ce qui a été passé au formulaire, avec notamment, la classe (et les éventuelles données pré-remplies), et nos options.
Un "dump
" de cette variable vous donnerai un affichage comme ci-dessous :
Vous pouvez constatez sur l'avant dernière ligne que nous avons notre codePostal. Pour le récupérer nous pourrons écrire la ligne 4 ci-dessous :
Maintenant nous pouvons utiliser notre variable $codePostal comme nous le souhaitons dans notre formulaire. Par exemple, le code ci-dessous permettrait de filtrer nos adresses de fournisseurs ayant uniquement code postal donné.
Quelques explications :
Les lignes 2 et 3 permettent de faire le lien avec l'entité Fournisseurs (voir S3)
La ligne 4 (jusque 10) permet d'éxecuter une requête pour filtrer les résultats sur l'entité.
Ligne 4 nous passons le repository qui permet de manipuler nos données, et on indique avec le use que l'on souhaite utiliser notre variable codePostal.
Les lignes 5 à 8 sont la requête qui permet de filtrer. Vous reconnaissez vaguement du SQL, mais en notamment objet, on appelle cela le DQL (Doctrine Query Language). On y reviendra en détail sur la partie repository.
N'oubliez pas d'ajouter les use qui vont bien ! (ou utilisez PhpStorm avec le plugin Symfony).
Modifiez vos fichier pour permettre de filtrer sur un code postal le fournisseur. Ajoutez des adresses et des fournisseurs (avec le formulaire que vous avez créé sur la séance précédente). Vérifiez que tout fonctionne et que vous filtrez bien les fournisseurs selon leur code postal.
Mise en forme du formulaire.
En reprenant les éléments de la séance 7 du S3, et en installant Bootstrap, affichez le formulaire article sur 2 colonnes avec le thème de formulaire adapté . Vous devez obtenir le résultat ci-dessous :
Pour initialiser cette séance, récupérez les fichiers ci-dessous et les mettre dans les bons répertoires (entity ou repository). Configurer et mettre à jour votre base de données avec des 3 entités.
Pour se simplifier un peu la tâche, on peut commencer avec une base générée par le "maker" avec la commande ci-dessous :
Répondez ensuite aux questions, la première est le nom du fichier qui va être créé, par exemple AdresseType dans cette partie. La seconde question concerne l'entité liée au formulaire (ou une classe). Indiquez ici Adresse.
Cela va donc créer le fichier AdresseType.php dans le répertoire Form.
Le fichier devrait ressembler à l'exemple ci-dessous :
On peut par exemple préciser les éléments sur le champs adresse1 en modifiant la ligne 15 :
Exercice :
Faites de même sur les autres champs.
En consultant la documentation de Symfony, vos connaissances du HTML, configurez le champs "codePostal" avec une taille maximale de 5 caractères.
Pour cela, on va créer un contrôleur (make:controller
) et ajouter la méthode new pour afficher le formulaire.
Et la vue qui va avec.
Rendez-vous sur l'url associée à votre méthode pour voir s'afficher le formulaire. Testé que le code postal est bien limité à 5 caractères par exemple.
L'entité Fournisseur est liée à l'entité Adresse. On peut donc construire un formulaire pour Fournisseurs, puis sélectionner une adresse parmi une liste d'adresse.
Mais ! Dans notre cas, Fournisseur et Adresse ont une liaison 1 vers 1 (c'est à dire qu'un fournisseur à une adresse et une adresse un seul fournisseur). On utilise ce type de découpage pour éviter de surcharger l'entité, ou parce que l'on va manipuler plusieurs fois un même type de données.
Pour résumer, la saisie d'un fournisseur implique la saisie de son adresse. Toujours pour éviter de multiplier le code, on va pouvoir réutiliser le formulaire AdresseType créé dans la partie précédente dans notre formulaire Fournisseur.
Créer le formulaire pour l'entitée Fournisseur. Paramétrez les champs, et définissez comme type de champs pour la propriété Adresse : AdresseType
.
N'oubliez pas le use associé (ou utilisez PhpStorm avec le plugin Symfony)
Ajoutez le contrôleur associé pour la gestion des fournisseurs, et la méthode pour afficher et sauvegarder le formulaire du fournisseur.
Testez votre code et constatez que l'adresse est stockée dans sa table, et que la clé étrangère d'adresse est dans la table de fournisseur.
Le terme "internationalisation" (souvent abrégé ) fait référence au processus d'extraction de chaînes et d'autres éléments spécifiques aux paramètres régionaux de votre application dans une couche où ils peuvent être traduits et convertis en fonction des paramètres régionaux de l'utilisateur (c'est-à-dire la langue et le pays). Pour le texte, cela signifie envelopper chacun avec une fonction capable de traduire le texte (ou "message") dans la langue de l'utilisateur :
Le processus de traduction dans Symfony comporte plusieurs étapes :
Activer et configurer le service de traduction de Symfony ;
définir les chaînes (c'est-à-dire "messages") à localiser/traduire en les enveloppant dans des appels au traducteur ("Translations");
Créer des ressources/fichiers de traduction pour chaque langue prise en charge qui traduisent chaque message dans l'application ;
Déterminez, définissez et gérez les paramètres régionaux de l'utilisateur sur l'ensemble de la session de l'utilisateur.
Il faut installer le bundle symfony/translation
:
Après l'installation, le service de traduction de Symfony est activé par défaut dans une application Symfony. Pour le configurer, il faut modifier le fichier config/packages/translation.yaml
:
Le paramètre default_locale
définit la langue par défaut de l'application. Le paramètre default_path
définit le chemin vers lequel Symfony doit rechercher les fichiers de traduction.
Pour définir les chaînes à localiser/traduire, il faut les envelopper dans des appels au traducteur :
Pour ce premier exemple, la traduction est gérée dans une méthode d'un de nos contrôleurs qui utilise le service translator
injecté dans la méthode. La chaîne de caractères à traduire est passée en paramètre de la méthode trans()
du service translator
.
Notez que nous avons ici la chaîne parfaitement compréhensible par l'utilisateur, mais nous pourrions aussi avoir une clé de traduction, par exemple home.welcome_message
qui serait plus facile à gérer pour les développeurs.
L'avantage de cette solution est que si la traduction n'existe pas, la chaîne de caractères passée en paramètre est retournée, et le texte reste donc lisible.
Il est possible de copier/coller les chaînes à traduire manuellement, mais Symfony propose une commande pour extraires automatiquement les textes à traduire depuis vos vues ou vos contrôlleurs (pour les autres, il faudra les ajouter à la main).
Cette commande est à exécuter pour chaque locale de votre site (ici en). Le format permet de définir le type de fichier de sortie.
Le --force
va écraser les données existantes pour ajouter les clés manquantes.
Pour créer des ressources/fichiers de traduction, il faut créer un fichier de traduction pour chaque langue prise en charge. Par exemple, pour la langue française, nous pouvons créer un fichier messages.fr.yaml
dans le dossier translations
:
Le nom de fichier des fichiers de traduction est important : chaque fichier de message doit être nommé selon le chemin suivant domain.locale.loader:
domain (ici message) : Le domaine de traduction ;
locale (ici fr) : La locale à laquelle les traductions sont destinées (par exemple en_GB, en, etc.) ;
loader (ici yaml) : Comment Symfony doit charger et analyser le fichier (par exemple xlf, php, yaml, etc).
Par défaut, Symfony fournit de nombreux "loader"" qui sont sélectionnés en fonction des extensions de fichiers suivantes :
.yaml: fichier YAML (vous pouvez également utiliser l'extension de fichier .yml) ;
.xlf: fichier XLIFF (vous pouvez également utiliser l'extension de fichier .xliff) ;
.php: un fichier PHP qui renvoie un tableau avec les traductions ;
.csv: fichier CSV ;
.json: fichier JSON ;
.ini: fichier INI ;
La traduction se produit en fonction des paramètres régionaux de l'utilisateur. La locale de l'utilisateur courant est stockée dans l'objet Request
:
Pour définir la locale de l'utilisateur, il faut utiliser la méthode setLocale()
de l'objet Request
:
Symfony récupère automatiquement la locale pour choisir le bon fichier de traduction.
Il est aussi possible de gérer la locale en utilisant les URL :
La variable _locale
est automatiquement injectée dans le contrôleur et peut être utilisée pour définir la locale de l'utilisateur.
Créer une page d'accueil qui affiche un message de bienvenue en français et en anglais ;
Créer un lien qui permet de changer de langue.
Twig permet de faciliter la localisation des dates, des heures, ...
Vous pouvez suivre les documentations selon vos besoins :
Dans tous les cas deux bundles sont nécessaires
Installez les éléments et testés différents cas de figure avec les dates.
Twig propose de nombreux filtres permettant d'interagir avec vos variables. Ces filtres sont très utiles pour formater vos données, les transformer, les comparer, etc. Une longue liste est disponible par défaut , mais vous pouvez également créer vos propres filtres .
Nous souhaitons formater les prix de notre application dans le format monétaire en euros. Nous pourrions le faire avec le filtre number_format
de twig, mais nous allons créer notre propre filtre, car nous aurons toujours la même configuration. Cela nous permettra également d'ajouter le symfony euro.
Pour créer un filtre nous devons créer une classe qui va étendre Twig\Extension\AbstractExtension. Cette classe peut se mettre n'importe où dans le dossier src, mais nous allons la mettre dans le dossier src/Twig.
Dans cette classe, nous avons créé une méthode getFilters
qui va nous permettre de définir les filtres que nous souhaitons créer. Cette méthode doit retourner un tableau d'objets TwigFilter
. Pour chaque filtre, nous devons définir un nom (qui est utilisé dans Twig) et une méthode qui va être appelée lorsque le filtre sera utilisé.
Dans notre exemple, nous avons créé un filtre price
qui va formater un nombre en ajoutant le symbole euro.
Pour l'utiliser dans Twig, il suffit d'utiliser le nom du filtre :
Nous pouvons améliorer notre filtre afin de pouvoir recevoir des paramètres si nous ne souhaitons pas avoir un format en euro, mais par exemple en dollar. Dans ce cas, le symbole est différent, le séparateur de décimales et de milliers également. Cependant notre cas le plus classique restant l'euro, nous allons mettre ces paramètres en option.
Et l'usage dans twig pourrait être
Nous allons créer un filtre qui va retourner du HTML. Nous allons créer un filtre qui va permettre d'afficher un nombre d'étoiles en fonction d'une note. Par exemple, si la note est 3, nous allons afficher 3 étoiles pleines, 2 étoiles vides.
Et l'usage dans twig pourrait être
Mais vous aurez une erreur, et un résultat affiché en HTML (en fait Symfony à empéché l'interpréation du HTML car il ne sait pas s'il peut le faire. Cela pourrait potentiellement être dangeureux). Pour cela, nous devons utiliser le filtre raw
de twig pour forcer l'affichage du HTML.
Comme ce filtre va toujours renvoyer un contenu HTML que nous allons vouloir afficher, nous pouvons configurer notre filtre pour qu'il retourne directement du HTML "affiché".
Nous ajoutons sur la déclaration du filtre un tableau avec la clé is_safe
et la valeur html
. Cela va permettre à twig de savoir que le filtre retourne du HTML et qu'il ne doit pas l'encoder (autrement dit l'afficher en texte).
Créer un filtre qui va permettre d'afficher la date du jour au format français. L'appel du filtre dans twig pourrait être :
Ou dateObjet
est un objet DateTime envoyé par le contrôleur
Modifier votre filtre pour que la syntaxe suivante puisse fonctionne en utilisant la date du jour
Créer un filtre qui va formatter un numéro de téléphone en ajoutant des espaces entre les groupes de chiffres. L'appel du filtre dans twig pourrait être :
Symfony (comme beaucoup de framework) émét des événements à chaque étape de son cycle de vie (préparation de la requête, traitement de la requête, génération de la réponse, etc.). Ces événements peuvent être écoutés par des listeners qui peuvent modifier le comportement de Symfony.
Les événements permettent aussi d'éviter de surcharger un contrôleur ou un service. Par exemple, un utilisateur s'inscrit sur votre site. Vous souhaitez lui envoyer un email, puis une notifaction push et notifier d'autres utilisateurs d'un nouvel inscrit par exemple.
Vous pourriez ajouter ces fonctionnalités dans le contrôleur qui gère l'inscription. Mais si vous avez besoin de faire la même chose dans un autre contrôleur, vous allez devoir dupliquer le code.
Si vous souhaitez retirer l'un des comportements, vous allez devoir le retirer dans tous les contrôleurs qui l'utilisent.
Si vous souhaitez ajouter un comportement vous allez devoir modifier tous les contrôleurs qui l'utilisent.
Les points 2 et 3 ne sont pas forcément problèmatique si vous êtes propriétaire du code. Mais si vous développez un bundle ou utilisez un bundle existant, vous ne pourrez peut etre pas modifier le code.
Dans ces trois exemples il est donc possible de définir nos événements qui pourront ensuite être écoutés par un ou plusieurs listeners, qui chacun pourra mener une action.
Nous allons donc voir dans cette partie comment écouter des événements, et comment définir nos propres événements.
Symfony (ou votre application) émet des événements à chaque étape de son cycle de vie. Ces événements sont écoutés par des listeners qui peuvent modifier le comportement de Symfony.
Pour écouter un événement, il faut créer une classe qui implémente l'interface Symfony\Component\EventDispatcher\EventSubscriberInterface
. Cette interface contient une méthode getSubscribedEvents()
qui retourne un tableau associatif. La clé est le nom de l'événement et la valeur est la méthode à appeler.
Un exemple pourrait être :
Dans cet exemple, la méthode onKernelRequest()
sera appelée à chaque fois que l'événement kernel.request
sera émis.
Pour définir nos propres événements il faut deux choses :
Définir une classe qui hérite de Symfony\Component\EventDispatcher\Event
qui va contenir la description et les données de notre événement.
"Emettre" l'événement avec la méthode dispatch()
de l'objet Symfony\Component\EventDispatcher\EventDispatcherInterface
quelque part dans notre application en lui passant un objet du type de la classe de l'événnement précédemment créée.
Pour définir notre événement, il faut créer une classe qui hérite de Symfony\Component\EventDispatcher\Event
. Cette classe doit contenir les données de l'événement. Par exemple, si nous voulons définir un événement qui sera émis à chaque fois qu'un utilisateur s'inscrit, nous pourrions avoir une classe comme celle-ci :
Pour émettre l'événement, il faut récupérer l'objet Symfony\Component\EventDispatcher\EventDispatcherInterface
et appeler la méthode dispatch()
en lui passant en premier paramètre le nom de l'événement et en second paramètre l'objet de l'événement.
Et bien sûr définir un listener pour écouter l'événement, comme décrit dans la partie précédente, et qui pourrait être pour notre exemple :
Créer un événement UserRegisteredEvent
qui sera émis à chaque fois qu'un utilisateur s'inscrit. Créer un listener qui enverra un email (en utilisant notre service) à l'utilisateur pour lui dire qu'il a bien été enregistré.
Créer un deuxième événement UserUpdatedEvent
qui sera émis à chaque fois qu'un utilisateur met à jour son profil. Créer un listener qui enverra un email (en utilisant notre service) à l'utilisateur pour lui dire qu'il a bien été mis à jour.
Dans cette partie nous allons revenir sur les notions de repository et de requêtes, afin de pouvoir écrire nos propres requêtes.
Un repository est une classe qui permet de faire des requêtes sur une table (par l'intérmédiaire de l'eentité associée) de la base de données. Dans Symfony, lorsque vous ajoutez une entité, un repository est automatiquement créé.
Ce fichier va contenir nos requêtes spécifiques, en utilisant au choix du SQL ou du DQL (Doctrine Query Language), qui permet de construire des requêtes en notation objet sans utiliser la syntaxe SQL qui nous rendrait dépendant de notre SGBD.
Reprenons l'exemple de la séance 2, avec les entités Adresse, Article et Fournisseur. Nous allons analyser le repository pour l'entité Adresse.
Tout d'abord ce fichier contient une classe qui étend la classe ServiceEntityRepository, qui est une classe fournie par Doctrine. Cette classe permet de faire des requêtes sur une table de la base de données.
La classe contient une méthode constructeur qui prend en paramètre un objet de type ManagerRegistry, qui est une classe fournie par Doctrine. Cette classe permet de récupérer des informations sur la base de données, notamment le nom de la table associée à l'entité.
La classe contient également 2 méthodes qui permettent de sauvegarder et de supprimer une entité dans la base de données. Ces méthodes sont très utiles, car elles permettent de ne pas avoir à écrire le code pour persister et supprimer une entité dans la base de données. Cet ajout est relativement récent dans l'éco-système Symfony. Il n'est pas obligatoire de l'utiliser, mais cela allège le code de vos contrôleurs.
Enfin, notez les lignes de commentaires avant la classe. Elles indiquent les 4 méthodes nativement proposées par les repository avec Doctrine, à savoir (voir S3 pour plus de détails) :
find : permet de récupérer une entité à partir de son identifiant
findOneBy : permet de récupérer une entité à partir d'un tableau de critères
findAll : permet de récupérer toutes les données d'une table et de les retourner sous forme de tableau
findBy : permet de récupérer toutes les données d'une table et les retourner sous forme de tableau à partir d'un tableau de critères.
Vous avez ensuite deux exemples de requêtes, qui sont commentés. Ces requêtes sont écrites en DQL, et permettent de récupérer des données à partir de critères. Vous pouvez les utiliser comme modèle pour écrire vos propres requêtes.
La première requête permet de récupérer toutes les adresses dont le champ exampleField
est égal à la valeur passée en paramètre. La seconde requête permet de récupérer une seule adresse dont le champ exampleField
est égal à la valeur ($value
) passée en paramètre. Pour que cet exemple fonctionne exampleField
doit être un champ de la table Adresse
.
Ce qui est à noter c'est que pour construire une requête on utilise la méthode createQueryBuilder
, qui prend en paramètre le nom de l'alias de l'entité. Dans notre exemple, l'alias est a
. Cet alias est utilisé pour construire la requête. Par exemple, si on veut récupérer les adresses dont le champ exampleField
est égal à la valeur passée en paramètre, on écrit a.exampleField = :val
. L'alias est donc utilisé pour indiquer le nom du champ de la table. Les alias doivent être unique si vous souhaitez faire des jointures.
Ensuite, on utilise la méthode andWhere pour ajouter une condition à la requête. Dans notre exemple, on ajoute une condition sur le champ exampleField
qui doit être égal à :val
. On utilise ce que l'on nomme des requêtes parametrées. Cela permet de sécuriser les requêtes, et d'éviter les injections SQL.
Il faut ensuite définir une valeur pour notre paramètre :val. Pour cela on utilise la méthode setParameter, qui prend en paramètre le nom du paramètre (ici :val
) et la valeur à lui attribuer (ici $value
). (il existe une méthode setParameters qui permet de définir plusieurs paramètres en même temps dans un tableau associatif).
On peut donc avoir plusieurs conditions et autant de paramètres que nécessaires.
Ensuite, on peut ajouter des ordres de tri sur les résultats de la requête. Pour cela on utilise la méthode orderBy
, qui prend en paramètre le nom du champ sur lequel on souhaite trier, et le sens du tri (ASC
ou DESC
). Si on souhaite ajouter d'autres tris, on peut ajouter des méthodes addOrderBy
qui prennent les mêmes paramètres. Le "add" permet de dire que l'on souhaite ajouter un autre ordre de tri.
Ensuite, on peut limiter le nombre de résultats retournés par la requête. Pour cela on utilise la méthode setMaxResults
, qui prend en paramètre le nombre de résultats maximum. Dans notre exemple, on limite à 10 résultats.
A ce stade la requête est prête mais non fonctionnelle. Pour l'exécuter, on utilise la méthode getQuery
, qui permet de récupérer l'objet Query qui représente la requête. Enfin, on utilise la méthode getResult
pour récupérer les résultats de la requête. La réponse est un tableau dans ce cas.
Pour tester cette requête, vous pouvez l'appeler depuis un contrôleur, un service, une méthode... par l'intermédiaire du repository. Par exemple, si vous avez un contrôleur qui s'appelle AdresseController
, vous pouvez écrire :
Cette requête est très similaire à la précédente, sauf qu'elle ne doit retourner qu'un seul résultat ou rien (getOneOrNullResult
). Dans ce cas, la réponse est un objet de type Adresse, ou null si aucun résultat n'est trouvé. Si la requête retour plus d'un résultat, alors une erreur est levée.
Proposer une requête qui permet de récupérer toutes les adresses dont le champ code_postal
est égal à 10000.
Proposer une requête qui permet de récupérer toutes les adresses dont le champ code_postal
est égal à 10000 et les réponses triés par rue.
Testez ces deux méthodes dans un contrôleur pour voir les résultats.
Ajoutez des données dans votre base de données pour effectuer vos tests.
Pour faire des jointures, il faut utiliser la méthode join
de la requête. Par exemple, si on souhaite récupérer les adresses et les villes associées (qui serait dans une autre entité), on peut écrire :
Dans cet exemple, on utilise la méthode join
pour faire une jointure sur la table ville
. On utilise l'alias v
pour la table ville
. On peut ensuite utiliser cet alias pour faire des requêtes sur la table ville
. Par exemple, si on souhaite récupérer les adresses dont la ville est Paris
, on peut écrire :
Les autres jointures sont :
leftJoin
: jointure gauche
rightJoin
: jointure droite
innerJoin
: jointure interne
Exemple de jointure gauche :
Il existe plusieurs opérateurs pour faire des requêtes. Par exemple, pour récupérer les adresses dont le champ code_postal
est supérieur à 10000, on peut écrire :
Il existe plusieurs opérateurs :
=
: égal
!=
: différent
>
: supérieur
<
: inférieur
>=
: supérieur ou égal
<=
: inférieur ou égal
LIKE
: contient. Le paramètre doit être une chaîne de caractères, et on peut utiliser le caractère %
pour remplacer un nombre quelconque de caractères.
NOT LIKE
: ne contient pas. Le paramètre doit être une chaîne de caractères, et on peut utiliser le caractère %
pour remplacer un nombre quelconque de caractères.
IN
: dans une liste. Le paramètre doit être un tableau.
NOT IN
: pas dans une liste. Le paramètre doit être un tableau.
BETWEEN
: entre deux valeurs. Le paramètre doit être un tableau de deux valeurs.
IS NULL
: est null. Dans ce cas il n'y a pas de paramètre attendu.
IS NOT NULL
: n'est pas null. Dans ce cas il n'y a pas de paramètre attendu.
EXISTS
: existe
NOT EXISTS
: n'existe pas
IS EMPTY
: est vide
IS NOT EMPTY
: n'est pas vide
Il existe plusieurs clauses pour faire des requêtes. Par exemple, pour récupérer les adresses dont le champ code_postal
est supérieur à 10000 et dont le champ rue
contient rue
, on peut écrire :
Il existe plusieurs clauses :
andWhere
: et
orWhere
: ou
andHaving
: et
orHaving
: ou
groupBy
: groupe par
orderBy
: tri par
setMaxResults
: nombre maximum de résultats
setFirstResult
: nombre de résultats à sauter
addSelect
: ajouter un champ à sélectionner
addOrderBy
: ajouter un champ à trier
addGroupBy
: ajouter un champ à grouper
L'ordre et les priorités sont les mês que pour SQL.
Comme en SQL il existe des fonctions qui peuvent s'appliquer sur la requête pour faire des sommes, moyennes, etc. Par exemple, pour récupérer la somme des codes postaux (ce qui ne sert à rien bien sûr !), on peut écrire :
Il existe plusieurs fonctions :
COUNT
: nombre d'éléments
SUM
: somme
AVG
: moyenne
MIN
: minimum
MAX
: maximum
...
Proposer une requête qui permet de récupérer toutes les articles dont le prix est supérieur à une valeur passée en paramètre.
Proposer une requête qui permet de récupérer toutes les articles dont le prix est supérieur à une valeur passée en paramètre et dont le nom contient une chaîne de caractères passée en paramètre.
Proposer une requête qui permet de récupérer toutes les articles dont le prix est supérieur à une valeur passée en paramètre et dont le nom contient une chaîne de caractères passée en paramètre, et qui sont triés par prix décroissant.
Proposer une requête qui permet de récupérer toutes les articles dont le prix est supérieur à une valeur passée en paramètre et dont le nom contient une chaîne de caractères passée en paramètre, et qui sont triés par prix décroissant, et qui est proposé par un fournisseur dont le code postal est 10000.
Ecrire les méthodes correspondantes dans le repository ArticleRepository
.
Utiliser les méthodes dans le contrôleur ArticleController
pour afficher les résultats dans une vue.
Affichez un formulaire avec les champs prix et code postal.
Récupérez ces données et les passer dans une requête qui filtre les produits inférieurs au prix saisie et dont le code postal du fournisseur est celui saisi. Triez par prix décroissant.
Par défaut, les lignes 15 à 18, sont les champs de votre entité (qui est précisée ligne 25). Ces champs sont automatiquement configurés selon leur type. On peut préciser le type de champ en paramètre 2, et le 3éme paramètre sont les options (cf ).
...
Les dates :
Les dates et heures :
Les heures :
Les monnaies :
Ces deux dépendances nécessites d'installé sur le serveur.
Il existe aussi la possibilité de définir des listener avec une syntaxe différente qui ne va écouter qun' seul événement précis :
Variable
Description
loop.index
The current iteration of the loop. (1 indexed)
loop.index0
The current iteration of the loop. (0 indexed)
loop.revindex
The number of iterations from the end of the loop (1 indexed)
loop.revindex0
The number of iterations from the end of the loop (0 indexed)
loop.first
True if first iteration
loop.last
True if last iteration
loop.length
The number of items in the sequence
loop.parent
The parent context
Depuis la version 7 de Symfony, il est conseillé d'utiliser une version sans webpack : https://symfony.com/doc/current/frontend/asset_mapper.html
Nous voyons ici la solution avec Webpack qui est très répendu, et parce que Webpack est un outil que l'on peut utiliser en dehors de Symfony.
Webpack (https://webpack.js.org/) est un outil logiciel open-source de type « module bundler » (littéralement, « groupeur de modules »), conçu pour faciliter le développement et la gestion de sites et d'applications web modernes. (source Wikipedia).
Webpack va permettre de gérer vos fichiers CSS/SCSS, JS, images, etc. Il va permettre de les regrouper, de les minifier, de les compiler, etc. Il permet aussi d'utiliser des langages comme VueJS, React, etc.
Utiliser Webpack dans un projet Symfony va vous permettre de gérer vos fichiers CSS/SCSS, JS, images, etc. comme dans un projet classique, puis de les regrouper, de les minifier, de les compiler, etc. et de les mettre dans le répertoire public pour qu'ils soient accessibles de vos vues.
Pour installer et utiliser Webpack, vous devez installer NodeJS (https://nodejs.org/en/), pour disposer de npm (https://www.npmjs.com/) ou de yarn (https://yarnpkg.com/).
Dans le contexte de Symfony, nous n'allons pas utiliser directement Webpack, mais une version adaptée de Webpack appelée Encore (https://symfony.com/doc/current/frontend.html). Ce package permet de simplifier l'utilisation de Webpack dans un projet Symfony, avec une gestion dans le projet et dans twig.
Pour installer Encore, il faut utiliser la commande suivante :
puis lancer la commande suivante :
Pour installer les dépendances "front".
Ces installations ont eu plusieurs effets :
Le fichier package.json
a été créé. Ce fichier contient les dépendances "front" de votre projet. Il est important de ne pas le supprimer.
Le fichier webpack.config.js
a été créé. Ce fichier contient la configuration de Webpack, et la liste de toutes les tâches qu'il doit réaliser. Il est important de ne pas le supprimer.
Le répertoire assets a été créé. Ce répertoire contient tous les fichiers "front" de votre projet (SCSS, CSS, JS, ...), dans leur version non compilée.
Regardons en détail ces fichiers.
Nous avons quelques données d'exemples dans les fichiers du repertoire assets. Mais si vous essayez de les utiliser, vous allez avoir une erreur. Cela est lié au fait que Webpack n'a pas encore été lancé, et n'a donc pas généré les fichiers compilés dans le répertoire public/build.
Donc, pour utiliser les fichiers "front" de votre projet, vous devez lancer la commande suivante :
Cette commande va lancer Webpack, et va générer les fichiers compilés dans le répertoire public/build. Un fichier entrypoints.json a été créé dans le répertoire public/build. Ce fichier contient la liste des fichiers compilés, et les chemins vers ces fichiers. C'est grâce à ce fichier que Twig va pouvoir charger les fichiers compilés.
Retournez dans votre navigateur pour réessayer d'afficher votre page, elle devrait fonctionner.
Essayez de modifier le fichier css dans le repertoire assets pour voir les changements. N'oublier pas de relancer la commande yarn encore dev
pour voir les changements.
Comme il peut être fastidieux de relancer la commande à chaque fois que vous modifiez un fichier, vous pouvez utiliser la commande suivante :
Cette commande va lancer Webpack, et va générer les fichiers compilés dans le répertoire public/build. En plus, elle va surveiller les fichiers du répertoire assets, et va relancer Webpack à chaque fois qu'un fichier est modifié, tant que vous ne fermez pas la fenêtre de votre terminal.
Si vous souhaitez utiliser des fichiers SCSS, vous devez créer ces fichiers, et les importer dans le fichier app.scss
(le fichier app.css doit être renommé en app.scss) du répertoire assets. Par exemple, si vous souhaitez créer un fichier style.scss
dans le répertoire assets/scss, vous devez ajouter la ligne suivante dans le fichier app.scss
:
Vous devez ensuite expliquer à webpack comment compiler ce fichier SCSS. Pour cela, vous devez ajouter (en fait décommenter) la ligne suivante dans le fichier webpack.config.js
:
Comme vous avez modifié le fichier webpack.config.js
, vous devez relancer la commande yarn watch
ou npm run watch
pour que les changements soient pris en compte.
Vous allez obtenir une erreur, car il manque quelques dépendances. Suivez les instructions du terminal et installez les dépendances manquantes.
Relancez la commande yarn watch
ou npm run watch
pour voir les changements.
Vous pouvez maintenant mettre en pratique ce que vous savez faire en SCSS/SASS dans un projet Symfony, sans avoir besoin de compiler vos fichiers SCSS/SASS à la main.
Créer un fichier style.scss
dans le répertoire assets/scss, et ajouter une couleur de fond à la page. Ajoutez aussi une couleur de texte.
Créer et modifier des contrôleurs et des vues pour tester votre style.
En vous appuyant sur la documentation de Boostrap et de Symfony, ajouter Bootstrap à votre projet (ni en CDN, ni en téléchargement, mais en l'installant avec npm ou yarn).
La documentation officielle se trouve ici : https://symfony.com/doc/current/validation.html
Depuis le S3, nous écrivons systèmatiqument le code suivant pour enregistrer un formulaire :
Ce code fait deux choses. Il vérifie que le formulaire a été soumis, et qu'il est valide. Jusqu'a présent le coté "validation" n'est pas géré car nous n'avons pas mis de règles de validation sur nos formulaires. Nous allons donc voir comment ajouter des règles de validation sur nos formulaires.
Pour ajouter des règles de validation sur un champ, nous allons utiliser les contraintes de validation. Pour cela nous devons modifier notre formulaire ArticleType
:
Dans cet exemple nous avons ajouté une contrainte de type Length
sur le champ designation
. Nous avons également ajouté une contrainte de type NotBlank
sur le champ description
. Nous pouvons ajouter autant de contraintes que nous le souhaitons sur un champ.
La liste des contraintes par défaut est disponible sur la documentation de Symfony.
Pour chaque contrainte on peut définir un message. Ce message sera affiché sous le champs qui pose problème. (form_errors
dans le thème de votre formulaire pour le personnaliser).
Cette syntaxe est intéressante, mais ne vérifie les données que si le formulaire est soumis. Dans les autres cas, les données ne sont pas forcéments validées. Selon votre fonctionnement, il peut être nécessaire de les valider (exemple envoie depuis une API, import de données, ...).
Pour valider les entités, nous allons utiliser les annotations de validation. Les possibilités sont les mêmes que pour les contraintes de validation sur les formulaires. La syntaxe utilise les attributs (depuis PHP 8.0) ou les annotations (PHP 7.4 et inférieur).
Pour cela nous pouvons modifier notre entité Adresse
par exemple, pour le code postal :
Ajoutez les contraintes de validations sur vos formulaires. Testez les en soumettant des données invalides.
Personnalisés l'affiche des erreurs dans vos formulaires
That's a tough question but thankfully, our team is on it. Please bear with us while we're investigating.
Yes, after a few months we finally found the answer. Sadly, Mike is on vacations right now so I'm afraid we are not able to provide the answer at this point.
Dans cette partie nous allons revenir sur les formulaires, et sur un cas particulier des collections de formulaires. La documentation officielle : https://symfony.com/doc/current/form/form_collections.html
Nous avons vu dans une séance précédente comment intégrer un formulaire dans un autre. C'est un cas d'usage pratique, mais si nous souhaitons avoir une collection de formulaires, c'est à dire pouvoir ajouter plusieurs éléments en lien avec un autre, nous allons devoir faire un peu plus de travail.
Prenons par exemple le cas où nous aimerions ajouter des "tags" à nos articles. Nous allons donc avoir une collection de tags, et chaque tag sera un formulaire (basique, ne contenant que le libellé).
Créer une entité Tag
avec un champ label
de type string
.
Créer une relation ManyToMany
entre Article
et Tag
.
Créer un formulaire TagType
avec un champ label
de type TextType
en lien avec l'entité Tag
.
Nous allons maintenant modifier notre formulaire ArticleType
qui va contenir une collection de tags. Pour cela, nous allons utiliser la classe CollectionType
de Symfony. La méthode buildForm
de notre formulaire ArticleType
va donc ressembler à ceci :
Il faut ajouter le champs dans la vue (si vous avez détaillez l'affichage du formulaire), ou alors il est déjà pris en compte.
Modifiez le formulaire ArticleType
pour ajouter une collection de tags.
Vérifiez que tout fonctionne dans la vue (à ce stade vous ne devriez vous que l'entrée "tags" apparaître)
Nous allons maintenant ajouter un tag à notre collection. Pour cela, nous allons autoriser l'ajout dans notre formulaire en mettant allow_add
à vraie dans le champs tags
.
Autoriser l'ajoute, ca ajouter un attribut sur notre formulaire (nommé data-prototype) qui va contenir le code HTML du formulaire de notre tag. L'idée c'est ensuite d'ajouter du JavaScript qui va récupérer ce code et le dupliquer à chaque fois que nous allons ajouter un tag.
Pour que ca fonctionne, nous devons légérement modifier l'affichage dans notre formulaire.
L'usage des balises ul est un choix pour l'exercice, vous pourriez le faire sous n'importe qu'elle forme.
data-index : permet de récupérer le dernier index de notre collection de tags, en fonction de ce qui serait déjà saisie (sur une modification par exemple).
data-prototype : contient le code HTML du formulaire de notre tag.
Nous devons ensuite ajouter le bouton permettant d'ajouter un tag. Par exemple le code ci-dessous.
Ensuite, nous avons besoin du code JavaScript qui va détecter l'appui sur le bouton et ajouter un nouveau tag.
Pour cela, et comme nous avons installé webpack-encore, nous allons ajouter notre code dans le fichier assets/app.js
.
Tout d'abord on écoute un événement "click" sur le bouton "add a tag".
Ensuite, on récupère le code HTML du prototype, et on l'ajoute à notre collection de tags, avec la méthode addFormToCollection
.
Cette méthode récupère le contenu de data-prototype, remplace /__name__/
par l'index de notre collection (le numéro du tag que nous ajoutons), cela pour avoir des formulaires valides avec des champs ayant un nom différent à chaque fois. Ensuite, on ajoute le code HTML à la suite (appendChild
) de notre formulaire déjà existant.
Petite particularité, le code JavaScript va s'instancier avant notre HTML, donc l'événement ne sera pas associé au bouton. Il faut donc dire au JavaScript, d'attendre que le dom soit chargé pour pouvoir ajouter l'événement, l'équivalent du document.ready
avec jQuery.
Ajoutez le code JavaScript pour ajouter un tag à votre formulaire.
Testez votre formulaire et l'ajout des tags.
Mettez en place une page permettant de voir l'ensemble des articles avec leurs tags (attention, c'est une collection (i.e. un tableau)).
Lorsque vous allez soumettre le formulaire avec les tags, vous allez avoir une erreur de base de données. En effet, lorsque nous ajoutons un articles, nous essayons également d'ajouter un tag. Ce tag doit s'ajouter dans une autre entité, liée à l'article. Pour cela, nous allons devoir ajouter une méthode persist
sur notre relation afin que Doctrine puisse ajouter le tag dans la table. Modifiez la relation de votre entité Article
pour ajouter la méthode persist
.
On ajoute également remove, pour permettre la suppression d'un tag si un article est supprimé.
Nous allons maintenant ajouter la possibilité de supprimer un tag. Pour cela, nous allons devoir ajouter un bouton "supprimer" à chaque tag. Tout d'abord, il faut autoriser la suppression dans notre formulaire en mettant allow_delete
à vraie dans le champs tags
de notre formulaire ArticleType
.
Ensuite, nous allons ajouter un bouton "supprimer" à chaque tag. Mais comme les tags n'existent pas dans notre vue, nous devons les gérer en JavaScript également, pour que l'ajout du bouton soit dynamique.
Cet extrait de code ajoute un bouton "supprimer" à chaque tag existant (en détectant le li des il, il faudrait adapter si vous affichez le formulaire autrement), et à chaque nouveau tag ajouté.
La méthode addTagFormDeleteLink
est la suivante, et elle permet de supprimer la ligne "li" du formulaire.
Ajoutez le code JavaScript pour ajouter un bouton "supprimer" à chaque tag.
Testez votre formulaire et la suppression des tags.
Ajouter une méthode permettant de modifier un article.
Sujet pour le groupe Alternant
En repartant du mini-projet des séances 11 et 12 - TP Révision vous vous assurerez du bon fonctionnement des demandes de ces séances (les points avec l'icône ) et vous mettrez en place les points suivant :
Le site est proposé en français et en anglais pour toutes les données statiques.
Modifiez la base de données pour gérer les éléments en français et en anglais sur les catégories (on se limitera à cette table pour l'exercice)
Ajouter une entité Réponse qui comprendra les champs :
id
id du message
date de publication
texte de la réponse
auteur de la réponse
On ne pourra pas répondre à une réponse directement, on ajouter une nouvelle réponse au message pour cela.
Afficher les réponses aux messages
Une page de recherche permettra de rechercher un message dans toutes les catégories (texte libre)
Si une réponse est postée sur un message, on fera un mail à l'auteur du message.
Le rendu se fera avec un dossier zippé (sans vendor et var), sur Moodle (WRA407D).
Dans cette séance nous allons reprendre l'ensemble des concepts vu cette année sur Symfony et développer un mini forum.
Installer un nouveau projet (réutiliser symfony.mmi-troyes.fr)
Configurer une base de données
Les entités sont :
Categorie
titre, string 255
ordre, integer
couleur, string 6 caractères
Message
titre, string 255
message, text
datePosted, datetime
Tags
libelle
couleur
User (utiliser make:user et ajouter)
Nom, string 50
Email, string 255
Ville, string 50
Un user peut déposer plusieurs messages,
Un message est dans une seule catégorie
Une catégorie peut avoir plusieurs messages
Seul un admin peut créer des catégories,
un message pourra avoir un ou plusieurs tags
Définir une page d'accueil
Reprenant les tags des messages et les 3 dernières messages publiés
Définir une page listant les catégories (avec la couleur)
Chaque catégorie permet d'accéder à une page listant les messages, triés par date
Mettre en place la sécurité
Chaque inscription doit envoyer un mail à l'utilisateur
Dans la page d'une catégorie il sera possible d'ajouter un message (formulaire personnalisé et mis en page)
Un admin pourra ajouter une catégorie depuis une page de gestion dédiée (CRUD possible, mais mis en page)
Une page de recherche permettra de rechercher un message dans toutes les catégories (texte libre)
On pourra filtrer par tag
Ajouter un événement déclenché à chaque ajout d'un message et diffuser aux utilisateurs par mail
Sous l'image docker MMI
Connexion sur le container Symfony de docker
Installation de symfony dans /var/www
Installation des dépendances (toutes les dépendances de webapp)
Dupliquer le .env (qui ne doit jamais être modifié avec vos données)
Configurer apache, en créant une configuration qui pointe vers le dossier nomduprojet
Relancer apache
Mettre en production un projet réalisé avec Symfony
La mise en production d'un projet avec Symfony est simple, mais implique de suivre les différentes étapes.
Pour effectuer une mise en ligne de votre projet avec un logiciel FTP, il faut télécharger sur le serveur les fichiers ou répertoires suivants.
Pour une mise à jour d'un produit existant, l'upload de src/
et templates/
peut suffire, selon ce que vous avez modifié sur votre projet.
Il vous faut ensuite exécuter les différentes opérations de mise en ligne de la partie
Pour une première mise en ligne vous pouvez faire un clone de votre dépôt.
Cette commande va créer un nouveau répertoire et copier les fichiers de votre dépôt sur votre serveur.
Pour mettre à jour un projet déjà existant sur votre serveur, et géré avec Git, vous devez executer la commande suivante
Où master est la branche qui contient la version de votre projet que vous souhaitez installer.
Si vous n'avez qu'un fichier .env
, vous devez le mettre à jour avec les informations de votre serveur de production (base de données, éventuellement serveur de mail, ...).
C'est également dans ce fichier d'environnement que vous devez définir que votre projet est en production et plus en développement :
En production Symfony utilise un système de cache, n'affiche plus le profiler ou les messages d'erreur, mais utilise les pages 404, 500 que vous avez définies.
Les erreurs peuvent être sauvegardées dans des fichiers de log, selon votre configuration.
Toutes les configuration se trouvant dans config/packages/dev
sont ignorées et celles se trouvant dans config/packages/prod
sont utilisées.
Le répertoire vendor n'est jamais installé ou téléchargé, quelque soit la méthode que vous utilisez. Il faut donc soit le créer (une première installation), soit le mettre à jour (si vous avez ajouté des bundles ou que vous souhaitez obtenir la dernière version des bundles que vous utilisez)
L'information "--no-dev" permet de ne pas installer les bundles qui ne servent qu'en développement (make, profiler, ...). Si vous souhaitez être en développement sur ce serveur, vous devez retirer cette instruction.
En production Syfmony utilise un système de cache très performant. Mais cela peut empêcher de voir vos dernières modifications. Il faut donc s'assurer de nettoyer le cache de Symfony.
La commande suivante permet de supprimer le cache :
Si vous avez ajouté des éléments dans vos entités, vous devez mettre à jour votre base de données. Pour cela vous pouvez executer la commande suivante :
Il vous faut ensuite exécuter les différentes opérations de mise en ligne de la partie
Il vous faut ensuite exécuter les différentes opérations de mise en ligne de la partie
Symfony propose une gestion poussée des environnements, et vous pourriez avoir plusieurs fichiers en fonction du contexte :