Clean Way to Throw PHP Exception Through Jquery/Ajax and JSON

Clean way to throw php exception through jquery/ajax and json

You could do something like this in PHP (assuming this gets called via AJAX):

<?php

try {
if (some_bad_condition) {
throw new Exception('Test error', 123);
}
echo json_encode(array(
'result' => 'vanilla!',
));
} catch (Exception $e) {
echo json_encode(array(
'error' => array(
'msg' => $e->getMessage(),
'code' => $e->getCode(),
),
));
}

In JavaScript:

$.ajax({
// ...
success: function(data) {
if (data.error) {
// handle the error
throw data.error.msg;
}
alert(data.result);
}
});

You can also trigger the error: handler of $.ajax() by returning a 400 (for example) header:

header('HTTP/1.0 400 Bad error');

Or use Status: if you're on FastCGI. Note that the error: handler doesn't receive the error details; to accomplish that you have to override how $.ajax() works :)

Exception caught in try-catch, returned in ajax error state not success

I see some solutions:

  1. [GOOD] Disable connect warnings and throw custom exception, to example https://stackoverflow.com/a/14049169/1559720
  2. [BAD] Bufferize output using ob_start

For first point, example:

@$mysql = new mysqli($host, $user, $password, $database);
if ($mysql->connect_errno) {
throw new \Exception($mysql->connect_error, $mysql->connect_errno);
}

Make PHP always respond with JSON, even when outputting errors

For PHP 5.x:

set_exception_handle to catch exceptions

set_error_handler to catch errors

There is no way to catch parse errors. From the docs:

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT

For PHP 7.x:

All errors are catchable, so you can wrap your php script in

try {
// your app here
} catch (Throwable $t) {
// convert it to json
}

For any php version:

No way to handle responses sent directly from the web server, like 404, 504 to name a few.

The best thing you can do is to check response code in your javascript, and process only 200 responses, which you can perfectly control in your php script.

Report PHP errors in JSON format complying with the regular content-type

If you must return PHP error/exceptions to the client-side, which is not recommended (but I know, it's easier for development), you gonna need a custom error/uncaught-exception handler for PHP. This way you're able to customize how the errors/exceptions are shown.

Here's a sample code that outputs errors and uncaught exceptions as JSON objects.

// Set error handler
set_error_handler('api_error_handler');

function api_error_handler($errno, $errstr) {
return api_error($errstr, $errno, 500);
}

// Set uncaught exceptions handler
set_exception_handler('api_exception_handler');

function api_exception_handler($exception) {
return api_error($exception->getMessage(), $exception->getCode(), 500);
}

// Error/Exception helper
function api_error($error, $errno, $code) {
// In production, you might want to suppress all these verbose errors
// and throw a generic `500 Internal Error` error for all kinds of
// errors and exceptions.
if ($environment == 'production') {
$errno = 500;
$error = 'Internal Server Error!';
}

http_response_code($code);
header('Content-Type: application/json');

return json_encode([
'success' => false,
'errno' => $errno,
'error' => $error,
]);
}

But that's not all; Since user-defined error handlers are not able to handle fatal errors, fatal error messages will still be displayed. You need to disable displaying errors with a call to ini_set():

ini_set('display_errors', 0);

So how to handle fatal errors? Fatal errors can be handled on shutdown with register_shutdown_function(). In the shutdown handler, we need to get the last error information with a call to error_get_last(). So:

// Set shutdown handler
register_shutdown_function('api_fatal_error_handler');

function api_fatal_error_handler() {
$error = error_get_last();

if ($error && error_reporting() && $error['type'] === E_ERROR) {
return api_error($error['message'], E_CORE_ERROR, 500);
}
}

Then on the javascript side of things, you have to add an error callback and show the error information to the user.

After all, why not using a mature error/exception handler package instead of implementing all of these? Meet Whoops.

PHP trigger AJAX error code without using array

If you want to trigger the AJAX error handler, just pass back something other than a 200! Try this:

<?php
header("HTTP/1.0 404 Not Found");
exit();
?>

Just remember to do two things:

  1. Try to send the correct error code to comply with HTTP methods. i.e. if you page throws an error, then you should return a 500, etc. You can see a reference here at the w3.org site.
  2. Make sure you send the header before ANY other content except whitespace.

This should be the clean solution you are going for.

Slim3/DRY - How to handle errors/exceptions correctly without duplicating code?

You can achieve similar similar results by creating an error handler which outputs JSON.

namespace Slim\Handlers;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;

final class ApiError extends \Slim\Handlers\Error
{
public function __invoke(Request $request, Response $response, \Exception $exception)
{
$status = $exception->getCode() ?: 500;
$data = [
"status" => "error",
"message" => $exception->getMessage(),
];
$body = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withStatus($status)
->withHeader("Content-type", "application/json")
->write($body);
}
}

You must also configure Slim to use your custom error handler.

$container = $app->getContainer();

$container["errorHandler"] = function ($container) {
return new Slim\Handlers\ApiError;
};

Check Slim API Skeleton for example implemention.

Returning an exception with PHP to javascript FETCH API

In your JavaScript you could use the reject handler to handle invalid data sent to the server:

fetch(
URL_TO_POST,
{
method: 'post',
body: new FormData(document.querySelector('form'))
}
)
.then(
function(response) {
//https://httpstatuses.com/422
if (response.status === 422) {
return Promise.reject(response.json());
}
//check for other things that could have gone wrong
return response.json();
}
).then(
function(json) {
console.log("received success json",json)
}
,function(json) {
console.log("received reject json",json)
}
)

Not clear what you use for PHP but you can provide status code of 422 with some json:

http_response_code(422);
echo json_encode(array("error" => "missing field", "field" => "email"));


Related Topics



Leave a reply



Submit