<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Psr\Log\LoggerInterface; // Para loguear las excepciones
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; // Para acceder a parámetros como kernel.environment
class ExceptionSubscriber implements EventSubscriberInterface
{
private LoggerInterface $logger;
private string $environment;
// Inyecta el logger y el entorno del kernel
public function __construct(LoggerInterface $logger, ParameterBagInterface $params)
{
$this->logger = $logger;
$this->environment = $params->get('kernel.environment');
}
/**
* Define a qué eventos se suscribe este listener.
*/
public static function getSubscribedEvents(): array
{
// Se suscribe al evento KernelEvents::EXCEPTION
// La prioridad de -128 asegura que se ejecute después de la mayoría de los otros listeners
// incluyendo los del componente de seguridad, lo que es útil para errores de autenticación.
return [
KernelEvents::EXCEPTION => ['onKernelException', -128],
];
}
/**
* Maneja el evento de excepción.
*/
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable(); // Obtiene la excepción lanzada
$request = $event->getRequest(); // Obtiene la petición actual
// Verifica si la petición es una API (por la ruta o el encabezado Accept)
// Por ejemplo, si la URL comienza con '/api'
$isApiRequest = str_starts_with($request->getPathInfo(), '/api');
// O si el cliente solicita explícitamente una respuesta JSON
$acceptsJson = str_contains($request->headers->get('Accept', ''), 'application/json');
if ($isApiRequest || $acceptsJson) {
// Determina el código de estado HTTP adecuado
// Si la excepción es una HttpException (ej. NotFoundHttpException, UnauthorizedHttpException),
// usa su código de estado. De lo contrario, usa 500 (Internal Server Error).
$statusCode = $exception instanceof HttpExceptionInterface
? $exception->getStatusCode()
: Response::HTTP_INTERNAL_SERVER_ERROR;
// Define el mensaje de error
// En desarrollo (dev), incluye el mensaje de la excepción para depuración.
// En producción (prod), usa un mensaje genérico por seguridad.
//$message = 'Ha ocurrido un error inesperado.';
//if ($this->environment === 'dev') {
// $message = $exception->getMessage();
//}
$message = $exception->getMessage();
// Opcional: Registra la excepción completa para depuración en los logs del servidor
$this->logger->error(
'API Exception caught: {message}',
['message' => $exception->getMessage(), 'exception' => $exception]
);
// Crea la respuesta JSON
$response = new JsonResponse([
'status' => 'error',
'message' => $message,
// Incluye la traza de pila completa solo en desarrollo
'trace' => $this->environment === 'dev' ? $exception->getTraceAsString() : null,
// Puedes añadir otros campos como un código de error interno, etc.
], $statusCode);
// Establece la respuesta generada como la respuesta del evento
$event->setResponse($response);
// Si deseas detener la propagación de otros listeners de eventos para esta excepción:
// $event->stopPropagation();
}
}
}