Añadir nuevas propiedades
En nuestra entidad VinylMix, olvidé añadir antes una propiedad: votes. Vamos a llevar la cuenta del número de votos a favor o en contra que tiene una determinada mezcla.
Modificación con make:entity
Bien... ¿cómo podemos añadir una nueva propiedad a una entidad? Bueno, podemos hacerlo a mano: todo lo que tenemos que hacer es crear la propiedad y los métodos getter y setter. Pero, una forma mucho más fácil es volver a nuestro comando favorito make:entity:
php bin/console make:entity
Éste se utiliza para crear entidades, pero también podemos utilizarlo para actualizarlas. Escribe VinylMix como nombre de la clase y... ¡ve que existe! Añade una nueva propiedad: votes... conviértela en integer, di "no" a anulable... y pulsa "intro" para terminar.
¿El resultado final? Nuestra clase tiene una nueva propiedad... y métodos getter y setter a continuación.
| // ... lines 1 - 9 | |
| class VinylMix | |
| { | |
| // ... lines 12 - 31 | |
| #[ORM\Column] | |
| private ?int $votes = null; | |
| // ... lines 34 - 99 | |
| public function getVotes(): ?int | |
| { | |
| return $this->votes; | |
| } | |
| public function setVotes(int $votes): self | |
| { | |
| $this->votes = $votes; | |
| return $this; | |
| } | |
| } |
Generar una segunda migración
Bien, pensemos. Tenemos una tabla vinyl_mix en la base de datos... pero aún no tiene la nueva columna votes. Tenemos que modificar la tabla para añadirla. ¿Cómo podemos hacerlo? Exactamente igual que antes: ¡con una migración! En tu terminal, ejecuta:
symfony console make:migration
Luego ve a ver la nueva clase.
| // ... lines 1 - 12 | |
| final class Version20220718170741 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 vinyl_mix ADD votes INT NOT NULL'); | |
| } | |
| // ... lines 25 - 31 | |
| } |
¡Esto es increíble! Dentro del método up(), dice
ALTER TABLE vinyl_mix ADD votes INT NOT NULL
Así que vio nuestra entidad VinylMix, comprobó la tabla vinyl_mix en la base de datos y generó una diferencia entre ellas. Se dio cuenta de que, para que la base de datos se pareciera a nuestra entidad, tenía que alterar la tabla y añadir esa columna votes. Eso es simplemente increíble.
De vuelta al terminal, si ejecutas
symfony console doctrine:migrations:list
verás que reconoce ambas migraciones y sabe que no ha ejecutado la segunda. Para ello, ejecuta:
symfony console doctrine:migrations:migrate
Doctrine es lo suficientemente inteligente como para saltarse la primera y ejecutar la segunda. ¡Qué bien!
Cuando despliegues a producción, todo lo que tienes que hacer es ejecutar doctrine:migrations:migratecada vez. Se encargará de ejecutar todas y cada una de las migraciones que la base de datos de producción aún no haya ejecutado.
Dar valores por defecto a las propiedades
Bien, una cosa más rápida mientras estamos aquí. Dentro de VinylMix, la nueva propiedad votestiene como valor por defecto null. Pero cuando creamos un nuevo VinylMix, tendría mucho sentido poner los votos por defecto a cero. Así que cambiemos esto a = 0.
¡Genial! Y si hacemos eso, la propiedad en PHP ya no necesita permitir null... así que elimina el ?. Como estamos inicializando a un entero, esta propiedad siempre será un int: nunca será nulo.
| // ... lines 1 - 9 | |
| class VinylMix | |
| { | |
| // ... lines 12 - 32 | |
| private int $votes = 0; | |
| // ... lines 34 - 110 | |
| } |
Pero... Me pregunto... porque he hecho este cambio, ¿tengo que modificar algo en mi base de datos? La respuesta es no. Puedo probarlo ejecutando un comando muy útil:
symfony console doctrine:schema:update --dump-sql
Es muy parecido al comando make:migration... pero en lugar de generar un archivo con el SQL, sólo imprime el SQL necesario para actualizar tu base de datos. En este caso, muestra que nuestra base de datos ya está sincronizada con nuestra entidad.
La cuestión es: si inicializamos el valor de una propiedad en PHP... eso es sólo un cambio en PHP. No cambia la columna en la base de datos ni le da un valor por defecto, lo cual está totalmente bien.
Autoconfiguración de createdAt
Vamos a inicializar otro campo: $createdAt. Sería increíble que algo estableciera automáticamente esta propiedad cada vez que creamos un nuevo objeto VinylMix... en lugar de tener que establecerla nosotros manualmente.
Podemos hacerlo creando un método PHP __construct() a la vieja usanza. Dentro, digamos $this->createdAt = new \DateTimeImmutable(), que por defecto será ahora mismo.
| // ... lines 1 - 9 | |
| class VinylMix | |
| { | |
| // ... lines 12 - 34 | |
| public function __construct() | |
| { | |
| $this->createdAt = new \DateTimeImmutable(); | |
| } | |
| // ... lines 39 - 115 | |
| } |
Y ya está Y... ya no necesitamos el = null ya que se inicializará aquí abajo... y tampoco necesitamos el ?, porque siempre será un objetoDateTimeImmutable.
| // ... lines 1 - 9 | |
| class VinylMix | |
| { | |
| // ... lines 12 - 29 | |
| private \DateTimeImmutable $createdAt; | |
| // ... lines 31 - 115 | |
| } |
¡Muy bien! Gracias a esto, la propiedad $createdAt se establecerá automáticamente cada vez que instanciemos nuestro objeto. Y eso es sólo un cambio de PHP: no cambia la columna en la base de datos.
Muy bien, tenemos una entidad VinylMix y la tabla correspondiente. A continuación, vamos a instanciar un objeto VinylMix y guardarlo en la base de datos.
8 Comments
Hello
I somehow messed one of my tables badly, so when I run Symfony console make:migration and all my files are committed (no changes) this migration is generated:
My Questions:
symfony console doctrine:migrations:diffto generate the current SQL and then remove all migrations except the first one, and in the "up" method to add the generated code. However it didn't mention what should one add in the "down" method. Can this approach be used as a way to resolve messed up migrations?Hi
I managed to resolve my issues, and I am posting the solution here in case that someone has similar issue(s).
I solved my issues in 3 steps:
nullable:truein the#[ORM\Column(length: 255)]attribute that I had, so my attribute now looks like this:#[ORM\Column(length: 255, nullable:true)]Cheers
Hey @t5810!
Thanks for posting that! You got the correct solution :). I'm not sure how your database got into the state it was, but you did perfectly to change your options on your entity and THEN generate a migration to keep your database in sync.
Anyway, good job and keep going!
We changed $votes property never to be null. So can we remove "?" in getVotes method (or this will provide errors during for example form saving)? And the same question about getCreatedAt method
Hey @Dima
Yes, you're right. Since those fields are not nullable anymore, you can do that refactoring, but as you said, if you are using Symfony Forms to create new
VinylMixrecords, then you can't remove it from the$votespropertyCheers!
concerning default values...
Am I right to understand that the way you've shown here (i.e. creating the entity > altering the code to insert defaults > not migrating the code changes), the table would still accept
nullvalues, but no value would ever benullbecause I changed the code?If so, how could I make the table no longer accept
nullvalues? Should I run thesymfony console make:migrationcommand again? Or isn't there a way to do this once it's been set as nullable?...? Can anybody help me with the above question?
Hey!
Sorry we missed your original question somehow! Not cool!
Not quite :). There are two totally independent systems going on:
A) By adding the defaults in PHP, it means that the properties on the PHP object will never been
null. So, you've got that correct.B) But, whether or not the database will accept
nullvalues is determined by something else: thenullable: option onORM\Column. For example, you could have:In this case, in PHP, the
nameproperty could never benull. But, technically, you COULD insert a row into the database with anullvalue (and yes, if you changednullable: false, you would need to run a database migration for that).So, to answer this question:
The answer is: if you want a column in your table to NO accept
nullvalues (i.e.to explode with an error ifnullis sent), usenullable: falseon that property (or omit thenullableoption entirely, becausefalseis the default value fornullable).Let me know if that clears things up... or if I'm still being confusing :).
Cheers!
"Houston: no signs of life"
Start the conversation!