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.
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.
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
, creationDate
et ratings
,
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.
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. Une approche classique pourrait être d'employer le bundle DoctrineExtensions, mais par souci de simplicité et de pédagogie, vous allez réaliser cette opération à la main à l'aide des fonctionnalités de 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.
setCreatedDateValue
à l'entité Bookmark
.
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.
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
.
rateAverage
.
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(Bookmark $bookmark): void
Cette méthode utilisera le QueryBuilder
de Doctrine pour mettre à jour l'attribut rateAverage
du Bookmark 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 son paramètre.
Vous ajouterez une classe de test BookmarkRateAverageCest
contenant les tests suivants :
0
.
BookmarkRepository::updateRateAverage
.
BookmarkRateAverageUpdateListener::postPersist
.
#[AsEntityListener]
.
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 :
0
de la moyenne des notes du bookmark.
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.
owner
.
Bookmark
: BookmarkFactory
.
BookmarkFixtures
.
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 :
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.
BookmarkIsPublicOrOwnedExtension
.
BookmarkGetCollectionCest
.
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
:
GET
d'un signet doit permettre
POST
n'est accessible que par un utilisateur authentifié.
PATCH
n'est accessible que par un utilisateur authentifié et il ne peut modifier que ses signets.
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,
url
doit être une URL valide.
Vous ajouterez des tests pour valider le fonctionnement votre API.
Bookmark
.
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 :
isPublic
de l'entité Bookmark
.
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.
isPublic
de l'entité Bookmark
.
BookmarkDenormalizer
.
Vous allez ajouter deux nouvelle opérations permettant d'obtenir des sous-ressources :
/bookmarks/{id}/ratings
»),
/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.
/bookmarks/{id}/ratings
».
/users/{id}/bookmarks
».