Decorando Servicios en Symfony 7
En proyectos profesionales de Symfony, a menudo necesitamos extender la funcionalidad de un servicio sin modificar su código original. Para ello, Symfony 7 ofrece la anotación #[AsDecorator]
, que nos permite sustituir automáticamente un servicio por su decorador sin necesidad de configuración manual en services.yaml
.
En este post, vamos a implementar un decorador de un servicio de notificaciones para agregar logging extra antes y después del envío de correos electrónicos.
Caso de uso
Tenemos un servicio NotificationService
que envía correos electrónicos. Queremos decorarlo con LoggingNotificationService
para que registre logs antes y después de enviar el email, sin modificar el código original.
1. Servicio Original (NotificationService
)
namespace App\Service;
use Psr\Log\LoggerInterface;
class NotificationService
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function sendEmail(string $to, string $message): void
{
// Simulación del envío de correo
echo "Enviando email a $to: $message";
// Registrar que el correo se ha enviado
$this->logger->info("Correo enviado a {$to}");
}
}
2. Crear el Decorador (LoggingNotificationService
)
Ahora creamos un decorador con #[AsDecorator]
que añade logs adicionales antes y después de llamar al servicio original:
namespace App\Service;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator(decorates: 'App\Service\NotificationService')]
class LoggingNotificationService
{
private NotificationService $notificationService;
private LoggerInterface $logger;
public function __construct(NotificationService $notificationService, LoggerInterface $logger)
{
$this->notificationService = $notificationService;
$this->logger = $logger;
}
public function sendEmail(string $to, string $message): void
{
// Agregar logging antes de enviar
$this->logger->info("Preparando para enviar un correo a {$to}");
// Llamamos al servicio original
$this->notificationService->sendEmail($to, $message);
// Agregar logging después de enviar
$this->logger->info("Correo enviado correctamente a {$to}");
}
}
3. Uso en un Controlador (NotificationController
)
Ahora probemos el decorador en un controlador:
namespace App\Controller;
use App\Service\NotificationService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class NotificationController extends AbstractController
{
#[Route('/send-notification', name: 'send_notification')]
public function sendNotification(NotificationService $notificationService): Response
{
$notificationService->sendEmail('usuario@example.com', '¡Bienvenido a nuestro servicio!');
return new Response('Correo enviado y logueado correctamente.');
}
}
4. Resultado esperado
Si accedemos a http://127.0.0.1/send-notification
, el sistema generará estos logs:
Preparando para enviar un correo a usuario@example.com
Enviando email a usuario@example.com: ¡Bienvenido a nuestro servicio!
Correo enviado correctamente a usuario@example.com
Y en el navegador veremos:
Correo enviado y logueado correctamente.
Beneficios de usar #[AsDecorator]
- Extiende la funcionalidad sin modificar el código original.
- Automatiza la sustitución del servicio sin configurar
services.yaml
. - Separa responsabilidades: el servicio original solo envía emails y el decorador solo agrega logs.
- Fácil mantenimiento y escalabilidad.