8

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:

prod

prod view of html for rendered exceptions

dev

dev view of html view for rendered exceptions

4
  • related: github.com/symfony/symfony/issues/25905 Commented Apr 9, 2020 at 17:04
  • 1
    Symfony - How to Customize Error Pages Commented Apr 9, 2020 at 17:13
  • @CodeSpirit That looks promising, will see if I get to the gist of it. Commented Apr 9, 2020 at 17:15
  • @CodeSpirit Feel free to post your answer (you may even copy mine), so I can accept yours. I'd then delete my own. Commented Apr 9, 2020 at 17:23

4 Answers 4

11

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.

Sign up to request clarification or add additional context in comments.

3 Comments

did you do anything else? The symfony docs do say if you want to change content you need to implement a Normalizer, but even adding that didn't work for me on a fresh install. Do you have any configuration anywhere that tells symfony to use json?
I needed to send the request with an Accept header. It would be better if I could just configure symfony to only support some formats and default to one.
@user1718888 You can open a follow up question with your use case, you may reference this question as a link when writing it.
4

Create a event listener ExceptionListener and register in services.yml

services:
    ...
    App\EventListener\ExceptionListener:
        tags:
            - { name: kernel.event_listener, event: kernel.exception }

ExceptionLister.php

// 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);
        }
    }
}

Comments

2

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)

See the documentation here

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);
    }
}

Comments

0

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());
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.