Persistir en la base de datos
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 SubscribeAhora que tenemos una clase de entidad y la tabla correspondiente, ¡estamos listos para guardar algunas cosas! Entonces... ¿cómo insertamos filas en la tabla? Pregunta equivocada! Sólo vamos a centrarnos en crear objetos y guardarlos. Doctrine se encargará de las consultas de inserción por nosotros.
Para ayudar a hacer esto de la forma más sencilla posible, vamos a hacer una página falsa de "nueva mezcla de vinilo".
En el directorio src/Controller/, crea una nueva clase MixController y haz que ésta extienda la normal AbstractController. Perfecto Dentro, añade unpublic function llamado new() que devolverá un Response de HttpFoundation. Para que esto sea una página, arriba, utiliza el atributo #[Route], dale a "tab" para autocompletarlo y llamemos a la URL /mix/new. Por último, para ver si esto funciona, 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 | |
| { | |
| ('/mix/new') | |
| public function new(): Response | |
| { | |
| dd('new mix'); | |
| } | |
| } |
En el mundo real, esta página podría mostrar un formulario. Entonces, al enviar ese formulario, tomaríamos sus datos, crearíamos un objeto VinylMix() y lo guardaríamos. Trabajaremos en cosas así en un futuro tutorial. Por ahora, vamos a ver si esta página funciona. Dirígete a /mix/new y... ¡ya está!
Bien, ¡vamos a crear un objeto VinylMix()! Hazlo con $mix = new VinylMix()... ¡y entonces podremos empezar a poner datos en él! Vamos a crear una mezcla de uno de mis artistas favoritos de la infancia. Voy a establecer rápidamente algunas otras propiedades... tenemos que establecer, como mínimo, todas las propiedades que tienen columnas necesarias en la base de datos. Para trackCount, qué tal un poco de aleatoriedad para divertirse. Y, para votes, lo mismo... incluyendo votos negativos... aunque Internet nunca sería tan cruel como para votar a la baja ninguna de mis mezclas. Por último, 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->setGenre('pop'); | |
| $mix->setTrackCount(rand(5, 20)); | |
| $mix->setVotes(rand(-50, 50)); | |
| dd($mix); | |
| } | |
| // ... lines 24 - 25 |
Hasta ahora, esto no tiene nada que ver con la Doctrine. Sólo estamos creando un objeto y poniendo datos en él. Estos datos están codificados, pero puedes imaginar que se sustituyen por lo que el usuario acaba de enviar a través de un formulario. Independientemente de dónde obtengamos los datos, cuando actualicemos... tendremos un objeto con datos en él. ¡Genial!
Servicios vs. Entidades
Por cierto, nuestra clase de entidad, VinylMix, es la primera clase que hemos creado que no es un servicio. En general, hay dos tipos de clases. En primer lugar, están los objetos de servicio, como TalkToMeCommand o el MixRepository que creamos en el último tutorial. Estos objetos funcionan... pero no contienen ningún dato, aparte de quizás alguna configuración básica. Y siempre obtenemos los servicios del contenedor, normalmente mediante autoconexión. Nunca los instanciamos directamente.
El segundo tipo de clases son las clases de datos como VinylMix. El trabajo principal de estas clases es mantener los datos. No suelen hacer ningún trabajo, salvo quizá alguna manipulación básica de datos. Y a diferencia de los servicios, no obtenemos estos objetos del contenedor. En su lugar, los creamos manualmente donde y cuando los necesitemos, ¡como acabamos de hacer!
¡Hola Gestor de Entidades!
De todos modos, ahora que tenemos un objeto, ¿cómo podemos guardarlo? Bueno, guardar algo en la base de datos es un trabajo. Y por eso, no es de extrañar, ¡ese trabajo lo hace un servicio! Añade un argumento al método, indicado con EntityManagerInterface. Llamémoslo $entityManager.
EntityManagerInterface es, con mucho, el servicio más importante para Doctrine. Lo vamos a utilizar para guardar, e indirectamente cuando hagamos una consulta. Para guardar, llamamos a$entityManager->persist() y le pasamos el objeto que queremos guardar (en este caso, $mix). Luego también tenemos que llamar a $entityManager->flush() sin argumentos.
| // ... 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 | |
| $entityManager->persist($mix); | |
| $entityManager->flush(); | |
| // ... lines 25 - 30 | |
| } | |
| } |
Pero... espera. ¿Por qué tenemos que llamar a dos métodos?
Esto es lo que pasa. Cuando llamamos a persist(), en realidad no guarda el objeto ni habla con la base de datos. Sólo le dice a Doctrine:
¡Oye! Quiero que seas "consciente" de este objeto, para que luego, cuando llamemos a
flush(), sabrá que debe guardarlo.
La mayoría de las veces, verás estas dos líneas juntas: persist() y luegoflush(). La razón por la que está dividido en dos métodos es para ayudar a la carga de datos por lotes... donde podrías persistir un centenar de objetos de $mix y luego vaciarlos en la base de datos todos a la vez, lo que es más eficiente. Pero la mayoría de las veces, llamarás a persist() y luego a flush().
Bien, para que sea una página válida, vamos a return new Response() de HttpFoundation y usaré sprintf para devolver un mensaje:mix %d is %d tracks of pure 80\'s heaven... y para esos dos comodines, pasaré $mix->getId() y $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', | |
| $mix->getId(), | |
| $mix->getTrackCount() | |
| )); | |
| } | |
| // ... lines 32 - 33 |
¡Vamos a probarlo! Muévete, refresca y... ¡sí! Vemos el "Mix 1". ¡Qué bien! En realidad, nunca pusimos el ID (lo que tiene sentido). Pero cuando guardamos, Doctrine cogió el nuevo ID y lo puso en la propiedad id.
Si refrescamos unas cuantas veces más, obtendremos las mezclas 2, 3, 4, 5 y 6. Es súper divertido. Todo lo que hemos tenido que hacer es persistir y vaciar el objeto. Doctrine se encarga de todas las consultas por nosotros.
Otra forma de demostrar que esto funciona es ejecutando:
symfony console doctrine:query:sql 'SELECT * FROM vinyl_mix'
Esta vez sí vemos los resultados. ¡Genial!
Bien, ahora que tenemos cosas en la base de datos, ¿cómo las consultamos? Vamos a abordar eso a continuación.
23 Comments
Hi,
I running on Wsl2 Ubuntu 24.04, Windows Desktop Docker, PHP 8.3. PDO drivers installed,
all original files from this course. All was working fine with database until I got to this point in the Chapter to test added code below:
refreshed page and get error - "An exception occurred in the driver: could not find driver"
Solution was very simple:
and restart a server:
I did not change anything in code and now all works. strange, maybe it was just my setup in wsl or something in windows.
Hey @OjarsBluzma
Happy that you solved the issue, unfortunately sometimes it happens.
Cheers!
Hello guys,
After creating the MixController class and refreshing the page in the browser I got a class not find error:
"Attempted to load class "ClassUtils" from namespace "Doctrine\Common\Util". Did you forget a "use" statement for another namespace?"
I think the problem seems to occur from these lines of code (shown below). Before them, I successfully created the VinylMix object (see it on the page when dumped), but the error happened when I tried to persist it and flush it to the database.
I tried with the same source code posted in the script section for both MixController and VinylMix classes and checked whether I imported exactly the Doctrine\ORM\EntityManagerInterface but got the same result. Thank you for your time!
Hey @valentin_valkanov
The Symfony ux-turbo package is missing a dependency. I'm not sure if that bug is already fixed, you can try upgrading it
composer up symfony/ux-turbo, or you can install the missing dep manuallycomposer require doctrine/common- or, if you're not using turbo you can remove itCheers!
Hello Symfonycasts team,
in this tutorial, when mentioning the creation of forms you say : "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!". Is this tutorial available? If yes could you tell me how it is labelled ?
I would like to have the tutorial where forms are created and used ?
Hey @Benoit-L
Could you tell me what you want to learn about forms? We have a tutorial specific to working with Symfony Forms, it's built on Symfony 4 but all the concepts are still relevant
https://symfonycasts.com/screencast/symfony-forms
In this video, we mention some of the new features of forms for Symfony 6 https://symfonycasts.com/screencast/symfony6-upgrade/form-improvements
Cheers!
I want to be able to edit the data in a form, to create an new entity through a form, eventually update an entity, etc. I see that is is bit different from what it used to be.
For example $form = $this->createForm(ProjectType::class, $project);
then return $this->render('.../.../...twig',[ 'project'=>$project, 'form'=>$form->createView() ]
Thank you for your reply.
Best regards.
Benoit
Got ya, you don't need anything fancy. I recommend our Symfony Forms tutorials, those are a great fit for you
Cheers!
Hi, I get the following error, but I have the pgsql driver in my php ini activated:
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?
Cheers!
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_URLlines 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.Cheers!
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:
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.Cheers!
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
PDO
pdo_pgsql
pgsql
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.
Cheeers!
I ran this command : php bin/console debug:config doctrine --env=dev
doctrine:
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!@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
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":"127.0.0.1","port":32768,"user":"app","password":"<redacted>","driverOptions":[],"defaultTableOptions":[],"sslmode":"disable","charset":"utf8"} {"params":{"driver":"pdo_pgsql","host":"127.0.0.1","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
symfonybinary for your web server (which we also recommend in the README), it detects that you're running the Docker container and overrides yourDATABASE_URLto 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
databasecontainer from thedocker-compose.yamlfile. Then, theDATABASE_URLenvironment variable will not be overridden and you can use whatever you'd like :). You may need to restart your web server through thesymfonybinary 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 ;).
Cheers!
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 :).
Cheers!
Hi there,
I have got the following error when I call the page https://127.0.0.1:8000/mix/new
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
....
#[ORM\Column]
private \DateTimeImmutable $createdAt;
#[ORM\Column]
private int $votes = 0;
public function __construct () {
}
and in MixController :
class MixController extends AbstractController
{
#[Route('/mix/new')]
public function new(EntityManagerInterface $entityManager) : Response {
}
}
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
DateTimeImmutableclass. You imported the one fromMonolog\DateTimeImmutablebut it should be the one from PHP, just prepend a\so it can look in the general namespace, e.g.new \DateTimeImmutable()Cheers!
Thank you.
"Houston: no signs of life"
Start the conversation!