Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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.

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

Ahora 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
{
#[Route('/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.

Leave a comment!

5
Login or Register to join the conversation
Stefan-P Avatar
Stefan-P Avatar Stefan-P | posted hace 4 meses

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

Reply

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!

Reply
Benoit-L Avatar
Benoit-L Avatar Benoit-L | posted hace 9 meses | edited

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 () {

$this->createdAt = new DateTimeImmutable();

}

and in MixController :

class MixController extends AbstractController
{

#[Route('/mix/new')]
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');
$mix->setTrackCount(round(5,20));
$mix->setVotes(round(-50,50));

$entityManager->persist($mix);
$entityManager->flush();

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

Reply

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()

Cheers!

1 Reply
Benoit-L Avatar

Thank you.

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
    }
}
userVoice