Más atributos de pereza
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeLo 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 ParentalControls
viviera 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
:
// ... 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
:
// ... 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()
:
// ... 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
:
// ... 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'
:
// ... 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
:
// ... 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()
:
// ... 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()
:
// ... 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!