Grâce à Faker, générez des données aléatoires dans votre base de données

Difficulté : | 10' Publié il y a 5 mois
Lorsque l'on développe un site avec Symfony, il est souvent pratique d'utiliser les fixtures pour remplir sa BDD de valeurs bidons. Le problème c'est qu'il faut écrire chacune de ces fausses données à la mano, une par une. C'est là qu'utiliser le petit framework Faker peut nous être utile, en nous permettant de créer beaucoup de fausses données lisibles par un humain.

Faker : le générateur de fixtures

Faker est un framework php créé par l'excellent François Zaninotto permettant de générer facilement tout type de données. Il est utilisable comme une bibliothèque indépendante mais nous allons voir ici comment l'intégrer dans un projet Symfony3 pour remplir la BDD de son site.

Concrètement Faker génère facilement des données compréhensibles par un humain, et pas des valeurs aléatoires type "Lorem ispum" !

C'est-à-dire que le code suivant :

$faker = Faker\Factory::create('fr_FR');

$populator = new Faker\ORM\Doctrine\Populator($faker, $em);
$populator->addEntity(User::class, 10);
$insertedPKs = $populator->execute();


permet d'obtenir ces utilisateurs en base de données :

exemple d'utilisateurs en BDD générés par Faker

Ce qui est également intéressant c'est qu'en changeant le deuxième paramètre de la méthode addEntity (10 dans cet exemple) je peux facilement me retrouver avec des centaines voire des milliers d'utilisateurs initialisés dans mon site !

Initialisation pour générer de la donnée

Dans un premier temps il faut installer Faker en utilisant composer :

composer require fzaninotto/faker

Ensuite vous devez créer un fichier de fixtures, par exemple src/AppBundle/DataFixtures/ORM/LoadUsers.php dont voilà un exemple :

<?php
// src/AppBundle/DataFixtures/ORM/LoadUsers.php

namespace AppBundle\DataFixtures\ORM;

use AppBundle\Entity\User;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Faker;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LoadUsers extends AbstractFixture implements ContainerAwareInterface, FixtureInterface, OrderedFixtureInterface
{
    private $container;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    public function load(ObjectManager $em)
    {
        // initialisation de l'objet Faker
        // on peut préciser en paramètre la localisation, 
        // pour avoir des données qui semblent "françaises"
        $faker = Faker\Factory::create('fr_FR');

        $populator = new Faker\ORM\Doctrine\Populator($faker, $em);
        $populator->addEntity(User::class, 10); 
        // le deuxième paramètre (10) correspond au nombre d'objets qui vont être créés
        $insertedPKs = $populator->execute();
    }

    public function getOrder()
    {
        return 1;
    }
}

que vous pouvez lancer en utilisant la commande

bin/console doctrine:fixtures:load

Pour plus d'info sur le Populator, qui permet de générer automatiquement les données d'une entité je vous renvoie vers la doc.

Par contre attention à ne pas utiliser cette manière de faire pour alimenter en données des entités possédant des relations avec d'autres entités, cette fonctionnalité n'est plus supportée.

Génération de données avec Faker et Symfony : exemple complexe

Dans le cas où l'on possède des entités avec des relations entre elles, il est préférable de générer les entités une par une dans des boucles. En faisant cela, on peut facilement assigner telle entité à une autre ou en rajouter une à la collection d'une autre.

Par exemple, si j'ai des Customers qui peuvent être liés à plusieurs Shops :

<?php
// src/AppBundle/DataFixtures/ORM/LoadData.php

namespace AppBundle\DataFixtures\ORM;

use AppBundle\Entity\Customer;
use AppBundle\Entity\Shop;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
// import de la classe Faker
use Faker;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LoadData extends AbstractFixture implements ContainerAwareInterface, FixtureInterface, OrderedFixtureInterface
{
    private $container;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    public function load(ObjectManager $em)
    {
        // initialisation de l'objet Faker
        $faker = Faker\Factory::create('fr_FR');

        // créations des shops
        $shops = [];
        for ($i=0; $i < 10; $i++) {
            $shops[$i] = new Shop();
            $shops[$i]->setName($faker->company)
                ->setSiret($faker->siret)
                ->setReference($faker->numberBetween(111111, 999999))
                ->setContactName($faker->name)
                ->setAddress($faker->address)
            ;
            $em->persist($shops[$i]);
        }

        // créations des customers
        $customers = [];
        for ($k=0; $k < 50; $k++) {
            $customers[$k] = new Customer();
            $customers[$k]->setFirstName($faker->firstName)
                ->setLastName($faker->lastName)
                ->setEmail($faker->mail)
                ->setPhone($faker->phone)
                ->setAddress($faker->address)
            ;

            // on récupère un nombre aléatoire de Shops dans un tableau
            $randomShops = (array) array_rand($shops, rand(1, count($shops)));
            // puis on les ajoute au Customer
            foreach ($randomShops as $key => $value) {
                $customers[$k]->addShop($shops[$key]);
            }
            $em->persist($customers[$k]);
        }

        $em->flush();
    }

    public function getOrder()
    {
        return 1;
    }
}

Fixtures et Faker : astuces

Choisir son type de donnée

Comme vous avez pu le voir dans l'exemple précédent, on peut choir le type de donnée qui nous intéresse en accédant à la propriété de l'objet $faker qui correspond.

Par exemple si on veut générer des emails on pourra faire $faker->email, ou si on veut le code hexadécimal d'une couleur $faker->hexcolor

Ces propriétés sont appelées Formatters et sont très nombreuses, et pour ne rien gâcher elles sont extensibles par vous (voir ci-dessous).

Avoir des données identiques à chaque génération

Vous pouvez avoir besoin de garder les mêmes données à chaque initialisation des fixtures. Il est possible de générer le même jeu de données à chaque fois en utilisant la méthode seed() :

$faker = Faker\Factory::create('fr_FR');
$faker->seed(1337);
// votre code

Le nombre fourni en paramètre peut être ce que vous voulez, mais si vous laissez le même d'un lancement des fixtures à l'autre les données seront identiques. Par contre cela ne fonctionne que pour la même machine. D'un ordinateur à l'autre le jeu de données sera différent.

Rajouter ses propres méthodes Faker

Si vous avez besoin de générer un type de donnée qui n'est pas pris en compte par Faker, il peut être intéressant de créer son Faker Provider.

Pour cela il faut créer une class qui étend \Faker\Provider\Base dans laquelle vous pouvez ajouter vos méthodes personnelles.

Pour plus d'informations je vous invite à RTFM comme on dit !

GitHub - fzaninotto/Faker: Faker is a PHP library that generates fake data for you.

Tags de l'article :

bonnes pratiques Doctrine Performance

Commentaires