feat(router): add error boundary with exception callback
This commit is contained in:
@@ -50,7 +50,7 @@ class Router
|
||||
*
|
||||
* @var callable $notFoundCallback
|
||||
*/
|
||||
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
|
||||
public static $notFoundCallback = Router::defaultNotFound(...);
|
||||
|
||||
/**
|
||||
* Default callback function for when
|
||||
@@ -64,6 +64,54 @@ class Router
|
||||
echo '<h2 style="text-align: center;margin: 25px 0px;">Error 404 - Page Not Found</h2>';
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback function to be executed when the router's boundary
|
||||
* catches an exception thrown anywhere in the matched route chain.
|
||||
* It receives the exception as a named argument: the handler must
|
||||
* declare its parameter as $exception (typeable as \Throwable).
|
||||
*
|
||||
* @var callable $exceptionCallback
|
||||
*/
|
||||
public static $exceptionCallback = Router::defaultException(...);
|
||||
|
||||
/**
|
||||
* Default callback function for exception responses.
|
||||
*
|
||||
* The HTTP status comes from the exception's code only when it is an
|
||||
* integer in the 400-599 range; anything else (0, arbitrary codes, the
|
||||
* SQLSTATE string carried by PDOException) answers 500. The body is
|
||||
* negotiated from the request's Accept header: JSON when the client asks
|
||||
* for application/json, plain text otherwise. The trace is serialized
|
||||
* from getTraceAsString() because json_encode() of a Throwable yields an
|
||||
* empty object: its properties are protected.
|
||||
*
|
||||
* @param \Throwable $exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function defaultException(\Throwable $exception): void
|
||||
{
|
||||
$code = $exception->getCode();
|
||||
http_response_code(is_int($code) && $code >= 400 && $code <= 599 ? $code : 500);
|
||||
|
||||
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
|
||||
|
||||
if (str_contains($accept, 'application/json')) {
|
||||
header('Content-Type: application/json');
|
||||
print(json_encode([
|
||||
'error' => $exception->getMessage(),
|
||||
'exception' => get_class($exception),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => explode(PHP_EOL, $exception->getTraceAsString()),
|
||||
]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
print($exception->getMessage() . PHP_EOL . $exception->getTraceAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* __construct
|
||||
*/
|
||||
@@ -363,50 +411,60 @@ class Router
|
||||
/**
|
||||
* Applies the route configuration.
|
||||
*
|
||||
* This method is the framework's error boundary: any \Throwable thrown
|
||||
* while running the matched route's callback chain, while printing the
|
||||
* returned data, or while resolving the not-found callback is caught
|
||||
* here and rendered through $exceptionCallback. A failing handler is
|
||||
* deliberately left uncaught (double failure falls back to PHP itself).
|
||||
*
|
||||
* @param string|null $path (optional) Path to use. If not defined, it detects the current path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function apply(?string $path = null): void
|
||||
{
|
||||
$path = $path ?? static::currentPath();
|
||||
$routers = match ($_SERVER['REQUEST_METHOD']) { // Selects an array of routers based on the method
|
||||
'POST' => static::$post,
|
||||
'PUT' => static::$put,
|
||||
'PATCH' => static::$patch,
|
||||
'DELETE' => static::$delete,
|
||||
default => static::$get
|
||||
};
|
||||
try {
|
||||
$path = $path ?? static::currentPath();
|
||||
$routers = match ($_SERVER['REQUEST_METHOD']) { // Selects an array of routers based on the method
|
||||
'POST' => static::$post,
|
||||
'PUT' => static::$put,
|
||||
'PATCH' => static::$patch,
|
||||
'DELETE' => static::$delete,
|
||||
default => static::$get
|
||||
};
|
||||
|
||||
foreach ($routers as $router) { // Checks all routers to see if they match the current path
|
||||
if (preg_match_all('/^' . $router['path'] . '\/?$/si', $path, $matches, PREG_PATTERN_ORDER)) {
|
||||
unset($matches[0]);
|
||||
foreach ($routers as $router) { // Checks all routers to see if they match the current path
|
||||
if (preg_match_all('/^' . $router['path'] . '\/?$/si', $path, $matches, PREG_PATTERN_ORDER)) {
|
||||
unset($matches[0]);
|
||||
|
||||
// Checking and storing the variable parameters of the route
|
||||
if (isset($matches[1])) {
|
||||
static::$params = new Neuron();
|
||||
foreach ($matches as $index => $match) {
|
||||
$paramName = $router['paramNames'][$index - 1];
|
||||
static::$params->{$paramName} = urldecode($match[0]);
|
||||
// Checking and storing the variable parameters of the route
|
||||
if (isset($matches[1])) {
|
||||
static::$params = new Neuron();
|
||||
foreach ($matches as $index => $match) {
|
||||
$paramName = $router['paramNames'][$index - 1];
|
||||
static::$params->{$paramName} = urldecode($match[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processes the callback queue
|
||||
foreach (array_reverse($router['callback']) as $callback) {
|
||||
$data = Synapsis::resolve($callback);
|
||||
}
|
||||
// Processes the callback queue
|
||||
foreach (array_reverse($router['callback']) as $callback) {
|
||||
$data = Synapsis::resolve($callback);
|
||||
}
|
||||
|
||||
// By default, prints as JSON if something is returned
|
||||
if (isset($data)) {
|
||||
header('Content-Type: application/json');
|
||||
print(json_encode($data));
|
||||
}
|
||||
// By default, prints as JSON if something is returned
|
||||
if (isset($data)) {
|
||||
header('Content-Type: application/json');
|
||||
print(json_encode($data));
|
||||
}
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no router matches, call $notFoundCallBack
|
||||
Synapsis::resolve(static::$notFoundCallback);
|
||||
// If no router matches, call $notFoundCallBack
|
||||
Synapsis::resolve(static::$notFoundCallback);
|
||||
} catch (\Throwable $exception) {
|
||||
Synapsis::resolve(static::$exceptionCallback, ['exception' => $exception]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user