Login to bookmark this video
Buy Access to Course
11.

Más atributos de pereza

|

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

Lo bueno de los "servicios perezosos" es que no requieren cambios en tu código (siempre que los servicios no sean finales). Pero, ¿y si el servicio ParentalControlsviviera dentro de un paquete de terceros y fuera final? ¡Difícil! Pero tenemos algunas opciones.

#[AutowireServiceClosure]

Imagina que ParentalControls es final y vive en un paquete de terceros. En VolumeUpButton, sustituye #[Lazy] por #[AutowireServiceClosure] pasandoParentalControls::class:

30 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
// ... lines 12 - 14
public function __construct(
#[AutowireServiceClosure(ParentalControls::class)]
private \Closure $parentalControls,
) {
}
// ... lines 20 - 28
}

Esto inyectará un cierre que devolverá una instancia de ParentalControls cuando se invoque (y sólo se instanciará cuando se invoque).

Para ayudar a nuestro IDE, añade un docblock encima del constructor:@param \Closure():ParentalControls $parentalControls:

30 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
/**
* @param \Closure():ParentalControls $parentalControls
*/
public function __construct(
// ... lines 16 - 17
) {
}
// ... lines 20 - 28
}

Ahora, abajo en la sentencia if del método press(), cambia false por true para que siempre detectemos que el volumen es demasiado alto. Como $parentalControls es un cierre, tenemos que envolver $this->parentalControls entre llaves e invocarlo con () antes de llamar a ->volumeTooHigh():

30 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
// ... lines 12 - 20
public function press(): void
{
if (true) { // determine if volume is too high
($this->parentalControls)()->volumeTooHigh();
}
dump('Change the volume up');
}
}

¡Compruébalo! Como hemos añadido el docblock, nuestro IDE proporciona autocompletado y nos permite hacer clic (con CMD+clic) en el método volumeTooHigh(). ¡Genial!

Quita el dump(), gira hasta nuestra aplicación, actualiza y pulsa el botón "subir volumen". Salta al perfilador. Vemos que se está llamando a la lógica volumeTooHigh(). ¡Estupendo! El servicio ParentalControls sólo se instala cuando se invoca el cierre, y sólo lo invocamos cuando es necesario.

#[AutowireCallable]

Veamos otra forma de hacer lo mismo. En VolumeUpButton, sustituye #[AutowireServiceClosure] por #[AutowireCallable]. ManténParentalControls::class como primer argumento, pero anteponle service:

34 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
// ... lines 12 - 14
public function __construct(
#[AutowireCallable(
service: ParentalControls::class,
// ... lines 18 - 19
)]
private \Closure $parentalControls,
) {
}
// ... lines 24 - 32
}

#[AutowireCallable] también inyecta un cierre. Pero en lugar de devolver el objeto de servicio completo, instanciará el servicio, llamará a un único método y devolverá el resultado.

Hazlo multilínea para tener más espacio. Añade un segundo argumento:method: 'volumeTooHigh':

34 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
// ... lines 12 - 14
public function __construct(
#[AutowireCallable(
// ... line 17
method: 'volumeToHigh',
// ... line 19
)]
private \Closure $parentalControls,
) {
}
// ... lines 24 - 32
}

Cuando Symfony instancie un servicio que utilice #[AutowireCallable], por defecto, instanciará su servicio. ¡Es un ricino ansioso! Para evitarlo, añade un tercer argumento: lazy: true:

34 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
// ... lines 12 - 14
public function __construct(
#[AutowireCallable(
// ... lines 17 - 18
lazy: true,
)]
private \Closure $parentalControls,
) {
}
// ... lines 24 - 32
}

Ahora, ParentalControls sólo se instanciará cuando se invoque el cierre.

En el docblock anterior, cambia el tipo de retorno del cierre a void para que coincida con el tipo de retorno de volumeTooHigh():

34 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
/**
* @param \Closure():void $parentalControls
*/
public function __construct(
// ... lines 16 - 21
) {
}
// ... lines 24 - 32
}

Abajo, en press(), elimina la llamada a ->volumeTooHigh():

34 lines | src/Remote/Button/VolumeUpButton.php
// ... lines 1 - 9
final class VolumeUpButton implements ButtonInterface
{
// ... lines 12 - 24
public function press(): void
// ... line 26
if (true) { // determine if volume is too high
($this->parentalControls)();
}
// ... lines 30 - 31
}
}

Ahora el cierre lo llama cuando se invoca.

Vuelve a la aplicación, actualízala, pulsa el botón "subir volumen" y salta al perfilador. Se sigue llamando a la lógica ParentalControls::volumeTooHigh(). ¡Perfecto!

#[AutowireCallable] es ciertamente genial, pero para la mayoría de los casos, prefiero utilizar#[AutowireServiceClosure] porque: Es perezoso por defecto. Es más flexible porque devuelve el objeto de servicio completo. * Y, con docblocks adecuados, obtendremos: autocompletado, navegación por métodos, soporte de refactorización y un mejor análisis estático con herramientas como PhpStan.


Bien equipo, ¡eso es todo por este curso! Pon un atributo #[TimeForVacation] en tu código y ¡relájate!

La configuración de servicios YAML no va a desaparecer del todo, pero estos atributos mejoran tu experiencia como desarrollador al mantener juntos tu código y la configuración de servicios.

Casi en cada nueva versión de Symfony se añaden más atributos. ¡Sigue el blog de Symfony para estar al día! Mira esto, en Symfony 7.2, ¡hay un nuevo atributo #[WhenNot]! Es básicamente lo contrario del atributo #[When] del que hablábamos antes. ¡Genial!

Consulta la sección "Inyección de dependencia" del documento Descripción general de los atributos de Symfony para ver una lista de todos los atributos de inyección de dependencia disponibles actualmente y cómo funcionan.

¡Hasta la próxima! ¡Feliz programación!