Decorar un servicio con AsDecorator
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 SubscribeEn el capítulo anterior, utilizamos #[AsAlias] para asignar un alias de RemoteInterface aButtonRemote, de modo que cuando tecleamos RemoteInterface, nos da el servicioButtonRemote. Pero, ¡esto rompió nuestro registro! Tenemos que decirle a Symfony que nos dé LoggerRemote en su lugar, pero que pase el servicio ButtonRemote aLoggerRemote.
#[AsDecorator]
Básicamente, tenemos que decirle a Symfony que ButtonRemote está siendo decorado por LoggerRemote. Para ello, en LoggerRemote, utiliza otro atributo: #[AsDecorator] pasando el servicio que decora: ButtonRemote::class:
| // ... lines 1 - 5 | |
| use Symfony\Component\DependencyInjection\Attribute\AsDecorator; | |
| // ... line 7 | |
| (ButtonRemote::class) | |
| final class LoggerRemote implements RemoteInterface | |
| // ... lines 10 - 38 |
Esto le dice a Symfony:
Oye, si algo te pide el servicio
ButtonRemote, daleLoggerRemoteen su lugar.
En esencia, Symfony intercambia los servicios y convierte ButtonRemote en el servicio "interno" de LoggerRemote. Esto solidifica la necesidad del RemoteInterface que creamos antes. Si intentáramos inyectar directamente ButtonRemote, obtendríamos un error de tipo porque Symfony estaría intentando inyectar LoggerRemote.
Decoración del servicio
Así que, sígueme en esto: autoconectamos RemoteInterface. Eso está aliasado a ButtonRemote, así que Symfony intenta darnos eso. Pero entonces, gracias a #[AsDecorator], lo cambia por LoggerRemote... pero pasa ButtonRemote a LoggerRemote. En resumen, AsDecorator nos permite decorar un servicio existente con otro.
Vuelve a la aplicación, actualízala y... pulsa "subir volumen". Comprueba el panel del perfilador "Registros" y... ¡estamos registrando de nuevo!
Decoradores múltiples
Utilizar #[AsDecorator] hace que sea superfácil añadir múltiples decoradores. Quizá queramos añadir un decorador de limitación de velocidad para evitar que los niños machaquen botones. Sólo tendríamos que crear una clase RateLimitingRemote que implemente RemoteInterface y añadir#[AsDecorator(ButtonRemote::class)].
#[AsDecorator(ButtonRemote::class)]
class RateLimitingRemote implements RemoteInterface
{
public function __construct(
private RateLimiter $rateLimiter,
private RemoteInterface $inner,
) {
}
// ...
}
A continuación: Añadiremos un canal de registro personalizado y exploraremos el "autocableado con nombre"
9 Comments
Hi everyone,
First thanks for your great course !
I'm trying to implement another decorator class (RateLimiterButton) in addition to LoggerRemote (with priority 5):
but i'm facing a Circular reference error:
I'm i missing something ?
Thanks
Hey @RobC25!
Small thing, instead of:
Inject the interface instead:
ButtonRemoterefers to the fully decorated service, in this caseRateLimiterRemoteso Symfony is trying to inject it into itself. When usingRemoteInterface, Symfony is smart enough to inject the service it's decorating.Hope that helps!
--Kevin
Thanks a lot, it's more clear now.
Hi All,
Thank you
I would add it's possible to add decorateor like that
#[AsDecorator(RemoteInterface::class)]
Hey @Amine!
You're absolutely right. I should have done that!
-Kevin
Hi there,
One thing is confusing to me here. In the previous chapter we said that we use
#[AsAlias]to tell Symfony which of our two services to use when we type-hintRemoteInterface. This was done while injectingRemoteInterfaceintoLoggerRemote. That's clear.Now, we mark
LoggerRemotewith#[AsDecorator], so anything asks for theButtonRemoteservice, give themLoggerRemoteinstead.So why doesn't it loop in
LoggerRemote? There we injectRemoteInterface, and via#[AsAlias]we expect and getButtonRemote, but via#[AsDecorator]Symfony should try to injectLoggerRemoteinstead. Service self injection?Hey @KamilS!
Yeah, this part can be a bit tough to conceptualize. Let's see if I can explain it a different way:
In the last chapter, when we added
#[AsAlias]toButtonRemote, this told Symfony to injectButtonRemotewhenever we autowireRemoteInterface.Now, when we add
#[AsDecorator(ButtonRemote::class)]toLoggerRemote, Symfony, behind the scenes, swaps theRemoteInterfacealias to now point toLoggerRemote.If we added a new service:
RateLimiterRemotewith#[AsDecorator(ButtonRemote::class)], Symfony would again swap the alias so thatRemoteInterfacepoints toRateLimiterRemote. It always swaps it to the top-level of the decoration stack.I hope that helps but let me know if it's still confusing.
--Kevin
Thanks @kbond!
But the question is: Why is
LoggerRemotestill injected withButtonRemoteand notLoggerRemotewhich is its decorator (or in the future withRateLimiterRemote)? Is it because it is the same class definition and at this stage Symfony does not know about decorating yet, and the current service implementingRemoteInterfaceis stillButtonRemote?Because
LoggerRemotehas theAsDecorator(ButtonRemote::class)attribute, this changes how theLoggerRemoteis wired up. For$inner, a new service is created internally (LoggerRemote.inner), and this is injected for$inner. This new service is the realButtonRemote. Additionally, the decorator logic changes theRemoteInterfacealias to point toLoggerRemoteinstead ofButtonRemote.It might help to use
bin/console debug:container remoteto see these services.I hope this helps but if not, please let me know and I can try and explain differently!
"Houston: no signs of life"
Start the conversation!