I faced an interesting question recently with regards to middleware: What happens when we go from a convention-based to a contract-based approach when programming?
Convention-based approaches usually allow for duck-typing; with middleware, it means you can write PHP callables — usually closures — and just expect them to work.
Contract-based approaches use interfaces. I think you can see where this is going.
PSR-7 Middleware
When PSR-7 was introduced, a number of middleware microframeworks adopted a common signature for middleware:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
function (
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
) : ResponseInterface
where $next had the following signature:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
function (
ServerRequestInterface $request,
ResponseInterface $response
) : ResponseInterface
This approach meant that you could wire middleware using closures, which makes for a nice, succinct, programmatic interface:
// Examples are using zend-stratigility
use Zend\Diactoros\Response\TextResponse;
use Zend\Stratigility\MiddlewarePipe;
$pipeline = new MiddlewarePipe();
$pipeline->pipe(function ($request, $response, callable $next) {
$response = $next($request, $response);
return $response->withHeader('X-ClacksOverhead', 'GNU Terry Pratchett');
});
$pipeline->pipe(function ($request, $response, callable $next) {
return new TextResponse('Hello world!');
});
Easy-peasey!
This convention-based approach was easy to write for, because there was no need to create discrete classes. You could, but it wasn't strictly necessary. Just throw any PHP callable at it, and profit.
(I'll note that some libraries, such as Stratigility, codified at least the middleware via an interface as well, though implementation of the interface was strictly optional.)
The big problem, however, is that it can lead to subtle errors:
- what happens if you expect more arguments than the middleware dispatcher provides?
- what happens if you expect different arguments and/or argument types than the middleware dispatcher provides?
- what happens if your middleware returns something unexpected?
Essentially, a convention-based approach has no type safety, which can lead to a lot of subtle, unexpected, runtime errors.
PSR-15 Middleware
The proposed PSR-15 (HTTP Server Middleware) is not convention-based, and instead proposes two interfaces:
namespace Interop\Http\ServerMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface MiddlewareInterface
{
/**
* Docblock annotations, because PHP 5.6 compatibility
*
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, DelegateInterface $delegate);
}
interface DelegateInterface
{
/**
* Docblock annotations, because PHP 5.6 compatibility
*
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request);
}
This leads to type safety: if you typehint on these interfaces (and, typically, for middleware dispatchers, you're only concerned with the MiddlewareInterface), you know that PHP will have your back with regards to invalid middleware.
However, this also means that for any given middleware, you must create a class!
Well, that makes things more difficult, doesn't it!
Or does it?
Anonymous classes
Starting in PHP 7, we now have the ability to declare anonymous classes. These are similar to closures, which can be thought of as anonymous functions (though with quite a lot more semantics and functionality!), applied at the class level.
Interestingly, anonymous classes in PHP allow for:
- Extension
- Interface implementation
- Trait composition
In other words, they behave just like any standard class declaration.
Let's adapt ou
Truncated by Planet PHP, read more at the original (another 4877 bytes)