• 12 hours
  • Hard

Free online content available in this course.

course.header.alt.is_certifying

Got it!

Last updated on 8/26/24

Développez plus vite grâce aux fixtures

Dans le chapitre sur les formulaires, nous avons fourni à Amélie le moyen de commencer à remplir la base de données, mais cela restera un travail fastidieux. De même pour les développeurs qui devront travailler sur l'application par la suite pour l'améliorer, y compris si c'est vous ! Ajouter des données de test à la main est très chronophage, et avoir des données prêtes rapidement leur faciliterait la vie.

Dans la plupart des cas, pendant le développement d’une application, il est très commun de travailler avec des données de test. Ces données nous permettent de contrôler l’état de notre application et de vérifier que tout ce que nous développons marche bien. On les appelle des fixtures. Plusieurs librairies PHP existent pour vous permettre d’en ajouter à votre projet. Nous allons utiliser la plus simple, qui fait partie de l’écosystème Doctrine : Doctrine Fixtures Bundle. Nous allons ajouter une deuxième dépendance au passage, appelée Foundry, qui va nous aider grandement.

Découvrez les librairies de fixtures

Pour l’ajouter à notre projet, ouvrez votre terminal et entrez la commande suivante :

symfony composer require orm-fixtures zenstruck/foundry --dev

Ici,  orm-fixtures  est un alias qui correspond en fait à la librairie  doctrine/doctrine-fixtures-bundle  .  zenstruck/foundry  est une deuxième librairie, que nous allons utiliser en complément.

C’est quoi ce  --dev  ?

C’est une option Composer pour différencier les dépendances qui nous servent dans tous les environnements de celles qui ne servent qu’en développement. Ouvrez votre fichier  composer.json  . En regardant bien, vous vous apercevrez que vous avez une section  require   et une section  require-dev  . 

{
    "type": "project",
    "license": "proprietary",
    "minimum-stability": "stable",
    "prefer-stable": true,
    "require": {
        "php": ">=8.2",
        // …
    },
    // …
    "require-dev": {
        "phpunit/phpunit": "^9.5",
        "symfony/browser-kit": "7.0.*",
        // …
    }
}

Concrètement, vous n’aurez pas besoin de fixtures lorsque l’application sera en production et ouverte au public. Au contraire, utiliser ce genre de librairie en production est une très mauvaise idée. En ajoutant l’option  --dev  , nous indiquons à Composer d’ajouter cette dépendance à la section  require-dev  . Ainsi, lorsque vous installerez votre application en production, vous n’aurez que ce dont vous avez besoin.

Revenons-en à nos fixtures. L’installation de Doctrine Fixtures Bundle a ajouté un dossier  DataFixtures  dans votre dossier source, et il contient un fichier  AppFixtures.php  . Ouvrez-le :

<?php

namespace App\DataFixtures;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class AppFixtures extends Fixture
{
    public function load(ObjectManager $manager): void
    {
        // $product = new Product();
        // $manager->persist($product);
        $manager->flush();
    }
}

Comme vous voyez, le fonctionnement est simple : vous créez des objets et vous les enregistrez, automatiquement.

Mais si je dois les créer un par un, ça fait un fichier énorme, non ?

Oui, c’est pour ça que nous allons demander à Foundry de les créer pour nous. Mieux, Foundry dispose d’une intégration avec MakerBundle ! Grâce à cette association de super-héros, nous allons pouvoir créer des classes appelées  factory  pour chacune de nos entités, et automatiser leur création.

Utilisez les fixtures dans vos projets

Retournez dans le terminal et lancez la commande suivante :

symfony console make:factory

Maker vous demande pour quelle classe nous souhaitons créer une factory. Faisons simple et répondons “All”, pour toutes les créer.

Création d'une factory

Ouvrez par exemple votre nouvelle  BookFactory  (dans  src/Factory/BookFactory.php  ) et regardez sa méthode  getDefaults  .

<?php
// …
final class BookFactory extends ModelFactory
{
    // …
    protected function getDefaults(): array
    {
        return [
            'cover' => self::faker()->text(255),
            'editedAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()),
            'isbn' => self::faker()->text(255),
            'pageNumber' => self::faker()->randomNumber(),
            'plot' => self::faker()->text(),
            'status' => self::faker()->randomElement(BookStatus::cases()),
            'title' => self::faker()->text(255),
        ];
    }
    // …
}

Cette méthode permet de retourner des valeurs par défaut pour les entités  Book  qui seront créées. Elle doit retourner un array dont les clés sont les noms des propriétés de la classe  Book  , et dont les valeurs sont celles qui seront mises dans chaque propriété. 

Et c’est quoi ce  faker  ?

C’est une dépendance qui a été installée en même temps que Foundry et dont le seul but est de générer des données aléatoirement pour vos fixtures ! Vous trouverez la liste de toutes les fonctions de Faker sur sa documentation officielle.

En cherchant un peu sur sa documentation, vous trouverez tout ce qu’il faut pour générer des données pertinentes pour nos factories :

  • des URL d'images pour nos couvertures (  imageUrl  ) ;

  • du texte d'une longueur arbitraire (  text  ) ;

  • des nombres aléatoires (  randomNumber  ) ;

  • une valeur au hasard dans un tableau (  randomElement  , auquel vous pouvez passer les différents status de votre énumération  BookStatus  par exemple, grâce à  BookStatus::cases()  si Maker ne l'a pas déjà fait) ;

  • des numéros ISBN (  isbn13  ) ;

  • et bien d'autres.

De plus, vous pouvez tout à fait appeler d'autres factories et leur demander de vous renvoyer un ou plusieurs objets pour représenter vos relations.

Une fois modifiée,  BookFactory::getDefaults  devrait ressembler à ça :

<?php
final class BookFactory extends ModelFactory
{
    // …
    protected function getDefaults(): array
    {
        return [
            'cover' => self::faker()->imageUrl(330, 500, 'couverture', true),
            'editedAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()),
            'editor' => EditorFactory::random(),
            'authors' => AuthorFactory::random(),
            'isbn' => self::faker()->isbn13(),
            'pageNumber' => self::faker()->randomNumber(),
            'plot' => self::faker()->text(),
            'status' => self::faker()->randomElement(BookStatus::cases()),
            'title' => self::faker()->unique()->sentence(),
        ];
    }
    // …
}

Complétez les factories pour Author, Editor et User de la même façon, avec les données qui vous semblent pertinentes.

Trois astuces :

1- Vous pouvez définir la langue utilisée par Faker en ajoutant dans  config/packages/zenstruck_foundry.yaml  les lignes suivantes :

when@dev: &dev
    zenstruck_foundry:
        # …
        faker:
            locale: fr_FR

2- Vous pouvez demander à ce qu'un champ contienne une valeur qui sera différente à chaque fois en ajoutant un appel à  unique()  comme dans l'exemple au-dessus.

3- Pour le mot de passe de vos utilisateurs de test, rappelez-vous que vous pouvez demander à recevoir les classes dont vous avez besoin dans le constructeur de vos factories, comme  UserPasswordHasherInterface  . Et pensez à donner le même mot de passe à tout le monde, sinon vous ne pourrez pas vous connecter…

Vous pouvez désormais appeler nos factories dans le fichier  src/DataFixtures/AppFixtures  (attention à l'ordre dans lequel vous les appelez, vos Authors et vos Editors doivent déjà exister pour pouvoir être liés à vos Books) :

<?php
class AppFixtures extends Fixture
{
    public function load(ObjectManager $manager): void
    {
        AuthorFactory::createMany(50);
        EditorFactory::createMany(20);
        UserFactory::createMany(5);
        BookFactory::createMany(100);
    }
}

Il ne vous reste plus qu’à lancer la commande qui permet de charger nos fixtures en base de données :  symfony console doctrine:fixtures:load  . Confirmez ensuite que vous souhaitez purger la base de données, et vos fixtures seront chargées.

Chargement des fixtures
Chargement des fixtures

Et voilà, vous avez une centaine de livres prêts à être utilisés pour vos tests !

À vous de jouer

Le code produit dans ce chapitre peut être complexe et déroutant, mais il vous donne un bon aperçu de ce qu'on peut faire pour faciliter nos tests et nos développements. Le corrigé est quant à lui disponible sur le dépôt GitHub du cours, dans le dossier du chapitre, comme d'habitude.

Vous avez de votre côté rempli le contrat qui avait été posé avec la médiathèque, et Amélie est plus que contente de son application. Grâce à vous, les habitants de Trifouillis-les-Oies peuvent désormais consulter le catalogue de leur médiathèque et connaître le statut de réservation des livres qui les intéressent. Félicitations !

En résumé

  • Les fixtures sont un moyen d’accélérer nos développements en créant rapidement des données de test.

  • Plusieurs librairies existent pour nous permettre de créer de telles fixtures.

  • Certaines librairies, comme Faker, permettent de générer des données aléatoires quand le besoin est simple.

Il est maintenant temps de finaliser ce cours en revenant sur le versioning et la façon de contribuer à Symfony ! Mais avant cela, je vous invite à tester vos connaissances dans le quiz final de ce cours !

Example of certificate of achievement
Example of certificate of achievement