How can I add menu and reuse my layout on the custom error page in Slim 4?

69 views Asked by At

I checked the code of the custom error middleware: https://www.slimframework.com/docs/v4/middleware/error-handling.html

It is far from obvious how I can use the PhpRenderer (or any renderer compatible with SLIM) with my current layout and menu to add a custom error page. Is there anyone who managed to do this?

I instantiate my renderer this way after I set up error handling.

    $this->renderer = new PhpRenderer(__DIR__ . '/template');
    $this->renderer->setLayout("layout.php");
    $this->renderer->addAttribute('csrf', $this->getCrossSiteRequestForgeryPreventionToken());
    $this->renderer->addAttribute('flash', $this->getFlashMessages());
    $this->renderer->addAttribute('html', new HTMLHelper($this->renderer));

While calling the error renderer looks like this:

    $errorMiddleware = $application->addErrorMiddleware($this->getConfiguration()->isDisplayingErrors(), $logErrors = true, $logErrorDetails = true);
    $errorHandler = $errorMiddleware->getDefaultErrorHandler();
    $errorHandler->registerErrorRenderer('text/html', ErrorRenderer::class);

And the class itself looks like this:

use Slim\Exception\HttpNotFoundException;
use Slim\Interfaces\ErrorRendererInterface;
use Throwable;

class ErrorRenderer implements ErrorRendererInterface {

    public function __invoke(Throwable $exception, bool $displayErrorDetails):string{
        $title = 'Error';
        $message = 'An error has occurred.';
        $trace = '';

        if ($exception instanceof HttpNotFoundException) {
            $title = 'Page not found';
            $message = 'This page could not be found.';
        }

        if ($displayErrorDetails){
            $message = $exception->getMessage();
            $trace = $exception->getTraceAsString();
        }

        return $this->renderHtmlPage($title, $message, $trace);
    }

    public function renderHtmlPage(string $title='', string $message='', string $trace=''):string {
        $title = htmlentities($title, ENT_NOQUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'utf-8');
        $message = htmlentities($message, ENT_NOQUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'utf-8');
        $trace = htmlentities($trace, ENT_NOQUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'utf-8');
        $trace = preg_replace('%&NewLine;%usD', '<br/>', $trace);

        return <<<EOT
<!DOCTYPE html>
<html>
<head>
  <title>$title - My website</title>
</head>
<body>
  <h1>$title</h1>
  <p>$message</p>
  <p>$trace</p>
</body>
</html>
EOT;
    }
}

I could reuse at least the HTMLHelper and do $html->addText(...) instead of using htmlentities directly. Adding the layout would be awesome too.

Yet another question that we appear to have 2 error pages, because if everything fails we fall back to the PHP error page if the php.ini allows it. So is there a way to avoid it?

1

There are 1 answers

2
inf3rno On BEST ANSWER

What I did is using dependency injection as @odan suggested.

protected $renderer;
public function __construct(PhpRenderer $renderer){
    $this->renderer = $renderer;
}

After it I used the fetch method on the layout and set the content parameter.

    $content = $this->renderer->fetch('error.php', ['title' => $title, 'message' => $message, 'trace' => $trace]);
    return $this->renderer->fetch('layout.php', ['content' => $content]);

This way I don't need to use the render method to have a layout, because that one requires a response object which the error renderer does not provide.