Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Answer Entity

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.

Oh hey there friends! Welcome back to part 2 of our Doctrine in Symfony series... you wonderful database nerds you.

Last time we mastered the basics, but good stuff! Creating an entity, migrations, fixtures, saving, querying and making the perfect omelette... I think. This time, we're going to do some mega study on Doctrine relations.

Project Setup

So let's get our project rocking. To avoid foreign key constraints in your brain while watching the tutorial, I recommend downloading the course code from this page and coding along with me. After unzipping the file, you'll find a start/ directory with all the fancy files that you see here. Check out the README.md file for all the fun details on how to get this project running.

The last step will be to find a terminal, move into the project and run:

symfony serve -d

I'm using the Symfony binary to start a local web server. Let's go see our site. Spin over to your browser and head to

Oh, hey there Cauldron Overflow! This is a site where the budding industry of witches and wizards can come to ask questions... after - sometimes - prematurely shipping their spells to production... and turning their clients into small adorable frogs. It could be worse.

The questions on the homepage are coming from the database... we rock! We built a Question entity in the first tutorial. But if you click into a question... yea. These answers? These are totally hard-coded. Time to change that.

Making the Answer Entity

I want you to, for now, forget about any potential relationship between questions and answers. It's really simple: our site has answers! And so, if we want to store those answers in the database, we need an Answer entity.

At your terminal, let's generate one. Run:

symfony console make:entity

Now, as a reminder, symfony console is just a fancy way of saying php bin/console. I'm using the Docker & Symfony web server integration. That's where the Symfony web server reads your docker-compose.yaml file and exposes environment variables to the services inside of it. We talked about that in the first Symfony 5 tutorial. By using symfony console - instead of running bin/console directly - my commands will be able to talk to my Docker services... which for me is just a database. That's not needed for this command, but it will be for others.

Anyways, run this and create a new entity called Answer. Let's give this a few basic properties like content which will store the answer itself. Set this to a text type: the string type maxes out at 255 characters. Say "no" to nullable: that will make this column required in the database.

Let's also add a username property, which will be a string. Eventually, in the security tutorial, we'll change this to be a relationship to a User entity. Use the 255 length and make it not nullable.

Oh, and one more: a votes property that's an integer so that people can up vote and down vote this answer. Make this not nullable and... done! Hit enter one more time to finish.

... lines 1 - 2
namespace App\Entity;
use App\Repository\AnswerRepository;
use Doctrine\ORM\Mapping as ORM;
* @ORM\Entity(repositoryClass=AnswerRepository::class)
class Answer
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
private $id;
* @ORM\Column(type="text")
private $content;
* @ORM\Column(type="string", length=255)
private $username;
* @ORM\Column(type="integer")
private $votes;
public function getId(): ?int
return $this->id;
public function getContent(): ?string
return $this->content;
public function setContent(string $content): self
$this->content = $content;
return $this;
public function getUsername(): ?string
return $this->username;
public function setUsername(string $username): self
$this->username = $username;
return $this;
public function getVotes(): ?int
return $this->votes;
public function setVotes(int $votes): self
$this->votes = $votes;
return $this;

Timestampable and Default votes Value

Before we generate the migration, go open up that class: src/Entity/Answer.php. So far... there's nothing special here! It looks pretty much like our other entity. Oh, but if you're using PHP 8, then the command may have generated PHP 8 attributes instead of annotations. That's great! They work exactly the same and you should use attributes if you can.

At the top of the class, add use TimestampableEntity. We talked about that in the last tutorial: it adds nice createdAt and updatedAt properties that will be set automatically.

... lines 1 - 6
use Gedmo\Timestampable\Traits\TimestampableEntity;
... lines 8 - 11
class Answer
use TimestampableEntity;
... lines 15 - 77

Oh, and one other thing: default the votes to zero. I made this column not nullable in the database. Thanks to this = 0, if we do not set the votes on a new answer, instead of getting a database error about null not being allowed, the Answer will save with votes = 0.

... lines 1 - 11
class Answer
... lines 14 - 32
* @ORM\Column(type="integer")
private $votes = 0;
... lines 37 - 77

Making the Migration

Now let's generate the migration. Find your terminal and run:

symfony console make:migration

As a reminder, this command is smart: it looks at all of your entities and your actual database structure, and generates the SQL needed to make them match. Go check out that new file... it's in the migrations/ directory. And... perfect! CREATE TABLE answer... and then it adds all of the columns.

... lines 1 - 2
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
* Auto-generated Migration: Please modify to your needs!
final class Version20210902130926 extends AbstractMigration
public function getDescription(): string
return '';
public function up(Schema $schema): void
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE answer (id INT AUTO_INCREMENT NOT NULL, content LONGTEXT NOT NULL, username VARCHAR(255) NOT NULL, votes INT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
public function down(Schema $schema): void
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE answer');

Run the migration with:

symfony console doctrine:migrations:migrate

All good! Our database now has a question table and an answer table. Next, let's relate them.

Leave a comment!

Login or Register to join the conversation
Joel R. Avatar

I'm trying to update my code that I've already created from previous S6 tutorial videos to align with the changes made ahead of this series to include stimulus and webpack encore. I believe I've added all of the dependecies I need and am running npm run watch to watch and bulid my webpack for my site. However, I'm getting the below error. I'm not familiar enough with nodejs to understand. Is it saying I'm missing webpack-encore? I've previously run composer require symfony/webpack-encore-bundle so that shouldn't be it. Also ran npm install encore just to be sure and am still getting the same issue.

root@2236ca7d93cd:/var/www/symfony_docker# npm run watch

> watch
> encore dev --watch

throw err;

Error: Cannot find module '../lib/config/parse-runtime'
Require stack:
- /var/www/symfony_docker/node_modules/.bin/encore
at Module._resolveFilename (node:internal/modules/cjs/loader:956:15)
at Module._load (node:internal/modules/cjs/loader:804:27)
at Module.require (node:internal/modules/cjs/loader:1022:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/var/www/symfony_docker/node_modules/.bin/encore:13:22)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
at Module.load (node:internal/modules/cjs/loader:998:32)
at Module._load (node:internal/modules/cjs/loader:839:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) {
requireStack: [ '/var/www/symfony_docker/node_modules/.bin/encore' ]

Node.js v18.6.0

UPDATE I finally gave up trying to figure this out and just downloaded the sample file package, deleted my previous work, and started from the start directory. After running composr install, npm install and npm update, npm run watch is now working. I'm still curious on what this was if anybody has any thoughts.

1 Reply
Amine Avatar


I think you forgot to put

$answer->setCreatedAt(new \DateTime('now'));
$answer->setUpdatedAt(new \DateTime('now'));

Default user avatar
Default user avatar steb mion | posted 10 months ago

Hello, thank you for great work. But I have problems with the installation. I am using postgres and I get blocked by sql errors while doing the migrations. Any quick solution?


Hey Steb!

Oh, yeah... our migrations are created for MySQL server, you can see it when open any migration. If you want to use a separate DB server - you would need to re-create those migration for your DB server. But the quick solution would be to do not use our migrations at all and instead execute:
bin/console doctrine:database:create
bin/console doctrine:schema:update --force

This will create the DB schema from scratch, but keep in mind that you may lose some data if you already have a DB with good data, but if no good data in your DB or you don't care if some information will be lost - then great.

Another solution would be to recreate all the migrations from scratch, you need to drop the DB, then create a new empty one, and run "bin/console make:migration" that will create a migration for your DB server. Then just migrate like we do in the README.


Default user avatar

Thank you Victor ! I should have thought about le --force option. It worked great !


Hey Steb,

Perfect! Yeah, creating schema via "doctrine:schema:create" command or using "doctrine:schema:update" is a good trick locally :) Migrations in turn are mostly needed for production.


Sajeev N. Avatar
Sajeev N. Avatar Sajeev N. | posted 1 year ago

Thank you for all the amazing work. One question I had is how do we use php8 attributes to replace Gedmo annotations? I switched doctrine's config to use attributes instead of annotations and replaced all the ORM annotations for the Question entity, however I am not sure how that works for the Gedmo extension. Also, is there a way to regenerate entity classes with attributes instead of annotations :D


Hey Sajeev N.

Unfortunately Gedmo is not ready for PHP8 attributes =( There is a pull request to add support, but it's still a draft https://github.com/doctrine...

So you have 2 choices:
1. do not use attributes until gedmo updates
2. do not use gedmo and add everything you need manually (sometimes it's not to hard)



Hello SymfonyCast Team - once again my big gratitude for your work! Amazing tutorial!
With you're huge help I have managed to finished my studies and my ecommerce project - and finally I got employed as a junior php dev.
Hope you will continue your awesome work!!
I have an of topic question - maybe you could suggest any tuts/resources besides documentation for Sylius, as for begginers.
Would really appreciate it. :)


Hey Andrejus.

Congratulations! You made our day ;) And good luck with your job as PHP dev!

We're going to release a course about Sylius some day, but not plans to release it in the near future, sorry! Also, I don't have any specific recommendations unfortunately, but I bet you may find something on the internet. Also, the best friend is always docs, so make sure to check it as well: https://sylius.com/ . And looks like they even have their own online course about it: https://sylius.com/online-c... - but I can't tell you anything about it.


1 Reply

Thank you for amazing tutorial! You are perfect there !

Q: Where is link to Doctrine part 1? Or it is mean Symfony 4 course?

P.s. Something happend with your voice, too tired or after Covid....


Hey Mepcuk!

Ah, I'm so happy you're liking it! ❤️

> Q: Where is link to Doctrine part 1? Or it is mean Symfony 4 course?

Yup, it's the Symfony tutorial with Doctrine - so, this one: https://symfonycasts.com/sc...

> P.s. Something happend with your voice, too tired or after Covid

Oh man, I didn't realize! Hopefully it's temporary - I don't have a cold or illness at the moment... so I'm not sure what might have happened. Well, my son HAS been missing a TON of school... so maybe it's just tired ;).


Production P. Avatar
Production P. Avatar Production P. | posted 1 year ago


Just to say thanks a lot for the new videos. Keep up the incredibly work, we appreciate <3


Hey Production P.

Thank you so much for the feedback <3 We appreciate it so much! Keep learning and enjoy videos :)


Ruslan Avatar

I glad to see new video for Doctrine. Thnak you! ;)
BTW, if it possible , Could you add pause/unpause functionality video for toch screens? I mean , like for desctop PC, when I can click in the middle of video frame. For Desktop PC I see big "Play" button, for my iPad no. May be I'm wrong and it's related with Safary only.


Hey Ruslan,

Thank you one more time for this suggestion! It was just implemented by our friend @sadikoff and should be available on production from now on :) Please, give it a try and feel free to share your feedback with us if you want ;)



Hey Ruslan!

Ah, I see that! That's annoying! We'll see if we can make this better for you - and I'm happy that you're happy about the new videos!


Cat in space

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

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.3", // v3.3.0
        "composer/package-versions-deprecated": "^1.11", //
        "doctrine/doctrine-bundle": "^2.1", // 2.4.2
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
        "doctrine/orm": "^2.7", // 2.9.5
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.1
        "pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
        "pagerfanta/twig": "^3.3", // v3.3.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
        "symfony/asset": "5.3.*", // v5.3.4
        "symfony/console": "5.3.*", // v5.3.7
        "symfony/dotenv": "5.3.*", // v5.3.7
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.3.*", // v5.3.7
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/runtime": "5.3.*", // v5.3.4
        "symfony/stopwatch": "5.3.*", // v5.3.4
        "symfony/twig-bundle": "5.3.*", // v5.3.4
        "symfony/validator": "5.3.*", // v5.3.14
        "symfony/webpack-encore-bundle": "^1.7", // v1.12.0
        "symfony/yaml": "5.3.*", // v5.3.6
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.1
        "twig/string-extra": "^3.3", // v3.3.1
        "twig/twig": "^2.12|^3.0" // v3.3.2
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.3.*", // v5.3.4
        "symfony/maker-bundle": "^1.15", // v1.33.0
        "symfony/var-dumper": "5.3.*", // v5.3.7
        "symfony/web-profiler-bundle": "5.3.*", // v5.3.5
        "zenstruck/foundry": "^1.1" // v1.13.1