Laminas MVC vs Mezzio: Choosing an HTTP Request Handler
Decide between Laminas MVC and Mezzio for new PHP HTTP services, with constraints, a comparison table, and a PSR-15 pipeline example.
16 Nov 2025, 03:58 UTC

Decision and constraints
You are starting a new PHP HTTP service on Laminas and must choose between Laminas MVC and Laminas Mezzio. The wrong choice couples you to an event-driven controller cycle you do not need, or leaves you assembling error handling and content negotiation by hand.
Useful takeaway: adopt Laminas Mezzio with a PSR-15 middleware pipeline for JSON APIs and SPA back ends. Use Laminas MVC only when you need server-rendered pages or legacy controller conventions.
Constraints for this decision:
- PHP 8.x runtime
- Laminas 3.x components
- PSR-7 HTTP messages and PSR-15 middleware interfaces
- PSR-11 container for dependency injection
- Routing through laminas-router
PSR-7 defines HTTP message interfaces, PSR-15 defines middleware interfaces, and PSR-11 defines a container interface for dependency injection.
Component versions are independent. Before adding laminas/mezzio, laminas/router, or laminas-stratigility, inspect composer.lock to confirm the versions resolve together. Do not assume a specific release date or version number.
Supported options compared
| Concern | Laminas MVC | Laminas Mezzio |
|---|---|---|
| Architecture | Event-driven front controller with dispatch, controller, and view events | Linear middleware pipeline processing requests in order |
| Request handling | Controller classes and action methods | PSR-15 middleware implementing process(ServerRequest, RequestHandler): ResponseInterface |
| Routing | Built-in route-to-controller mapping | laminas-router, commonly with FastRoute, inside the middleware pipe |
| Views | Built-in view layer, view helpers, and plugins | No built-in view layer; register rendering services explicitly if needed |
| Testing | Bootstrap the full MVC application or use controller test utilities | Boot MiddlewarePipe and dispatch a PSR-7 ServerRequest directly |
| Best fit | Server-rendered pages, legacy controller conventions | JSON APIs, SPA back ends, explicit pipeline control |
Trade-offs
Laminas MVC
MVC provides convention over configuration. Controllers, view scripts, and helpers are ready to use, which speeds up server-rendered sites. The cost is tighter coupling to the MVC event cycle. Controllers are not PSR-15 middleware and cannot be placed directly inside a Mezzio pipe without an adapter.
Laminas Mezzio
Mezzio gives you explicit control over middleware execution order. Unit tests can boot the pipe and assert on a single request without the MVC application bootstrap. The cost is manual assembly: error handling, content negotiation, and view rendering are not provided by default and must be wired through the PSR-11 container.
Implementation: Mezzio pipeline with a health check route
This example wires a MiddlewarePipe (Laminas Stratigility's PSR-15 pipeline), registers a route for GET /health, and returns a JSON 200 response. Replace version placeholders with values that resolve together in your composer.lock.
composer.json (partial)
{
"require": {
"php": "^8.1",
"laminas/mezzio": "*",
"laminas/router": "*",
"laminas/diactoros": "*",
"psr/container": "^1.0|^2.0"
}
}
Run composer validate from the project root to confirm the manifest is syntactically valid. Then run composer install and inspect composer.lock for resolved versions of laminas/mezzio, laminas/router, and a PSR-11 container implementation.
Pipeline factory
<?php
// config/pipeline.php
use Laminas\Stratigility\MiddlewarePipe;
use Psr\Container\ContainerInterface;
return function (ContainerInterface $container): MiddlewarePipe {
$pipe = new MiddlewarePipe();
$pipe->pipe($container->get(\App\Middleware\RoutingMiddleware::class));
return $pipe;
};
Health middleware
<?php
// src/Middleware/HealthMiddleware.php
namespace App\Middleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class HealthMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($request->getMethod() !== 'GET' || $request->getUri()->getPath() !== '/health') {
return $handler->handle($request);
}
return new JsonResponse(['status' => 'ok'], 200);
}
}
Exact Diactoros constructor signatures vary by installed version. Confirm the installed laminas/diactoros release before adapting this code.
Validation
Boot the pipe, dispatch a PSR-7 request for GET /health, and assert the response status and body. This test does not require a web server.
<?php
// tests/PipelineTest.php
use Laminas\Diactoros\ServerRequest;
use Laminas\Stratigility\MiddlewarePipe;
use PHPUnit\Framework\TestCase;
class PipelineTest extends TestCase
{
public function testHealthRouteReturnsJson200(): void
{
$container = $this->createMock(\Psr\Container\ContainerInterface::class);
$pipe = (require __DIR__ . '/../config/pipeline.php')($container);
$request = new ServerRequest('GET', '/health');
$response = $pipe->handle($request);
$this->assertSame(200, $response->getStatusCode());
$this->assertStringContainsString('"status":"ok"', (string) $response->getBody());
}
}
Run vendor/bin/phpunit from the project root. A passing test confirms the pipe resolves the route and returns JSON 200. To verify execution order, add a mock middleware to the container and assert it is invoked before the health middleware.
Limitations and pre-flight checks
- Mezzio does not provide built-in error handling or content negotiation. Register equivalent services explicitly if your API needs them.
- PSR-15 middleware signatures differ across laminas-stratigility major versions. Confirm process(ServerRequest, RequestHandler): ResponseInterface compatibility before upgrading.
- Do not place laminas-mvc controller classes directly inside a Mezzio middleware pipe; their lifecycle is not PSR-15 compatible without an adapter.
- Laminas MVC view helpers and plugins are not automatically available in Mezzio.
Practical check: after implementation, run composer validate, inspect composer.lock, and execute the PHPUnit pipeline test. If the test returns status 200 and the expected JSON body, the pipeline is wired correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.