07 - Symfony : Les fixtures
Objectifs
- ✅ Installer le composant DoctrineFixturesBundle,
- ✅ Utiliser les fixtures dans Symfony de façon simple
Pré-requis
Avoir terminé la quête
Introduction
Les fixtures, ou l'art de créer des jeu de données de test pour nos applications web ! Lors du développement d'une application, il est souvent nécessaire, pour avancer dans de bonnes conditions, d'avoir du contenu. Par contenu, on entend :
- Textes :
- Descriptif/présentation de l'entreprise,
- Articles de blog,
- Pages de CGU,
- etc.
- Médias
- galeries photos,
- etc.
- Composés
- comptes utilisateurs,
- Produits à vendre sur un site eCommerce,
- etc.
Malheureusement, il est assez fréquent que le contenu final ne soit pas encore disponible (pas encore créé, confidentiel, inexploitable, etc.).
Et quand bien même serait-il disponible, l'application évolue généralement si rapidement durant son développement (ajout de champs ou modification des relations, ...) que les données peuvent vite devenir caduques.
Pour pallier ce problème, il existe des outils qui nous permettent de faire du data seeding, c'est-à-dire de renseigner du contenu de manière automatisée dans notre base de données.
Les données générées ainsi, bien que fictives, seront proches des données finales, afin de simuler ce qui se passera une fois le site en production. Cela a comme avantages de pouvoir :
- tester le site en conditions réelles,
- tester les performances avec des bases de données très remplies,
- partager avec l'équipe de développement un jeu identique de données fictives.
- remettre à zéro notre jeu de données autant de fois qu'on le souhaite en une simple commande.
Sommaire
Installation du Composant DoctrineFixturesBundle
Dans cette partie, peu importe la structure de base utilisée, tu dois installer le composant DoctrineFixturesBundle à la main. Pour ça, rends-toi dans ton projet Wild Séries et exécute le code ci-dessous :
composer require --dev orm-fixtures
Grâce à Symfony flex, tu n'as rien d'autre à faire pour l'installer.
Si tu es observateur ou observatrice, tu as sûrement remarqué l'option --dev. Cela contraint le composant à n’être disponible qu'en environnement de développement. Ainsi, une fois en production, les fonctionnalités de fixtures ne sont plus disponibles, car tu n'en as pas besoin. Cela serait même dangereux de les laisser disponibles. Imagine que quelqu'un joue les fixtures en production involontairement, ceci écraserait toutes les données 😱
Si tout s'est bien passé, tu peux observer dans le dossier /vendor de ton projet l'apparition des répertoires doctrine/data-fixtures et doctrine/doctrine-fixtures-bundle.
Utilisation simple des fixtures : création des catégories.
Tu vas maintenant procéder à la création de ta première fixture. Pour cela, dans /src/DataFixtures, crée la classe CategoryFixtures comme ci-dessous :
<?php
namespace App\DataFixtures;
use App\Entity\Category;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class CategoryFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
$category = new Category();
$category->setName('Horreur');
$manager->persist($category);
$manager->flush();
}
}
Tu peux également générer un fichier modèle de fixtures via la commande
symfony console make:fixtures
Ici tu n'es pas dans un contrôleur, tu n'as donc pas le droit d'injecter directement un CategoryRepository. Tu dois donc accéder à un objet un peu plus bas niveau du nom d' ObjectManager. Tu vas utiliser deux méthodes de cet objet: persist($category) qui dit à Doctrine de prendre en compte l'objet $category qui a été instancié; puis la méthode flush() qui permet d'exécuter toutes les requêtes SQL nécessaires. Doctrine comprend qu'il doit faire un INSERT en base de données pour ajouter la nouvelle catégorie.
Si tu analyses le code ci-dessus, tu peux voir :
- Ligne 5, la référence à l'entité que tu vas manipuler,
- Ligne 11, la méthode
loadqui est appelée lors du chargement des fixtures, - Ligne 13, l'instanciation d'un nouvel objet
Category, - Ligne 14, la définition du nom de cette nouvelle catégorie,
- Lignes 15 et 16, la persistance en base de données.
Ce qu'il faut retenir ici, c'est qu'il faut instancier un nouvel objet, lui attribuer les propriétés nécessaires via ses setters et enfin le persister en base de données.
Une fois que tu as reproduit cette classe, il faut l’exécuter.
Depuis ton terminal :
symfony console doctrine:fixtures:load
Lorsque s'affiche la question suivante : Careful, database will be purged. Do you want to continue y/N ?, tu orientes ta réponse en fonction de l'état de ta base de données.
C'est une mise en garde car, à chaque chargement de fixtures, toute la base de données est entièrement vidée. Ceci afin d'avoir un contenu cohérent, et éviter d'avoir des données orphelines ou des incohérences dans les relations entre les clés primaires et étrangères.
Si toutes tes données sont déjà gérées par des fixtures (ce qui ne devrait pas être ton cas) ou que les données présentes sont sans importances et peuvent être supprimées, alors tu peux taper y.
Sinon, tapes N et relance les fixtures avec l'option --append.
symfony console doctrine:fixtures:load --append
Cela va ajouter les données au lieu de supprimer d'abord toutes les données déjà présentes en base.
En contrôlant le contenu de ta table category, tu peux constater que ta nouvelle catégorie est bien présente :
symfony console doctrine:query:sql SELECT * FROM category;
---- -------------
id name
---- -------------
1 Horreur
---- -------------
💡 À noter : l'utilisation de la commande
symfony console doctrine:query:sql (d:q:s)suivie de la requête te permet d'exécuter une requête SQL dans le terminal sans te connecter à mysql, pratique 🤓.
Si tu obtiens ce résultat, c'est que tu viens de réaliser correctement ta première fixture. Félicitations !
Selon ta situation, tu n'as peut être pas exactement l'id 1, ou tu as peut être d'autres catégories déjà présentes. Si tu souhaites vider intégralement ta base et également réinitialiser les id, c'est l'option --purge-with-truncate qu'il faut utiliser au chargement des fixtures.
Une c'est bien, mais plusieurs, c'est mieux. Comme toute portion de code, tu peux imbriquer celui-ci dans une boucle afin d'en générer plusieurs à la volée.
Soit tu souhaites une génération de contenu très abstrait et dans ce cas-là, ce simple code pourrait suffire :
for ($i = 1; $i <= 50; $i++) {
$category = new Category();
$category->setName('Nom de catégorie ' . $i);
$manager->persist($category);
}
$manager->flush();
Tu peux constater que le flush est sorti de la boucle. En effet, s’il est impératif de persister chaque objet, l'appel à la méthode flush n’est nécessaire qu'une seule fois. De plus, tu dois limiter au maximum le nombre de flush et faire appel à la méthode une fois tous les éléments persistés. Le risque est de saturer ta mémoire et de provoquer une erreur.
Ce qui te permettrait d'avoir en BDD le résultat suivant :
symfony console d:q:s "SELECT * FROM category"
---- --------------------
id name
---- --------------------
2 Nom de catégorie 1
3 Nom de catégorie 2
4 Nom de catégorie 3
5 Nom de catégorie 4
6 Nom de catégorie 5
...
49 Nom de catégorie 48
50 Nom de catégorie 49
51 Nom de catégorie 50
---- ---------------------
Soit tu enregistres du contenu plus concret, mais de manière tout aussi simple, en suivant les instructions suivantes :
- Ajoute à la classe
CategoryFixturesune constante nomméeCATEGORIES, de type tableau, contenant la liste de tes catégories sous forme de chaîne de caractères. Ex. :
const CATEGORIES = [
'Action',
'Aventure',
'Animation',
'Fantastique',
'Horreur',
];
- Boucle sur la constante CATEGORIES et instancie un nouvel objet catégorie à chaque tour, en lui attribuant via
setName()la catégorie en cours. Ce qui te donne quelque chose comme :
public function load(ObjectManager $manager)
{
foreach (self::CATEGORIES as $key => $categoryName) {
$category = new Category();
$category->setName($categoryName);
$manager->persist($category);
}
$manager->flush();
}
Le résultat obtenu est plus parlant :
SELECT * FROM category;
---- -------------
id name
---- -------------
2 Action
3 Aventure
4 Animation
5 Fantastique
6 Horreur
---- -------------
Tu as noté les id des catégories qui augmentent sans cesse ? Cela vient du fait qu'à chaque chargement des fixtures, les tables sont vidées, mais les valeurs auto-incrémentées ne sont pas remises à zéro. Les fixtures suppriment les données via un DELETE et non un TRUNCATE. Si jamais tu as besoin de revenir à 0 dans tes IDs, tu peux rajouter l'option --purge-with-truncate lors du chargement des fixtures.
La documentation officielle sur la création de fixtures.
Attention, tu pourrais avoir envie de continuer sur ta lancée et de créer des fixtures pour l'entité Program. Ne le fais pas tout de suite : tu as besoin d'en voir un peu plus sur les fixtures pour savoir comment gérer les relations (tu te rappelles qu'un Program appartient à une Category). Patience, cela viendra dans quelques quêtes ;)
true|||true|||true
# Comment installer les fixtures ?
[x] Avec composer
[] C’est automatique lorsque l’on crée un nouveau projet Symfony
[] Avec yarn
# Que fait la commande symfony console make:fixtures ?
[x] Elle sert uniquement à générer la base d’un fichier de fixtures
[] Elle sert à installer les fixtures dans un projet
[] Elle sert à exécuter les fixtures
# Que se passe t-il si l’on joue les fixtures deux fois d’affilée ?
[] Une erreur survient
[x] Pas d’erreur, les données précédentes sont écrasées par les nouvelles
[] Les données seront en double dans la base de données.
# Lequel de ces workflow est valide ?
[x] symfony console make:entity / symfony console make:migration / symfony console doctrine:migrations:migrate / symfony console make:fixtures / symfony console doctrine:fixtures:load
[] symfony console make:entity / symfony console make:migration / symfony console doctrine:migrations:migrate / symfony console doctrine:fixtures:load /symfony console make:fixtures
[] symfony console doctrine:fixtures:load /symfony console make:fixtures / symfony console make:entity / symfony console make:migration / symfony console doctrine:migrations:migrate
💪 Challenge
Pour ce challenge, tu devras simplement ajouter des catégories à l'aide des fixtures. Si tu as bien suivi cette quête, tout est déjà fait !
🧐 Critères de validation
- Lorsque
symfony console doctrine:fixtures:loadest exécuté, il n y a pas de message d'erreur, - Les fixtures génèrent plusieurs catégories,
- Les noms des catégories sont définis "à la main" dans la classe
App\DataFixtures\CategoryFixtures, - Le code est disponible sur un repository GitHub, avec une branche correspondant à cette quête.
Ressources partagées sur cette quête
- Doc officiel des fixtureshttps://symfony.com/doc/current/DoctrineFixturesBundle/index.html#sln