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.
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.
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 projects1.
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 argument2.
1 2 3 4 5 | 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:
1 2 3 4 5 | 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.
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:
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.
Footnotes
- interested in how it works? I addressed the skeleton installer and my own repository template here.
- in fact, it does more than this but for the sake of simplicity, we will just address the basic cases.