Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Consulta de una entidad única para una página de "Show"

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

Nuestros usuarios realmente necesitan poder hacer clic en una mezcla y navegar a una página con más información sobre ella... ¡como eventualmente su lista de canciones! ¡Así que hagamos que eso sea posible! Vamos a crear una página para mostrar los detalles de una mezcla.

Creación de la nueva Ruta y Controlador

Dirígete a src/Controller/MixController.php. Después de la acción new, añadepublic function show() con el atributo [#Route()] anterior. La URL para esto será... qué tal /mix/{id}, donde id será el ID de esa mezcla en la base de datos. A continuación, añade el argumento $id correspondiente. Y... sólo para ver si esto funciona, dd($id).

... lines 1 - 10
class MixController extends AbstractController
{
... lines 13 - 33
#[Route('/mix/{id}')]
public function show($id): Response
{
dd($id);
}
}

¡Genial! Gira y ve a, qué tal, /mix/7. ¡Genial! ¡Nuestra ruta y nuestro controlador están conectados!

Consulta de un solo objeto

Bien, ahora que tenemos el ID, tenemos que buscar en la base de datos el VinylMix que coincida con él. Y ya sabemos cómo consultar: a través del repositorio. Añade un segundo argumento al método que se ha indicado con VinylMixRepository y llámalo$mixRepository. Ahora sustituye el dd() por $mix = $mixRepository-> y, por primera vez, vamos a utilizar el método find(). Es muy sencillo: busca un único objeto utilizando la clave primaria. Así que pásale $id. Para asegurarnos de que funciona, dd($mix).

... lines 1 - 5
use App\Repository\VinylMixRepository;
... lines 7 - 11
class MixController extends AbstractController
{
... lines 14 - 35
public function show($id, VinylMixRepository $mixRepository): Response
{
$mix = $mixRepository->find($id);
dd($mix);
}
}

Ahora mismo no sabemos qué IDs tenemos en nuestra base de datos, así que como solución, ve a /mix/new para crear una nueva mezcla. En mi caso, tiene el ID 16. Genial: ve a /mix/16 y... ¡hola VinylMix id: 16 ! Lo importante es que esto devuelve un objeto VinylMix. A menos que hagas algo personalizado, Doctrine siempre nos devuelve un único objeto o una matriz de objetos, dependiendo del método que llames.

Renderización de la plantilla

Ahora que tenemos el objeto VinylMix, vamos a renderizar una plantilla y a pasarla. Hazlo con return $this->render() y llama a la plantilla mix/show.html.twig. La ruta de la plantilla podría ser cualquier cosa, pero como estamos dentro de MixController, el directorio mix tiene sentido. Y como estamos en la acción show,show.html.twig también tiene sentido. ¡La coherencia es una gran manera de hacer amigos con tus compañeros de equipo!

Pasa una variable llamada mix ajustada al objeto VinylMix $mix .

... lines 1 - 35
public function show($id, VinylMixRepository $mixRepository): Response
{
... lines 38 - 39
return $this->render('mix/show.html.twig', [
'mix' => $mix,
]);
}
... lines 44 - 45

Muy bien, vamos a crear esa plantilla. En templates/, añade un nuevo directorio llamado mix/... y dentro de él, un nuevo archivo llamado show.html.twig. Casi todas las plantillas van a empezar de la misma manera. Empieza diciendo{% extends 'base.html.twig' %}.

{% extends 'base.html.twig' %}
... lines 2 - 8

Como recordatorio, base.html.twig tiene varios bloques. El más importante aquí abajo es block body. Eso es lo que anularemos con nuestro contenido. En la parte superior, también hay un block title, que nos permite controlar el título de la página. Anulemos ambos.

Digamos {% block title %}{% endblock %} y, en medio, {{ mix.title }} Mix. Luego anulemos {% block body %} con {% endblock %} abajo. Dentro, sólo para empezar, añade un <h1> con {{ mix.title }}.

... lines 1 - 2
{% block title %}{{ mix.title }} Mix{% endblock %}
{% block body %}
<h1>{{ mix.title }}</h1>
{% endblock %}

Cuando lo probemos... ¡hola página! Esto es súper simple -el <h1> ni siquiera está en el lugar correcto- pero está funcionando. Ahora podemos añadir algo de dinamismo.

Hacer que la página tenga un aspecto elegante

Voy a volver a mi plantilla y a pegar un montón de contenido nuevo. Puedes copiarlo del bloque de código de esta página. La parte superior es exactamente igual: se extiende base.html.twig y el block title tiene el mismo aspecto que antes. Pero luego, en el cuerpo, tenemos un montón de marcas nuevas, imprimimos el título de la mezcla... y aquí abajo, tengo unos cuantos TODOen los que imprimiremos más detalles.

... lines 1 - 4
{% block body %}
<div class="container">
<h1 class="d-inline me-3">{{ mix.title }}</h1>
<div class="row mt-5">
<div class="col-12 col-md-4">
<svg width="100%" height="100%" viewBox="0 0 496 496" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
... lines 12 - 32
</svg>
</div>
<div class="col-12 col-md-8 ps-5">
TODO: print track count, genre and description
</div>
</div>
</div>
{% endblock %}

Si refrescas ahora... ¡qué bien! Incluso tenemos el simpático SVG del disco... que probablemente reconozcas de la página de inicio. Eso es genial... excepto que duplicar todo este SVG en ambas plantillas es... no tan genial. Vamos a arreglar esa duplicación.

Evitar la duplicación con una plantilla parcial

Selecciona todo este contenido de <svg>, cópialo, y en el directorio mix/, crea un nuevo archivo llamado _recordSvg.html.twig. ¡Pégalo aquí!

<svg width="100%" height="100%" viewBox="0 0 496 496" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#C380F3" offset="0%"></stop>
<stop stop-color="#4A90E2" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Mixed-Vinyl" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group">
<g id="record-vinyl" fill="#000000" fill-rule="nonzero">
<path d="M248,144 C190.562386,144 144,190.562386 144,248 C144,305.437614 190.562386,352 248,352 C305.437614,352 352,305.437614 352,248 C352,190.562386 305.437614,144 248,144 L248,144 Z M248,272 C234.745166,272 224,261.254834 224,248 C224,234.745166 234.745166,224 248,224 C261.254834,224 272,234.745166 272,248 C272,261.254834 261.254834,272 248,272 Z M248,0 C111,0 0,111 0,248 C0,385 111,496 248,496 C385,496 496,385 496,248 C496,111 385,0 248,0 Z M248,376 C177.307552,376 120,318.692448 120,248 C120,177.307552 177.307552,120 248,120 C318.692448,120 376,177.307552 376,248 C376,281.947711 362.514324,314.505012 338.509668,338.509668 C314.505012,362.514324 281.947711,376 248,376 Z" id="Shape"></path>
</g>
<g id="record-vinyl" transform="translate(144.000000, 144.000000)" fill="url(#linearGradient-1)" fill-rule="nonzero">
<path d="M104,0 C46.562386,0 0,46.562386 0,104 C0,161.437614 46.562386,208 104,208 C161.437614,208 208,161.437614 208,104 C208,46.562386 161.437614,0 104,0 L104,0 Z M104,128 C90.745166,128 80,117.254834 80,104 C80,90.745166 90.745166,80 104,80 C117.254834,80 128,90.745166 128,104 C128,117.254834 117.254834,128 104,128 Z" id="Shape"></path>
</g>
<circle id="Oval" stroke="#979797" cx="248" cy="248" r="235"></circle>
<circle id="Oval" stroke="#979797" cx="248" cy="248" r="215"></circle>
<circle id="Oval" stroke="#979797" cx="248" cy="248" r="195"></circle>
<circle id="Oval" stroke="#979797" cx="248" cy="248" r="175"></circle>
<circle id="Oval" stroke="#979797" cx="248" cy="248" r="155"></circle>
</g>
</g>
</svg>

La razón por la que he prefijado el nombre con _ es para indicar que se trata de un parcial de plantilla. Eso significa que es una plantilla que no incluye una página completa, sino sólo parte de una página. El _ es opcional... y sólo es algo que se hace como convención común: no cambia ningún comportamiento.

Gracias a esto, podemos entrar en show.html.twig y{{ include('mix/_recordSvg.html.twig) }}

... lines 1 - 4
{% block body %}
<div class="container">
... lines 7 - 8
<div class="col-12 col-md-4">
{{ include('mix/_recordSvg.html.twig') }}
</div>
... lines 12 - 16
</div>
{% endblock %}

Vamos a hacer lo mismo en la plantilla de la página de inicio: templates/vinyl/homepage.html.twig. Aquí está el mismo SVG, así que incluiremos esa misma plantilla.

... lines 1 - 4
{% block body %}
<div class="container">
... lines 7 - 8
<div class="col-12 col-md-4">
{{ include('mix/_recordSvg.html.twig') }}
</div>
... lines 12 - 34
</div>
{% endblock %}

Muy bien Si vamos a comprobar la página de inicio... ¡sigue teniendo un aspecto estupendo! Y si volvemos a la página de la mezcla y la actualizamos... ¡también se ve muy bien!

Para terminar la plantilla, vamos a rellenar los detalles que faltan. Añade un <h2> conclass="mb-4", y dentro, digamos {{ mix.trackCount }} songs, seguido de una etiqueta <small>con (genre: {{ mix.genre }})... y debajo de ésta, una etiqueta <p> con{{ mix.description }}.

... lines 1 - 4
{% block body %}
... lines 6 - 7
<div class="row mt-5">
... lines 9 - 11
<div class="col-12 col-md-8 ps-5">
<h2 class="mb-4">{{ mix.trackCount }} songs <small>(genre: {{ mix.genre }})</small></h2>
<p>{{ mix.description }}</p>
</div>
</div>
... line 17
{% endblock %}

Y ahora... ¡esto empieza a cobrar vida! Todavía no tenemos una lista de canciones... porque esa es otra tabla de la base de datos que crearemos en un futuro tutorial. Pero es un buen comienzo.

Vinculación con la página de espectáculos

Para completar la nueva función, cuando estemos en la página /browse, tenemos que enlazar cada mezcla con su página de espectáculo. Abre templates/vinyl/browse.html.twig y desplázate hacia abajo hasta el lugar en el que hacemos el bucle. Bien: cambia la etiqueta <div> que lo rodea todo por una etiqueta <a>. Luego... rompe esto en varias líneas y añade href="". Como puedes ver, PhpStorm fue lo suficientemente inteligente como para actualizar la etiqueta de cierre por una a automáticamente.

Para enlazar a una página en Twig, utilizamos la función path() y pasamos el nombre de la ruta. ¿Cuál... es el nombre de la ruta a nuestra página de presentación? La respuesta es... ¡no tiene ninguno! Vale, Symfony autogenera un nombre... pero no queremos confiar en eso. En cuanto queramos enlazar a una ruta, debemos darle un nombre adecuado. ¿Qué te parece app_mix_show.

... lines 1 - 11
class MixController extends AbstractController
{
... lines 14 - 34
#[Route('/mix/{id}', name: 'app_mix_show')]
public function show($id, VinylMixRepository $mixRepository): Response
... lines 37 - 47
}

Copia eso, vuelve a browse.html.twig y pégalo.

Pero esta vez, ¡pegar el nombre de la ruta no va a ser suficiente! Comprueba este dulce error:

Faltan algunos parámetros obligatorios ("id") para generar una URL para la ruta "app_mix_show".

¡Eso tiene sentido! Symfony está intentando generar la URL de esta ruta, pero tenemos que decirle qué valor comodín debe utilizar para {id}. Lo hacemos pasando un segundo argumento en forma de matriz con {}. Dentro ponemos id a mix.id.

... lines 1 - 2
{% block body %}
... lines 4 - 28
{% for mix in mixes %}
<div class="col col-md-4">
<a href="{{ path('app_mix_show', {
id: mix.id
}) }}" class="mixed-vinyl-container p-3 text-center">
... lines 34 - 42
</a>
... line 44
{% endfor %}
... lines 46 - 48
{% endblock %}

Y ahora... ¡la página funciona! Y podemos hacer clic en cualquiera de ellas para entrar y ver más detalles.

Bien, ¡ya tenemos el camino feliz funcionando! ¿Pero qué pasa si no se encuentra ninguna mezcla para un determinado ID? Siguiente: hablemos de las páginas 404 y aprendamos cómo podemos ser aún más perezosos haciendo que Symfony consulte el objeto VinylMix por nosotros.

Leave a comment!

2
Login or Register to join the conversation
wh Avatar
wh Avatar wh | posted hace 7 meses | edited

why (at -0:37) you say "array" but use an object syntax?

Reply

Hey Wh,

Actually, that's just a Twig syntax for associative arrays. For indexed arrays you're using ['a', 'b', 'c'] but for associative ones: {'a': 'b', 'c': 'd'}. So, it looks like a JS object but technically it's a simple PHP array :)

Cheers!

1 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