Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Persisting to the Database

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Now that we have an entity class and corresponding table, we're ready to save some stuff! So... how do we insert rows into the table? Wrong question! We're only going to focus on creating objects and saving them. Doctrine will handle the insert queries for us.

To help do this in the simplest way possible, let's make a fake "new Vinyl Mix" page.

In the src/Controller/ directory, create a new MixController class and make this extend the normal AbstractController. Perfect! Inside, add a public function called new() that will return a Response from HttpFoundation. To make this a page, above, use the #[Route] attribute, hit "tab" to autocomplete that and let's call the URL /mix/new. Finally, to see if this is working, dd('new mix').

... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MixController extends AbstractController
public function new(): Response
dd('new mix');

In the real world, this page might render a form. Then, when we submit that form, we would take its data, create a VinylMix() object and save it. We'll work on stuff like that in a future tutorial. For now, let's just see if this page works. Head over to /mix/new and... got it!

Ok, let's go create a VinylMix() object! Do that with $mix = new VinylMix()... and then we can start setting data on it! Let's create a mix of one of my absolute favorite artists as a kid. I'll quickly set some other properties... we need to set, at the very least, all of the properties that have required columns in the database. For trackCount, how about some randomness for fun. And, for votes, the same thing... including negative votes... though the Internet would never be so cruel as to downvote any of my mixes that much. Finally, dd($mix).

... lines 1 - 12
public function new(): Response
$mix = new VinylMix();
$mix->setTitle('Do you Remember... Phil Collins?!');
$mix->setDescription('A pure mix of drummers turned singers!');
$mix->setTrackCount(rand(5, 20));
$mix->setVotes(rand(-50, 50));
... lines 24 - 25

So far, this has nothing to do with Doctrine. We're just creating an object and setting data onto it. This data is hard-coded, but you can imagine replacing this with whatever the user just submitted via a form. Regardless of where we get the data, when we refresh... we have an object with data on it. Cool!

Services vs Entities

By the way, our entity class, VinylMix, is the first class we've created that is not a service. There are generally two types of classes. First, there are service objects, like TalkToMeCommand or the MixRepository we created in the last tutorial. These objects do work... but they don't hold any data besides maybe some basic config. And we always fetch services from the container, usually via autowiring. We never instantiate them directly.

The second type of classes are data classes like VinylMix. The primary job of these classes is to hold data. They don't usually do any work except maybe some basic data manipulation. And unlike services, we don't fetch these objects from the container. Instead, we create them manually wherever and whenever we need them, like we just did!

Hello Entity Manager!

Anyway, now that we have an object, how can we save it? Well, saving something to the database is work. And so, no surprise, that work is done by a service! Add an argument to the method, type-hinted with EntityManagerInterface. Let's call it $entityManager.

EntityManagerInterface is, by far, the most important service for Doctrine. We're going to use it to save, and indirectly when we query. To save, call $entityManager->persist() and pass it the object that we want to save (in this case, $mix). Then we also need to call $entityManager->flush() with no arguments.

... lines 1 - 5
use Doctrine\ORM\EntityManagerInterface;
... lines 7 - 10
class MixController extends AbstractController
... line 13
public function new(EntityManagerInterface $entityManager): Response
... lines 16 - 22
... lines 25 - 30

But... wait. Why do we have to call two methods?

Here's the deal. When we call persist(), that doesn't actually save the object or talk to the database at all. It just tells Doctrine:

Hey! I want you to be "aware" of this object, so that later when we call flush(), you'll know to save it.

Most of the time, you'll see these two lines together - persist() and then flush(). The reason it's split into two methods is to help with batch data loading... where you could persist a hundred $mix objects and then flush them to the database all at once, which is more efficient. But most of the time, you'll call persist() and then flush().

Okay, to make this a valid page, let's return new Response() from HttpFoundation and I'll use sprintf to return a message: mix %d is %d tracks of pure 80\'s heaven... and for those two wildcards, pass $mix->getId() and $mix->getTrackCount().

... lines 1 - 13
public function new(EntityManagerInterface $entityManager): Response
... lines 16 - 25
return new Response(sprintf(
'Mix %d is %d tracks of pure 80\'s heaven',
... lines 32 - 33

Let's try it! Move over, refresh and... yes! We see "Mix 1". That's so cool! We never actually set the ID (which makes sense). But when we saved, Doctrine grabbed the new ID and put that onto the id property.

If we refresh a few more times, we get mixes 2, 3, 4, 5, and 6. That's super fun. All we had to do is persist and flush the object. Doctrine handles all of the querying stuff for us.

Another way we can prove this is working is by running:

symfony console doctrine:query:sql 'SELECT * FROM vinyl_mix'

This time, we do see the results. Awesome!

Okay, now that we have stuff in the database, how do we query for it? Let's tackle that next.

Leave a comment!

Login or Register to join the conversation
Stefan-P Avatar
Stefan-P Avatar Stefan-P | posted 8 months ago | edited

Hi, I get the following error, but I have the pgsql driver in my php ini activated:

Doctrine\DBAL\Exception\DriverException in D:\code-symfony-fundamentals\start\vendor\doctrine\dbal\src\Driver\API\PostgreSQL\ExceptionConverter.php (line 87)
// We have to match against the SQLSTATE in the error message in these cases.
if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) {
    return new ConnectionException($exception, $query);
return new DriverException($exception, $query);

Whats wrong? I use the same source code as in your tutorial, on a windows 10 machine, with the docker postgresql db running..
Looking forward to your help, with kind regards, stefan


Hey @Stefan-P

At what point do you get that error?
Did you follow the setup steps described in the README from the project's source code?


Briantes Avatar

Hi. I get the same error too, and I have checked that the pgsql driver in my php.ini is activated. I have completed previous chapter without error. I could create VinylMix entity and added some properties.
I get the error when I added $entityManager->persist($mix);;.
From command line, I can run all database and query commands without error. For example:
symfony console doctrine:query:sql 'SELECT * FROM vinyl_mix'
But It returns (obviously): [OK] The query yielded an empty result set.


Hey @Briantes!

Sorry for the slow reply. Even more sorry that I really don't know what's causing this :/. From some quick checking, it looks like the postgres server might be crashing / losing connection. That would be consistent with it working sometimes, but not other times. But WHY it's crashing, I have no idea. A quick fix is to switch to sqlite for the tutorial. In .env:

Uncomment this line (and make sure any other DATABASE_URL lines ARE commented:


Then, turn show down docker: docker compose down. That should be it: it'll start using this local file database, and you should be able to keep going.


Manpreet-K Avatar
Manpreet-K Avatar Manpreet-K | weaverryan | posted 10 days ago | edited

I have the same error, I even uncomment database url you suggested but I get the same error. When the code tries to persist it throws the error:

An exception occurred in the driver: could not find driver


Hey Manpreet,

It seems your current PHP version does not have a DB driver installed. Please, make sure you install a DB extension. You can see the list of installed extensions by running php -m. Make sure you have PDO driver installed, if not - try google how to install it on your specific system.


Manpreet-K Avatar
Manpreet-K Avatar Manpreet-K | Victor | posted 9 days ago

Hey Victor! Thanks for you reply, I am using ubuntu 20.04.1
I think I have the drivers for the postgresql. This is the related result from php -m
Is there any other extensions that I need ?


Hey Manpreet,

Hm, seems good to me, it should work with Postgresql I suppose, just don't forget to set up the correct DATABASE_URL in the .env / env.local files, you need to point it to Postgresql DB. Also, just in case, make sure your Postgresql DB server up and running.

Btw, if you're using Docker - it might be a bit more complex setup.


Manpreet-K Avatar
Manpreet-K Avatar Manpreet-K | Victor | posted 8 days ago | edited

I ran this command : php bin/console debug:config doctrine --env=dev

            url: '%env(resolve:DATABASE_URL)%'
            server_version: '15'
            driver: pdo_mysql

The strange part here is driver: pdo_mysql for dev environment its reading pdo_mysql. However my env and env.local both has DATABASE_URL="postgresql://app:!ChangeMe!@"
Anyone knows where/how can I change it? Is it even a problem or normal behaviour?

In dev.log This exception was thrown:
[2023-11-28T13:21:58.837438+00:00] doctrine.INFO: Connecting with parameters array{"driver":"pdo_pgsql","host":"","port":32768,"user":"app","password":"<redacted>","driverOptions":[],"defaultTableOptions":[],"sslmode":"disable","charset":"utf8"} {"params":{"driver":"pdo_pgsql","host":"","port":32768,"user":"app","password":"<redacted>","driverOptions":[],"defaultTableOptions":[],"sslmode":"disable","charset":"utf8"}} [] [2023-11-28T13:21:58.840638+00:00] console.CRITICAL: Error thrown while running command "doctrine:database:create". Message: "An exception occurred in the driver: could not find driver" {"exception":"[object] (Doctrine\DBAL\Exception\DriverException(code: 0): An exception occurred in the driver: could not find driver at /home/mkaur/Sites/mixed_vinyl/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php:87)\n[previous exception] [object] (Doctrine\DBAL\Driver\PDO\Exception(code: 0): could not find driver at /home/mkaur/Sites/mixed_vinyl/vendor/doctrine/dbal/src/Driver/PDO/Exception.php:28)\n[previous exception] [object] (PDOException(code: 0): could not find driver at /home/mkaur/Sites/mixed_vinyl/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php:34)","command":"doctrine:database:create","message":"An exception occurred in the driver: could not find driver"} []


Hey @Manpreet-K!

Are you using the Docker integration that I talk about in the README.md file? If so, that Docker container runs Pgsql. And, when using the symfony binary for your web server (which we also recommend in the README), it detects that you're running the Docker container and overrides your DATABASE_URL to point to the Docker container. However, while that container handles actually runniing the pgsql server, you still need to have the pgsql driver for PHP installed locally.

So, to answer your question about "how can I make this use mysql" (which is a perfectly great option), the answer is: turn off Docker. Or at least remove the database container from the docker-compose.yaml file. Then, the DATABASE_URL environment variable will not be overridden and you can use whatever you'd like :). You may need to restart your web server through the symfony binary after removing the container / turning off docker, but I can't remember for certain!

Let me know this helps... or if my guess is way off ;).


Manpreet-K Avatar
Manpreet-K Avatar Manpreet-K | weaverryan | posted 5 days ago | edited

Thanks @weaverryan fir your reposonse. I actually use postgres not mysql.
Turns out that I just needed to restart symfony web server. I did everything else : restarting docker, cache clear, updating env.local except restarting symfony server, Now My browser request to persist in db and it works. Dont know what role symfony local server has to play in fixing this. Very wiered. But Thanks a lot, I took this solution from the list of steps you suggested.


Glad you got it! The symfony web server is the layer that detects the running docker database container and adds/overrides the DATABASE_URL environment variable to point to that container. I’m not sure why you needed to restart it (though that might be needed if you start the server first then docker - I can’t remember), but hopefully this clears a bit how the layers work together :).


Benoit-L Avatar
Benoit-L Avatar Benoit-L | posted 1 year ago | edited

Hi there,

I have got the following error when I call the page

Too few arguments to function Monolog\DateTimeImmutable::__construct(), 0 passed in C:\wamp64\www\code-symfony-doctrine\start\src\Entity\VinylMix.php on line 38 and at least 1 expected

Here is my code below

in VinylMix class

private \DateTimeImmutable $createdAt;

private int $votes = 0;

public function __construct () {

$this->createdAt = new DateTimeImmutable();


and in MixController :

class MixController extends AbstractController

public function new(EntityManagerInterface $entityManager) : Response {

$mix = new VinylMix();
$mix->setTitle('Do you remember... Phil Collins');
$mix->setDescription('A pure mix of drummers turned singers');


return new Response(sprintf('Mix %d is %d tracks of pure 80\'s heaven'), $mix->getId, $mix->getTrackCount());



I have tried to clear the cache, but it did not help.

I have removed the reference to the field createdAt (that I don't see in the finish directory) and now I have another error message : Attribute "Doctrine\ORM\Mapping\Column" must not be repeated


Hey Benoit,

The problem is that you're instantiating the wrong DateTimeImmutable class. You imported the one from Monolog\DateTimeImmutable but it should be the one from PHP, just prepend a \ so it can look in the general namespace, e.g. new \DateTimeImmutable()


1 Reply
Benoit-L Avatar

Thank you.

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