Laminas Framework Series: Middleware Deep Dive

Doğan Uçar
7 min readJun 12, 2024

--

👨‍💻 Originally published on my blog: https://dogan-ucar.de/laminas-framework-series-middleware-deep-dive/

The 5th post in the Laminas Framework series is about having a closer look into the middleware concept in Mezzio, a fast, flexible and modern web framework for Laminas. In Mezzio, each request has an associated request handler which can get wrapped by middlewares. The idea of middleware is therefore a key concept in the request-response flow in the Laminas framework and especially in Mezzio.

Laminas Framework Middleware. Image generated by ChatGPT4o
Laminas Framework Middleware. Image generated by ChatGPT4o

GET EXPERT PHP SUPPORT AND ANSWERS IN 24 HOURS

💻 Do you have PHP-related questions? My dedicated support platform provides guaranteed answers within 24 hours. Whether you are a beginner or an expert, you can ask any PHP question and receive reliable, expert guidance.

🔗 Interested? Just follow this link to subscribe and get immediately access to the platform.

or learn more here.

Brief Recap: What are Middlewares (in Laminas)

The second post in this series compared middleware to enterprise architecture like Model-View-Controller (short: MVC). In summary, middleware represents a more contemporary method for designing web applications compared to traditional techniques.

By design, each incoming request in Mezzio is addressed to be handled by one request handler. The request handler usually stores/retrieves/modifies data and gives a corresponding response — e.g. 201 for “created”. While every request handler is intended to be unique, there are recurring tasks between similar request handlers. For instance, a GET and POST request to a resource usually wants first to locate the resource – e.g. by reading the resource id from the URL parameters and querying the database. But before the instance is getting read/modified, we first need to authenticate and/or authorize the user. This is where a middleware come into play.

Here we can create two middlewares that are attached before the request handler to check whether the user is authorized and authenticated. The middlewares can attached to both, the GET and POST request handler and doing this, they are not only protected the same way, but also both request handlers can focus on their own work.

Similar approaches are also available in MVC architecture. But they often use event driven architectures which may get hard to understand (especially for junior developers). In contrast, PSR-15 middlewares are chained in a FIFO queue. If one middleware stops the flow, subsequent middlewares do not come into action.

Middleware in the Laminas framework and Mezzio base on the PSR-15 “HTTP Server Request Handlers” standard which is a approach to standardize PHP frameworks and application.

Double Pass and Lambda Middlewares

For middlewares, the PSR-15 standard differentiated between two middleware approaches: Double Pass and Single Pass (Lambda). Each method has distinct characteristics and applications.

The Double Pass approach is based on a callable function, passing three arguments: a ServerRequestInterface implementation, a ResponseInterface implementation, and a callable for delegating to the next middleware. This approach, which has been widely adopted by early HTTP message users (PSR-7), allows middleware to manipulate both the request and response objects.
However, it has also downsides. First, it is not possible to type hint the callable (at least so far not) and thus, callable can whatever but not a middleware. Next, there is no guarantee that the response object passed to the current middleware is always in a valid or usable state. The response might got altered in ways that render it incomplete or incorrect, especially if multiple middleware components are modifying the response.

In contrast, the Single Pass (Lambda) approach is defined with a specific interface and involves passing two arguments: an implementation of ServerRequestInterface and a RequestHandlerInterface, responsible for generating an HTTP response message. The current middleware has the chance to modify the response before returning it back to the caller.
The Single Pass approach avoids the state issues present in the Double Pass approach and supports dependency inversion through the use of factories or empty message instances.

In practice, the Single Pass approach has established itself and Mezzio in Laminas uses this approach for its middlewares. More details about the why with Double Pass here.

The Laminas Framework Series is part of my upcoming book regarding to Middlewares with Mezzio. The series is a foretaste to the book and the first blog post addressed “Background and Basics”, the second post was about middlewares in Mezzio, the third one is about Getting Started with Mezzio and the fourth post is a comparision of Mezzio vs Laminas MVC. While each post is independent and does not require the knowledge from it successor(s), it is highly recommended to read them first to have a good overview to Laminas.

Middleware in Action in Laminas Mezzio Framework

Having the historical and theoretical information, we can now start looking at the practical implementation in Laminas’ Mezzio framework. First, I want to show how request handlers and middlewares are registered in applications created by the skeleton installer of Mezzio. Next, I will demonstrate middlewares in detail in repositories I use for my own projects (interested in how it works? I addressed the skeleton installer and my own repository template here).

Skeleton Installer Applications

The skeleton installer has two major places where we can place our middlewares: the config/pipeline.php file and the config/routes.php file.
The pipeline.php file returns an anonymous function with among others the Application instance as parameter. This instance provides a pipe() method which takes the name of the middleware as an argument (in fact, it does more than this but for the sake of simplicity, we will just address the basic cases).

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
....
};

The routes.php file is similar to the pipeline.php in its structure: it returns an anonymous function with – among others – the Application instance as a parameter to this function. The instance provides the get/post/put/delete/patch() methods that take the path the request handler, a string or array with the middleware(s) and request handler and the name as parameters:

return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
$app->get('/api/ping', App\Handler\PingHandler::class, 'api.ping');
$app->get('/', [App\Middleware\AuthenticationMiddleware::class,App\Middleware\AuthorizationMiddleware::class, App\Handler\HomePageHandler::class], 'home');
...
};

The code above defines two routes: /api/ping and /. Where /api/ping passes the handler name as a string, / passes two middlewares and the handler class as an array. This results in the following: the request for /api/ping is directly passed to the handler but the request for / goes first through AuthenticationMiddleware, then AuthorizationMiddleware and finally to HomePageHandler in exactly this order.

Request-Response Flow with Middlewares in Laminas/Mezzio Framework
Request-Response Flow with Middlewares in Laminas/Mezzio Framework

But what is the difference? Both configuration pipe middlewares to request handlers but middlewares in pipeline.php are globally configured middlewares, meaning that each and every request goes through them whereas the middlewares in routes.php are defined for this specific route.

Middleware in my Applications created with Laminas/Mezzio Framework

As said before, I do not use and deviate from the skeletion setup. But my own choosen setup does not make things much different. In fact, I also use two main places where pipelines come into play: a config/pipeline.php, which returns an anonymous function with the global middlewares applied to all requests. And a similar approach for routes, except than there is not one global routes.php, but a routes section in the ConfigProvider of each module in src/.

Middleware beyond Laminas

Middlewares are not limited to PSR-15 routing or Laminas. In fact, the approach is very flexible and can applied to almost every scenario where actions are wrapped into outer layers and each layer has the power to stop the propagation.

For instance, Doctrine, a widely used Object-Relational Mapper (ORM) for PHP, utilizes middleware concepts in the form of event listeners and subscribers to manage various aspects of entity lifecycle events and interactions with the database.
Yii and CakePHP, two famous frameworks in PHP, use the middleware paradigm for security enhancements and session management.

Outside of PHP, the middleware paradigm is widely adopted across various programming languages and frameworks for handling requests and responses in a modular and reusable manner. In JavaScript, Express.js utilizes middleware to manage logging, authentication, and error handling. Python’s Django framework processes requests globally with middleware for security and session management. Ruby’s Rack connects web applications with servers through middleware components for tasks like caching and authentication. Java’s Spring Boot employs filters similar to middleware for request processing. Elixir’s Phoenix uses Plugs for routing and request modifications, while Go’s Gin applies middleware for logging and security. ASP.NET Core, in the .NET ecosystem, uses middleware for authentication, authorization, and session management. These examples highlight middleware’s versatility and importance in modern web development.

Conclusion

The blog post addressed middlewares in detail and showed the basic idea, the various forms and practical implementation. Further, I tried to draw a broader picture while looking beyond Laminas and PHP to demonstrate how powerful the middleware approach is.

The Laminas Series on my blog is just a foretaste of my upcoming book addressing Laminas Mezzio development. If interested, do not forget to subscribe to my newsletter on my website.

Further, I launched a platform for PHP developers. I want to share my 10+ years of PHP development with you, answering your questions, discussing your ideas and supporting in overall questions regarding to everything in and around PHP, Linux, SQL, etc.

🔗 Interested? Just follow this link to subscribe and get immediately access to the platform.

or learn more here.

If you liked this article and would like to support me, make sure to:

  • 👏 Clap for this story
  • 🔔 Follow me on Medium

--

--

Doğan Uçar
Doğan Uçar

Written by Doğan Uçar

Software Engineer, PHP/Laminas (Zend), Backend, Cloud, Freelancer & CEO, Open Source Contributor