When you need to handle a 404 (not found) error in Mezzio, how do you do it? It’s trivial in Laravel, Symfony, CakePHP, and other, larger PHP frameworks. But, it’s not so clear in Mezzio. In this tutorial, I’ll show you three ways to do so.
Recently, while building a small app using Mezzio, I realised that I had forgotten how handle 404 (Not Found) errors.
Doing so is pretty normal for web apps and APIs, so I’m surprised that I was so unsure about how to do so with Mezzio – especially when it’s pretty trivial (and baked in) in other PHP frameworks.
For example, in Laravel, you’d do this:
In a Symfony controller action, you’d do either:
throw $this->createNotFoundException('<Your 404 message>');
Or, you’d do this.
throw new NotFoundHttpException('<Your 404 message>');
I could list several other examples, but I think you get the point.
So how do you do it in Mezzio?
Call me out if I’m missing something, but it’s not clear (yet) in the Mezzio documentation, and searching around didn’t turn up much either.
But, after asking in the Laminas Slack, answers a plenty came my way.
So, I thought that I’d share them, along with some reasonably copy+pasteable code examples.
Let’s begin!
Option 1 - Redirect to a route handled by Mezzio’s NotFoundHandler
While I was searching through Mezzio’s source (and after creating a small class that was going, largely, in the same direction) I came across \Mezzio\Handler\NotFoundHandler.
Without digging into it in too much depth, the class returns a ResponseInterface
object with:
- Its body set to either a text response, or an HTML Response rendered with Mezzio’s 404 template (
error::404
), which is usually templates/error/404.html.
- Its status code set to HTTP 404 Not Found.
To make use of it, without it becoming a dependency of one or more of your route handlers, you’d first register a route which, when requested, dispatches to the NotFoundHandler
, such as the following which you could add to config/routes.php.
$app->get('/404', \Mezzio\Handler\NotFoundHandler::class);
Then, in your handler’s handle()
function, you’d return a RedirectResponse
to the route, as in the example below.
return new RedirectResponse('/404');
If you’re unfamiliar with the RedirectResponse
object, they are a programmatic way of performing either an HTTP 301 or HTTP 302 response.
To me, this is the simplest and quickest approach to implement.
Option 2 - Use the NotFoundHandler directly
Now, you could use the NotFoundHandler
directly if you prefer.
As it implements RequestHandlerInterface
, like your handlers, it has to implement the handle()
function which accepts a ServerRequestInterface
object.
Given that, you could refactor your handler class’ constructor to take a NotFoundHandler
instance as a constructor parameter.
Then, you’d call the instance’s handle()
function, providing it the current request, and return the result.
Here’s a short example showing how to do so.
<?php
declare(strict_types=1);
namespace App\Handler;
use Mezzio\Handler\NotFoundHandler;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class HomePageHandler implements RequestHandlerInterface
{
public function __construct(
private NotFoundHandler $handler,
private ?TemplateRendererInterface $template = null
) {}
public function handle(
ServerRequestInterface $request
): ResponseInterface
{
return $this->handler->handle($request);
}
}
Two things are important to note in this approach:
- You’ll have to refactor the handler’s factory to pass it a
NotFoundHandler
instance during instantiation.
Alternatively, you could use the Reflection-based Abstract Factory, and not need to implement or refactor an instantiating factory class.
- Your DI container needs to have a service that returns a
TemplateRendererInterface
instance, as it’s a required constructor parameter of the NotFoundHandler
.
To be fair, both of these tasks are fairly trivial with Mezzio though.
I’ve been looking at this approach for a little while now, and, while I like it, I feel that it’s both a bit of work to implement, and when used it may not be the most intuitive as to what’s going on.
You could make it slightly more intuitive, though, by naming the NotFoundHandler
instance better than I have in the example above.
For example $notFoundHandler
instead of $handler
.
But, still, it’s a little unclear.
What do you think?
Option 3 - Use a custom helper
Now, as Mezzio has a number of existing helpers, this might be an approach that makes a lot of sense to you.
To take this approach, you’d first need to create the helper class, which might look like the example below.
<?php
declare(strict_types=1);
namespace App\Helper;
use Fig\Http\Message\StatusCodeInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
readonly class NotFoundHelper
{
public function __construct(
private TemplateRendererInterface $templateRenderer
) {
}
public function notFound(
ServerRequestInterface $request,
string $message = ''
): ResponseInterface
{
$template = 'error::404';
return new HtmlResponse(
$this->renderBody($request, $template, $message),
StatusCodeInterface::STATUS_NOT_FOUND,
);
}
private function renderBody(
ServerRequestInterface $request,
string $template,
string $message
): string
{
$data = [
'request' => $request,
'message' => $message,
];
return $this->templateRenderer->render($template, $data);
}
}
You can see that it takes a TemplateRendererInterface
object in the constructor which the NotFoundHandler
requires.
It then defines a notFound()
method which returns an HtmlResponse
object, where the body is the result of rendering Mezzio’s default 404 error template, using the string ($message
) provided.
Full credit, I borrowed the example code above from Thomas in the Laminas Slack.
Thanks, Thomas!
Then, in your handler, you could instantiate the NotFoundHelper
directly and return the result of calling its notFound()
function, as in the example below.
<?php
declare(strict_types=1);
namespace App\Handler;
use App\Helper\NotFoundHelper;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class HomePageHandler implements RequestHandlerInterface
{
public function __construct(
private ?TemplateRendererInterface $template = null
) {}
public function handle(
ServerRequestInterface $request
): ResponseInterface
{
return new NotFoundHelper($this->template)
->notFound($request, "Oh no! It's a 404.");
}
}
You could also require the NotFoundHelper
as a constructor parameter.
Your choice.
To me, this is the second most intuitive approach, but likely the most amount of work, as you’d have to determine the helper’s requirements, develop and maintain it.
That said, it’d work exactly as you need it to.
That’s how to handle 404s in Mezzio
While there isn’t one, baked-in, approach in Mezzio, as there are in other PHP frameworks, Mezzio offers you a range of options for handling 404 errors.
By doing so, you can choose the approach that makes the most sense for your application.
Which approach do you prefer?
Join the discussion
comments powered by Disqus