Jérôme Cutrona

Version statique de l'intranet de Jérôme Cutrona - Rejoindre la version dynamique 🔒

Les exemples et corrections sont potentiellement non fonctionnels.

B.U.T. Informatique - IUT de Reims - Université de Reims

Symfony 6 « avancé »

Navigation

Objectifs de la séance

  • Prendre en main Symfony dans sa version 6.3
  • Consolider les objectifs du TP de découverte de Symfony 5
    • Utiliser le routage
    • Développer des « templates » Twig
    • Écrire des tests fonctionnels
    • Utiliser l'ORM Doctrine
    • Gérer des relations entre entités
    • Générer des données factices (« fixtures ») avec « Foundry »
    • Utiliser le composant Form
    • Utiliser le composant Security
  • Développer en respectant les bonnes pratiques de Symfony
  • Mettre en place des outils de qualité de code
  • Automatiser l'utilisation des outils de qualité de code
  • Conteneuriser certains services avec Docker

Préambule

L'objectif de ce TP est de consolider vos acquis en développement d'applications Symfony (découverte de Symfony 5 puis API Platform), mais également de vous faire découvrir de nouvelles possibilités. Le sujet vous donnera des consignes et des pointeurs vers de la documentation et des ressources, vous mettant ainsi dans une situation de réalisation professionnelle. L'accent sera une nouvelle fois mis sur les bonnes pratiques de Symfony, la qualité de développement et l'automatisation.

Le fil conducteur sera un site de petites annonces, dont les fonctionnalités évolueront au fil de vos nouveaux apprentissages.

Mise en place d'une application Symfony 6.3

Vous allez créer des applications Symfony de façon conventionnelle, en utilisant l'outil « symfony » en ligne de commande (« Symfony CLI »). Une nouvelle application Symfony nécessite un peu plus de 80 Mo de sources PHP pour fonctionner (environ 10.000 fichiers dans un peu moins de 2.000 répertoires !). Ces données seront rapidement agrémentées de plusieurs dizaines de mégaoctets de cache générés par Symfony.

Vous allez installer Symfony à l'aide de l'outil « symfony » en ligne de commande (« Symfony CLI ») et pour cela, vous allez devoir l'installer. Cet outil utilise Composer dont vous devrez vous assurer du bon fonctionnement.

Quelques considérations liées à la configuration système de l'IUT

Remarque importante

Si vous travaillez sur votre ordinateur personnel, sur Linux ou sur Windows, vous pouvez placer le répertoire « symfony-for-sale » du projet où bon vous semble. La partie qui suit concerne uniquement le travail sur les ordinateurs de l'IUT. Cela ne vous empêche pas d'en prendre connaissance.

Votre répertoire d'accueil « $HOME » se trouve sur un emplacement réseau NFS, ce qui vous permet de retrouver vos fichiers quel que soit le PC du département sur lequel vous vous connectez. Symfony utilise le sous-répertoire « var » du projet pour la gestion de son cache, des traces d'exécution et des données de session. Ces tâches génèrent de nombreuses entrées-sorties sur le système de fichiers, particulièrement inadaptées aux systèmes de fichiers en réseau. Il en va de même pour PhpStorm qui surveille les modifications de tous les fichiers de votre projet et ne peut pas fonctionner normalement lorsque ces fichiers sont sur un partage NFS. Afin d'améliorer les performances de votre application et d'éviter de saturer le serveur de fichiers du département informatique, vous allez travailler dans le répertoire « /working/votre_login » de la machine sur laquelle vous êtes connecté.

En fin de séance

En fin de séance, vous devez impérativement synchroniser votre dépôt distant (en ligne de commande ou avec PhpStorm)

git commit
puis
git push
faute de quoi vous perdrez votre travail entre chaque séance !

Pensez également à supprimer votre répertoire de travail /working/votre_login/symfony-for-sale pour éviter la saturation du disque local :

rm -Rf /working/votre_login/symfony-for-sale

En début de séance

Lorsque vous reprenez le travail la séance suivante, vous avez trois choix possibles, qui dépendent de la façon dont vous avez travaillé :

  • Mettre à jour votre dépôt local
    cd /working/votre_login/symfony-for-sale
    git pull
  • Effacer votre dépôt local et repartir de zéro
    cd /working/votre_login
    rm -Rf /working/votre_login/symfony-for-sale
    git clone https://iut-info.univ-reims.fr/gitlab/votre_login/symfony-for-sale.git
    cd /working/votre_login/symfony-for-sale
  • Réinitialiser votre dépôt local selon le dépôt distant
    cd /working/votre_login/symfony-for-sale
    git fetch
    git reset --hard origin/main

Ensuite, dans le répertoire de votre projet, vous devez (ré)installer les composants nécessaires à son fonctionnement :

composer install

Vous devrez également reconfigurer votre accès base de données en redéfinissant le fichier « .env.local »

Installation / mise à jour de l'exécutable « symfony » et de Composer

Travail à réaliser
  1. Vérifiez que l'outil « symfony » en ligne de commande (« Symfony CLI ») est installé et à jour en reprenant la procédure d'installation vu l'an passé
  2. Contrôlez la compatibilité du système avec la commande :
    symfony check:requirements  --verbose
  3. Vérifiez que « Composer » est installé et à jour en reprenant la procédure d'installation vu l'an passé
  4. Vérifiez que Composer fonctionne correctement :
    composer about
  5. Mettez à jour Composer :
    composer self-update

Création du projet Symfony

Vous allez pouvoir créer un projet Symfony à l'aide de l'outil « symfony » en ligne de commande (« Symfony CLI »).

Travail à réaliser
  1. Vérifiez que vous êtes bien dans le répertoire « /working/votre_login » (ou n'importe quel répertoire de votre choix si vous êtes sur votre ordinateur personnel)
  2. Lancez la création d'un nouveau projet Symfony version « 6.3.* » :
    symfony --version 6.3 --webapp new symfony-for-sale

Vérification du bon fonctionnement de l'application

Si vous avez réalisé toutes les étapes précédentes sans encombre, votre application doit fonctionner. Vous allez donc le vérifier. Vous allez utiliser l'environnement de développement de Symfony qui est celui qui est activé par défaut en mode développement.

Travail à réaliser
  1. Placez-vous dans le répertoire de votre application
  2. Dans le terminal, lancez le serveur Web local avec la commande suivante :
    symfony serve
    Le serveur Web fonctionnera tant que vous n'aurez pas terminé l'exécution de l'outil ligne de commande « symfony » avec « CTRL+C ».
  3. Lancez votre navigateur web et saisissez l'URL suivante :
    http://127.0.0.1:8000
  4. Vérifiez que le serveur Web local répond correctement
  5. Terminez l'exécution de l'outil ligne de commande « symfony » avec « CTRL+C »

Gestion des versions avec Git

Afin de fiabiliser votre méthode de développement, vous allez utiliser Git pour gérer le suivi des versions de votre application Symfony.

Dépôt local

Vous allez configurer votre dépôt Git local.

Travail à réaliser
  1. Assurez-vous d'être dans le répertoire du projet symfony-for-sale
  2. Constatez que le dépôt Git local a été initialisé par l'outil « symfony » en ligne de commande (« Symfony CLI »)
    git log

Dépôt distant

Passons maintenant à la configuration du dépôt distant.

Travail à réaliser
  1. Créez un nouveau projet « symfony-for-sale » sur GitLab (pensez à décocher la case « Initialize repository with a README »)
  2. Associez le dépôt local et le dépôt distant
  3. Poussez votre branche locale

GitHub Copilot

Une partie du code que vous produisez est prévisible, au moins en partie. C'est pourquoi GitHub a développé un outil d'assistance à la saisie de code, GitHub Copilot, qui s'appuie sur l'intelligence artificielle et l'apprentissage automatique pour vous aider à écrire du code. Cet outil est utilisé en entreprise pour augmenter la productivité des développeurs. Il nous semble donc intéressant de vous le présenter, tout en vous sensibilisant à son utilisation éclairée et raisonnée.

Information

Certaines informations sont nécessaires au bon déroulement de votre inscription au « GitHub Student Developer Pack ». Lisez l'intégralité des consignes fournies dans le sujet avant de vous lancer dans la création de votre compte GitHub et de vous inscrire au « GitHub Student Developer Pack ».

Travail à réaliser
  1. Si vous n'en possédez pas déjà un, créez un compte sur GitHub, de préférence avec votre mail universitaire
  2. Inscrivez-vous au « GitHub Student Developer Pack » : https://education.github.com/pack
  3. Si votre mail universitaire n'est pas connu de GitHub, vous devez le renseigner, car il est nécessaire pour être reconnu comme étudiant Mail universitaire reconnu
  4. Fournissez une photo de votre carte d'étudiant ou de votre certificat de scolarité
    Information

    Vous devez impérativement fournir une photo de votre carte d'étudiant ou de votre certificat de scolarité. Vous pouvez utiliser votre smartphone pour obtenir un fichier image au format JPEG. Les captures d'écran du certificat de scolarité seront rejetées.

  5. Constatez l'état de votre demande qui devrait être approuvée : Demande approuvée
  6. Patientez quelques jours avant de profiter de votre accès à GitHub Copilot !
  7. Après 3 ou 4 jours d'attente, vos avantages sont disponibles : Avantages disponibles
  8. Accédez à vos paramètres de compte concernant GitHub Copilot
  9. Activez GitHub Copilot en cliquant sur le bouton « Start free trial » : Paramètres de compte GitHub Copilot
  10. Vous devriez voir apparaître un message donnant accès à la version gratuite de GitHub Copilot : Accès à la version gratuite de GitHub Copilot
  11. Cliquez sur le bouton « Get access to GitHub Copilot »
  12. Choisissez ensuite vos préférences de GitHub Copilot : Préférences de GitHub Copilot

Création du projet dans PhpStorm

La gestion d'une application Symfony nécessitant de modifier de nombreux fichiers dans une arborescence complexe, vous utiliserez PhpStorm pour l'édition de texte. Des greffons apportent le support de Symfony, Composer, des fichiers YAML, Twig, … Certains sont préinstallés dans la configuration générale.

Vous allez créer un nouveau projet PHP à partir de PhpStorm pour gérer votre application Web.

Remarque importante

PhpStorm est un outil puissant parmi d'autres développés par JetBrains. Leur utilisation dans le cadre professionnel impose de s'acquitter d'une licence annuelle. Cependant, dans la cadre universitaire, vous pouvez disposer d'une licence gratuite pour les étudiants. Ceci vous permet d'utiliser les outils sur votre ordinateur personnel, tant que vous êtes étudiant. L'utilisation de PhpStorm au sein du département ne nécessite pas de démarche particulière puisque nous avons un serveur de licences JetBrains.

Je vous invite néanmoins à demander une licence et à travailler sur votre ordinateur personnel en dehors des séances de TP.

Travail à réaliser
  1. Dans votre terminal et dans le répertoire du projet, lancez la commande
    phpstorm . &
    Remarque importante

    La création du projet PhpStorm induit la création du répertoire « .idea » dans le répertoire de votre projet Symfony. En temps normal, en particulier lors d'un développement collaboratif comme la SAÉ de S3, il est impensable d'inclure le répertoire « .idea » au dépôt Git. En effet, chaque modification de la configuration de PhpStorm faite par l'un des développeurs va engendrer la modification de la configuration de tous les autres développeurs une fois que la modification se retrouvera dans la branche principale.

    Néanmoins, à cause de la configuration particulière au sein du département informatique qui vous oblige à travailler dans le répertoire local « /working/votre_login » sur des ordinateurs différents, et parce que vous êtes l'unique développeur de votre projet, il est légitime d'inclure le répertoire de configuration de PhpStorm, « .idea », afin que vous n'ayez pas à reconfigurer votre environnement lorsque vous clonerez votre dépôt en début de séance.

Qualité de code et automatisation

Consignes de codage et de nommage

Le code de l'écosystème Symfony est intégralement en anglais. Tous les composants que vous allez utiliser possèdent donc des noms anglais. Afin de maintenir une bonne lisibilité de votre code, vous allez respecter les règles de nommage en langue anglaise pour les noms de classes, de propriétés, de méthodes, de variables, de fonctions, de fichiers, … Ceci vaudra également pour les commentaires ainsi que les messages de commit. Les instructions et informations notables consignées dans le fichier « README.md » pourront être en français. L'interface utilisateur de l'application sera également en français.

Documentation du projet

Tout projet de développement doit fournir les clés pour l'installation, la configuration et le démarrage par un nouveau développeur. Cet effort de documentation peut également s'avérer bénéfique pour le créateur du projet qui peut oublier certains points essentiels de configuration au fil du temps. Ainsi, vous allez rédiger le mode d'emploi de votre projet dans un fichier « REAMDE.md » et le tenir à jour au fil du développement.

Travail à réaliser
  1. Créez le fichier « README.md » à l'aide de PhpStorm (qui vous propose de l'ajouter à l'index Git)
  2. Complétez les informations suivantes :
    1. un titre de niveau 1 contenant le titre explicite du projet
    2. un titre de niveau 2 « Auteur(s) » vous permettant de préciser votre nom et votre prénom (ainsi que ceux de votre binôme, le cas échéant)
    3. un titre de niveau 2 « Installation / Configuration » précisant les points essentiels permettant d'installer, de configurer et de lancer le projet
  3. Validez votre travail dans Git

Style de code avec PHP CS Fixer

Travail à réaliser
  1. Installez PHP CS Fixer sans oublier l'option « --dev »
  2. Configurez PHP CS Fixer dans PhpStorm

Mise en place de scripts Composer

Les dépendances de votre projet Symfony sont gérées par Composer. Profitons-en pour mettre en place quelques scripts Composer qui faciliteront la gestion quotidienne du projet.

Travail à réaliser
  1. Ajoutez un script « start » qui lance le serveur web de test (« symfony serve ») sans restriction de durée d'exécution
  2. Ajoutez un script « test:cs » qui lance la commande de vérification du code par PHP CS Fixer
  3. Ajoutez un script « fix:cs » qui lance la commande de correction du code par PHP CS Fixer
  4. Ajoutez un script « test:twig » qui lance la commande de la console Symfony permettant la vérification des fichiers Twig contenus dans le répertoire « templates »
  5. Consultez l'aide de la console Symfony pour l'espace de noms « lint » :
    php bin/console lint
  6. Ajoutez un script « test:yaml » qui lance la commande de la console Symfony permettant la vérification des fichiers YAML contenus dans le répertoire « config »
  7. Documentez vos scripts dans le fichier « composer.json »
  8. Documentez vos scripts dans le fichier « README.md »

Automatisation des contrôles de qualité de code avec GrumPHP

Travail à réaliser
  1. Installez GrumPHP sans oublier l'option « --dev »
  2. Configurez GrumPHP :
    1. désactivez le « fixer » intégré
    2. ajoutez la tâche « composer »
    3. ajoutez la tâche « git_blacklist » et interdisez les expressions « dump( », « var_dump( », « print_r( », « die( », « exit( » et « exit; »
    4. lisez la spécification « Commits Conventionnels »
    5. ajoutez la tâche « git_commit_message » avec les options suivantes :
      • le sujet du commit ne commence pas forcément par une majuscule
      • les types de commit se limitent à « build », « ci », « chore », « docs », « feat », « fix », « perf », « refactor », « revert », « style » et « test »
      • les portées des commits (« scopes ») sont libres
    6. ajoutez la tâche « composer_script » pour déclencher le script Composer « test:yaml »
    7. ajoutez la tâche « composer_script » pour déclencher le script Composer « test:twig »
      Information

      Vous ne pouvez pas ajouter directement deux tâches GrumPHP portant le même nom. Vous devez donc ajouter une tâche « composer_script » pour chaque script Composer que vous souhaitez exécuter selon la méthode définie dans la documentation.

    8. ajoutez la tâche « phpcsfixer »
      Information

      N'oubliez pas l'option « config » de cette tâche.

    Remarque importante

    Selon la façon dont votre environnement Linux est configuré, et si vous utilisez Git à travers PhpStorm, il se peut que vous rencontriez l'erreur suivante lors des tâches GrumPHP :

    The executable for "composer" could not be found.

    Ceci est lié aux variables d'environnement accessibles par PhpStorm, en particulier « $PATH », selon que vous le lancez par le menu de votre gestionnaire de fenêtres ou en ligne de commande. Pour résoudre ce problème, référez-vous au point « Exécution de Composer à travers PhpStorm » du guide « Installation et configuration de PhpStorm ».

Installation / configuration des greffons PhpStorm

Vous allez maintenant installer les greffons nécessaires à la gestion de votre projet Symfony.

Travail à réaliser
  1. Installez les greffons suivants :
  2. Activez le support de Symfony pour le projet
  3. Configurez le greffon GitHub Copilot dont l'icône doit apparaître dans la barre d'état en bas de la fenêtre de PhpStorm

Conteneurisation des services de base de données

Les projets Symfony générés avec l'outil ligne de commande « symfony » contiennent un fichier « docker-compose.yml » et un fichier « docker-compose.override.yml ». Les images utilisées par défaut sont celles de PostgreSQL dans sa variante alpine et de MailCatcher.

Nous allons dans un premier temps nous concentrer sur la base de données pour changer l'image PostgreSQL utilisée et ajouter une image dérivée de Adminer permettant la connexion automatique à l'interface.

Travail à réaliser
  1. Modifiez le fichier « docker-compose.yml » pour utiliser l'image PostgreSQL classique en remplacement de celle basée sur alpine
  2. Modifiez le fichier « docker-compose.override.yml » pour :
    • exposer le port du service de base de données
    • ajouter l'image « michalhosna/adminer-docker » en tant que service
    • configurer les variables d'environnement du service « adminer » pour que l'interface soit disponible sur le port « 8080 » et que la connexion à la base de données soit automatique
      Information

      Ne touchez ni à configuration du conteneur PostgreSQL, ni au « DATABASE_URL » par défaut du fichier « .env ». Modifiez uniquement les variables d'environnement du service « adminer » pour les faire correspondre au reste de la configuration.

Les annonces dans l'application

La base de l'application s'appuie sur les annonces d'objets à vendre.

Information

Une attention très limitée sera portée à l'aspect visuel des annonces. Vous mettrez pour l'instant l'accent sur la structuration HTML des informations présentées, puis vous améliorerez la présentation de l'application dans une prochaine partie.

Création d'une entité « Advertisement »

Afin de respecter le codage en langue anglaise, l'entité correspondant à une annonce sera nommée « Advertisement ».

Travail à réaliser
  1. Utilisez la commande du « MakerBundle » Symfony pour générer l'entité « Advertisement » avec les propriétés suivantes :
    • « title » de type string(100), non null
    • « description » de type text, non null
    • « price » de type int, non null
    • « createdAt » de type DateTimeImmutable, non null
    • « updatedAt » de type DateTimeImmutable
    • « location » de type string(100), non null
  2. Créez une migration

Automatisation des dates de création et de mise à jour

Un certain nombre de tâches de gestion de données se retrouvent dans de nombreux projets. C'est le cas des champs de date de création ou de mise à jour. Aussi, « StofDoctrineExtensionsBundle », une extension de Doctrine, propose un ensemble de fonctionnalités qui peuvent être activées pour automatiser ces tâches ainsi que d'autres dans le même esprit. La mécanique repose sur l'utilisation d'attributs PHP 8 associés aux propriétés des entités.

Travail à réaliser
  1. Installez l'extension « StofDoctrineExtensionsBundle »
  2. Activez l'extension « Timestampable »
  3. Utilisez les attributs PHP 8 pour automatiser les dates de création et de mise à jour :
    • La date de création doit être automatiquement renseignée lors de la création d'une annonce
    • La date de mise à jour doit être automatiquement renseignée lors de la modification des propriétés « title », « description », « price » ou « location » d'une annonce

Génération de données factices

Afin de pouvoir tester votre application, vous allez proposer des données factices correspondant aux annonces. Pour cela, vous utiliserez le « DoctrineFixturesBundle » ainsi que le paquet « Foundry ».

Travail à réaliser
  1. Installez le « DoctrineFixturesBundle »
  2. Installez le paquet « Foundry »
  3. Créez un modèle d'annonces avec « Foundry »
  4. Créez une classe de données factices « AdvertisementFixtures »
  5. Créez 500 annonces aléatoires

Création de la base de données

La création de la base de données en mode développement est une opération classique qui doit être facilement accessible. Vous devez disposer d'un script Composer.

Travail à réaliser
  1. Ajoutez un script Composer « db » permettant de :
    • forcer la suppression de la base de données, si elle existe
    • créer la base de données
    • appliquer les migrations
    • charger les données factices
  2. Donnez une description de votre script dans le fichier « composer.json »
  3. Documentez votre script dans le fichier « README.md »
  4. Utilisez le service « Adminer » pour vérifier que les données factices ont bien été insérées dans la base de données

Visualisation des annonces

Les annonces étant désormais créées en base de données, il est temps de les afficher. Dans un premier temps, vous proposerez une liste des annonces. Dans un second temps, vous proposerez une page de détail pour chaque annonce.

Travail à réaliser
  1. Utilisez la commande du « MakerBundle » Symfony pour générer un contrôleur « AdvertisementController »
  2. Proposez la liste des annonces triées par ordre inverse de création dans l'action « index() »
    • Ajoutez une méthode « findAllOrderedByDate() » dans le dépôt « AdvertisementRepository »
    • Utilisez la précédente méthode pour obtenir la liste des annonces et la transmettre à la vue qui se chargera de construire une liste HTML
  3. Proposez le détail d'une annonce dans l'action « show() »
  4. Ajoutez un lien vers la page de détail de chaque annonce dans la liste des annonces

Formulaire d'édition / création d'une annonce

Le même formulaire doit permettre d'effectuer la saisie d'une nouvelle annonce ou l'édition d'une annonce existante.

Travail à réaliser
  1. Utilisez la commande du « MakerBundle » Symfony pour générer une classe de formulaire « AdvertisementType » pour l'entité « Advertisement »
  2. Supprimez les champs de saisie de date de création et de modification qui sont gérés automatiquement
  3. Précisez les types de champs de saisie restants
  4. Ajoutez des contraintes de validation sur l'entité « Advertisement » :
    • « title » est non vide et contient entre 10 et 100 caractères
    • « description » est non vide et contient entre 20 et 1000 caractères
    • « price » est positif ou nul (« ≥ 0 » mais pas « null »)
    • « location » est non vide et contient entre 2 et 100 caractères
  5. A l'aide de l'option « attr » des champs de la classe de formulaire, précisez les attributs HTML améliorant l'ergonomie de la saisie :
    • « title » contient entre 10 et 100 caractères
    • « description » contient entre 20 et 1000 caractères
    • « price » est positif ou nul
    • « location » contient entre 2 et 100 caractères

Utilisation du formulaire d'édition / création d'une annonce

Le formulaire de saisie d'une nouvelle annonce ou d'édition d'une annonce existante étant créé, il convient à présent de l'utiliser.

Travail à réaliser
  1. Créez un modèle « advertisement/_form.html.twig » qui affiche le formulaire de saisie d'une annonce et le bouton de soumission, selon les bonnes pratiques concernant les formulaires
  2. Ajoutez une action « new() » dans le contrôleur « AdvertisementController », qui utilise « advertisement/_form.html.twig » pour afficher le formulaire de saisie d'une nouvelle annonce
  3. Ajoutez une action « edit() » dans le contrôleur « AdvertisementController », qui utilise « advertisement/_form.html.twig » pour afficher le formulaire d'édition d'une annonce existante
  4. Rendez les actions fonctionnelles afin qu'elles puissent créer une nouvelle annonce ou enregistrer les modifications apportées à une annonce existante

Catégories d'annonces

Les annonces seront plus faciles à trouver si elles sont rangées par catégorie.

Information

Une attention très limitée sera portée à l'aspect visuel des annonces. Vous mettrez pour l'instant l'accent sur la structuration HTML des informations présentées, puis vous améliorerez la présentation de l'application dans une prochaine partie.

Création de l'entité « Category »

Vous allez tout d'abord générer l'entité « Category ».

Travail à réaliser
  1. Utilisez la commande du « MakerBundle » Symfony pour générer l'entité « Category » avec les propriétés suivantes :
    • « name » de type string(50), non null
  2. Vérifiez le code généré

Relations entre les entités « Advertisement » et « Category »

L'entité « Advertisement » doit être liée à l'entité « Category » afin de pouvoir associer une annonce à une catégorie. Une annonce appartient forcément à une catégorie et une catégorie peut contenir plusieurs annonces.

Travail à réaliser
  1. Utilisez la commande du « MakerBundle » Symfony pour modifier l'entité « Advertisement » :
    • ajoutez une relation « category » obligatoire vers l'entité « Category »
    • ajoutez la relation inverse « advertisements » dans l'entité « Category »
  2. Créez une migration

Données factices

Afin d'apporter un peu de lisibilité au jeu de données d'essai, vous allez construire la collection de catégories à partir d'un fichier contenant une liste réelle.

Travail à réaliser
  1. Récupérez le fichier « category.txt » et placez-le dans le répertoire « data » du projet
  2. Générez une classe de « fixtures » pour l'entité « Category »
  3. Générez une « Factory » pour l'entité « Category »
  4. Importez les données du fichier dans la base de données en utilisant la « Factory » et la classe de « fixtures »
  5. Modifiez la « Factory » de l'entité « Advertisement » pour qu'elle associe chaque nouvelle annonce à une nouvelle catégorie
  6. Vérifiez le bon chargement des données avec le service Docker Adminer

Données factices, introduction de « Story »

Les jeux de données d'essai peuvent devenir complexes à construire et sont alors difficilement réutilisables. Foundry propose le concept de « Story » qui permet de définir un ensemble de données cohérentes et réutilisables.

Travail à réaliser
  1. Lisez le chapitre de la documentation de Foundry consacré aux « Stories »
  2. Générez une « Story » pour l'entité « Category »
  3. Importez les données du fichier « category.txt » dans un tableau PHP
  4. Extrayez la première catégorie de votre import pour créer un « State » que vous nommerez « category_without_advertisement »
  5. Utilisez le reste du tableau de noms de catégories pour construire un « Pool » que vous nommerez « categories »
  6. Modifiez la classe de « fixtures » associée à l'entité « Category » afin qu'elle utilise la « Story » pour générer toutes les catégories
  7. Modifiez la classe de « fixtures » associée à l'entité « Advertisement » afin qu'elle utilise la « Story » pour générer des annonces dont la catégorie sera tirée au hasard dans le « Pool » « categories »
  8. Améliorez les performances de la génération du jeu d'essai en utilisant la méthode « Factory::delayFlush() » pour toutes les « fixtures »

Liste des annonces par catégorie

Les utilisateurs doivent pouvoir visualiser les annonces par catégorie. Vous allez donc proposer la liste des catégories ainsi que la liste des annonces pour chaque catégorie.

Travail à réaliser
  1. Utilisez la commande du « MakerBundle » Symfony pour générer un contrôleur « CategoryController »
  2. Utilisez la route par défaut du contrôleur pour afficher la liste des catégories triées par ordre alphabétique
  3. Définissez une route « show » pour afficher la liste des annonces d'une catégorie
    Information

    Puisque vous avez déjà proposé une vue d'une liste d'annonces, il serait judicieux de mettre le code en commun dans un fragment de « template » qui sera nommé selon les bonnes pratiques. Ce dernier sera inclus ou intégré selon votre choix, et comprendra le titre « h1 » qui pourra être personnalisé.

  4. Ajoutez un lien sur chaque catégorie de la liste pour afficher les annonces de la catégorie

Formulaire de saisie d'une annonce

L'annonce possède une relation vers la catégorie. Il convient donc de proposer un champ de saisie permettant de sélectionner la catégorie de l'annonce.

Travail à réaliser
  1. Lisez la documentation de Symfony sur les champs de formulaires associés aux entités
  2. Ajoutez un champ de saisie de catégorie dans le formulaire de saisie d'une annonce
  3. Affichez le nouveau champ de saisie dans la vue

Affichage de la catégorie d'une annonce

L'ergonomie de l'interface utilisateur sera améliorée si chaque annonce affiche la catégorie à laquelle elle est associée, aussi bien dans la vue détaillée de l'annonce que dans les listes d'annonces.

Travail à réaliser
  1. Ajoutez le nom de la catégorie dans la vue détaillée d'une annonce
  2. Ajoutez le nom de la catégorie dans la liste des annonces
  3. Ajoutez un lien sur la catégorie pour afficher les annonces de la catégorie

Considérations sur les performances

L'ORM Doctrine facilite la tâche du développeur en proposant une interface PHP pour la gestion des données. Cependant, cette interface a un coût en termes de performance et il est nécessaire d'analyser les requêtes pour voir s'il est possible d'optimiser les performances de l'application. Dans le cas présent, la relation entre les annonces et les catégories implique d'effectuer, sur une collection d'annonces, une nouvelle requête en base de données pour récupérer la catégorie de chacune des annonces.

Catégorie de l'annonce dans la liste des annonces

Vous allez analyser puis optimiser le requêtage des annonces au sein des listes.

Travail à réaliser
  1. Affichez la liste des annonces dans votre navigateur
  2. Observez les requêtes effectuées dans la partie Doctrine du « Profiler » accessible par la barre de débogage (« Web Debug Toolbar »)
  3. Expliquez pourquoi une requête est effectuée pour chaque catégorie
  4. Proposez une solution pour optimiser le requêtage au niveau du « Repository »
    Information

    Le nom de la méthode du « Repository » doit refléter votre démarche d'optimisation. Vous devez donc le compléter avec un suffixe qui indique les entités supplémentaires sélectionnées.

  5. Vérifiez que votre solution diminue le nombre de requêtes effectuées

Taille de la liste des annonces

Du point de vue des performances, mais également de l'expérience utilisateur, les listes de grande taille doivent être paginées. C'est une problématique courante, dont la solution peut être trouvée dans un « bundle ». Concrètement, d'un point de vue base de données, il s'agit de limiter le nombre de résultats retournés par une requête. Cependant, il est nécessaire de connaître le nombre total de résultats pour pouvoir proposer une pagination. C'est pourquoi, il est nécessaire d'effectuer deux requêtes : une pour récupérer les résultats et une pour récupérer le nombre total de résultats.

Travail à réaliser
  1. Lisez les explications de la limitation de sélection de résultats dans la documentation « PHP MySQL Limit Data Selections » de W3Schools
  2. Parcourez rapidement la documentation de « KnpPaginatorBundle »
  3. Installez « KnpPaginatorBundle » dans votre projet
  4. Utilisez « KnpPaginatorBundle » pour paginer la liste de toutes les annonces
  5. Observez la probable absence de répercussions sur les requêtes effectuées (absence de « LIMIT » dans les requêtes)
    Information

    « KnpPaginatorBundle » permet de paginer de nombreuses formes de collections. Si vous appliquez la pagination à la liste des annonces sous forme de résultat de requête, vous paginez un tableau de toutes les annonces. « KnpPaginatorBundle » doit avoir accès à la requête pour pouvoir la modifier. C'est pourquoi, il est nécessaire de modifier votre approche en paginant une requête et non une collection issue d'un résultat de requête.

  6. Modifiez le code de la méthode pour qu'elle retourne la requête et non son résultat
  7. Modifiez le nom de la méthode « findAllOrderedByDate…() » du « Repository » en « queryAllOrderedByDate…() » pour refléter le changement d'approche
  8. Vérifiez que la pagination fonctionne correctement et que les requêtes sont optimisées (présence de « LIMIT »)
  9. Procédez de la même manière pour la liste des annonces d'une catégorie

Catégorie dans les détails de l'annonce

La récupération de l'annonce lors de son affichage détaillé peut bénéficier d'une requête optimisée. En effet, la relation entre l'annonce et la catégorie est connue lors de la récupération de l'annonce. Il est donc possible de récupérer la catégorie en même temps que l'annonce, en utilisant une jointure.

Travail à réaliser
  1. Lisez la documentation « Automatically Fetching Objects (EntityValueResolver) », en particulier le point sur l'utilisation d'une expression avec l'attribut PHP « MapEntity »
  2. Définissez une méthode dans le « Repository » pour répondre à la problématique
  3. Associez la méthode du « Repository » à l'action « show() » à l'aide de l'attribut PHP « MapEntity »
  4. Vérifiez les requêtes effectuées lors de l'affichage détaillé d'une annonce
  5. Associez la méthode du « Repository » à l'action « edit() » à l'aide de l'attribut PHP « MapEntity »
  6. Vérifiez les requêtes effectuées lors de l'édition d'une annonce

Interface utilisateur

Le début de votre projet est (volontairement) pauvre en mise en forme. Vous allez maintenant l'améliorer en y incorporant Bootstrap. La nouveauté par rapport à l'an passé va résider dans une intégration locale de Bootstrap, afin de ne pas dépendre d'un CDN. Ce sera également l'occasion de découvrir le bundle « Webpack Encore » qui va faciliter l'intégration de Webpack dans votre application. Cet outil va vous permettre de gérer les divers « assets » JavaScript et CSS de votre projet. Vous aurez également le loisir de découvrir Sass qui va vous permettre de gérer les feuilles de style de manière plus efficace, structurée et flexible. Vous pourrez notamment personnaliser Bootstrap en utilisant Sass.

Installation de Webpack Encore

Vous allez commencer par installer Webpack Encore.

Travail à réaliser
  1. Installez le bundle « Webpack Encore »
  2. Installez les modules Node.js nécessaires
  3. Lancez la construction des « assets »
  4. Observez l'ensemble des fichiers créés dans le projet
  5. Complétez la documentation de votre projet

Installation de Sass

Le langage de script Sass doit être interprété par Webpack afin de générer les feuilles de style CSS de votre projet. npm propose un module Node.js qui permet d'interpréter Sass : sass.

Travail à réaliser
  1. Configurez Sass dans votre projet
  2. Lancez la construction des « assets »
  3. Complétez la documentation de votre projet

Installation de Bootstrap

L'avantage des CDN est de faciliter la mise en cache des bibliothèques JavaScript et CSS par les navigateurs. Cependant, il est préférable de ne pas dépendre d'un CDN pour des raisons de performance et de sécurité. L'intérêt majeur de ne pas utiliser de CDN pour Bootstrap réside dans les possibilités de personnalisation de la copie locale. Vous allez donc installer « Bootstrap » localement.

Travail à réaliser
  1. Installez Bootstrap
    Information

    jQuery n'est pas nécessaire !

  2. Importez les fichiers JavaScript et CSS de Bootstrap dans votre projet

Utilisation de Bootstrap dans l'interface utilisateur

Boostrap propose des composants HTML et des classes CSS qui facilitent la mise en forme des contenus. Vous allez donc les utiliser pour améliorer l'interface utilisateur de votre projet.

Travail à réaliser
  1. Structurez le « template » de base de votre projet en utilisant les classes de « Bootstrap » :
  2. Donnez du style aux listes d'annonces
  3. Transformez les étiquettes de catégorie en badges
  4. Appliquez le thème Bootstrap aux formulaires
  5. Utilisez le « template » Bootstrap pour la pagination

Personnalisation du thème de couleurs de Bootstrap

Les couleurs et l'aspect de Bootstrap sont trop classiques et trop reconnaissables. Il est donc nécessaire de personnaliser le thème afin de ne pas avoir un site qui ressemble à tous les autres.

Travail à réaliser
  1. Choisissez un thème Boostrap qui vous convient sur https://huemint.com/bootstrap-plus/
  2. Appliquez le thème à votre projet
    Information

    Attention à ne pas utiliser directement le code fourni par https://huemint.com/bootstrap-plus/, vous devez respecter les consignes de la documentation de Bootstrap pour personnaliser le thème.

    Mettez en commentaires les couleurs « accent 1 », « accent 2 » et « accent 3 » qui ne font par partie du thème, mais que nous utiliserons plus tard.

Ajout de couleurs de thème dans Bootstrap

Bootstrap est conçu autour d'un thème de couleurs symboliques (« primary », « secondary », « success », …) qui sont accessibles à travers de multiples sélecteurs CSS. Par exemple, la couleur « primary » se retrouve dans les sélecteurs « .btn-primary », « .bg-primary », « .text-primary », … Il est possible d'ajouter des nouvelles couleurs symboliques afin d'augmenter la palette de couleurs disponibles. Il ne faut toutefois pas en abuser, car chaque couleur symbolique ajoutée augmente la taille et la complexité des feuilles de style générées.

Travail à réaliser
  1. Lisez la documentation de l'ajout de couleurs de thème Bootstrap
  2. Ajoutez une nouvelle couleur symbolique « accent1 » dans le thème de Bootstrap
    Information

    Utilisez une des trois couleurs supplémentaires générées par https://huemint.com/bootstrap-plus/.

  3. Utilisez un sélecteur associé à la nouvelle couleur symbolique pour modifier la couleur du texte de l'élément « navbar-brand » de la barre de navigation

Personnalisation d'éléments de mise en forme de Bootstrap

Vous utilisez les classes CSS de Bootstrap pour mettre en forme votre application. Vous pouvez également les utiliser pour les modifier.

Travail à réaliser
  1. Ajoutez un sélecteur CSS pour modifier les éléments de listes afin d'alterner la couleur de fond des lignes
    Information

    Vous pouvez utiliser la variante subtile (« bg-*-subtle ») d'une des couleurs du thème.

  2. Changez la police des badges pour qu'elle soit plus grosse (« 1rem ») et pas en caractères gras

Optimisation de Bootstrap

La copie locale de Bootstrap est trop volumineuse. Vous allez donc optimiser le chargement des fichiers JavaScript et Sass en n'incluant que les éléments réellement utilisés.

Travail à réaliser
  1. Lisez la partie optimisation de la documentation de la personnalisation de Bootstrap
  2. Optimisez les imports de fichiers JavaScript en n'incluant que les éléments réellement utilisés
  3. Optimisez les imports de fichiers Sass en n'incluant que les éléments réellement utilisés
    Information

    Il n'est pas toujours simple de trouver les fichiers nécessaires, mais il est primordial de respecter l'ordre d'inclusion défini dans le fichier « scss/bootstrap.scss » de Bootstrap. Partez du contenu de ce fichier et commentez les imports inutiles.

Bootstrap Icons

Les bibliothèques d'icônes sont largement utilisées dans les applications Web. Vous allez intégrer celle de Bootstrap.

Travail à réaliser
  1. Lisez la documentation des Bootstrap Icons
  2. Installez les Bootstrap Icons dans votre projet avec npm
  3. Ajoutez une icône Bootstrap à la barre de navigation, au niveau de l'élément « navbar-brand »
  4. Cette icône étant purement décorative, masquez-la aux outils d'accessibilité

Gestion du mode clair/sombre dans Bootstrap

Bootstrap propose des modes de couleurs depuis la version 5.3. L'utilisation classique est de proposer un mode clair et un mode sombre. La fonctionnalité peut être étendue à tout thème de couleur.

Travail à réaliser
  1. Lisez la documentation de Bootstrap sur les modes de couleurs
  2. Activez le mode sombre dans votre projet et vérifiez que l'affichage est correct
  3. Récupérez le code HTML de la fonctionnalité de basculement du mode clair/sombre du site de Bootstrap (en haut à droite de la page)
  4. Étudiez le code JavaScript de basculement du mode clair/sombre proposé dans la documentation de Bootstrap
  5. Insérez le précédent code Javascript dans votre projet, dans un fichier « assets/js/color-mode.js » qui sera importé dans « assets/app.js »
  6. Modifiez le code HTML de la fonctionnalité de basculement du mode clair/sombre du site de Bootstrap pour qu'il fonctionne avec votre projet :
    • Façon dont les icônes sont incluses
      Information

      Dans son propre site, Bootstrap utilise la méthode des sprites pour insérer les icônes. Si vous observez les attributs « href » des balises « use », vous pouvez constater que ce sont des liens relatifs au document HTML et non des liens désignant le fichier SVG contenant toutes les icônes. En étudiant le code HTML du site de Bootstrap, vous pouvez constater qu'une partie du fichier SVG est inclus dans le début du document HTML et est masqué. Afin de vous épargner l'automatisation de ce processus, vous pouvez faire référence au fichier SVG complet. Pour cela, vous devez modifier les attributs « href » des balises « use » en remplaçant le chemin relatif par un asset Twig ciblant le fichier « SVG ». Ce dernier n'étant pas disponible dans la sous-arborescence de « public », vous devez utiliser la méthode « copyFiles() » de Encore.

    • Taille des icônes
      Information

      Les icônes n'ont pas de taille définie dans la structure que vous venez de construire. Aussi, elles vont être affichées en grande taille. Explorez la feuille de style de Bootstrap pour trouver le sélecteur qu'ils utilisent pour définir la taille des icônes de la liste déroulante.

    • Couleur des icônes
      Information

      Inspectez une nouvelle fois la feuille de style de Bootstrap pour trouver le sélecteur et la propriété qu'ils utilisent pour définir la couleur des icônes.

    • Affichage de l'icône du mode actif dans la liste
      Information

      Une nouvelle fois, la réponse se trouve dans l'inspection du site de Bootstrap.

Mise en place de tests

Un projet informatique qui inclut des tests présente de nombreux avantages, directement liés à la détection précoce des problèmes :

  • amélioration de la qualité du code
  • non régression lors des évolutions
  • possibilité de développement itératif par intégration continue et déploiement continu
  • facilitation de la collaboration entre développeurs en contenant les effets de bord des modifications de chacun
  • fiabilisation des mises à jour des composants (serveur, langage de programmation, « frameworks », « toolkits », …)

Les projets Web demandent un effort particulier sur les tests fonctionnels ou d'acceptation. Symfony s'appuie sur PHPUnit pour proposer des tests unitaires, d'intégration et fonctionnels. Les tests fonctionnels (« Application Tests ») sont particulièrement adaptés aux projets Web, car ils permettent de tester l'application dans son ensemble, en simulant un navigateur qui va émettre une requête HTTP vers l'application pour provoquer une réponse qui sera récupérée, analysée et testée.

Sur le même principe, Codeception propose un module Symfony. Nous utiliserons Codeception pour la lisibilité apportée par la syntaxe de ses tests.

Installation/configuration de Codeception

Vous allez dans un premier temps installer Codeception et les modules nécessaires.

Travail à réaliser
  1. Installez Codeception, sans oublier que c'est une dépendance de développement
    Information

    Ajoutez l'option « --no-interaction » pour éviter d'avoir à répondre à des questions et surtout pour empêcher la génération des suites de tests par défaut.

  2. Installez les modules « Asserts », « Symfony » et « Doctrine2 » de Codeception
  3. Lisez le guide de démarrage rapide de Codeception
  4. Consultez les options d'amorçage (« bootstrap ») de Codeception
  5. Amorcez Codeception dans votre projet en précisant le namespace « App\Tests » et en ne générant pas les suites par défaut
    Information

    En ligne de commande, si vous souhaitez passer un paramètre contenant le caractère « \ », vous devez le doubler puisque le shell utilise ce même caractère « \ » pour échapper les caractères. Ainsi, le namespace « App\Tests » devient « App\\Tests » dans les paramètres de la ligne de commande.

  6. Chargez les paramètres de configuration dans la configuration générale de Codeception depuis les fichiers « .env » et « .env.test »
  7. Configurez l'environnement de test de Symfony pour qu'il utilise une base de données « SQLite » (trouvez le « DATABASE_URL » adéquat dans le fichier « .env »)
  8. Excluez les sources générées par Codeception (« tests/Support/_generated ») de l'analyse de code de PHP CS Fixer
  9. Créez un script Composer nommé « test:codeception » qui :
    • nettoie les fichiers générés par Codeception
    • détruit la base de données dans l'environnement de test
    • crée la base de données dans l'environnement de test
    • crée le schéma de la base de données dans l'environnement de test
    • exécute les tests de Codeception
  10. Créez un script Composer nommé « test » qui exécute, dans un ordre logique, tous les scripts de tests que vous avez créés jusqu'à présent
  11. Décrivez ces scripts dans « composer.json » et dans la documentation de votre projet

Suite de tests de « Application »

Votre première suite de tests regroupera tous les tests fonctionnels de l'application. Vous utiliserez ensuite des espaces de noms pour organiser les diverses catégories de tests.

Travail à réaliser
  1. Générez une suite de tests « Application »
  2. Activez et configurez les modules « Asserts », « Symfony » et « Doctrine2 » pour la suite de tests « Application »

« Cest » pour la liste des annonces

Vos premières réalisations ont concerné la liste des annonces. Ce sera donc la première fonctionnalité que vous testerez

Travail à réaliser
  1. Générez un « Cest » « Advertisement\List » qui regroupera les tests fonctionnels de la liste des annonces
  2. Effectuez les tests suivants, et corrigez votre code si nécessaire :
    1. La liste des annonces est correctement affichée si elle est vide
    2. Une liste de 15 annonces s'affiche correctement, pagination comprise

« Cest » pour le CRUD des annonces

Les annonces peuvent être créées, visualisées, modifiées et effacées (« CRUD »). Ces actions doivent naturellement être présentes et testées.

Travail à réaliser
  1. Générez un « Cest » « Advertisement\CRUD » qui regroupera les tests fonctionnels concernant les annonces
  2. Effectuez les tests suivants, et corrigez ou complétez votre code si nécessaire :
    1. La création d'une annonce à partir du formulaire de création fonctionne correctement et implique donc la présence des données en base de données
    2. L'affichage d'une annonce comporte bien ses caractéristiques
    3. La modification d'une annonce à partir du formulaire d'édition fonctionne correctement et implique donc la mise à jour des données en base de données
  3. Ajoutez la fonctionnalité de suppression d'une annonce :
    1. Ajoutez une route et une action pour supprimer une annonce
    2. Créez un « template » partiel pour le bouton de suppression d'une annonce
      Information

      Le « template » partiel sera inclus dans chaque vue où un bouton de suppression d'une annonce est nécessaire. Il est constitué d'un formulaire HTML contenant uniquement un bouton d'envoi. Afin d'éviter les suppressions intempestives, une confirmation réalisée en JavaScript sera associée à l'événement de soumission du formulaire.

    3. Protégez la suppression de l'annonce des attaques « CSRF » en ajoutant un jeton « CSRF » au formulaire de suppression
    4. Contrôlez la présence du jeton « CSRF » dans l'action associée à la suppression
  4. Dans la vue des détails d'une annonce, ajoutez un bouton d'édition ainsi que le formulaire de suppression
  5. Écrivez le test de la fonctionnalité de suppression d'une annonce

« Smoke testing », test de fumée

L'objectif des tests est de détecter précocement les problèmes de l'application. Le premier niveau de vérification consiste donc à vérifier que les URL de l'application répondent correctement. Chaque URL sera alors contrôlée a minima, en attendant que des tests détaillés soient écrits pour vérifier tous les aspects de la fonctionnalité implémentée. C'est ce que l'on appelle le « smoke testing ». Ces tests devront être exécutés avant tous les autres tests, ce qui va demander une utilisation particulière de Codeception faisant appel aux groupes.

Travail à réaliser
  1. Générez un « Cest » « Availability » qui regroupera les tests de fumée
  2. Créez un test « pageIsAvailable » qui vérifie que l'URL passée en paramètre est disponible
    Information

    Réfléchissez aux « fixtures » dans ce contexte.

  3. Créez un « dataProvider » qui fournit les URL à tester
  4. Associez le « dataProvider » au test « pageIsAvailable »
  5. Exécutez le test « pageIsAvailable » pour toutes les URL connues de l'application
  6. Lisez la documentation de Codeception sur les groupes
  7. Créez un groupe « available » qui regroupe tous les tests de fumée
  8. Lisez l'auto-documentation de la commande « run » de « vendor/bin/codecept »
  9. Modifiez le script Composer « test:codeception » pour qu'il exécute, dans l'ordre :
    1. les tests de fumée (ceux du groupe « available »)
    2. les tests de l'application (tous sauf ceux du groupe « available »)

Amélioration de l'interface et de l'expérience utilisateur

L'accueil de l'application affiche toujours la page de test de Symfony dans l'environnement de développement et une erreur HTTP 404 en production. Il faut donc effectuer une redirection vers la liste des annonces. Il manque également une fonctionnalité de rechercher des annonces.

Redirection depuis l'accueil de l'application

Vous allez simplement rediriger le visiteur vers la liste des annonces depuis l'accueil de l'application.

Travail à réaliser
  1. Générez un nouveau contrôleur « HomeController »
  2. Dans l'action « index » de ce contrôleur, redirigez le visiteur vers la liste des annonces
  3. Supprimez la vue inutile associée à l'action « index »
  4. Testez la fonctionnalité dans « Application\HomeCest »

Recherche des annonces par titre ou description

Il est temps d'ajouter une barre de recherche.

Travail à réaliser
  1. Ajoutez un formulaire comportant champ de recherche dans la barre de navigation
  2. Définissez la route correspondant à la liste des annonces comme action associée à la recherche
  3. Utilisez l'icône « search » de Bootstrap pour le bouton de recherche (la méthode « Icon Font » est la plus appropriée)
  4. Modifiez la methode de « AdvertisementRepository » afin qu'elle :
    • admette un paramètre correspondant à la chaîne de recherche
    • effectue une recherche insensible à la casse sur le titre et la description des annonces
      Information

      Inutile de complexifier la requête si la chaîne de recherche est vide.

    • retourne la requête des annonces correspondant à la recherche
  5. Récupérez la valeur de la chaîne de recherche dans l'action affichant la liste des annonces
  6. Donnez cette valeur à la méthode de recherche de « AdvertisementRepository »
  7. Reportez la valeur de la chaîne de recherche dans le champ de recherche du formulaire
  8. Ajustez le titre de la page (« title » et « h1 ») en fonction de la présence ou non d'une chaîne de recherche
  9. Testez la fonctionnalité dans « Application\Advertisement\SearchCest »

Création d'une extension Twig pour l'affichage des dates

Ceci est une question complémentaire, qui n'est pas obligatoire. Les détails sont donnés au sein de l'ensemble des questions complémentaires.

Sécurité

Jusqu'ici, l'application donne accès à toutes les fonctionnalités à toute personne y ayant accès. Naturellement, il faut restreindre l'accès à certaines fonctionnalités. Vous allez donc mettre en place un système d'authentification, des restrictions d'accès et du cloisonnement de données.

Authentification

Utilisateur

La première étape de sécurisation de l'application réside dans la création des utilisateurs.

Travail à réaliser
  1. Générez une entité utilisateur « User » avec les propriétés suivantes :
    • « email » de type string(180), unique, not null
    • « password »
    • « firstname » de type string(100), not null
    • « lastname » de type string(150), not null
  2. Créez, à l'aide d'une « Story », des utilisateurs factices dont le mot de passe sera « test » :
    • « admin@example.com », avec un rôle administrateur
    • « admin2@example.com », avec un rôle administrateur
    • « user@example.com », avec un rôle utilisateur
    • « user2@example.com », avec un rôle utilisateur
    • 10 utilisateurs aléatoires
    Information

    Afin d'identifier facilement les utilisateurs non aléatoires dans votre application, définissez leurs nom et prénom dans les données factices.

    Pensez à hacher les mots de passe des utilisateurs factices.

  3. Documentez les informations des utilisateurs factices

Connexion

Les utilisateurs doivent pouvoir se connecter pour s'identifier et se déconnecter.

Travail à réaliser
  1. Créez un formulaire de connexion en français et vérifiez manuellement son fonctionnement (succès/échec)
  2. Ajoutez un bouton de connexion et un bouton de déconnexion dans la barre de navigation
  3. Dans un nouveau « Cest » « Application\Security\AuthenticationCest », testez :
    • qu'un utilisateur peut se connecter
    • qu'un utilisateur connecté peut se déconnecter
    • qu'un test de connexion avec des identifiants erronés échoue
  4. Complétez les tests de fumée avec les URL de connexion et de déconnexion

Autorisations

Les utilisateurs maintenant identifiés peuvent être autorisés ou non à accéder à certaines fonctionnalités.

Travail à réaliser
  1. Affichez ou cachez les boutons de connexion/déconnexion de la barre de navigation en fonction de l'état de connexion de l'utilisateur
  2. Limitez l'accès à la page de création d'une annonce aux utilisateurs connectés
  3. Masquez le lien de création d'une annonce dans la barre de navigation aux utilisateurs non connectés
  4. Modifiez le test de création d'une annonce pour qu'il soit exécuté par un utilisateur connecté
  5. Ajoutez un test qui vérifie que la création d'une annonce par un utilisateur non connecté échoue

Cloisonnement de données

Les annonces, actuellement anonymes, vont être associées à un utilisateur et les autorisations vont être ajustées en conséquence.

Association d'une annonce à un utilisateur

Pour commencer, chaque annonce doit être associée à un utilisateur.

Travail à réaliser
  1. Ajoutez une propriété « owner » à l'entité « Advertisement » permettant d'associer un seul utilisateur à chaque annonce
  2. Affectez automatiquement l'utilisateur connecté à la création d'une annonce en utilisant « Blameable » de « StofDoctrineExtensionsBundle »
  3. Modifiez les « fixtures » pour que les 500 annonces soient créées par les 10 utilisateurs aléatoires de la « Story »
  4. Ajoutez 20 annonces pour l'utilisateur « user@example.com » de la « Story »
  5. Affichez le prénom de l'utilisateur propriétaire dans les détails de l'annonce sous forme d'un badge Bootstrap de couleur « accent1 »
  6. Testez qu'une annonce nouvellement créée est bien associée à l'utilisateur connecté
    Information

    L'utilisation de « Blameable » en conjonction avec Codeception demande la persistance du service « BlameListener » pour fonctionner correctement. Cet ajustement vous est fourni sous forme d'un « Helper » Codeception que vous devez activer dans la suite « Application ». Vous en profiterez pour ajouter le « Helper » déjà fourni au semestre 3, permettant de réinitialiser l'« EntityManager » avant chaque test pour éviter les échecs en cascade. Notez que ces deux « Helper » dépendent du module « Symfony » de Codeception.

Annonces d'un utilisateur

Lors de la recherche dans les annonces, il peut être intéressant de consulter les annonces d'un utilisateur, un cas particulier étant les annonces de l'utilisateur connecté. Il faut donc ajouter cette fonctionnalité.

Travail à réaliser
  1. Créez une action permettant d'afficher les annonces d'un utilisateur dans un contrôleur dédié
  2. Affichez les annonces de l'utilisateur paginées et triées par date de création décroissante, en réutilisant le fragment de « template » réalisé plus tôt
  3. Lorsque la liste d'annonces est celle de l'utilisateur connecté, le titre de la page doit être « Mes annonces »
  4. Une entrée de menu « Mes annonces » doit permettre d'accéder aux annonces de l'utilisateur connecté
  5. Transformez le prénom de l'utilisateur dans les détails d'une annonce en un lien permettant de consulter ses annonces
  6. Complétez les tests de fumée

Création d'un « Voter »

Symfony propose la notion de « Voter » pour gérer les autorisations. Ce concept permet de centraliser la logique des autorisations plutôt que d'effectuer des tests à divers endroits du code. Vous allez créer un « Voter » vérifiant les autorisations de modification et de suppression d'une annonce basées sur le contrôle du propriétaire de l'annonce.

Travail à réaliser
  1. Utilisez le « MakerBundle » pour générer un « Voter »
  2. Adaptez le « Voter » pour vérifier les autorisations de modification et de suppression d'une annonce par son propriétaire uniquement
  3. Utilisez le « Voter » pour cloisonner les actions de modification et de suppression d'une annonce
  4. Utilisez le « Voter » pour afficher les boutons de modification et de suppression d'une annonce uniquement au propriétaire de ladite annonce
  5. Ajustez les tests de modification et de suppression d'une annonce
  6. Ajoutez des tests permettant de contrôler qu'un utilisateur ne peut ni modifier ni supprimer une annonce qui ne lui appartient pas

Inscription d'un nouvel utilisateur dans l'application

Les visiteurs anonymes doivent pouvoir s'inscrire pour profiter des fonctionnalités de l'application. Il leur sera alors demandé de confirmer leur adresse mail avant de pouvoir utiliser les fonctionnalités nécessitant d'être connecté.

Formulaire d'inscription

Le « MakerBundle » permet de générer un formulaire d'inscription avec différentes options selon vos réponses aux questions posées. Vous allez donc générer un formulaire d'inscription et l'adapter à vos besoins.

Travail à réaliser
  1. Utilisez le « MakerBundle » pour générer un formulaire d'inscription avec les spécificités suivantes :
    • Envoyer un mail de confirmation d'inscription
    • Inclure l'identifiant de l'utilisateur dans le lien de confirmation contenu dans le mail
    • Authentifier l'utilisateur après son inscription
    Information

    Lisez bien le compte-rendu de la commande pour savoir comment finaliser le formulaire d'inscription.

  2. Supprimez le champ généré « You should agree to our terms. » du formulaire d'inscription
  3. Traduisez en français le formulaire d'inscription, le modèle de mail et les messages des contraintes
  4. Ajoutez un lien vers l'inscription dans la page de connexion
  5. Ajoutez les nom et prénom de l'utilisateur au formulaire d'inscription
    Information

    Respectez les bonnes pratiques en déléguant la responsabilité de présentation des « labels » à la vue.

  6. Ajoutez des contraintes de validation (taille maximale, type, non vide…) aux propriétés « email », « firstname » et « lastname » de l'entité « User »
  7. Améliorez l'ergonomie de la saisie des champs du formulaire d'inscription en imposant en HTML les mêmes contraintes que celles définies dans l'entité « User »
    Information

    Le mot de passe peut recevoir l'attribut HTML « autocomplete="new-password" » pour indiquer au navigateur qu'il s'agit d'un nouveau mot de passe et qu'il ne doit pas proposer la saisie semi-automatique.

  8. Complétez les tests de fumée

Configuration des services d'envoi et de consultation de mails

Le formulaire d'inscription que vous avez généré doit conduire à l'envoi d'un mail lorsque l'utilisateur valide sa saisie. Ceci va nécessiter la configuration de « Messenger » et de « Mailer » ainsi que la prise en main de MailCatcher pour consulter les mails envoyés durant les essais en phase de développement.

Travail à réaliser
  1. Exposez les ports de MailCatcher dans le « docker-compose.override.yaml » de l'application
  2. Déportez la définition du mail et du nom de l'émetteur dans un fichier « .env » et utilisez les variables d'environnement dans la configuration de « Mailer »
    Information

    Pensez à supprimer le « from() » dans le contrôleur d'inscription.

  3. Configurez le transport de mail par SMTP sur la boucle locale pour vous connecter à MailCatcher
  4. Modifiez le routage des mails pour qu'il soit synchrone, uniquement en environnement de développement
  5. Vérifiez que l'inscription d'un nouvel utilisateur conduit bien à la réception d'un mail dans MailCatcher
  6. Utilisez le lien contenu dans le mail de confirmation et vérifiez que l'utilisateur est bien validé (champ « is_verified » de la table)

Validation de l'adresse mail du nouvel l'utilisateur

Le mail envoyé à l'utilisateur contient un lien permettant de valider son inscription. Il se peut que l'utilisateur ne reçoive pas le mail ou qu'il le supprime par mégarde. Il faut donc prévoir une fonctionnalité permettant de renvoyer le mail de confirmation.

Tant que l'utilisateur n'a pas validé son adresse mail, il ne doit pas pouvoir utiliser les fonctionnalités nécessitant d'être connecté. Juste après l'inscription, l'utilisateur est automatiquement connecté à l'application. Il est donc identifié et sera systématiquement redirigé vers la page de demande de confirmation de son adresse mail. Ceci permet de le forcer à valider son adresse mail. Dans un cas réel, il serait préférable de laisser l'utilisateur accéder aux parties publiques de l'application tant qu'il n'a pas validé son inscription.

Travail à réaliser
  1. Créez une action « validateUserMail() » dans le contrôleur « Registration » permettant d'afficher à l'utilisateur connecté la demande de la confirmation de son adresse mail
  2. Définissez le code de ce nouveau contrôleur afin qu'il :
    • redirige l'utilisateur connecté vers la page d'accueil si son adresse mail est déjà validée
    • construise un formulaire de demande de l'envoi du mail de confirmation de l'adresse mail de l'utilisateur
    • redirige simplement l'utilisateur vers la page d'accueil si le formulaire est soumis (vous modifierez ce comportement plus tard)
    • effectue le rendu de la vue dans les autres cas
  3. Créez un « EventListener » « ForceValidateMailListener » qui intercepte toutes les requêtes faites sur votre application et qui redirige vers la page de demande de confirmation de l'adresse mail si l'utilisateur n'a pas validé son adresse mail
    Information

    Vérifiez que la requête interceptée est bien la requête principale de l'application et non une sous-requête.

    Ne redirigez pas les routes utiles à l'utilisateur dont l'adresse mail n'est pas validée, comme la page de confirmation de l'adresse mail ou la page de déconnexion.

    Vous aurez besoin de générer des URL à partir de noms de routes.

  4. Complétez les tests de fumée

Événement d'inscription d'un nouvel l'utilisateur

La gestion de l'envoi d'un mail de confirmation d'inscription et de validation de l'adresse mail d'un utilisateur sont des fonctionnalités qui peuvent être réutilisées dans d'autres contextes de l'application. Il est donc intéressant de les découpler du contrôleur de gestion des inscriptions. En effet, l'envoi d'un mail de confirmation d'inscription n'est qu'une conséquence souhaitée de l'inscription d'un nouvel utilisateur. Un nouvel utilisateur pourrait être inscrit dans d'autres circonstances et d'autres actions pourraient être déclenchées à cette occasion. Il est donc intéressant de créer un événement « UserRegistered » qui sera déclenché à l'inscription d'un nouvel utilisateur. Un « EventListener » personnalisé pourra alors intercepter les événements « UserRegistered » et envoyer un mail de demande de validation de l'adresse mail de l'utilisateur. Si l'utilisateur demande une nouvelle fois l'envoi du mail de confirmation, un événement « UserConfirmationEmailNotReceived » sera déclenché. Il sera alors également intercepté par le « EventListener » qui enverra un nouveau mail de confirmation d'inscription.

Travail à réaliser
  1. Consultez le support de cours de Floran Brutel et Nicolas Hart sur les événements et les écouteurs
  2. Créez la classe d'événement « UserRegistered »
  3. Créez la classe d'événement « UserConfirmationEmailNotReceived »
  4. Créez un « EventListener » « EmailVerifierListener » qui intercepte les événements « UserRegistered » et « UserConfirmationEmailNotReceived » pour envoyer un mail de demande de validation de l'adresse mail à l'utilisateur
    Information

    Le code d'envoi du mail sera recopié à l'identique depuis de contrôleur d'inscription.

  5. Déclenchez un événement « UserConfirmationEmailNotReceived » lors de l'appui sur le bouton de demande de renvoi du mail de confirmation
  6. Déclenchez un événement « UserRegistered » à la création d'un nouvel utilisateur lors de l'inscription (en remplacement de l'envoi du mail)

Gestion des messages « flash »

Les diverses actions de l'application doivent pouvoir informer l'utilisateur du résultat de l'opération. Il est donc nécessaire de mettre en place un système de messages « flash ». Certains messages sont déjà présents dans le code du contrôleur d'inscription, mais ils ne sont que partiellement fonctionnels. Vous allez donc les compléter et les afficher à l'utilisateur.

Travail à réaliser
  1. Modifiez le code du contrôleur d'inscription pour qu'il utilise uniquement les catégories de messages « flash » « success » et « error »
  2. Inspirez-vous du code de la vue « register.html.twig » pour afficher les messages « flash » « success » et « error » dans « base.html.twig », avec des styles appropriés
  3. Supprimez le code de gestion des messages flash de la vue « register.html.twig »
  4. Ajoutez un message « flash » « success » lors de la demande d'inscription et de renvoi du mail de confirmation

Sécurisation du mot de passe de l'utilisateur

Le mot de passe choisi par l'utilisateur lors de son inscription n'est pas confirmé par une double saisie. Ceci est une mauvaise pratique que vous devez corriger. De plus, la complexité du mot de passe choisi par l'utilisateur n'est pas évaluée. Vous allez donc ajouter une contrainte de complexité du mot de passe dans le formulaire d'inscription.

Travail à réaliser
  1. Modifiez le formulaire d'inscription pour qu'il demande la confirmation du mot de passe avec un champ « RepeatedType »
    Information

    Respectez les bonnes pratiques en déléguant la responsabilité de présentation des « labels » à la vue.

  2. Installez le bundle « RollerworksPasswordStrength Validator »
  3. Ajoutez dans le formulaire d'inscription une contrainte de complexité du mot de passe afin qu'il contienne :
    • au moins 10 caractères
    • au moins une lettre minuscule et une lettre majuscule
    • au moins un chiffre
    • au moins un caractère spécial

Tests d'inscription

La vérification de l'adresse mail de l'utilisateur et le choix de son mot de passe lors de l'inscription sont des étapes importantes de la sécurisation de votre application. Il est donc nécessaire de tester ces procédures.

Travail à réaliser
  1. Désactivez « Mailer » durant les tests pour éviter de générer des mails (« MAILER_DSN=null://null » dans « .env.test »)
  2. Utilisez les ancres et références de YAML pour effectuer la même configuration pour « Mailer » dans les environnements de développement et de test
    Information

    Vous pouvez vous inspirer du fichier « config/packages/zenstruck_foundry.yaml » pour la syntaxe.

  3. Créez un « Cest » « Registration\RegisterCest »
  4. Testez les points clés de l'inscription :
    • Saisie des données dans le formulaire d'inscription, réception du mail de confirmation et validation de l'adresse mail
      Information

      Pour tester si un mail est envoyé, vous devez désactiver le suivi des redirections comme indiqué dans la documentation.

      Vous devez utiliser le lien de vérification de l'adresse mail contenu dans le mail de confirmation. Pour cela, il faut extraire le contenu du mail. Vous pourrez ensuite extraire le lien de vérification de l'adresse mail à l'aide d'une expression régulière correspondant à la balise HTML du lien et capturant le contenu de l'attribut « href ».

    • Refus des données dans le formulaire d'inscription en cas confirmation de mot de passe erronée
    • Refus des données dans le formulaire d'inscription en cas de mot de passe incompatible avec les contraintes de complexité
    • Redirection systématique sur la page de confirmation de l'adresse mail si l'utilisateur n'a pas validé son adresse mail

Intégration continue

L'intégration continue demande l'automatisation des tests pour détecter au plus tôt les erreurs afin d'éviter les régressions. Vous avez déjà mis en place des outils de qualité de code automatisés sur le poste de travail du développeur avec PHP CS Fixer et GrumPHP. Vous disposez également d'une batterie de tests fonctionnels. Il est temps de les automatiser dans le processus d'intégration continue.

Vous allez installer et configurer GitLab Runner sur une machine virtuelle Ubuntu dans la ferme OpenNebula du département informatique. Le « runner » sera ensuite associé à votre dépôt GitLab pour que les tests soient exécutés automatiquement à chaque « push ».

Configuration du « runner » dans une machine virtuelle

Vous allez tout d'abord déployer une machine virtuelle Ubuntu dans la ferme OpenNebula du département informatique. La machine virtuelle Ubuntu sera ensuite configurée pour pouvoir exécuter les tests de l'application. Elle doit donc disposer de PHP, de Composer et de Node.js. Vous allez ensuite installer et configurer un « GitLab Runner » pour exécuter les tests à chaque « push ».

Travail à réaliser
  1. Consultez le support de cours de Floran Brutel et Nicolas Hart sur l'intégration continue
  2. Suivez le guide de déploiement d'une machine virtuelle Ubuntu sur OpenNebula en utilisant le « template » « Ubuntu Minimal 22.04 »
  3. Connectez-vous en ssh à la machine virtuelle et vérifiez qu'elle fonctionne correctement
  4. Vérifiez que vous avez bien suivi les consignes en affichant la version du système d'exploitation :
    lsb_release -a
    No LSB modules are available.
    Distributor ID:	Ubuntu
    Description:	Ubuntu 22.04.3 LTS
    Release:	22.04
    Codename:	jammy
  5. Effectuez les actions suivantes permettant de contourner de possibles désagréments ayant été constatés :
    • Ajoutez « localhost.localdomain » dans « /etc/hosts » en passant la commande :
      sed -i "s:^127.0.0.1 localhost$:127.0.0.1 localhost.localdomain localhost:" /etc/hosts
    • Mettez à jour les dépôts :
      apt update
    • Ajoutez un éditeur de texte, « vim » ou « nano » (Ubuntu Minimal ne contient pas d'éditeur de texte) :
      apt install nano
    • Limitez l'attente de la configuration réseau à 5 secondes en éditant le service « systemd-networkd-wait-online.service » avec la commande :
      systemctl edit --full systemd-networkd-wait-online.service
      puis en ajoutant l'option «  --timeout=5 » à la fin de la ligne commençant par « ExecStart= »
      Information

      Mettez bien un espace entre la fin de la commande et l'option que vous ajoutez.

    • Mettez à jour le système :
      apt upgrade
      Information

      Lors de la mise à jour du système, vous serez invité à confirmer la mise à jour du fichier « /etc/ssh/sshd_config ». Conservez le fichier installé.

      Lors des installations à vernir, vous serez invité à redémarrer les services. Il n'est pas utile de le faire, vous redémarrez la machine virtuelle à la fin de la configuration.

  6. Ajoutez le dépôt des versions supportées de PHP « ppa:ondrej/php »
  7. Installez « php8.1-cli » avec les extensions « sqlite3 », « curl », « intl », « mbstring », « xml » et « zip »
  8. Ajoutez le dépôt officiel de Node.js en version 20
  9. Installez « nodejs »
  10. Ajoutez le dépôt officiel de Gitlab Runner
  11. Installez « gitlab-runner »
  12. Installez Composer dans « /usr/local/bin »
  13. Créez un « runner » Linux pour votre dépôt GitLab avec les « tags » « php8.1 », « composer », « npm » et « node.js »
  14. Enregistrez le « runner » avec la commande proposée et fournissez interactivement les informations nécessaires :
    • le jeton d'enregistrement de votre dépôt GitLab (déjà renseigné via les options de la commande)
    • l'URL du serveur GitLab (déjà pré-renseigné via les options de la commande)
    • le nom du « runner » : « symfony-for-sale »
    • l'« executor » du « runner » : « shell »
  15. Désactivez les « shared runners » de votre dépôt GitLab
  16. Redémarrez la machine virtuelle et vérifiez que le « runner » est bien actif dans votre dépôt GitLab

Intégration continue dans le projet

Vous allez pouvoir définir les tâches d'intégration continue pour votre projet. Afin de limiter le temps d'exécution et les ressources OpenNebula consommées, vous définirez une seule tâche d'intégration continue qui installera les composants nécessaires au projet et exécutera les tests.

Travail à réaliser
  1. Créez le fichier « .gitlab-ci.yml » à la racine du projet
  2. Définissez une tâche « test » qui :
    • nécessite les « tags » « php8.1 », « composer », « npm » et « node.js »
    • installe les dépendances du projet
      Information

      Utilisez la commande « clean-install » de npm.

    • construit les « assets »
    • exécute les tests
    • conserve les artefacts des échecs des tests de Codeception
      Information

      Vous pouvez peaufiner la définition des artéfacts de Codeception en excluant le fichier « .gitignore » contenu dans le répertoire concerné.

  3. Poussez le fichier « .gitlab-ci.yml » dans votre dépôt GitLab
  4. Vérifiez que le « pipepline » d'intégration continue est bien exécuté et se termine avec succès

Commandes de console

Symfony propose de nombreuses fonctionnalités à travers le script « bin/console ». Ces commandes sont créées à partir du composant « Console » et peuvent être enrichies par des commandes personnalisées. Vous allez créer une commande personnalisée permettant de supprimer les utilisateurs dont l'adresse mail n'a pas été vérifiée depuis un nombre de jours donné.

Date d'inscription d'un utilisateur

La date d'inscription des utilisateurs n'est pas mémorisée. Vous allez donc ajouter cette information dans l'entité « User » en faisant une nouvelle fois usage de « Timestampable ».

Travail à réaliser
  1. Ajoutez une propriété obligatoire « registeredAt » à l'entité « User »
  2. Rendez la nouvelle propriété « Timestampable » à la création
  3. Ajustez la « Factory » pour que tout utilisateur créé aléatoirement ait une date d'inscription comprise entre 1 jour et 2 mois avant la date du jour
  4. Modifiez la « Story » pour ajouter un « Pool » « unverified users » de 4 utilisateurs non vérifiés

Ajout de fonctionnalités au « Repository » des utilisateurs

Maintenant que vous disposez de la date d'inscription des utilisateurs, vous devez pouvoir sélectionner et supprimer des utilisateurs non vérifiés en fonction du nombre de jours écoulés depuis leur inscription. Ces fonctionnalités sont à la charge du « Repository » des utilisateurs et vous allez les écrire.

Travail à réaliser
  1. Ajoutez une méthode « findUnverifiedUsersSince() » au « Repository » des utilisateurs permettant de sélectionner les utilisateurs non vérifiés depuis un nombre de jours donné facultatif
  2. Ajoutez une méthode « deleteUnverifiedUsersSince() » au « Repository » des utilisateurs permettant de supprimer les utilisateurs non vérifiés depuis un nombre de jours donné facultatif
    Information

    Les méthodes étant très similaires, factorisez tout le code qui peut l'être.

Création de la commande de suppression des utilisateurs non vérifiés

Vous allez créer une commande personnalisée permettant de supprimer les utilisateurs dont l'adresse mail n'a pas été vérifiée depuis un nombre de jours donné.

Par défaut, la commande affiche tous les utilisateurs non vérifiés. Vous pouvez filtrer les utilisateurs à considérer en précisant le nombre de jours depuis lequel l'utilisateur n'a pas vérifié son adresse mail avec l'option « --days=DAYS ». Une option « --delete » permet de supprimer les utilisateurs listés, après confirmation. Une dernière option « --force » permet de supprimer les utilisateurs listés sans confirmation.

Travail à réaliser
  1. Utilisez le « MakerBundle » pour générer une commande personnalisée « app:purge-registration »
  2. Ajoutez les options « --days=DAYS », « --delete » et « --force » à la commande
  3. Vérifiez que l'option « --days=DAYS » est bien un nombre entier positif, si elle est présente
  4. Réalisez l'affichage sous forme de tableau des utilisateurs non vérifiés (nom, prénom, adresse mail et nombre de jours écoulés) depuis le nombre de jours donné facultatif
  5. Supprimez, après confirmation, les utilisateurs non vérifiés depuis le nombre de jours donné facultatif si l'option « --delete » est présente
  6. Outrepassez la confirmation de suppression si l'option « --force » est présente
  7. Affichez le nombre d'utilisateurs supprimés en cas de suppression effective

Tests de la commande de suppression des utilisateurs non vérifiés

Votre commande de suppression des utilisateurs non vérifiés effectue des opérations critiques puisqu'elle supprime des utilisateurs. Il est primordial de la tester.

Travail à réaliser
  1. Générez un « Cest » « Command\PurgeRegistration »
  2. Lisez la documentation de « runSymfonyConsoleCommand »
  3. Testez la commande de suppression des utilisateurs non vérifiés avec ou sans les options « --days=DAYS », « --delete » et « --force » :
    • Affichage des utilisateurs non vérifiés
    • Suppression des utilisateurs non vérifiés (et non suppression des autres utilisateurs)

Réalisation d'un système de « likes »

L'une des forces d'un développeur est d'être capable de transposer une fonctionnalité déjà implémentée dans un nouveau projet. La plus grande force d'un bon développeur est d'être capable d'acquérir de nouvelles compétences en s'appuyant sur celles qu'il possède. Vous allez donc réaliser une fonctionnalité de « likes » en vous appuyant sur les connaissances acquises lors des semestres précédents et dans ce projet. Vous serez peu guidé lorsque la tâche fait appel à des connaissances déjà acquises. Vous serez néanmoins guidé lorsque la tâche fait appel à des connaissances en cours d'acquisition.

Formulation de la demande client

La demande du client est formulée comme une liste de règles de fonctionnement :

  • les utilisateurs peuvent « liker » les annonces
  • un utilisateur ne peut « liker » une même annonce qu'une seule fois et peut supprimer ce « like » s'il le désire
  • les visiteurs voient le nombre de « likes » d'une annonce dans la liste des annonces et sur le détail de l'annonce
  • les utilisateurs voient s'ils ont « liké » ou non une annonce particulière en plus du nombre de « likes » dans la liste des annonces et sur le détail de l'annonce
  • un utilisateur ne peut pas « liker » ses propres annonces

Le client propose un visuel pour afficher les « likes » : likes wireframe

Adapter le modèle de données

La demande du client doit être traduite en termes de modèle de données. Vous allez donc l'adapter pour prendre en compte les « likes ».

Travail à réaliser
  1. Réfléchissez à la manière dont vous allez modéliser les « likes »
  2. Transcrivez ces modifications dans le modèle de données
  3. Effectuez une migration de la base de données
  4. Créez de nouvelles « fixtures » pour les « likes » afin que l'utilisateur factice « user@example.com » ainsi que tous les utilisateurs aléatoires aient « liké » entre 50 et 100 annonces aléatoires
    Information

    Les annonces « likées » par un utilisateur doivent être différentes des annonces qu'il a créées.

Construire un composant Twig

Le nombre de « likes » d'une annonce doit être affiché dans la liste des annonces et sur le détail de l'annonce. Vous allez donc construire un composant Twig permettant d'afficher ces informations en reprenant le visuel proposé par le client.

Travail à réaliser
  1. Lisez l'introduction des « Twig Components »
  2. Installez les « Twig Components » avec Composer
  3. Utilisez la commande du « MakerBundle » Symfony pour générer un composant Twig « AdvertisementLikes »
  4. Ajoutez un « Advertisement » comme « Props » du composant
    Information

    Dans l'exemple de la documentation, les « Props » sont de simples « string ». Vous pouvez naturellement utiliser un « Advertisement » comme « Props » de votre composant.

  5. Ajoutez une méthode « getLikesCount() » au composant pour récupérer le nombre de « likes » de l'annonce
    Information

    Il suffit de compter le nombre d'utilisateurs ayant « liké » l'annonce, les « Collection » de Doctrine implémentent l'interface « Countable »

  6. Structurez la vue du composant pour qu'elle affiche le nombre de « likes » de l'annonce précédé d'un cœur
    Information

    Dans la vue, vous devez utiliser « this » pour accéder au composant et donc à ses propriétés et méthodes.

  7. Ajoutez le composant dans la liste des annonces et sur le détail de l'annonce
  8. Ajoutez une méthode « isLikedByUser() » au composant pour savoir si l'utilisateur a « liké » l'annonce
  9. Modifiez la vue du composant pour qu'elle affiche le nombre de « likes » de l'annonce précédé d'un cœur plein si l'utilisateur a « liké » l'annonce ou d'un cœur si l'utilisateur n'a pas « liké » l'annonce
    Information

    Pensez à étudier le cas où aucun utilisateur n'est connecté.

Vue des annonces préférées de l'utilisateur

L'objectif des « likes » est de mettre en avant une annonce en matérialisant l'intérêt qu'elle suscite, mais ils permettent aussi à un utilisateur de marquer les annonces qu'il préfère. Il est donc logique de lui proposer une vue de ses annonces préférées.

Travail à réaliser
  1. Créez une nouvelle action « liked » dans le contrôleur « AdvertisementController »
  2. Créez une méthode « queryLikedByUser…() » dans le « Repository » des annonces permettant de récupérer les annonces « likées » par un utilisateur donné
    Information

    Cette méthode doit rester compatible avec une utilisation optimisée de la pagination.

  3. Construisez la vue associée à l'action
  4. Ajoutez une entrée « Annonces préférées » dans le menu de l'application

Rendre le composant « Live »

Symfony UX propose des « Live Components » qui peuvent s'actualiser selon les interactions de l'utilisateur. Il est ainsi possible de bénéficier de la souplesse et la puissance d'AJAX dans l'interface utilisateur sans écrire de JavaScript.

L'intérêt pour l'application sera de transformer le composant « AdvertisementLikes » en composant « Live » afin de pouvoir « liker » une annonce et mettre à jour le nombre de « likes » sans recharger la page.

Travail à réaliser
  1. Installez les « Live Components » avec Composer
    Information

    Comme indiqué dans la documentation, n'oubliez pas d'installer le « StimulusBundle » ni les paquets npm nécessaires.

  2. Lisez le chapitre « Making your Component "Live" » de la documentation
  3. Transformez le composant « AdvertisementLikes » en composant « Live »
  4. Ajoutez une méthode « toggleLike() » au composant pour ajouter ou supprimer le « like » de l'annonce pour l'utilisateur connecté
    Information

    Pensez à étudier le cas où aucun utilisateur n'est connecté.

  5. Ajoutez un attribut PHP à la méthode « toggleLike() » pour en faire une « LiveAction »
  6. Modifiez la vue du composant pour qu'elle appelle la méthode « toggleLike() » lors d'un clic sur le cœur contenu dans le composant
    Information

    Le cœur peut être transformé en bouton Bootstrap, uniquement si un utilisateur est connecté, pour faciliter la gestion du clic.

Gestion des droits d'accès

L'un des principes de fonctionnement souhaités demande à ce que l'utilisateur ne puisse pas « liker » ses propres annonces. L'application doit impérativement veiller au respect de cette règle. Vous allez donc compléter le « Voter » « AdvertisementVoter » pour gérer ce cas.

Travail à réaliser
  1. Ajoutez un attribut « LIKE » au « AdvertisementVoter » pour gérer le droit de « liker » les annonces dont l'utilisateur n'est PAS l'auteur
  2. Utiliser le « Voter » pour limiter l'utilisation de la méthode « toggleLike() » du composant « AdvertisementLikes »
  3. Utilisez le « Voter » pour désactiver le clic sur le cœur dans le composant « AdvertisementLikes »

Optimisations

Comme précédemment, l'accès à de nouvelles ressources liées à une entité entraine de nouvelles requêtes qu'il convient d'étudier pour déterminer s'il est nécessaire de les optimiser.

Travail à réaliser
  1. Trouvez ce qui engendre de nouvelles requêtes
  2. Optimisez les requêtes
    Information

    Lorsque vous faites évoluer le comportement de certaines méthodes, il convient souvent de les renommer pour refléter ces changements. Pensez à la refactorisation de code accessible par le raccourci « SHIFT+F6 » de PHPStorm pour gagner en efficacité et en fiabilité.

  3. Déterminez s'il existe dans l'application des requêtes équivalentes qui peuvent être optimisées
  4. Optimisez les éventuelles requêtes le nécessitant

Introduction d'un flux de travail (« workflow ») pour la gestion des annonces

Les annonces de l'application sont actuellement gérées de manière très classique. Dès qu'une annonce existe, elle est visible par tous les utilisateurs. Le propriétaire d'une annonce peut la modifier ou la supprimer. Vous allez introduire un flux de travail pour la gestion des annonces afin de permettre aux utilisateurs de créer des annonces sans les publier immédiatement, d'archiver des annonces et de les marquer comme terminées. La mise en place d'un état pour les annonces, ainsi que les transitions possibles entre ces états, est une démarche classique dans une application. Ainsi, Symfony propose un composant « Workflow » qui permet de gérer ces états et transitions.

Description du flux de travail

La description complète de la mécanique du flux de travail est fournie par le client :

  • une annonce nouvellement crée est dans un état « brouillon »  : « draft »
  • le propriétaire de l'annonce « brouillon » peut la publier, elle passe alors dans l'état « publiée » : « published »
  • le propriétaire de l'annonce « publiée » peut la marquer comme terminée, elle passe alors définitivement dans l'état « terminée » : « closed »
  • le propriétaire de l'annonce « publiée » peut l'archiver, elle passe alors dans l'état « archivée » : « archived »
  • le propriétaire de l'annonce « brouillon » peut la supprimer, elle est alors supprimée de la base de données
  • le propriétaire de l'annonce « archivée » peut la republier, elle passe alors dans l'état « publiée » : « published »

L'ensemble des transitions possibles peut être représenté par le diagramme suivant : workflow

Travail à réaliser
  1. Parcourez la documentation de « Workflow »
  2. Installez le composant « Workflow » avec Composer
  3. Configurez le composant « Workflow » pour gérer les états et transitions décrits ci-dessus
  4. Modifiez l'entité « Advertisement » pour qu'elle mémorise un état
  5. Définissez des constantes pour les états (« STATE_* ») et transitions (« TRANSITION_* ») dans l'entité « Advertisement »
  6. Utilisez les nouvelles constantes dans la configuration YAML du composant « Workflow »

Utilisation du flux de travail

Le flux de travail défini va engendrer des modifications dans l'application. Vous allez donc les réaliser.

Travail à réaliser
  1. Adaptez les « fixtures » pour que les annonces créées aléatoirement soient dans l'état « publiée »
  2. Modifiez les annonces associées à l'utilisateur factice « user@example.com » pour qu'elles soient aléatoirement dans tous les états possibles
  3. Modifiez les requêtes du « Repository » des annonces pour qu'elles ne retournent que les annonces dans l'état « publiée »
  4. Modifiez le « Voter » « AdvertisementVoter » pour qu'il ne permette pas de consulter des annonces non publiées, sauf leur auteur et à condition qu'elles ne soient pas terminées
  5. Ajoutez la consultation des annonces dans l'état « brouillon » pour l'utilisateur connecté
  6. Ajoutez au « Voter » « AdvertisementVoter » la capacité de contrôler qu'une annonce peut être « publiée », « terminée » ou « archivée », uniquement par son auteur
    Information

    Le « Workflow » permet de contrôler les transitions possibles. Utilisez cette capacité plutôt que de contrôler vous-même l'état nécessaire à la transition.

    Pour accéder au service « Workflow », vous devez l'injecter dans le « Voter » en utilisant l'attribut « Target ».

  7. Modifiez le processus de suppression d'une annonce pour contrôler que l'annonce peut bien être supprimée (elle doit être dans l'état « brouillon »)
  8. Sur le modèle de la suppression, ajoutez la possibilité de :
    1. publier (ou republier) une annonce
    2. archiver une annonce
    3. terminer une annonce
    Information

  9. Ajoutez des tests