Parte 3: Cómo crear datos falsos en una base de datos con Symfony 6
En este tutorial aprenderemos a usar herramientas para facilitar la creación de datos falsos en nuestra base de datos para un proyecto con Symfony 6.
¡Hola! 👋 Seguimos con la motivación a tope, así que siguen saliendo tutoriales de Symfony 6 🤣. Esta semana continuamos con la serie de tutoriales para crear un blog con Symfony 6 y esta vez vamos a ver como podemos crear datos falsos de forma sencilla para trabajar en nuestro proyecto en el entorno local.
Antes de empezar 🛑. Si te has perdido alguno de los tutoriales de esta serie, aquí te dejo todos los tutoriales escritos hasta ahora:
- Parte 1: Cómo crear una webapp con Symfony 6
- Parte 2: Cómo crear entidades, migraciones y conectarnos a una base de datos con Symfony 6
- Parte 3: Cómo crear datos falsos en una base de datos con Symfony 6
- Parte 4: Controladores, templates y estilos con Tailwind CSS en Symfony 6
- Parte 5: Optimización de queries con Doctrine, ajustes y página de post en Symfony 6
- Parte 6: Autenticación y registro con Symfony 6
Requisitos 🤔
Para empezar, necesitaremos instalar dos nuevos paquetes a nuestro proyecto, DoctrineFixturesBundle y Foundry. Ya que son para trabajar en local, solo los instalaremos en un entorno de desarrollo utilizando el flag --dev.
composer require orm-fixtures --dev
composer require zenstruck/foundry --dev
DoctrineFixturesBundle
Este paquete nos permite de forma fácil cargar datos de prueba en la base de datos. Podemos definir "fixtures", que son scripts PHP que podremos configurar para definir como poblamos la base de datos. Por ejemplo, crea 6 usuarios o crea 10 post y asígnale uno de los usuarios existentes. Esto lo veremos a continuación con más profundidad.
Al instalar este paquete ya nos habrá generado un fixture llamado src/DataFixtures/AppFixtures.php que podremos configurar más adelante para cargar nuestros datos.
Foundry
El paquete Foundry nos permitirá crear los Factories de forma sencilla, los cuales son clases que utilizaremos para crear y configurar los datos que queremos generar para poder poblar la base de datos.
Creación de los Factories ⚒️
Como he dicho antes vamos a crear los Factories. Gracias a la instalación de Foundry ahora tenemos nuevos comandos y el siguiente nos ayudará a crear un Factory para cada entidad, el cual nos permitirá generar los datos de prueba.
Para ello lanzamos el siguiente comando:
php bin/console make:factory
Como en ocasiones anteriores, se ejecutará el asistente y nos preguntará para qué entidades queremos que nos cree los Factories. Le diremos que para todas.
Una vez hecho esto, se creará una carpeta llamada Factory dentro de src y esta contendrá cuatro archivos, uno por cada entidad.
Si los revisamos, puedes ver que tenemos una clase que extiende de ModelFactory. Al extender de esta clase, nos permitirá acceder a un objeto llamado faker() el cual nos permitirá generar datos de prueba.
Os dejo un enlace a su documentación para que veáis todos los tipos de datos que se pueden generar:
También, en todos los factories, hay un método llamado getDefaults(). Este método indica que tipo de datos queremos que almacenen nuestras tablas. Un ejemplo puede ser que genere un texto, o una fecha, un número, etc.
Para que funcionen exactamente como queremos, vamos a editarlos y el primero será el archivo src/Factory/PostFactory.php:
use Symfony\Component\String\Slugger\AsciiSlugger;
....
protected function getDefaults(): array
{
$slugger = new AsciiSlugger();
$title = self::faker()->unique()->sentence();
$created_and_publicated = \DateTimeImmutable::createFromMutable(self::faker()->dateTime());
return [
'content' => self::faker()->text(),
'created_at' => $created_and_publicated,
'publication_date' => $created_and_publicated,
'is_draft' => false,
'slug' => strtolower($slugger->slug($title)),
'title' => $title,
];
}
Como puedes ver, he importado una librería nueva llamada AsciiSlugger que nos permitirá convertir el título a slug.
Dentro del array de configuración he modificado.
- En content, utilizamos el objecto faker() para generar un texto aleatorio utilizando el método text(). Recuerda que más arriba te deje un enlace con la doc. de faker para que veas todos los tipos de datos que puedes generar y su funcionamiento más en profundidad.
- El campo created_at guardamos el valor que viene por defecto en la variable $created_and_publicated y lo asignamos tanto en created_at como en publication_date.
- is_draft para que siempre sea false y así podamos visualizarlos todos.
- El título lo cargué anteriormente en una variable y utilicé faker para generar una frase única que nos sirva de título.
- El campo slug he almacenado el valor de la variable título convertido a slug.
- He eliminado user, ya que lo asignaremos desde otra parte.
El siguiente a editar es src/Factory/TagFactory.php:
protected function getDefaults(): array
{
return [
'name' => self::faker()->unique()->word(),
];
}
En este solo he modificado el tipo de dato para que sea una palabra en vez de un texto.
El Factory de comentarios solo necesita un cambio, y es modificar el UserFactory de new() a random() para que cuando creemos un comentario, este utilice uno de los existentes y no genere un usuario nuevo. En el caso de post no hace falta tocarlo, ya que le forzaremos un post desde otra parte.
protected function getDefaults(): array
{
return [
'content' => self::faker()->text(),
'post' => PostFactory::new(),
'created_at' => new \DateTimeImmutable(),
'user' => UserFactory::random()
];
}
Por último, vamos a editar el Factory de usuarios que además será el que tendrá más chicha, src/Factory/UserFactory.php:
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
...
public function __construct(private UserPasswordHasherInterface $hasher)
{
parent::__construct();
}
...
protected function getDefaults(): array
{
return [
'email' => self::faker()->email(),
'password' => 'login123',
'roles' => ['ROLE_USER'],
'username' => self::faker()->unique()->name(),
];
}
...
protected function initialize(): self
{
return $this
->afterInstantiate(function(User $user): void {
$user->setPassword(
$this->hasher->hashPassword($user, $user->getPassword())
);
})
;
}
En este caso hemos realizado los siguientes cambios:
- Hemos importado la librería UserPasswordHasherInterface la cual utilizaremos para hashear los passwords en la base de datos. De esta manera nunca tendremos el password en texto plano de nuestro usuario.
- En el constructor, inyectamos la dependencia de UserPasswordHasherInterface. De esta manera, ya tenemos el objeto para hashear nuestros passwords listo para ser utilizado.
- Ya en getDefaults(), he cambiado el faker del email para que sea de tipo email.
- El password lo he forzado con el valor "login123" para que conozcamos el login por defecto de cualquier usuario si queremos hacer pruebas con nuestros datos falsos.
- Ya que estamos usando el sistema de roles de Symfony, he añadido el rol ROLE_USER. Este rol podremos usarlo más adelante para limitar accesos a ciertas páginas solo los usuarios que estén registrados y autenticados.
- Por último modifiqué username para que reciba un nombre único.
En el método initialize(), realicé las modificaciones para que cuando se cree un usuario, antes de insertarlo en la base de datos, hashee el password.
Configurar los datos que queremos crear 🧑💻
Para generar los datos, tenemos que editar un último archivo, este se llama src\DataFixtures\AppFixtures.php y como comenté anteriormente, este se generó cuando instalamos el paquete orm-fixtures.
use App\Factory\TagFactory;
use App\Factory\PostFactory;
use App\Factory\CommentFactory;
use App\Factory\UserFactory;
...
public function load(ObjectManager $manager): void
{
UserFactory::createOne([
'username' => 'Admin',
'email' => 'admin@blog-symfony.com',
'roles' => ['ROLE_ADMIN']
]);
TagFactory::createMany(20);
UserFactory::createMany(10);
PostFactory::createMany(20, function () {
return [
'comments' => CommentFactory::new()->many(0, 8),
'tags' => TagFactory::randomRange(1, 5),
'user' => UserFactory::random(),
];
});
}
En esta clase, primero importaremos todos nuestros factories, ya que los necesitaremos para generar nuestros datos.
Toda la lógica de la generación de datos se realizará desde el método load() así que ahí he añadido la funcionalidad que necesitamos y que explicaré a continuación.
Primero he creado un usuario con una configuración por defecto. Para ello utilizo el método createOne() que nos permite crear un registro y recibe un array con la información que quiero forzar. Si no enviamos este parámetro, utilizará la configuración que asignamos en getDefaults() en UserFactory.
Esto lo hacemos así para forzar a que tenga un email que conozcamos y no tengamos que mirar la bbdd cada vez que la reiniciemos los datos para buscar un usuario con el que autenticarnos. Además, le asigné el rol ROLE_ADMIN que más adelante veremos para qué sirve, aunque el nombre de alguna pista 🤣.
El siguiente paso ha sido crear 20 tags nuevos con el método createMany() el cual recibe como primer parámetro el número de registros a crear, esta vez sin enviar ninguna configuración adicional. Estos los utilizaremos posteriormente para asignárselos a nuestros posts.
Después he creado 10 usuarios más. Al igual que con tags, esta vez no he forzado ninguna configuración adicional.
Por último, he generado 20 posts. En este caso, como puedes ver, recibe una función anónima. Esta función se ejecutará cada vez que se cree uno de los 20 post y en ella realizo tres acciones.
- Primero, para comments, uso CommentFactory::new()->many(0, 8) el cual generará de 0 a 8 comentarios nuevos y estarán asociados a este post.
- Después, para tags, uso TagFactory::randomRange(1, 5). El método randomRange, escogerá de 1 a 5 tags existentes y los asociará a este post.
- Por último, utilizo UserFactory::random(). El método random retornará uno de los usuarios ya existentes y los asociará a este post.
Para finalizar, solo nos queda generar los datos. Para ello debemos lanzar el siguiente comando:
php bin/console doctrine:fixtures:load
Nos aparecerá el asistente y nos avisará que antes de insertar los datos purgará los datos ya existentes. Como es la primera vez nos da igual, pero en el futuro si lo vais a utilizar y tenéis algún dato que os interese conservar tened esto en cuenta.
Careful, database "blog_symfony" will be purged. Do you want to continue? (yes/no) [no]:
> yes
> purging database
> loading App\DataFixtures\AppFixtures
Y listo, ya tenemos los datos de pruebas, así que con esto podemos dar por cerrado este tutorial. Nos vemos en el siguiente en que aprenderemos a utilizar las plantillas y empezaremos a ver nuestro blog en funcionamiento 💪.
Como siempre también os dejo en enlace al repo https://github.com/albertorc87/blog-symfony-tutorial/tree/como-crear-datos-falsos-base-de-datos-symfony-6
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 👋.