Login to bookmark this video
Buy Access to Course
04.

Lista de botones con AutowireIterator

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

Hemos refactorizado nuestra aplicación para utilizar el patrón Comando para ejecutar cada botón ¡Genial! Nuevo objetivo: hacer que los botones sean más dinámicos: a medida que añadamos nuevas clases de botones, me gustaría no tener que editar nuestra plantilla.

Empieza dentro de ButtonRemote. Necesitamos una forma de obtener una lista de todos los nombres de los botones: los índices de nuestro contenedor. Para ello, crea aquí un método public llamado buttons(), que devolverá un array. Éste será una matriz de cadenas: ¡nuestros nombres de botones!

31 lines | src/Remote/ButtonRemote.php
// ... lines 1 - 7
final class ButtonRemote
{
// ... lines 10 - 20
/**
* @return string[]
*/
public function buttons(): iterable
{
// ... lines 26 - 28
}
}

#[AutowireIterator]

El minicontenedor es estupendo para obtener servicios individuales. Pero no puedes hacer un bucle sobre todos los servicios de botones que hay dentro. Para solucionarlo, cambia #[AutowireLocator] por #[AutowireIterator]. Esto le dice a Symfony que inyecte un iterable de nuestros servicios, por lo que éste ya no será un ContainerInterface. En su lugar, utiliza iterable y renombra$container a $buttons aquí... y aquí. ¡Estupendo!

31 lines | src/Remote/ButtonRemote.php
// ... lines 1 - 7
final class ButtonRemote
{
public function __construct(
#[AutowireIterator(ButtonInterface::class)]
private iterable $buttons,
) {
}
// ... line 15
public function press(string $name): void
{
$this->buttons->get($name)->press();
}
// ... lines 20 - 29
}

Ahora, abajo, haz un bucle sobre los botones:foreach ($this->buttons as $name => $button). $button es el servicio real, pero vamos a ignorarlo por completo y sólo cogeremos el $name, y lo añadiremos a esta matriz $buttons. En la parte inferior, return $buttons.

35 lines | src/Remote/ButtonRemote.php
// ... lines 1 - 23
public function buttons(): iterable
{
$buttons = [];
foreach ($this->buttons as $name => $button) {
$buttons[] = $name;
}
return $buttons;
}
// ... lines 34 - 35

Pasar botones a la plantilla

De vuelta en el controlador, ya estamos inyectando ButtonRemote, así que abajo, donde renderizamos la plantilla, pasamos una nueva variable buttons con 'buttons' => $remote->buttons():

35 lines | src/Controller/RemoteController.php
// ... lines 1 - 12
final class RemoteController extends AbstractController
{
// ... line 15
public function index(Request $request, ButtonRemote $remote): Response
{
// ... lines 18 - 29
return $this->render('index.html.twig', [
'buttons' => $remote->buttons(),
]);
}
}

Añade un dd() para ver qué devuelve:

37 lines | src/Controller/RemoteController.php
// ... lines 1 - 29
dd($remote->buttons());
return $this->render('index.html.twig', [
// ... lines 33 - 37

Vale, de vuelta al navegador, actualiza la página y... hm... no es exactamente lo que queremos. En lugar de una lista de números, queremos una lista de nombres de botones. Para solucionarlo, vuelve a ButtonRemote, busca #[AutowireIterator]. #[AutowireLocator], el atributo que teníamos antes, utiliza automáticamente la propiedad $index de #[AsTaggedItem] para las claves de servicio. #[AutowireIterator] ¡no lo hace! Sólo nos da un iterable con claves enteras.

#[AutowireIterator]'s indexAttribute

Para decirle que clave el iterable utilizando #[AsTaggedItem]'s $index, añadeindexAttribute set a key:

31 lines | src/Remote/ButtonRemote.php
// ... lines 1 - 9
public function __construct(
#[AutowireIterator(ButtonInterface::class, indexAttribute: 'key')]
private iterable $buttons,
// ... lines 13 - 31

Ahora, cuando hagamos un bucle sobre $this->buttons, $name será $index que, en nuestro caso, es el nombre del botón.

En nuestro controlador, seguimos teniendo este dd(), así que volvemos a nuestra aplicación, actualizamos y... ¡ya está! ¡Ya tenemos los nombres de los botones! ¡Genial!

Elimina el dd(), luego abre index.html.twig.

37 lines | src/Controller/RemoteController.php
// ... lines 1 - 29
dd($remote->buttons());
return $this->render('index.html.twig', [
// ... lines 33 - 37

Renderizar botones dinámicamente

Aquí tenemos una lista de botones codificada. Añade un poco de espacio y luegofor button in buttons:

51 lines | templates/index.html.twig
// ... lines 1 - 4
{% block body %}
<div class="mx-auto max-w-5xl">
<div class="bg-[#1B1B1D] w-[477px] mx-auto rounded-xl p-6">
// ... lines 8 - 18
<form method="post">
<div class="flex justify-center">
<ul class="grid grid-cols-2 row-span-3 gap-8">
{% for button in buttons %}
// ... lines 23 - 35
{% endfor %}
</ul>
</div>
</form>
// ... lines 40 - 47
</div>
</div>
{% endblock %}

En la interfaz de usuario, probablemente hayas notado que el primer botón, el de "Encendido", tiene un aspecto diferente: es rojo y más grande. Para mantener ese estilo especial, añade un if loop.first aquí, y un else para el resto de los botones:

51 lines | templates/index.html.twig
// ... lines 1 - 21
{% for button in buttons %}
{% if loop.first %}
// ... lines 24 - 28
{% else %}
// ... lines 30 - 34
{% endif %}
{% endfor %}
// ... lines 37 - 51

Copia el código del primer botón y pégalo aquí. En lugar de codificar "power" como valor del botón, utiliza la variable button. Lo mismo para el nombre del icono Twig:

51 lines | templates/index.html.twig
// ... lines 1 - 22
{% if loop.first %}
<li class="col-span-2 flex justify-center -mb-4">
<button name="button" value="{{ button }}" class="flex rounded-full border border-[#3F4241] hover:border-[#C33E21] w-[100px] h-[100px] justify-center items-center focus:bg-[#C33E21] group">
<twig:ux:icon name="{{ button }}" width="184" height="184" class="fill-[#C33E21] group-focus:fill-[#ffffff]" />
</button>
</li>
{% else %}
// ... lines 30 - 51

Para el resto de botones, copia el código del segundo botón, pégalo y sustituye el atributo value del botón y el nombre del icono por la variable button:

51 lines | templates/index.html.twig
// ... lines 1 - 28
{% else %}
<li>
<button name="button" value="{{ button }}" class="flex rounded-full border border-[#3F4241] hover:border-white w-[80px] h-[80px] justify-center items-center focus:bg-[#ffffff] group">
<twig:ux:icon name="{{ button }}" width="36" height="36" class="fill-white group-focus:fill-[#0E0E0E]" />
</button>
</li>
{% endif %}
// ... lines 36 - 51

Bien. Celébralo borrando el resto de botones codificados.

¡Vamos a probarlo! Vuelve a nuestra aplicación y actualiza... hm... Está mostrando los botones, pero no están en el orden correcto. Queremos que éste esté arriba, así que... ¿qué hacemos?

Ordenar los servicios con AsTaggedItem::$priority

Tenemos que imponer el orden de nuestros botones en el iterador. Para ello, abre PowerButton. #[AsTaggedItem] tiene un segundo argumento: priority.

Antes, con #[AutowireLocator], esto no era importante porque sólo buscábamos servicios por su nombre. Pero ahora que sí nos importa el orden, añadepriority y ponlo en, qué tal, 50:

15 lines | src/Remote/Button/PowerButton.php
// ... lines 1 - 6
#[AsTaggedItem('power', priority: 50)]
final class PowerButton implements ButtonInterface
// ... lines 9 - 15

Ahora vamos al botón "Subir canal" y añadimos una prioridad de 40:

15 lines | src/Remote/Button/ChannelUpButton.php
// ... lines 1 - 6
#[AsTaggedItem('channel-up', priority: 40)]
final class ChannelUpButton implements ButtonInterface
// ... lines 9 - 15

Al botón "Canal Abajo", una prioridad de 30:

15 lines | src/Remote/Button/ChannelDownButton.php
// ... lines 1 - 6
#[AsTaggedItem('channel-down', priority: 30)]
final class ChannelDownButton implements ButtonInterface
// ... lines 9 - 15

"Subir Volumen" una prioridad de 20:

15 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 6
#[AsTaggedItem('volume-up', priority: 20)]
final class VolumeUpButton implements ButtonInterface
// ... lines 9 - 15

y "Bajar volumen", una prioridad de 10:

15 lines | src/Remote/Button/VolumeDownButton.php
// ... lines 1 - 6
#[AsTaggedItem('volume-down', priority: 10)]
final class VolumeDownButton implements ButtonInterface
// ... lines 9 - 15

Cualquier botón sin prioridad asignada tiene una prioridad por defecto de 0.

Vuelve a nuestra aplicación y actualízala... ¡todo bien! ¡Ya estamos de nuevo en marcha! Todos los botones se han añadido automáticamente y en el orden correcto.

Pero te habrás dado cuenta de que tenemos un gran problema. Pulsa cualquier botón y... ¡Error!

Se ha intentado llamar a un método no definido "get" de la clase RewindableGenerator.

¿Eh?

Este RewindableGenerator es el objeto iterable que Symfony inyecta con #[AutowireIterator]. Podemos hacer un bucle sobre él, pero no tiene un método get(). ¡Buf!

A continuación, vamos a solucionarlo inyectando un objeto que sea a la vez un iterador de servicio y un localizador.