Symfony. Migrando de ids a uuids en Doctrine

Descubre cómo migrar de ids autoincrementales generados por la base de datos a uuids

Puede que en algún momento quieras comenzar a usar uuids como identificadores de tus entidades. Esto permite centralizar en la aplicación la generación de los identificadores en vez de delegarlo a un agente externo, ganando un mayor control sobre ellos.

Si estás empezando una aplicación desde cero esto es trivial pero… ¿qué sucede si ya estabas usando identificadores autoincrementales en base de datos? Aquí la cosa se complica, porque es necesario cambiar no sólo las tablas de las entidades, sino también todas las claves foreáneas y esto ya no es tan sencillo.

Para ayudarte en esta tarea he escrito el siguiente artículo para mostrarte cómo puedes pasar de identificadores autoincrementales a uuids en una aplicación basada en Symfony + Doctrine.

¡Comencemos!

Instalación de ramsey/uuid-doctrine

Lo primero que haremos será instalar la librería ramsey/uuid-doctrine empleando composer:

composer require ramsey/uuid-doctrine

Esta librería permite añadir a Doctrine el tipo de columna uuid de modo que podamos usarlo en el mapeo de nuestras entidades (realmente un uuid no deja de ser una cadena de texto de 32 caracteres + 4 guiones). Además, también se traerá la propia librería ramsey/uuid que nos dará las interfaces necesarias para generar nuestros identificadores cuando lo necesitemos:

use Ramsey\Uuid\Uuid;

$uuid = Uuid::uuid4();

Tras la instalación se ejecutará una receta flex que se encargará de añadir la configuración necesaria para la definición del tipo uuid.

Migrando nuestras entidades a uuid

El siguiente paso es modificar el mapeo de nuestras entidades para comenzar a usar este nuevo tipo. En el caso de que estemos usando anotaciones modificaremos la definición anterior de la propiedad id:

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class SomeEntity
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;

Por la siguiente que empleará el nuevo tipo uuid:

use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Doctrine\UuidGenerator;

/**
* @ORM\Entity
*/
class SomeEntity
{
/**
* @var \Ramsey\Uuid\UuidInterface
*
* @ORM\Id
* @ORM\Column(type="uuid", unique=true)
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class=UuidGenerator::class)
*/

private $id;

❗️ Importante. Si te fijas, estamos añadiendo un CustomIdGenerator a nuestra propiedad id, de modo que el id será generado al vuelo cuando la entidad vaya a ser insertada en base de datos. Esto es justo lo que queremos evitar cuando trabajamos con uuids pero de momento necesitaremos ese autogenerador mientras migramos nuestros identificadores al nuevo sistema.

Preparando la migración

Supongamos que ahora nuestra entidad MyEntity tiene una relación ManyToMany con otra entidad llamada Category, por lo que en base de datos existe una tabla llamada my_entity_category que almacena dicha asociación. En esta tabla se encuentran definidas dos claves foráneas apuntando a las columnas id de las tablas my_entity y category . Es aquí donde nos va a surgir el problema.

Si generamos una migración para actualizar la base de datos:

bin/console make:migration

Veremos que se genera un nuevo archivo con la migración a realizar en base de datos:

ALTER TABLE my_entity CHANGE id id CHAR(36) NOT NULL COMMENT \'(DC2Type::uuid)\''ALTER TABLE my_entity_category CHANGE my_entity_id my_entity_id CHAR(36) NOT NULL COMMENT \'(DC2Type::uuid)\'';

Sin embargo, al ejecutarla con bin/console doctrine:migrations:migrate obtendremos el siguiente error:

General error referencing column and referenced column in a foreign key constraint are incompatible

Es decir al cambiar el tipo de la columna id MySQL detecta que los tipos asociados a sus claves foráneas son inválidos por lo que la migración falla.

¿Qué habría que hacer?

Los pasos a seguir para realizar una migración de id a uuid en base de datos podéis encontrarlos en el siguiente enlace a Stackoverflow:

Como veis no son pocos e implica trastear con las claves foráneas durante el proceso para dejar todo como estaba.

Sin embargo por suerte en Symfony tenemos un atajo en forma de bundle.

Id to uuid

Gracias al siguiente bundle:

La migración en base de datos es muy sencilla. Bastará con instalarlo mediante composer:

composer require habbim/id-to-uuid

Y escribir una migración personalizada donde especifiquemos el nombre de las tablas que se van a ver afectadas por el cambio a uuid (no hace falta especificar las tablas intermedias como my_entity_category )

<?php

namespace Application\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Habbim\IdToUuid\IdToUuidMigration;

class VersionXYZ extends IdToUuidMigration
{
public function postUp(Schema $schema): void
{
$this->migrate('my_entity');
}
}

A continuación ejecutaremos esta migración normalmente:

bin/console doctrine:migrations:migrate

y automágicamente veremos cómo nuestros identificadores han sido migrados a uuids.

Es aquí dónde se hace necesario haber mantenido la generación automática de nuestros uuids, de modo que al ejecutarse la migración queden ya generados:

* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class=UuidGenerator::class)

❗️ Por desgracia el bundle sólo funciona con la versión 2 de Doctrine. Supongo que se podrá forkear para que funcione con la 3 pero no me he lanzado a hacerlo.

Conclusiones

Este proceso que os describo aquí es el que he seguido en dos proyectos y no he tenido problemas. No obstante puede que en proyectos más complejos os veáis obligados o bien a forkear el bundle o bien a implementar por vosotros mismos los pasos, ya que las operaciones a realizar en base de datos son bastante sensibles.

Si queréis ver el proceso en directo os dejo el enlace al vídeo donde explico esto y mucho más:

¿Quieres recibir más artículos como este?

Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇

Entre paseo y paseo con Simba desarrollo en Symfony y React

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store