juliangut/slim-exception

error handlers per status code

Closed this issue · 1 comments

interface HttpExceptionHandler
{
    public function handle(RequestInterface $request, ResponseInterface $response, HttpException $exception);
}

class HttpExceptionManager
{
    /**
     * @var HttpExceptionHandler[]
     */
    protected $handlers = [];

    /**
     * @var HttpExceptionHandler
     */
    protected $defaultHandler;

    public function __construct(HttpExceptionHandler $defaultHandler)
    {
        $this->defaultHandler = $defaultHandler;
    }

    public function addHandler(string $httpStatusCode, HttpExceptionHandler $handler)
    {
        $this->handlers[$httpStatusCode] = $handler;
    }

    public function getErrorHandler(): callable
    {
        $errorHandler = [$this, 'handleError'];

        return function(ServerRequestInterface $request, ResponseInterface $response, \Throwable $exception) use ($errorHandler) {
            return $errorHandler($request, $response, $exception);
        };
    }

    public function getNotAllowedHandler(): callable
    {
        // Handle OPTIONS request

        $errorHandler = [$this, 'handleError'];

        return function(ServerRequestInterface $request, ResponseInterface $response, array $methods = []) use ($errorHandler) {
            return $errorHandler($request, $response, HttpExceptionFactory::methodNotAllowed('Method "XXX" not allowed. Must be ...'));
        };
    }

    public function getNotFoundHandler(): callable
    {
        $errorHandler = [$this, 'handleError'];

        return function(ServerRequestInterface $request, ResponseInterface $response) use ($errorHandler) {
            return $errorHandler($request, $response, HttpExceptionFactory::notFound('Not found'));
        };
    }

    protected function handleError(RequestInterface $request, ResponseInterface $response, HttpException $exception)
    {
        $handler = $this->defaultHandler;

        $statusCode = $exception->getStatusCode();
        if (array_key_exists($statusCode, $this->handlers)) {
            $handler = $this->handlers[$statusCode];
        }

        return $handler->handle($request, $response, $exception);
    }
}

$exceptionManager = new HttpExceptionManager();

$container['errorHandler'] = $exceptionManager->getErrorHandler();
$container['phpErrorHandler'] = $exceptionManager->getErrorHandler();
$container['notFoundHandler'] = $exceptionManager->getNotFoundHandler();
$container['notAllowedHandler'] = $exceptionManager->getNotAllowedHandler();

$app->run();

Exception handlers:

abstract class AbstractHttpExceptionHandler implements HttpExceptionHandler
{
    public function handle(RequestInterface $request, ResponseInterface $resposne, HttpException $exception): string
    {
        return $this->getErrorOutput($this->getContentType($request), $exception, $request);
    }

    protected function getContentType(RequestInterface $request): string
    {
        $requestedContentTypes = explode(',', $request->getHeaderLine('Accept'));
        $selectedContentTypes = array_intersect($requestedContentTypes, $this->getContentTypes());

        if (count($selectedContentTypes)) {
            return reset($selectedContentTypes);
        }

        return $requestedContentTypes[0] ?? '';
    }

    abstract protected function getContentTypes(): array;

    abstract protected function getErrorOutput(string $contentType, HttpException $exception, RequestInterface $request): string;
}

class NotFoundHttpExceptionHandler extends AbstractHttpExceptionHandler
{
    protected function getContentTypes(): array
    {
        return [
            'text/plain',
            'text/json',
            //...
        ];
    }

    protected function getErrorOutput(string $contentType, HttpException $exception, RequestInterface $request): string
    {
        switch ($contentType) {
            case 'text/plain':
                return $this->getTextErrorOutput($exception, $request);

            case 'text/json':
                return $this->getJsonErrorOutput($exception, $request);

            / ...

            case 'text/html':
            default:
                return $this->getHtmlErrorOutput($exception, $request);
        }
    }

    protected function getTextErrorOutput(httpException $exception, RequestInterface $request): string
    {
        return ''';
    }

    / ...
}