View file app/Handlers/DefaultErrorHandler.php

File size: 5.25Kb
<?php

namespace App\Handlers;

use App\Exceptions\AbortException;
use App\Services\View;
use DomainException;
use Fig\Http\Message\StatusCodeInterface;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Slim\Exception\HttpException;
use Slim\Interfaces\ErrorHandlerInterface;
use Throwable;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;
use Whoops\Util\Misc;

/**
 * Default Error Renderer.
 */
final class DefaultErrorHandler implements ErrorHandlerInterface
{
    public function __construct(
        protected View $view,
        protected LoggerInterface $logger,
        protected ResponseInterface $response,
    ) {}

    /**
     * Invoke.
     *
     * @param ServerRequestInterface $request The request
     * @param Throwable $exception The exception
     * @param bool $displayErrorDetails Show error details
     * @param bool $logErrors Log errors
     * @param bool $logErrorDetails Log error details
     *
     * @return ResponseInterface The response
     */
    public function __invoke(
        ServerRequestInterface $request,
        Throwable $exception,
        bool $displayErrorDetails,
        bool $logErrors,
        bool $logErrorDetails
    ): ResponseInterface {
        $code = $this->getHttpStatusCode($exception);

        if (setting('logError')) {
            $error = $this->getErrorDetails($exception);
            $error['method'] = $request->getMethod();
            $error['url']    = $request->getUri()->getPath();

            $this->logger->error($exception->getMessage(), $error);
        }

        if (strtolower($request->getHeaderLine('X-Requested-With')) === 'xmlhttprequest') {
            $error = [
                'error' => [
                    'code'    => $code,
                    'message' => $this->getErrorMessage($exception),
                ]
            ];
            $this->response->getBody()->write((string) json_encode($error));

            return $this->response->withStatus($code)->withHeader('Content-Type', 'application/json');
        }

        if (
            class_exists(Run::class)
            && ! $exception instanceof HttpException
        ) {
            $handler = Misc::isAjaxRequest() ?
                new JsonResponseHandler() :
                new PrettyPageHandler();

            $whoops = new Run();
            $whoops->pushHandler($handler);

            $this->response->getBody()->write($whoops->handleException($exception));

            return $this->response;
        }

        $view = $this->view->exists('errors/' . $code) ? $code : 'default';
        $response = $this->view->render(
            $this->response,
            'errors/' . $view,
            ['message' => $this->getErrorMessage($exception)]
        );

        return $response->withStatus($code);
    }

    /**
     * Get http status code.
     *
     * @return int The http code
     */
    private function getHttpStatusCode(Throwable $exception): int
    {
        // Detect status code
        $statusCode = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR;

        if ($exception instanceof HttpException) {
            $statusCode = $exception->getCode();
        }

        if (
            $exception instanceof DomainException
            || $exception instanceof InvalidArgumentException
        ) {
            $statusCode = StatusCodeInterface::STATUS_BAD_REQUEST;
        }

        $file = basename($exception->getFile());
        if ($file === 'CallableResolver.php') {
            $statusCode = StatusCodeInterface::STATUS_NOT_FOUND;
        }

        return $statusCode;
    }

    /**
     * Get error message.
     *
     * @param Throwable $exception The error
     *
     * @return array The error details
     */
    private function getErrorDetails(Throwable $exception): array
    {
        if (setting('logErrorDetails')) {
            return [
                'message'  => $exception->getMessage(),
                'code'     => $exception->getCode(),
                'file'     => $exception->getFile(),
                'line'     => $exception->getLine(),
                'previous' => $exception->getPrevious(),
                'trace'    => $exception->getTrace(),
            ];
        }

        return [
            'message' => $exception->getMessage(),
        ];
    }

    /**
     * Get error message.
     *
     * @param Throwable $exception The error
     *
     * @return string The message
     */
    private function getErrorMessage(Throwable $exception): string
    {
        $errorMessage = '500 Internal Server Error';

        if ($exception->getCode() === 403) {
            $errorMessage = '403 Access denied';
        } elseif ($exception->getCode() === 404) {
            $errorMessage = '404 Not Found';
        } elseif ($exception->getCode() === 405) {
            $errorMessage = '405 Method Not Allowed';
        } elseif ($exception->getCode() >= 400 && $exception->getCode() <= 499) {
            $errorMessage = sprintf('%s Error', $exception->getCode());
        }

        if (
            setting('displayErrorDetails')
            || $exception instanceof AbortException
        ) {
            $errorMessage = $exception->getMessage();
        }

        return $errorMessage;
    }
}