Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Adding new Properties

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

In our VinylMix entity, I forgot to add a property earlier: votes. We're going to keep track of the number of up votes or down votes that a particular mix has.

Modifying with make:entity

Ok... so how can we add a new property to an entity? Well, we can absolutely do it by hand: all we need to do is create the property and the getter and setter methods. But, a much easier way is to head back to our favorite make:entity command:

php bin/console make:entity

This is used to create entities, but we can also use it to update them. Type VinylMix as the class name and... it sees that it exists! Add a new property: votes... make it an integer, say "no" to nullable.. then hit "enter" to finish.

The end result? Our class has a new property... and getter and setter methods below.

... lines 1 - 9
class VinylMix
... lines 12 - 31
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;

Generating a Second Migration

Ok, let's think. We have a vinyl_mix table in the database... but it does not yet have the new votes column. We need to alter the table to add it. How can we do that? The exact same way as before: with a migration! At your terminal, run:

symfony console make:migration

Then go check out the new class.

... 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

This is amazing! Inside the up() method, it says


So it saw our VinylMix entity, checked out the vinyl_mix table in the database, and generated a diff between them. It realized that, in order to make the database look like our entity, it needed to alter the table and add that votes column. That's simply amazing.

Back over at the terminal, if you run

symfony console doctrine:migrations:list

you'll see that it recognizes both migrations and it knows that it has not executed the second one. To do that, run:

symfony console doctrine:migrations:migrate

Doctrine is smart enough to skip the first and execute the second. Nice!

When you deploy to production, all you need to do is run doctrine:migrations:migrate each time. It will handle executing any and all migrations that the production database hasn't yet executed.

Giving Properties Default Values

Ok, one more quick thing while we're here. Inside of VinylMix, the new votes property defaults to null. But when we create a new VinylMix, it would make a lot of sense to default the votes to zero. So let's change this to = 0.

Cool! And if we do that, the property in PHP no longer needs to allow null... so remove the ?. Because we're initializing to an integer, this property will always be an int: it will never be null.

... lines 1 - 9
class VinylMix
... lines 12 - 32
private int $votes = 0;
... lines 34 - 110

But... I wonder... because I made this change, do I need to alter anything in my database? The answer is no. I can prove it by running a helpful command:

symfony console doctrine:schema:update --dump-sql

This is very similar to the make:migration command... but instead of generating a file with the SQL, it just prints out the SQL needed to bring your database up to date. In this case, it shows that our database is already in sync with our entity.

The point is: if we initialize the value of a property in PHP... that's just a PHP change. It doesn't change the column in the database or give the column a default value, which is totally fine.

Auto-Setting createdAt

Let's initialize one other field: $createdAt. It would be amazing if something automatically set this property whenever we created a new VinylMix object... instead of us needing to set it manually.

Whelp, we can do that by creating a good, old-fashioned PHP __construct() method. Inside, say $this->createdAt = new \DateTimeImmutable(), which will default to right now.

... lines 1 - 9
class VinylMix
... lines 12 - 34
public function __construct()
$this->createdAt = new \DateTimeImmutable();
... lines 39 - 115

That's it! And... we don't need the = null anymore since it will be initialized down here... and we also don't need the ?, because it will always be a DateTimeImmutable object.

... lines 1 - 9
class VinylMix
... lines 12 - 29
private \DateTimeImmutable $createdAt;
... lines 31 - 115

Nice! Thanks to this, the $createdAt property will automatically be set every time we instantiate our object. And that's just a PHP change: it doesn't change the column in the database.

All right, we have a VinylMix entity and the corresponding table. Next, let's instantiate a VinylMix object and save it to the database.

Leave a comment!

Login or Register to join the conversation
t5810 Avatar

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:

public function up(Schema $schema): void
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('ALTER TABLE recruiter DROP CONSTRAINT FK_DE8633D8D8A48BBD');
        $this->addSql('DROP INDEX IDX_DE8633D8D8A48BBD');
        $this->addSql('ALTER TABLE recruiter ALTER postal_code SET NOT NULL');
        $this->addSql('ALTER TABLE recruiter RENAME COLUMN country_id TO country_id_id');
        $this->addSql('CREATE INDEX IDX_DE8633D8D8A48BBD ON recruiter (country_id_id)');

    public function down(Schema $schema): void
        // this down() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE SCHEMA public');
        $this->addSql('ALTER TABLE recruiter DROP CONSTRAINT fk_de8633d8d8a48bbd');
        $this->addSql('DROP INDEX idx_de8633d8d8a48bbd');
        $this->addSql('ALTER TABLE recruiter ALTER postal_code DROP NOT NULL');
        $this->addSql('ALTER TABLE recruiter RENAME COLUMN country_id_id TO country_id');
        $this->addSql('ALTER TABLE recruiter ADD CONSTRAINT fk_de8633d8d8a48bbd FOREIGN KEY (country_id) REFERENCES country (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
        $this->addSql('CREATE INDEX idx_de8633d8d8a48bbd ON recruiter (country_id)');

My Questions:

  1. My field postal_code in table recruiter is null, and I want to remain to be null. Why on earth generated migration "insists" to set it back to not null? What I have done wrong?
  2. I have renamed the country_id_id in country_id and I want to remain country_id. Why the generated migration is "insisting" to return back the old name with id_id?
  3. I had field named country, and I remove it, and I don't know if the IDX and the FK refers to that field or on some other. How can I prevent those "proposals" to show again and again in each generated migration?
  4. I have read somewhere that on possible way to resolve such issue is to use the command symfony console doctrine:migrations:diff to 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?
t5810 Avatar

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:

  1. I migrate the migration above
  2. Then in the Recruiter entity, I add the nullable:true in the #[ORM\Column(length: 255)] attribute that I had, so my attribute now looks like this: #[ORM\Column(length: 255, nullable:true)]
  3. I rename the countryId property in country, and my field in the database is now country_id, instead of country_id_id as it was



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!

1 Reply
Dima Avatar
Dima Avatar Dima | posted 4 months ago | edited

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 VinylMix records, then you can't remove it from the $votes property


1 Reply
wh Avatar

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 null values, but no value would ever be null because I changed the code?
If so, how could I make the table no longer accept null values? Should I run the symfony console make:migration command again? Or isn't there a way to do this once it's been set as nullable?

wh Avatar

...? Can anybody help me with the above question?



Sorry we missed your original question somehow! Not cool!

the table would still accept null values, but no value would ever be null because I changed the code?

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 null values is determined by something else: the nullable: option on ORM\Column. For example, you could have:

#[ORM\Column(nullable: true)
private $name = 'ryan';

In this case, in PHP, the name property could never be null. But, technically, you COULD insert a row into the database with a null value (and yes, if you changed nullable: false, you would need to run a database migration for that).

So, to answer this question:

how could I make the table no longer accept null values? Should I run the symfony console make:migration command again? Or isn't there a way to do this once it's been set as nullable?

The answer is: if you want a column in your table to NO accept null values (i.e.to explode with an error if null is sent), use nullable: false on that property (or omit the nullable option entirely, because false is the default value for nullable).

Let me know if that clears things up... or if I'm still being confusing :).


2 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.7", // v3.7.0
        "doctrine/doctrine-bundle": "^2.7", // 2.7.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.12", // 2.12.3
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.2", // v6.2.6
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
        "symfony/asset": "6.1.*", // v6.1.0
        "symfony/console": "6.1.*", // v6.1.2
        "symfony/dotenv": "6.1.*", // v6.1.0
        "symfony/flex": "^2", // v2.2.2
        "symfony/framework-bundle": "6.1.*", // v6.1.2
        "symfony/http-client": "6.1.*", // v6.1.2
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
        "symfony/runtime": "6.1.*", // v6.1.1
        "symfony/twig-bundle": "6.1.*", // v6.1.1
        "symfony/ux-turbo": "^2.0", // v2.3.0
        "symfony/webpack-encore-bundle": "^1.13", // v1.15.1
        "symfony/yaml": "6.1.*", // v6.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.1
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.1.*", // v6.1.0
        "symfony/maker-bundle": "^1.41", // v1.44.0
        "symfony/stopwatch": "6.1.*", // v6.1.0
        "symfony/web-profiler-bundle": "6.1.*", // v6.1.2
        "zenstruck/foundry": "^1.21" // v1.21.0