Il s'agit d'une version statique de l'intranet d'A. Jonquet, certaines fonctionnalités sont donc potentiellement non fonctionnelles.
Rejoindre la version dynamique 🔒
R401
Navigation

Retour sur les bookmarks

Pour finir, vous allez configurer les opérations sur les bookmarks.

Vous commencerez par ajouter une moyenne des notes d'un bookmark, que vous maintiendrez à jour lors des intéraction avec les notes du signet. Vous en profiterez pour ajouter une opération permettant d'obtenir la liste des notes d'un bookmark.

Et enfin, vous ajouterez un propriétaire aux bookmarks et vous en tiendrez compte lors de leur affichage.

Remarque importante

Les notes des TP seront obtenues à partir de vos dépôts Git, vous devrez donc prendre grand soin de la qualité de ces dépôts et de leurs « commits ».

De manière similaire, les descriptions de vos « commits » devront être claires et informatives, afin de permettre l'évaluation de la progression de votre travail.

Objectifs de la séance

  • Gestion de relations entre les tables
  • Modification automatique de propriétés
  • Surcharge des requêtes d'accès aux ressources

Mise en place de groupes de sérialisation

Vous allez ajouter les groupes de sérialisation permettant de sélectionner les propriétés accessibles par les différentes opérations de l'entité Bookmark.

Vous allez gérer trois groupes :

  • Bookmark_read pour l'accès aux propriétés id, name, isPublic et url,
  • Bookmark_detail pour l'accès aux propriétés description et creationDate,
  • et Bookmark_write pour l'écriture des propriétés name, description, isPublic et url.

Vous affecterez ces groupes aux propriétés de l'entité Bookmark en conséquence et vous corrigerez les tests de l'entité Bookmark pour qu'ils soient à nouveau valides, notamment l'accès à la collection qui ne retourne plus les propriétés description et creationDate.

Vous en profiterez pour spécifier les opérations suportées : GET pour une entité ou la collection d'entité, POST, PATCH et DELETE.

Seules les opérations GET d'une entité, POST et PATCH auront accès au détail d'un signet.

Travail réalisé dans cette partie
  • Ajout des groupes de sérialisation.
  • Spécification des opérations supportées.
  • Correction des tests.

Initialisation automatique de la date de création

Les bonnes pratiques déconseillent l'usage des triggers de base de données, car ils vont à l'encontre de l'usage de Doctrine, qui permet de décorréler le projet du moteur de base de données, ainsi qu'offrir un déploiement/redéploiement simplifié puisque la configuration de la base de données est réalisée par Symfony. Vous allez donc utiliser le cycle de vie des entités de Doctrine pour automatiser l'initialisation' de l'attribut creationDate de l'entité Bookmark.

Comme précisé dans la documentation, lorsque l'intervention est simple, comme définir la date de création ou de modification par exemple, il s'agit juste de définir un callback dans l'entité.

Vous ajouterez donc une nouvelle méthode setCreatedDateValue initialisation la date de création du signet. Vous l'enregistrerez comme callback pour l'événement prePersist de l'entité Bookmark par la présence de l'attribut #[ORM\PrePersist] et vous préciserez à Symfony que l'entité comporte des fonctions de rappel pour les événements du cycle de vie de Doctrine à l'aide de l'attribut #[ORM\HasLifecycleCallbacks].

Contrairement à la documentation, afin que la date de création puisse être initialisée, notamment dans les tests, vous prendrez soin dans la méthode setCreatedDateValue de ne pas initialiser la date de création si elle est déjà initialisée.

Travail réalisé dans cette partie
  • Ajout de la méthode setCreatedDateValue à l'entité Bookmark.
  • Ajout des attributs appropriés.

Ajout de la moyenne des notes

Pour ne pas avoir à recalculer la moyenne des notes d'un bookmark lors de l'accès à celui-ci, vous allez lui ajouter une nouvelle propriété rateAverage, qui contiendra la moyenne des notes du signet.

Comme précédemment, vous utiliserez le maker de Symfony, pour ajouter une propriété rateAverage à l'entité Bookmark :

bin/console make:entity Bookmark

Il s'agira d'une propriété float ne pouvant pas être null et vous prendrez soin de lui affecter une valeur par défaut de 0. Même si ce n'est pas nécessaire, afin d'avoir un schéma de base de données valide, vous ajouterez aussi la valeur pas défaut pour la colonne.

Vous penserez à mettre à jour votre base de données au travers l'utilisation d'une migration :

bin/console make:migration
bin/console doctrine:migrations:migrate

Vous relancerez vos jeux de tests pour constater que les tests de l'entité Bookmark ne sont plus valides, en effet, la propriété rateAverage doit être ajoutée à la liste des propriétés attendues. Vous corrigerez le problème pour que l'ensemble des tests soit valide.

Remarque importante

La validation des types des propriétés JSON, dans les tests, est réalisée à l'aide de la méthode seeResponseMatchesJsonType du module REST, vous vous référerez à la documentation pour associer un type entier ou réel à la propriété rateAverage.

Travail réalisé dans cette partie
  • Ajout de la propriété rateAverage.
  • Mise à jour de la base de données.
  • Correction des tests.

Mise à jour de la moyenne à la création d'une note

Pour mettre à jour la moyenne des notes, vous allez vous integrer au cycle de vie des entités de Doctrine. Cependant, avant de vous intéresser aux événements, vous allez ajouter à la classe BookmarkRepository la méthode suivante :

public function updateRateAverage(int $bookmarkId): void

Cette méthode utilisera le QueryBuilder de Doctrine pour mettre à jour l'attribut rateAverage du Bookmark dont l'identifiant est passé en paramètre. Vous veillerez à considérer le cas particulier où la moyenne ne peut pas être calculée parce qu'il n'y a pas d'évaluations pour le Bookmark.

Vous allez maintenant pouvoir définir un écouteur externe à l'entité Rating permettant de réagir à la suite de la création(postPersist) de chaque note afin mettre à jour la moyenne des notes du bookmark associé. Vous ajouterez le squelette de la classe BookmarkRateAverageUpdateListener (télécharger) à votre projet dans le répertoire src/EntityListener.

La méthode postPersist permettra de mettre à jour le signet associé à l'instance de Rating passé en paramètre à l'aide de la méthode updateRateAverage que vous venez d'écrire. Votre écouteur est enregistré comme un service attaché à l'événement postPersist de l'entité Rating par la présence de l'attribut #[AsEntityListener] et de ses paramètres.

Vous ajouterez une classe de test BookmarkRateAverageCest contenant les tests suivants :

  • À la création d'un nouveau bookmark la moyenne de ses notes est 0.
  • La création de 2 notes d'un bookmark provoque la mise à jour de la moyenne des notes.
Travail réalisé dans cette partie
  • Ajout de la méthode BookmarkRepository::updateRateAverage.
  • Ajout d'un écouteur d'événement BookmarkRateAverageUpdateListener::postPersist.
  • Configuration du service par attribut #[AsEntityListener].
  • Mise en place d'un test de la fonctionnalité.

Gestion de la modification/suppression d'une note

De manière similaire, vous ajouterez la méthode postUpdate à la classe BookmarkRateAverageUpdateListener que vous enregistrerez comme écouteur d'événement pour l'événement postUpdate de l'entité Rating.

Vous validerez son bon fonctionnement à l'aide d'un test de la classe BookmarkRateAverageCest réalisant la modification d'une note d'un bookmark, qui provoquera la mise à jour de la moyenne des notes du bookmark.

Puis, vous ferez de même pour la suppression d'une note. Vous ajouterez deux tests :

  • Le premier testera la suppression d'une note d'un bookmark et la mise à jour de la moyenne des notes du bookmark.
  • Le second testera la suppression de la dernière note d'un bookmark et la mise 0 de la moyenne des notes du bookmark.
Travail réalisé dans cette partie
  • Ajout d'écouteurs d'événements pour la modification et la suppression d'une note.
  • Mise en place des tests des fonctionnalités.

Ajout du propriétaire d'un signet

Pour en finir avec l'ajout de propriété, vous ajouterez une nouvelle propriété owner à l'entité Bookmark, qui sera une relation ManyToOne, ne pouvant être null, vers l'entité User, et inversable par la propriété bookmarks de l'entité User. Vous utiliserez le maker de Symfony pour ajouter cette propriété :

bin/console make:entity Bookmark

Vous prendrez soin de mettre à jour votre base de données à l'aide d'une migration :

bin/console make:migration

Mais vous constaterez que la migration n'est pas applicable sur votre base de donnée. En effet, la propriété n'ayant pas de valeur par défaut et votre base de données contenant déjà des bookmarks, il n'est pas possible de leur affecter un propriétaire automatiquement.

Si vous deviez gérer une base de donnée en production, vous devriez ici éditer votre migration pour rendre la propriété nullable, affecter une valeur intelligente à tous les enregistrements déjà présent dans la base de données, puis rendre la propriété de nouveau non nullable. Mais comme nous somme encore en phase de développement, vous allez simplement supprimer la base de données et la recréer à l'aide de la commande :

composer db

Mais avant cela, vous allez ajouter la propriété owner à la forge BookmarkFactory, ainsi que faire en sorte qu'un utilisateur soit tiré aléatoirement comme propriétaire de chaque bookmark lors de la génération des données factices.

La génération des signets suppose que les utilisateurs soient dans la base de données, vous devez donc vous assurer que les fixtures pour les utilisateurs soient exécutées avant les signets. Vous préciserez donc des dépendances entre ces deux fixtures.

Vous devriez maintenant pouvoir regénérer votre base de données, vous pourrez controller le bon déroulement en réalisant une requête :

bin/console dbal:run-sql "SELECT owner_id, COUNT(*) AS bookmarks FROM bookmark GROUP BY owner_id"

Vous devriez obtenir un résultat similaire à :

 --------- --------- 
  owner_id   bookmarks  
 ---------- ----------- 
  1          4          
  2          1          
  3          4          
  4          2          
  5          7          
  6          2          
  7          4          
  8          1          
  9          4          
  10         2          
  11         2          
  12         2          
  13         2          
  14         3          
  15         4          
  16         4          
  17         3          
  18         1          
  19         3          
  20         3          
  21         4          
  22         1          
  23         4          
 ---------- -----------

N'hésitez cependant pas à regarder le contenu de vos tables à l'aide de phpMyAdmin afin de vérifier précisément la qualité des données générées.

Comme d'habitude, vous constaterez que certains de vos tests ne fonctionnent plus, vous les corrigerez pour que l'ensemble des tests soit valide.

Travail réalisé dans cette partie
  • Ajout de la propriété owner.
  • Mise à jour du script de génération d'entité Bookmark : BookmarkFactory.
  • Création d'un script de génération de données : BookmarkFixtures.
  • Mise à jour de la base de données.
  • Correction des tests.

Modification des requêtes d'accès aux ressources

Vous avez dû constater qu'un utilisateur à accès à tous les signets, même les signets privés des autres utilisateurs.

Vous allez donc modifier les requêtes d'accès aux ressources pour que les signets privés ne soient accessibles que par leur propriétaire.

Dans cette optique, Doctrine propose des extensions permettant de modifier les requêtes d'accès aux ressources. Vous allez donc ajouter une extension à Doctrine pour modifier les requêtes d'accès aux ressources de l'entité Bookmark.

Vous ajouterez une classe BookmarkIsPublicOrOwnedExtension dans le répertoire src/Doctrine de votre projet, qui étendra la classe QueryCollectionExtensionInterface. Cette classe enregistrera le service Security dans son constructeur.

Enfin, dans la méthode applyToCollection, si la ressource est une instance de Bookmark, vous ajouterez à la requête passée en paramètre ($queryBuilder) les conditions suivantes :

  • que le signet soit public
  • ou que le signet soit la propriété de l'utilisateur authentifié

Normalement, si vous relancez les tests, les tests produits par la méthode getFilteredBookmarkCollection de la classe BookmarkGetCollectionCest devraient échoués. En effet, certain des bookmarks générés ne sont pas public et aucun utilisateur n'est authentifié. Pour résoudre ce problème, vous allez créer un utilisateur que vous authentifierez et vous l'ajouterez comme propriétaire des 4 signets générés.

Une fois que tous les tests sont à nouveau valides, vous ajouterez deux tests à la classe BookmarkGetCollectionCest  :

  • anonymousUserAccessOnlyPublicBookmarks qui vérifiera qu'un utilisateur anonyme n'a accès qu'aux signets publics.
  • authenticatedUserAccessPublicAndOwnBookmarks qui vérifiera qu'un utilisateur authentifié accède à tous les signets publics, ainsi qu'à ses signets privés.
Travail réalisé dans cette partie
  • Ajout de la classe BookmarkIsPublicOrOwnedExtension.
  • Correction des tests de la classe BookmarkGetCollectionCest.
  • Ajout des tests d'accès aux signets.

Sécurité des données

Vous n'avez défini l'extension Doctrine que pour les collections, il reste donc de nombreuses failles de sécurités.

Vous ferez en sorte que d'appliquer les restiction suivantes aux opérations de l'entité Bookmark :

  • l'opération GET d'un signet doit permettre
    • uniquement l'accès aux signets publics pour un utilisateur anonyme,
    • l'accès à tous ses signets et les signets publics pour un utilisateur authentifié.
  • l'opération POST n'est accessible que par un utilisateur authentifié.
  • l'opération PATCH n'est accessible que par un utilisateur authentifié et il ne peut modifier que ses signets.
  • l'opération DELETE n'est accessible que par un utilisateur authentifié et il ne peut supprimer que ses signets.

Vous en profiterez pour ajouter des contrôles sur la validité des données soumise par l'utilisateur :

  • name et description ne peuvent pas contenir les caractères « < », « > », « & » et « " » afin de vous protéger contre l'injection de code JavaScript,
  • et url doit être une URL valide.

Vous ajouterez des tests pour valider le fonctionnement votre API.

Travail réalisé dans cette partie
  • Sécurisation des opérations de l'entité Bookmark.
  • Validation des données soumises par l'utilisateur.
  • Validation du code par les tests.

(optionnel) Amélioration de la DX (« Developer eXperience »)

Il est toujours un peu fastidieux de devoir transmettre les données lors des requêtes utilisant votre API. Vous allez donc réduire le nombre de propriétés nécessaire à la création d'un signet :

  • en affectant une valeur par défaut à faux à la visibilité du signet. Il suffit d'affecter une valeur par défaut à la propriété isPublic de l'entité Bookmark.
  • en créant une nouvelle classe BookmarkDenormalizer qui injectera l'utilisateur connecter comme owner si la propriété n'est pas présente.

Vous pourrez ajouter un test à la classe BookmarkCreateCest pour valider le fonctionnement de vos ajouts.

Travail réalisé dans cette partie
  • Ajout d'une valeur par défaut à la propriété isPublic de l'entité Bookmark.
  • Ajout d'une nouvelle classe BookmarkDenormalizer.
  • Ajout de test validant les nouvelles fonctionnalités.

(optionnel) Ajout des sous-ressources

Vous allez ajouter deux nouvelle opérations permettant d'obtenir des sous-ressources :

  • la liste des notes d'un signet (« /bookmarks/{id}/ratings »),
  • et la liste des signets d'un utilisateur (« /users/{id}/bookmarks »).

Comme précédemment, vous ferrez en sorte que la liste des notes d'un signet contienne des informations nichées de l'utilisateur concerné (id, firstname et lastname).

Ces deux routes ne doivent être accessibles que par un utilisateur authentifié.

Selon les choix réalisés lors de la sécurisation des sous-routes « /bookmarks » en GET, il est possible que vous ayez été permissif puisqu'elles étaient toutes accessible par les utilisateurs anonymes. Vous devrez donc peut-être être plus strict.

Vous ajouterez des tests pour valider le fonctionnement votre API.

Travail réalisé dans cette partie
  • Ajout d'une route « /bookmarks/{id}/ratings ».
  • Ajout d'une route « /users/{id}/bookmarks ».
  • Ajout de test validant le fonctionnement de l'API.
A. Jonquet DUT-INFO/REIMS