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 hemos guardado algunas cosas en la base de datos, ¿cómo podemos leerlas o consultarlas? Una vez más, al menos para las cosas sencillas, Doctrine no quiere que te preocupes de consultarlas. En lugar de eso, simplemente pedimos a Doctrine los objetos que queremos.
Dirígete a src/Controller/VinylController.php
y busca la acción browse()
.
... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 37 | |
public function browse(string $slug = null): Response | |
{ | |
$genre = $slug ? u(str_replace('-', ' ', $slug))->title(true) : null; | |
$mixes = $this->mixRepository->findAll(); | |
return $this->render('vinyl/browse.html.twig', [ | |
'genre' => $genre, | |
'mixes' => $mixes, | |
]); | |
} | |
} |
Aquí, estamos cargando todo el $mixes
en nuestro proyecto... y actualmente lo estamos haciendo a través de esta clase de servicio MixRepository
que creamos en el último episodio. Esta clase habla con un repositorio de GitHub y lee desde un archivo de texto codificado.
Vamos a dejar de usar este MixRepository
y en su lugar cargaremos estos $mixes
desde la base de datos.
Bien: para guardar los objetos, aprovechamos el servicio EntityManagerInterface
, que es el más importante con diferencia en Doctrine. Además, este servicio puede consultar los objetos. Aprovechemos eso. Añade un nuevo argumento a browse()
, de tipo EntityManagerInterface
... y llámalo $entityManager
.
... lines 1 - 6 | |
use Doctrine\ORM\EntityManagerInterface; | |
... lines 8 - 12 | |
class VinylController extends AbstractController | |
{ | |
... lines 15 - 39 | |
public function browse(EntityManagerInterface $entityManager, string $slug = null): Response | |
{ | |
... lines 42 - 51 | |
} | |
} |
A continuación, sustituye la línea $mixes
por dos líneas. Empieza con$mixRepository = $entityManager->getRepository()
pasándole el nombre de la clase desde la que queremos consultar. Sí, pensamos en consultar desde una clase de entidad, no desde una tabla. En este caso, queremos consultar desde VinylMix::class
.
Hablaremos más sobre este concepto de repositorio en un minuto. A continuación, para obtener las mezclas en sí, digamos $mixes = $mixRepository->
y llamemos a uno de los métodos de la misma: findAll()
.
Para ver qué nos da esto, vamos a dd($mixes)
.
... lines 1 - 39 | |
public function browse(EntityManagerInterface $entityManager, string $slug = null): Response | |
{ | |
... lines 42 - 43 | |
$mixRepository = $entityManager->getRepository(VinylMix::class); | |
$mixes = $mixRepository->findAll(); | |
dd($mixes); | |
... lines 47 - 51 | |
} | |
... lines 53 - 54 |
Bien, ¡es hora de probar! Gira, vuelve a la página de inicio, haz clic en "Examinar las mezclas" para realizar esa acción, y... ¡voilá! ¡Obtenemos seis resultados! Y cada uno de ellos, lo más importante, es un objeto VinylMix
.
Entre bastidores, Doctrine consultó la tabla y las columnas. Pero en lugar de darnos esos datos en bruto, los puso en objetos y nos los dio, lo cual es mucho más agradable.
Si eliminamos el dd()
... este array de objetos VinylMix
se pasará a la plantilla, en lugar del array de datos que teníamos antes. Pero... la página sigue funcionando. Sin embargo, estas imágenes están rotas porque, al parecer, el servicio que estoy utilizando para cargarlas no funciona en este momento. Ah... las alegrías de la grabación de vídeo. ¡Pero eso no nos detendrá!
El hecho de que todos los datos se sigan renderizando sin errores es... en realidad un poco por suerte. Cuando renderizamos la plantilla - templates/vinyl/browse.html.twig
- hacemos un bucle sobre todos los mixes
. La plantilla funciona porque el antiguo archivo de texto del repositorio de GitHub tenía las mismas claves (como title
, trackCount
y genre
) que nuestra claseVinylMix
.
... lines 1 - 28 | |
{% for mix in mixes %} | |
... line 30 | |
<div class="mixed-vinyl-container p-3 text-center"> | |
... line 32 | |
<p class="mt-2"><strong>{{ mix.title }}</strong></p> | |
<span>{{ mix.trackCount }} Tracks</span> | |
| | |
<span>{{ mix.genre }}</span> | |
| | |
<span>{{ mix.createdAt|ago }}</span> | |
... lines 39 - 40 | |
{% endfor %} | |
... lines 42 - 46 |
Sin embargo, aquí ocurre algo interesante. Cuando decimos mix.genre
,mix
es ahora un objeto... y esta propiedad genre
es privada. Eso significa que no podemos acceder a ella directamente. Pero Twig es inteligente. Se da cuenta de que es privada y busca un método getGenre()
. Así que en nuestra plantilla, decimos mix.genre
, pero en realidad, llama al método getGenre()
. Eso es bastante asombroso.
¿Sabes qué más es impresionante? ¡Podemos ver las consultas que hace cualquier página! En la barra de herramientas de depuración de la web, Doctrine nos ofrece un nuevo y elegante icono. Oooo. Y si hacemos clic en él... ¡tah dah! Hay una consulta a la base de datos... e incluso podemos ver de qué se trata. También se puede ver una versión formateada de la misma... aunque tengo que actualizar la página para que esto funcione... porque la biblioteca Turbo JavaScript que instalamos en el primer tutorial no siempre se lleva bien con esta zona del perfilador. De todos modos, también podemos ver una versión ejecutable de la consulta o ejecutar "Explicar" sobre ella.
Muy bien, de vuelta al controlador, aunque podemos consultar a través deEntityManagerInterface
, normalmente consultamos a través de algo llamado repositorio.dd()
este objeto $mixRepository
para obtener más información sobre él
... lines 1 - 12 | |
class VinylController extends AbstractController | |
{ | |
... lines 15 - 39 | |
public function browse(EntityManagerInterface $entityManager, string $slug = null): Response | |
{ | |
... lines 42 - 44 | |
dd($mixRepository); | |
... lines 46 - 51 | |
} | |
} |
Luego vuelve a la página /browse
y... es un objetoApp\Repository\VinylMixRepository
. Oye, ¡conocemos esa clase! Vive en nuestro código, en el directorio src/Repository/
. Fue generada por MakerBundle.
Dentro del atributo ORM\Entity
sobre nuestra clase de entidad, MakerBundle generó una opción repositoryClass
que apunta a esto. Gracias a esta configuración, nuestra entidad, VinylMix
, está vinculada a VinylMixRepository
. Así que cuando le pides a Doctrine que nos dé el repositorio de la clase VinylMix
, sabe que debe devolver el objetoVinylMixRepository
.
El repositorio de una entidad lo sabe todo sobre cómo consultar sus datos. Y, sin que nosotros hagamos nada, ya tiene un montón de métodos útiles para las consultas básicas, como findAll()
, findOneBy()
y varios más. Dentro de un rato, aprenderemos a añadir nuevos métodos al repositorio para realizar consultas personalizadas.
De todos modos, VinylMixRepository
es en realidad un servicio en el contenedor... así que podemos obtenerlo más fácilmente autoconectándolo directamente. Añade un argumentoVinylMixRepository $mixRepository
... y entonces no necesitaremos esta línea. Esto es más sencillo... ¡y sigue funcionando!
... lines 1 - 38 | |
public function browse(VinylMixRepository $mixRepository, string $slug = null): Response | |
{ | |
... lines 41 - 42 | |
$mixes = $mixRepository->findAll(); | |
... lines 44 - 48 | |
} | |
... lines 50 - 51 |
La conclusión es ésta: si quieres consultar una tabla, lo harás a través del repositorio de la entidad cuyos datos necesitas.
Siguiente: El hecho de que hayamos cambiado nuestro código para cargarlo desde la base de datos y no hayamos tenido que actualizar nuestra plantilla Twig en absoluto fue algo impresionante Y por cortesía de un poco de magia Twig. Vamos a hablar más de esa magia y a crear una propiedad virtual que podemos imprimir en la plantilla.
Hey Stefan,
Thanks for sharing this alternative solution. Another one would be to use "bind" keyword in the services.yaml and put that autowired var there :) And from the error I see it seems you typehinted that var as bool $isDebug
in the constructor, so it also would require that typehint in the bind
too, i.e. bool $isDebug: '%kernel.debug%'
.
Cheers!
When I hit the 127.0.0.1:8000/browse endpoint I see the dd($mixRepository); output but it is an object containing lazyObjectState { status: UNINITIALIZED_PARTIAL }
Hey Yard,
Perhaps you're on a newer Doctrine version? That's happening because Doctrine uses proxies under the hood for performance reasons. Try calling any method on the repository before the dump call
Cheers!
Is there support for object versioning in Doctrine ORM? I have a case where I'm every now and then hit by "race conditions" i.e. simultaneous requests to the server will create unwanted behaviour, like the logical outline below
SQL find all entities where stoptime == NULL
if ([check other condition]) {
set a stoptime for entity
create new entity
}
In manual testing, it works fine. But every now and then, there slips into the DB a duplicate entity, i e the above if-clause evaluates to true for two separate requests. I assume the reason is that both requests run the SQL query and pick up that entity where stoptime == NULL, and then one request steps into the if-clause and goes ahead and sets a stop-time, and creates a new entity. And then the second request does the same thing.
I have tried putting an extra check in there before the "create new entity" which basically looks up the DB again and asks "is there STILL an entity with stoptime == NULL?) but it doesn't help. I suppose that is also timed in a way that it gets a positive answer and right after that the first request does it's UPDATE job.
Perhaps object versioning doesn't even solve my problem? =) I want to avoid locking an entire table at all cost, I know that business can be poisonous. Although that might be my only option here..?
Hey MattWelander!
Yup, I think you are diagnosing the problem correctly! And yes, I think locking is your solution. If you don't want to lock the entire table, you could use Symfony's lock system and create a lock with a name that is very specific to the task that you are accomplishing. Then, if another request tries to accomplish that exact same task, the lock would cause that request to wait. But, it wouldn't be a database lock.
Let me know if that sounds like a good solution :).
Cheers!
Hey, you keep mentioning MixRepository
service "...that we created in the last episode", but that episode seems to be missing.
Hey Andrey,
About the episode, we meant the previous course, not a chapter. Take a look at this chapter: https://symfonycasts.com/screencast/symfony-fundamentals/create-service
Cheers!
// 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
}
}
Received following error: Cannot autowire service "App\Controller\VinylController": argument "$isDebug" of method "__construct()" is type-hinted "bool", you should configure its value explicitly. The Solution: I had to define another service in the services.yaml, on the last rows, after the Mixrepository Service: