Description
Symfony version(s) affected
7.0, 7.1
Description
Hello.
I'm struggling to get a valid JSON API response in case of PHP fatal error when using built-in Symfony server.
I have an API controller (it has attribute format: "json"
) which can fail with an exception, and I wrote JsonProblemNormalizer (as adviced here: Overriding Error output for non-HTML formats)
//...
class JsonProblemNormalizer implements NormalizerInterface
{
public function normalize($exception, ?string $format = null, array $context = []): array
{
return [
'status' => 'error',
'message' => $exception->getMessage(),
'code' => $exception->getStatusCode(),
];
}
// ...
}
It lets my API controller return exception message in a JSON object which I then use on client side to display an error message.
But I'm getting weird response, like this:
<br />
<b>Fatal error</b>: Maximum execution time of 30 seconds exceeded in <b>REDACTED/vendor/symfony/serializer/Serializer.php</b> on line <b>269</b><br />
{"status":"error","message":"Error: Maximum execution time of 30 seconds exceeded","code":500}
The error itself is legit, but the problem is that the response seems to be a JSON prepended by some HTML which obviously can't be parsed on the client side as a valid JSON.
UPD.
I use symfony serve
as a development web-server. Looks like this command adds an HTML snippet to every response on a PHP fatal error.
How to reproduce
- Start built-in Symfony server:
symfony serve
- Define some controller file, like
BrokenJsonResponsePocController.php
- Write some API controller method:
use Symfony\Component\HttpFoundation\JsonResponse;
// ...
#[Route('/api_broken_json_response_poc', name: 'api_broken_json_response_poc', methods: ['GET'], format: "json")]
public function api_broken_json_response_poc(Request $request): JsonResponse
{
set_time_limit(5);
// hopefully this will exceed 5 seconds of maximum allowed time for script execution:
$idx = 0;
while ($idx < 9223372036854775807)
$idx++;
return $this->json(['idx' => $idx]);
}
- On client side, make a request:
fetch('/api_broken_json_response_poc')
.then(response => response.json())
.then(json => {
console.log(JSON.stringify(json))
})
.catch(err => {
console.log(err)
})
- Look at the request in browser dev console and observe the following on the 'Response' tab:
<br />
<b>Fatal error</b>: Maximum execution time of 5 seconds exceeded in <b>.../src/Controller/BrokenJsonResponsePocController.php</b> on line <b>64</b><br />
{"status":"error","message":"Error: Maximum execution time of 5 seconds exceeded","code":500}
Possible Solution
No response
Additional Context
I'm using PHP 8.2.26
on Linux Mint 20.3 Una (the base is Ubuntu 20.04).
Also, in dev environment, if you do this:
use Symfony\Component\HttpFoundation\JsonResponse;
// ...
#[Route('/api_broken_json_response_poc', name: 'api_broken_json_response_poc', methods: ['GET'], format: "json")]
public function api_broken_json_response_poc(Request $request): JsonResponse
{
throw new RuntimeException('Unhandled runtime exception');
}
then on client side you get normal JSON in the response without any HTML:
{"status":"error","message":"Unhandled runtime exception","code":500}