Actualización de la Nave Estelar: Añadir campos Slug y Timestamp
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeHan llegado nuevos requisitos de los almirantes del Cuartel General de la Flota Estelar. En lugar de ver el id
en la URL, como /starship/1
, quieren ver un nombre legible por humanos, como /starship/enterprise
. Para conseguirlo, tenemos que añadir un nuevo campo a nuestra entidad Starship
.
Añadir campos a una entidad existente
Podríamos añadirlo a mano, superfácilmente: añadir la propiedad, el getter, el setter y el atributo #[ORM\Column]
. ¡O podemos hacer trampas! Ejecuta:
symfony console make:entity Starship
En lugar de crear una nueva entidad, esta vez añadiremos campos a una entidad existente. Añade slug
, tipo string
, longitud 255
. "¿Debería ser anulable?" - no, pero eligeyes
por ahora. Añadamos otros 2 campos útiles: updatedAt
, datetime_immutable
, ¿anulable? sí, temporalmente, y createdAt
, datetime_immutable
, ¿anulable? sí.
Pulsa Enter
para salir del comando. Antes de crear la migración, ve a comprobar la entidadStarship
: src/Entity/Starship.php
:
// ... lines 1 - 8 | |
class Starship | |
{ | |
// ... lines 11 - 30 | |
#[ORM\Column(length: 255, nullable: true)] | |
private ?string $slug = null; | |
#[ORM\Column(nullable: true)] | |
private ?\DateTimeImmutable $createdAt = null; | |
#[ORM\Column(nullable: true)] | |
private ?\DateTimeImmutable $updatedAt = null; | |
// ... lines 39 - 153 | |
} |
¡Genial! E incluso podemos eliminar length: 255
por $slug
. Eso es por defecto.
Primera migración
Nuevo campo, ¡comprobado! ¿Pero existe la nueva columna en la base de datos? No! Eso es tarea para una migración.
En tu terminal, crea una con:
symfony console make:migration
Abre el nuevo archivo de migración. Recuerda que las migraciones se generan comparando la base de datos con la clase de entidad. Doctrine ve los nuevos campos en la clase, no ve las columnas correspondientes en la tabla, genera el SQL para solucionarlo y lo anida de forma segura en el método up()
:
// ... lines 1 - 12 | |
final class Version20241201203154 extends AbstractMigration | |
{ | |
// ... lines 15 - 19 | |
public function up(Schema $schema): void | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->addSql('ALTER TABLE starship ADD slug VARCHAR(255) DEFAULT NULL'); | |
$this->addSql('ALTER TABLE starship ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); | |
$this->addSql('ALTER TABLE starship ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); | |
$this->addSql('COMMENT ON COLUMN starship.created_at IS \'(DC2Type:datetime_immutable)\''); | |
$this->addSql('COMMENT ON COLUMN starship.updated_at IS \'(DC2Type:datetime_immutable)\''); | |
} | |
// ... lines 29 - 37 | |
} |
Es opcional, pero añadamos una descripción: "Añadir slug y timestamps a starship":
// ... lines 1 - 12 | |
final class Version20241201203154 extends AbstractMigration | |
{ | |
public function getDescription(): string | |
{ | |
return 'Add slug and timestamps to starship'; | |
} | |
// ... lines 19 - 37 | |
} |
En tu terminal, ejecuta la migración:
symfony console doctrine:migrations:migrate
¡Éxito! Se han añadido las nuevas columnas. Compruébalo ejecutando:
symfony console doctrine:query:sql 'SELECT name, slug, updated_at, created_at FROM starship'
¡Sí! Las columnas están ahí, pero siguen vacías. Con el tiempo, configuraremos Doctrine para que establezca automáticamente estos campos por nosotros. Pero antes, todas estas columnas deben ser obligatorias en la base de datos, lo que en Doctrine se conoce como nullable: false
.
Hacer que los campos sean obligatorios
Abre Starship
. Encima de $slug
, elimina nullable: true
. Esto significa ahoranullable: false
: ése es el valor por defecto. En otras palabras, esto indica a Doctrine que la columna debe ser obligatoria en la base de datos.
Establece también unique: true
para que sea una columna única. Para $updatedAt
y $createdAt
, elimina también nullable: true
.
// ... lines 1 - 8 | |
class Starship | |
{ | |
// ... lines 11 - 30 | |
#[ORM\Column(unique: true)] | |
private ?string $slug = null; | |
#[ORM\Column] | |
private ?\DateTimeImmutable $createdAt = null; | |
#[ORM\Column] | |
private ?\DateTimeImmutable $updatedAt = null; | |
// ... lines 39 - 153 | |
} |
Segunda migración
Una vez más, hemos realizado cambios en nuestra entidad que no se reflejan en la base de datos ¡Hora de migrar! Ejecuta:
symfony console make:migration
Abre la nueva migración. ¡Genial! En el método up()
, altera las tres columnas para convertirlas en NOT NULL
y crea un índice único en la columna slug
:
// ... lines 1 - 12 | |
final class Version20241201203519 extends AbstractMigration | |
{ | |
// ... lines 15 - 19 | |
public function up(Schema $schema): void | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->addSql('ALTER TABLE starship ALTER slug SET NOT NULL'); | |
$this->addSql('ALTER TABLE starship ALTER created_at SET NOT NULL'); | |
$this->addSql('ALTER TABLE starship ALTER updated_at SET NOT NULL'); | |
$this->addSql('CREATE UNIQUE INDEX UNIQ_C414E64A989D9B62 ON starship (slug)'); | |
} | |
// ... lines 28 - 37 | |
} |
Añade una descripción: "Hacer que slug y timestamps no sean anulables":
// ... lines 1 - 12 | |
final class Version20241201203519 extends AbstractMigration | |
{ | |
public function getDescription(): string | |
{ | |
return 'Make slug and timestamps not nullable'; | |
} | |
// ... lines 19 - 37 | |
} |
¡Ejecútalo en el terminal!
symfony console doctrine:migrations:migrate
¡Error! Estos campos no se pueden establecer en NOT NULL
porque contienen valores de null
. ¡Doh! Esta es una situación complicada en la que tenemos que hacer las cosas en 3 pasos: añadir las nuevas columnas, darles un valor a cada una y luego hacer que sean NOT NULL
.
Editar la migración con SQL personalizado
Vuelve a abrir la última migración. La mayoría de las veces, Doctrine hace todo el trabajo por nosotros, pero podemos añadir nuestro propio SQL a una migración.
En el método up()
, antes del SQL generado, escribe$this->addSql('UPDATE starship SET slug = id, updated_at = arrived_at,
created_at = arrived_at'):
// ... lines 1 - 12 | |
final class Version20241201203519 extends AbstractMigration | |
{ | |
// ... lines 15 - 19 | |
public function up(Schema $schema): void | |
{ | |
$this->addSql('UPDATE starship SET slug = id, created_at = arrived_at, updated_at = arrived_at'); | |
// ... lines 23 - 28 | |
} | |
// ... lines 30 - 39 | |
} |
Vamos a descomprimir esto. Estamos actualizando la tabla de naves estelares, haciendo que slug
sea igual a id
. ¿Por qué? Porque id
es único y no nulo, exactamente lo que necesitamos para slug
. También estamos haciendo queupdated_at
y created_at
sean iguales a arrived_at
. Sabemos que arrived_at
también es una marca de tiempo y no nula.
De nuevo en el terminal, vuelve a ejecutar las migraciones:
symfony console doctrine:migrations:migrate
¡Ha funcionado! Ejecuta de nuevo la consulta para ver los datos:
symfony console doctrine:query:sql 'SELECT name, slug, updated_at, created_at FROM starship'
¡Fíjate! Tres nuevos campos rellenados con datos.
Recargar las Fijaciones
Pero ahora tenemos un problema. ¡Maldita sea! Recarga los accesorios:
symfony console doctrine:fixtures:load
¡Explosión! No hay nada en nuestros dispositivos que establezca estos tres campos obligatorios.
Podríamos actualizar nuestro StarshipFactory
para establecer valores por defecto para estos campos... pero quiero mostrarte una forma diferente: un paquete de "extensión de doctrina" que puede establecerlos automáticamente. Es lo mejor, ¡y es lo siguiente!