DoctrineFixturesBundle

Los accesorios se utilizan para cargar en una base de datos un juego de datos controlado. Puedes utilizar estos datos para pruebas o podrían ser los datos iniciales necesarios para ejecutar la aplicación sin problemas. Symfony2 no tiene integrada forma alguna de administrar accesorios, pero Doctrine2 cuenta con una biblioteca para ayudarte a escribir accesorios para el ORM u ODM de Doctrine.

Instalando y configurando

Los accesorios de Doctrine para Symfony se mantienen en el `DoctrineMigrationsBundle`_. El paquete utiliza la biblioteca externa de Datos accesorios de Doctrine.

Sigue estos pasos para instalar el paquete y la biblioteca en la edición estándar de Symfony. Añade lo siguiente a tu archivo composer.json:

{
    "require": {
        "doctrine/doctrine-fixtures-bundle": "dev-master"
    }
}

Actualiza las bibliotecas de proveedores:

$ php composer.phar update

Si todo va bien, el DoctrineFixturesBundle ahora se puede encontrar en vendor/doctrine/doctrine-fixtures-bundle.

Nota

DoctrineFixturesBundle instala la biblioteca de Datos accesorios de Doctrine. La biblioteca se puede encontrar en vendor/doctrine/data-fixtures.

Por último, registra el paquete DoctrineFixturesBundle en app/AppKernel.php.

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

Escribiendo accesorios sencillos

Los accesorios de Doctrine2 son clases PHP que pueden crear y persistir objetos a la base de datos. Al igual que todas las clases en Symfony2, los accesorios deben vivir dentro de uno de los paquetes de tu aplicación.

Para un paquete situado en src/Acme/HelloBundle, las clases accesorio deben vivir dentro de src/Acme/HelloBundle/DataFixtures/ORM o src/Acme/HelloBundle/DataFixtures/MongoDB, para ORM y ODM respectivamente, esta guía asume que estás utilizando el ORM — pero, los accesorios se pueden agregar con la misma facilidad si estás utilizando ODM.

Imagina que tienes una clase User, y te gustaría cargar un nuevo Usuario:

// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php

namespace Acme\HelloBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\HelloBundle\Entity\User;

class LoadUserData implements FixtureInterface
{
    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {
        $userAdmin = new User();
        $userAdmin->setUsername('admin');
        $userAdmin->setPassword('test');

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

En Doctrine2, los accesorios sólo son objetos en los que cargas datos interactuando con tus entidades como lo haces normalmente. Esto te permite crear el accesorio exacto que necesitas para tu aplicación.

La limitación más importante es que no puedes compartir objetos entre accesorios. Más adelante, veremos la manera de superar esta limitación.

Ejecutando accesorios

Una vez que has escrito tus accesorios, los puedes cargar a través de la línea de ordenes usando la orden doctrine:fixtures:load

php app/console doctrine:fixtures:load

Si estás utilizando ODM, en su lugar usa la orden doctrine:mongodb:fixtures:load:

php app/console doctrine:mongodb:fixtures:load

La tarea verá dentro del directorio DataFixtures/ORM (o DataFixtures/MongoDB para ODM) de cada paquete y ejecutará cada clase que implemente la FixtureInterface.

Ambas ordenes vienen con unas cuantas opciones:

  • --fixtures=/ruta/al/accesorio — Usa esta opción para especificar manualmente el directorio de donde se deben cargar las clases accesorio;
  • --append — Utiliza esta opción para añadir datos en lugar de eliminarlos antes de cargarlos (borrar primero es el comportamiento predeterminado);
  • --em=manager_name — Especifica manualmente el gestor de la entidad a utilizar para cargar los datos.

Nota

Si utilizas la tarea doctrine:mongodb:fixtures:load, reemplaza la opción --em= con --dm= para especificar manualmente el gestor de documentos.

Un ejemplo de uso completo podría tener este aspecto:

php app/console doctrine:fixtures:load --fixtures=/ruta/al/accesorio1 --fixtures=/ruta/al/accesorio2 --append --em=gestor_foo

Compartiendo objetos entre accesorios

Escribir un accesorio básico es sencillo. Pero, ¿si tienes varias clases de accesorios y quieres poder referirte a los datos cargados en otras clases accesorio? Por ejemplo, ¿qué pasa si cargas un objeto Usuario en un accesorio, y luego quieres mencionar una referencia a un accesorio diferente con el fin de asignar dicho usuario a un grupo particular?

La biblioteca de accesorios de Doctrine maneja esto fácilmente permitiéndote especificar el orden en que se cargan los accesorios.

// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php
namespace Acme\HelloBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\HelloBundle\Entity\User;

class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {
        $userAdmin = new User();
        $userAdmin->setUsername('admin');
        $userAdmin->setPassword('test');

        $manager->persist($userAdmin);
        $manager->flush();

        $this->addReference('admin-user', $userAdmin);
    }

    /**
     * {@inheritDoc}
     */
    public function getOrder()
    {
        return 1; // el orden en el cual serán cargados los accesorios
    }
}

La clase accesorio ahora implementa la OrderedFixtureInterface, la cual dice a Doctrine que deseas controlar el orden de tus accesorios. Crea otra clase accesorio y haz que se cargue después de LoadUserData devolviendo un orden de 2:

// src/Acme/HelloBundle/DataFixtures/ORM/LoadGroupData.php

namespace Acme\HelloBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\HelloBundle\Entity\Group;

class LoadGroupData extends AbstractFixture implements OrderedFixtureInterface
{
    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {
        $groupAdmin = new Group();
        $groupAdmin->setGroupName('admin');

        $manager->persist($groupAdmin);
        $manager->flush();

        $this->addReference('admin-group', $groupAdmin);
    }

    /**
     * {@inheritDoc}
     */
    public function getOrder()
    {
        return 2; // el orden en el cual serán cargados los accesorios
    }
}

Ambas clases accesorio extienden AbstractFixture, lo cual te permite crear objetos y luego ponerlos como referencias para que se puedan utilizar posteriormente en otros accesorios. Por ejemplo, más tarde puedes referirte a los objetos $userAdmin y $groupAdmin a través de las referencias admin-user y admin-group:

// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserGroupData.php

namespace Acme\HelloBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\HelloBundle\Entity\UserGroup;

class LoadUserGroupData extends AbstractFixture implements OrderedFixtureInterface
{
    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {
        $userGroupAdmin = new UserGroup();
        $userGroupAdmin->setUser($this->getReference('admin-user'));
        $userGroupAdmin->setGroup($this->getReference('admin-group'));

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

    /**
     * {@inheritDoc}
     */
    public function getOrder()
    {
        return 3;
    }
}

Los accesorios ahora se ejecutan en el orden ascendente del valor devuelto por getOrder(). Cualquier objeto que se establece con el método setReference() se puede acceder a través de getReference() en las clases accesorio que tienen un orden superior.

Los accesorios te permiten crear cualquier tipo de dato que necesites a través de la interfaz normal de PHP para crear y persistir objetos. Al controlar el orden de los accesorios y establecer referencias, puedes manejar casi todo por medio de accesorios.

Usando el contenedor en los accesorios

En algunos casos necesitarás acceder a algunos servicios para cargar los accesorios. Symfony2 hace esto realmente fácil. El contenedor se inyectará en todas las clases accesorio que implementen la Symfony\Component\DependencyInjection\ContainerAwareInterface.

Vamos a rescribir el primer accesorio para codificar la contraseña antes de almacenarla en la base de datos (una muy buena práctica). Esto utilizará el generador de codificadores para codificar la contraseña, garantizando que está codificada en la misma forma que la utiliza el componente de seguridad al efectuar la verificación:

// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php

namespace Acme\HelloBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Acme\HelloBundle\Entity\User;

class LoadUserData implements FixtureInterface, ContainerAwareInterface
{
    /**
     * @var ContainerInterface
     */
    private $container;

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

    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {
        $user = new User();
        $user->setUsername('admin');
        $user->setSalt(md5(uniqid()));

        $encoder = $this->container
            ->get('security.encoder_factory')
            ->getEncoder($user)
        ;
        $user->setPassword($encoder->encodePassword('secret', $user->getSalt()));

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

Como puedes ver, todo lo que necesitas hacer es agregar Symfony\Component\DependencyInjection\ContainerAwareInterface a la clase y luego crear un nuevo método setContainer() que implemente esa interfaz. Antes de ejecutar el accesorio, Symfony automáticamente llamará al método setContainer(). Siempre y cuando guardes el contenedor como una propiedad en la clase (como se muestra arriba), puedes acceder a él en el método load().

Nota

Si te da flojera implementar el método necesario setContainer(), entonces, puedes extender tu clase con Symfony\Component\DependencyInjection\ContainerAware.

Bifúrcame en GitHub