Reputation: 66500
Within a symfony5 controller, I can return json responses via:
return $this->json(['key' => 'content');
Yet when I throw an HttpException, I see the default html error page in both dev and production.
I want to create a restful api, so I want to convert all HttpExceptions into json.
I want to configure all my controllers to format their response as json. At most, I want to add one Exception handler that would transform the excpetions into proper messages. (In prod it should have less information, in dev it may contain the exception stacktrace.)
How can I achieve this? I thought I could use the format
option of the @Route
annotation but it doesn't work.
This is my example controller:
<?php declare(strict_types=1);
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
class StatusController extends AbstractController
{
/**
* @Route("/status", name="status", format="json")
* @Template
* @return JsonResponse
*/
public function status()
{
if (true) {
// this will render as html, how to serialize it as json?
throw new NotFoundHttpException("This is an example");
}
$ok = new \stdClass();
$ok->status = "OK";
return $this->json($ok);
}
}
While looking for this I came across this PR which seems to achieve what I am trying to do, yet I am unsure what I am missing.
On the symfony blog I found following answer by Yonel Ceruto saying
you will need to install/enable the serializer component,
yet I have no idea what this entails.
In dev and prod I got these html views instead of a json response:
Upvotes: 7
Views: 14701
Reputation: 21
In an API development context the simplest and most effective thing is to Override the Default ErrorController. This allows you to format the error output according to the JSON Api standard used in your application (Jsend on the example)
First create your ApiErrorController :
php bin/console make:controller --no-template ApiErrorController
Second set the framework configuration to use it:
framework:
...
error_controller: App\Controller\ApiErrorController::show
Third edit your ApiErrorController :
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Psr\Log\LoggerInterface;
use Throwable;
class ApiErrorController extends AbstractController
{
#[Route('/api/error', name: 'app_api_error')]
public function show(Throwable $exception, LoggerInterface $logger): JsonResponse
{
//Transform Warning status code from 0 to 199
$statusCode = (!$exception->getCode() || $exception->getCode()==0)? 199 : $exception->getCode() ;
//Log the error
if($statusCode<=199){
$logger->warning($exception->getMessage());
} else {
$logger->error($exception->getMessage());
}
//Prepare basic data output coforming on JSend STD.
$data = [
'status'=>($statusCode>=400)? 'error' : 'fail',
'message' => $exception->getMessage(),
];
//If Dev add trace
if ($this->getParameter('kernel.environment') === 'dev') {
$data['trace'] = $exception->getTrace();
}
//return Json
return $this->json($data, $statusCode);
}
}
Upvotes: 2
Reputation: 986
Create a event listener ExceptionListener
and register in services.yml
services:
...
App\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
// src/EventListener/ExceptionListener.php
namespace App\EventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ExceptionListener
{
public function onKernelException(ExceptionEvent $event)
{
// You get the exception object from the received event
$exception = $event->getThrowable();
// Get incoming request
$request = $event->getRequest();
// Check if it is a rest api request
if ('application/json' === $request->headers->get('Content-Type'))
{
// Customize your response object to display the exception details
$response = new JsonResponse([
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'traces' => $exception->getTrace()
]);
// HttpExceptionInterface is a special type of exception that
// holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
}
// sends the modified response object to the event
$event->setResponse($response);
}
}
}
Upvotes: 4
Reputation: 144
The package symfony/serializer-pack
not work in my environment.
Finally I create A ErrorController to response json .
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Throwable;
class JsonErrorController
{
public function show(Throwable $exception, LoggerInterface $logger)
{
return new JsonResponse($exception->getMessage(), $exception->getCode());
}
}
Upvotes: 0
Reputation: 66500
Turns out all I was missing was installing the serializer-pack as pointed out in the symfony docs:
composer require symfony/serializer-pack
Afterwards, my exceptions render as json fine.
Upvotes: 10