What’s the difference between PSR-15 Handlers and Middleware?

What’s the difference between PSR-15 Handlers and Middleware?

While building a simple user manager for Mezzio projects, recently, it turns out I’d gotten my understanding of PSR-15 Request Handlers and Middleware a little mixed up. Some friends set me straight about the difference, so I want to talk about that today.


But first, let’s put this in context. For quite some time now, when building web applications I’ve handled them with two classes:

  • One to render a form that a user can fill out and submit
  • One that processes the submission of said form

A simplistic mockup of a website’s login form

For example, if I were adding a login route to an application, I’d have one class to render the form, including error and success messages, and one class to process the request, including validating the user and firing off any events, etc.

The motivation for this approach is based on several factors, but primarily to help me avoid:

I appreciate that it may be a little more work to create multiple classes, write tests for them, register them with the app’s DI container, and configure them in the application’s routing table. But, to me, the effort is justified.

What’s more, the approach works really well when using the Mezzio framework (and I assume other PSR-7/PSR-15 oriented frameworks), because they’re oriented around Request Handlers and Middleware.

What are Request Handlers and Middleware classes?

If you’ve not heard of them before, or haven’t read PSR-15 (HTTP Server Request Handlers), here’s the definition of both:

Request Handler

A request handler is an individual component that processes a request and produces a response, as defined by PSR-7 (HTTP message interfaces). A request handler MAY throw an exception if request conditions prevent it from producing a response. The type of exception is not defined.

Middleware
A middleware component is an individual component participating, often together with other middleware components, in the processing of an incoming request and the creation of a resulting response, as defined by PSR-7. A middleware component MAY create and return a response without delegating to a request handler, if sufficient conditions are met.

My friend Joey Smith summarises Request Handlers and Middleware very succinctly:

One (Handlers) MAY return a response and one MUST (Middleware) return a response. Handlers are always routed, but Middleware can be piped in front of a route.

So, Request Handlers (with or without Middleware classes) simplify handling HTTP requests. They processes the request and produce a response which is sent back to the requesting client.

Whereas Middleware classes:

  • Augment the request: such as adding additional header including Content-Length, Content-Encoding, or Cache-Control, middleware would be the place to do it
  • Redirect (or terminate) the request: such as checking for an authenticated user, Middleware would have made sense

This approach is quite different to be a common approach in frameworks, such as Laravel with its Resource Controller. Here, one class handles all requests to an endpoint calling different functions based on the HTTP method used to make the request.

I can see the benefit of this approach. But, I feel that it is easier to create fat controllers this way, whereas Request Handlers and Middleware push you away from doing so.

Think of a request like an onion

It might be helpful, at this point, to visualise requests as being like an onion, as in the image below.

The middleware “onion” diagram

They’re different to earlier approaches in PHP (and other languages) as there isn’t a one-to-one relationship between a route and the class that handles that route in your application. Instead, requests to a route are handled by a request pipeline, which can be composed of any number of Middleware classes, likely finishing with a Request Handler class.

Why would you use them?

There are a number of reasons why you’d use a pipeline to handle requests, including:

  • They reduce the likelihood of creating a “god” object. This might just be a subjective opinion on my part, but I’ve found it to be true. However, like all things of value, it takes discipline and practice to achieve.
  • You create (Middleware) classes that can be used throughout your application. By doing so, you avoid tying some logic to one request.

How would you use them?

Let’s step through an example to make it easier to visualise. Let’s say that you have a route in your application that displays the user’s profile. What will you need to do to properly handle the request?

Firstly, you’ll need an authenticated user. If a user isn’t authenticated (i.e., it’s a guest session), then who do you get profile data for? You have no user, so you can’t. Given that, you’ll have to redirect the request to a route that doesn’t require authentication, such as the login page.

If you do have an authenticated user, where will it be stored and how will you retrieve it? More than likely, you’ll have a minimalist object stored in session, or in a request attribute. From there, you can retrieve the user and check it’s authenticated status.

So, based on this short thought process, what will you need to do to render the user’s profile? Here’s what I think:

  • Check if the current user is set in session and is marked as being authenticated
    • If not: the user is redirected somewhere, such as a login route
    • If so: their details are retrieved from the application’s underlying data store and rendered in a view for them to see
  • The request ends

Given this, steps one and two could be wrapped up into a Middleware class (perhaps named IsAuthenticatedMiddleware), and steps three and four could be put into a Request Handler class (perhaps named UserProfileHandler).

A simplistic example of a request pipeline

With those two classes, a request pipeline would be created in the routing table with the Middleware added before the Request Handler; as in the pseudo example route configuration below.

[
    'path'            => '/user/profile',
    'middleware'      => [
        IsAuthenticatedMiddleware::class,
        UserProfileHandler::class
    ],
    'allowed_methods' => ['GET'],
],

Because of this approach, any other route that needs to have an authenticated user (such as logout) could use IsAuthenticatedMiddleware. The logic doesn’t need to be put into a trait or a utility function, and follows a formalised convention making it much easier to find, use, debug, and share.

Here’s an excerpt from AuthenticationMiddleware in Mezzio Authentication that does something similar:

public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler)
: ResponseInterface
{
    $user = $this->auth->authenticate($request);
    if (null !== $user) {
        return $handler->handle(
            $request->withAttribute(
                UserInterface::class,
                $user
            )
        );
    }
    return $this->auth->unauthorizedResponse($request);
}

So, what’s my problem with understanding them?

For the longest time (and as I wrote in Mezzio Essentials) I’m sure that I understood the difference between Request Handlers and Middleware. But, during the development of Simple User Manager for Mezzio, based on some of the classes that I created, I’m not so sure (or I forgot a bit).

If you look at release 0.1.2, you’ll see a number of classes in src/Handler, such as ForgotPasswordHandler, ResetPasswordHandler, and RegisterUserHandler. These classes render forms when a user has forgotten their password, needs to reset their password, and when a user wants to register for the first time, respectively.

Then, if you look in src/Middleware, you’ll see classes such as ForgotPasswordMiddleware, ResetPasswordMiddleware, and RegisterUserMiddleware. These are, the classes that process the requests of the former three.

To me, at first, that seemed the right way to go; as I mistakenly came to see Request Handlers as lightweight form renders, and Middleware classes as their request processing compliment.

What I should have done (and will do) is refactor all of the Middleware classes to be Request Handlers and move them out of src/Middleware to src/Handlers. This is because the respective Middleware classes are all the final point in the respective request pipelines, not intermediate steps.

That’s the difference between PSR-15 Handlers and Middleware

While the difference is, somewhat, subtle – even in the PSR – it’s important. Request Handlers are only ever the final step of a request pipeline (or the only component), whereas Middleware classes may be the final step, but often are stacked earlier in the process before a Request Handler.

A positive that’s come out of this, at least for me, is that I have my understanding all sorted out now and feel confident that the Simple User Manager and future Mezzio projects will be better written.

What about you? What do you think of Request Handlers and Middleware? Are they the approach that you use to build web applications and APIs with PHP?

The layers icons in the post’s main image was created by Freepik - Flaticon.

Want to Learn More About Mezzio?

Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework. It's a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application.

You might also be interested in these tutorials too...

Enable Mezzio Modules with laminas-component-installer
Mon, Dec 16, 2024

Enable Mezzio Modules with laminas-component-installer

When building reusable Mezzio packages, such as for user management, payments, and authentication, do users have to enable them manually, or are you automating it for them? In this short tutorial, I’ll show you how to enable them almost automatically, saving your users time and effort.

How Do You Use CSRF Tokens in a Mezzio Application?
Tue, Mar 2, 2021

How Do You Use CSRF Tokens in a Mezzio Application?

No matter how small your web app may be, security is essential! In this tutorial, you’ll learn how to add a CSRF token in forms used in Mezzio-based applications, to prevent attackers from being able to force your users to execute malicious actions.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus