Doctrine2 et les fixtures dans Symfony2

Thibaut Cuvelier

Lors de tests, il est souvent pratique de repartir d'une base de données vierge ; or, une base de données vierge est… vierge de données. Les fixtures permettent, lors de la création de la base, d'ajouter des données, les fixtures.

Elles ne sont pas supportées de base dans Doctrine 2, au contraire de la première version, il faudra donc installer un bundle supplémentaire.

Commentez

- Titre : Doctrine2 et les fixtures dans Symfony2

- Auteur : Thibaut Cuvelier

- Parution : 8 août 2011

- Mise à jour : 10 août 2011

- Licence : Copyright ® 2011 Thibaut Cuvelier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Installation de Doctrine Data Fixtures

- II. Première fixture

- III. Ordre de chargement

- IV. Pour insérer plusieurs entités à la fois

- V. Remerciements

I - Installation de Doctrine Data Fixtures

Dans l'édition Standard de Symfony2, l'appel du script bin/vendors.php lance la mise à jour et le téléchargement d'une série de dépôts Git, précisés dans un fichier de dépendances. Par défaut, ce fichier est rempli avec le framework Symfony2 et les paquets nécessaires pour former l'édition standard. On peut cependant y ajouter de nouveaux dépôts à télécharger. C'est la méthode qui sera utilisée ici.

Tout d'abord, dans le fichier de dépendances deps, il faut ajouter ces quelques lignes :

[doctrine-fixtures]
git=http://github.com/doctrine/data-fixtures.git

[DoctrineFixturesBundle]
git=http://github.com/symfony/DoctrineFixturesBundle.git
target=/bundles/Symfony/Bundle/DoctrineFixturesBundle

Elles indiquent qu'il y a deux nouvelles dépendances à télécharger. Il faut ensuite lancer la mise à jour des vendors afin de les installer effectivement et, le cas échéant, de mettre à jour votre installation de Symfony2 :

bin/vendors install

Le bundle est maintenant installé, il faut que l'application l'inclue dans son noyau (kernel) et autoloading.

Il faut ajouter le nouvel espace de noms dans ceux connus de Symfony2 ; cependant, on ne peut pas ajouter cette entrée n'importe où : si on met le nouvel espace (Doctrine\Common\DataFixtures) après l'actuel de Doctrine (Doctrine\Common), toute requête sur cet espace finira en erreur, Symfony2 ira d'abord chercher dans l'espace commun de Doctrine.

Dans le fichier app/autoload.php :

$loader->registerNamespaces(array(
// ...
'Doctrine\\Common\\DataFixtures' => __DIR__.'/../vendor/doctrine-fixtures/lib',
'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',
// ...
));

Ensuite, il faut charger ce bundle. Pour ce faire, dans le fichier app/AppKernel.php :

public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(),
// ...
);
// ...
}

II - Première fixture

Les fixtures sont simplement des classes dont la méthode load() est exécutée. Ces classes doivent se trouver dans le dossier DataFixtures/ORM de votre bundle si vous utilisez l'ORM, DataFixtures/ODM pour l'ODM (MongoDB, CouchDB ou autres).

Pour le nommage, il est préférable d'utiliser la convention suivante : LoadEntityData, où Entity est le nom de l'entité.

On met dans le fichier un contenu similaire à ce qui suit :

<?php

namespace ...\...\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use ...\...\Entity\En;

class LoadEnData implements FixtureInterface
{
public function load($manager)
{
$en = new En();
// Quelques modifications sur l'entité pour qu'elle serve à quelque chose

$manager->persist($en);
$manager->flush();
}
}

Une fois ce fichier créé, rempli et adapté, il faut importer ces fixtures en lançant la commande suivante :

app/console doctrine:fixtures:load

En fonction de la base de données sous-jacente, il faudra possiblement adapter la commande :

app/console doctrine:mongodb:fixtures:load

Remarquons la flexibilité que ce système offre par rapport à son équivalent pour Doctrine dans sa première version : on peut générer très facilement des fixtures dynamiquement, en fonction des résultats d'une requête Google, par exemple.

III - Ordre de chargement

Il n'est pas possible en l'état de se baser sur l'existence ou non d'entités en base, alors que la définition de nouvelles entités requiert l'existence d'autres (on ne peut pas créer un article sans catégorie associée sur un blog, notamment).

On utilise alors un ordre de chargement : les fixtures seront chargées selon l'ordre de chargement indiqué. On le spécifie en définissant une méthode publique getOrder() dans le fichier de fixtures. Il renverra un entier : le fichier de fixtures ayant l'entier le plus bas sera chargé en premier, suivi des autres dans l'ordre croissant.

<?php

namespace ...\...\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use ...\...\Entity\En;

class LoadEnData implements FixtureInterface
{
public function load($manager)
{
$en = new En();
// Quelques modifications sur l'entité pour qu'elle serve à quelque chose

$manager->persist($en);
$manager->flush();
}

public function getOrder()
{
return 1;
}
}

IV - Pour insérer plusieurs entités à la fois

Il n'est pas rare de devoir insérer plusieurs entités à la fois, parfois même de créer les entités en fonction d'un résultat externe. Dans ces cas, il est probablement plus pratique d'externaliser la création de l'entité et sa persistance comme ceci :

<?php

namespace ...\...\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use ...\...\Entity\En;

class LoadEnData implements FixtureInterface
{
private $manager;

public function load($manager)
{
$this->manager = $manager;

$this->generateEntities();

$this->manager->flush();
}

public function getOrder()
{
return 1;
}

private function generateEntities()
{
$vars = array('cats', 'dogs');

foreach($vars as $v)
{
$this->newEntity($v);
}
}

private function newEntity($param)
{
$en = new En();
$en->setParam($param);
$this->manager->persist($en);
}
}

À noter que la fonction generateEntities() n'est pas requise, elle permet simplement d'avoir des fonctions courtes et qui n'ont qu'une seule utilité : load() initialise et lance la génération des entités et valide leur entrée en base de données ; generateEntities() génère les données nécessaires à la création de ces entités ; newEntity() crée l'entité.

V - Remerciements

Merci à Claude Leloup pour sa relecture orthographique !