zend-stratigility has had a number of refactors put into place for its upcoming version 3. Among these:
- It will only accept PSR-15
MiddlewareInterface
instances toMiddlewarePipe::pipe
. -
MiddlewarePipe::pipe
no longer accepts a path argument. - The
MiddlewarePipe
itself has been markedfinal
. - It now provides a number of decorators:
-
PathMiddlewareDecorator
allows providing path-segregated middleware, and accepts a string path and aMiddlewareInterface
middleware to its constructor. Apath()
utility function acts as a convenience factory for these instances. -
CallableMiddlewareDecorator
allows decorating callable middleware that follows the PSR-15 signature, and accepts the middleware to its constructor. The utility functionmiddleware()
acts as a factory for the decorator. -
DoublePassMiddlewareDecorator
allows decorating callable middleware that follows the double-pass signature, and accepts the middleware to its constructor, as well as aResponseInterface
. The utility functiondoublePassMiddleware()
acts as a factory for the decorator.
-
With these changes, we began to rethink some of the architecture for the zend-expressive package, and have come up with the following.
Proposal for version 3
- Pin to the Stratigility 3.0.0 release, which will have the PSR-15 features and new architecture.
- The
Application
class will now implement the PSR-15MiddlewareInterface
andRequestHandlerInterface
, and compose aMiddlewarePipe
instance internally. -
Application::pipe()
will accept only a$middleware
argument, and no longer do path segregation directly. - The
$middleware
argument to bothpipe()
androute()
(and the various routing-related methods) will allow the same arguments as v2, except they will no longer allow callable double-pass middleware. - Routing and dispatch middleware will be services; no more
pipeRoutingMiddleware()
orpipeDispatchMiddleware()
. Just pipe the services. - We will remove the
MarshalMiddlewareTrait
, andApplication
and other classes will no longeruse
it. More on this below. - We will add two new classes:
-
MiddlewareContainer
will implement the PSR-11ContainerInterface
, and decorate a PSR-11 container. Internally, it will:- resolve service names that are not registered in the container but resolve to classes
- raise an exception if the service pulled from the composed container is not a
MiddlewareInterface
instance
-
MiddlewareFactory
will compose aMiddlewareContainer
and provide methods for decorating middleware:callable(callable $middleware) : CallableMiddlewareDecorator
- `lazy(string $middleware) : LazyLoadingMiddleware
-
prepare($middleware) : MiddlewareInterface
will accept a string, callable, array, orMiddlewareInterface
and return aMiddlewareInterface
based on the argument type. -
pipeline(...$middleware) : MiddlewarePipe
will create aMiddlewarePipe
, piping each argument after first passing it toprepare()
. -
path(string $path, $middleware) : PathMiddlewareDecorator
will create aPathMiddlewareDecorator
after first passing$middleware
toprepare()
.
-
-
Application
will create aMiddlewareFactory
from the container it composes, and use that container to resolve all middleware passed topipe()
or one of the routing methods. -
Application
will expose the composedMiddlewareFactory
via a new method,getMiddlewareFactory()
.
Double pass middleware MUST be passed to pipe()
or one of the routing methods by first decorating it in a DoublePassMiddlewareDecorator
instance (either via direct instantiation, or using the doublePassMiddleware()
utility function).
By exposing the factory, users can create āutility functionsā by fetching the factory and creating callables.
Examples:
// In pipeline.php:
$factory = $app->getMiddlewareFactory();
$path = [$factory, 'path'];
$pipeline = [$factory, 'pipeline'];
// Pondering renaming `ErrorHandler` to `ErrorMiddleware` to disambiguate
// the name.
$app->pipe(ErrorMiddleware::class);
$app->pipe(ServerUrlMiddleware::class);
// Here's path-segregated middleware:
$app->pipe($path('/auth', OAuth2CallbackMiddleware::class));
// This one combines path-segregated with pipeline middleware, while still using
// v2 semantics for the pipeline:
$app->pipe($path('/api', [
ProblemDetailsMiddleware::class,
CorsMiddleware::class,
SessionMiddleware::class,
AuthenticationMiddleware::class,
AuthorizationMiddleware::class,
]);
// The above could be done more explicitly as:
$app->pipe($path('/api', $pipeline(
ProblemDetailsMiddleware::class,
CorsMiddleware::class,
SessionMiddleware::class,
AuthenticationMiddleware::class,
AuthorizationMiddleware::class // Note: no trailing slash; it's an argument!
));
// The routing middleware now becomes a first-class service instead:
$app->pipe(RouteMiddleware::class);
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(UrlHelperMiddleware::class);
// Dispatch middleware also is now a first-class service:
$app->pipe(DispatchMiddleware::class);
// Pondering renaming `NotFoundHandler` to `NotFoundMiddleware` to disambiguate
// the name.
$app->pipe(NotFoundMiddleware::class);
// In routes.php:
$uuidRegex = '. . .';
$factory = $app->getMiddlewareFactory();
$path = [$factory, 'path'];
$pipeline = [$factory, 'pipeline'];
$app->get('/', HomePageMiddleware::class, 'home');
// V2 standards continue to work:
$app->get('/auth/status', [
ProblemDetailsMiddleware::class,
CorsMiddleware::class,
SessionMiddleware::class,
AuthenticationMiddleware::class,
LoginStatusHandler::class,
], 'auth.status');
// Or, more explicitly, using the pipeline factory:
$app->get('/auth/status', $pipeline(
ProblemDetailsMiddleware::class,
CorsMiddleware::class,
SessionMiddleware::class,
AuthenticationMiddleware::class,
LoginStatusHandler::class
), 'auth.status');
$app->get('/api/books', SearchMiddleware::class, 'books');
$app->post('/api/books', [
BodyParamsMiddleware::class,
AddBookValidationMiddleware::class,
AddBookMiddleware::class,
]);
$app->get('/api/books/{id:' . $uuidRegex . '}', BookMiddleware::class, 'book');
$app->patch('/api/books/{id:' . $uuidRegex . '}', [
BodyParamsMiddleware::class,
UpdateBookValidationMiddleware::class,
UpdateBookMiddleware::class
]);
Proposal for version 2.2.0
- Pin to the Stratigility 2.2.0 release, which will have the forwards compatibilty features.
- Backport the
MiddlewareFactory
andMiddlewareContainer
, but update theMiddlewareFactory
to differentiate between interop and double-pass middleware. When decorating double-pass middleware, emit anE_USER_DEPRECATED
, urging developers to decorate usingdoublePassMiddleware()
orDoublePassMiddlewareDecorator
instances. The factory will also require aResponseInterface
instance in order to generateDoublePassMiddlewareDecorator
instances. - Update
Application
to use theMiddlewareFactory
for marshaling middleware. - Continue accepting the path argument to
Application::pipe
, but:- trigger an
E_USER_DEPRECATED
urging developers to usepath()
or thegetMiddlewareFactory()->path()
functionality. - Use the
MiddlewareFactory
to decorate the path and middleware before piping it.
- trigger an
- Continue accepting the
pipeRoutingMiddleware()
method, but:- Provide a factory for the routing middleware that consumes the same
RouterInterface
service, and aResponseInterface
service. - trigger an
E_USER_DEPRECATED
urging developers to simply pipe theRouteMiddleware
service. - pull the
RouteMiddleware
service from the container and pipe it.
- Provide a factory for the routing middleware that consumes the same
- Continue accepting the
pipeDispatchMiddleware()
method, but:- Provide a factory for the routing middleware that consumes the same
RouterInterface
service, aResponseInterface
service, and the container itself. (In v3, it would only accept the container.) - trigger an
E_USER_DEPRECATED
urging developers to simply pipe theDispatchMiddleware
service. - pull the
DispatchMiddleware
service from the container and pipe it.
- Provide a factory for the routing middleware that consumes the same
- Update
LazyLoadingMiddleware
:- Create and use a
MiddlewareFactory
internally instead of using theMarshalMiddlewareTrait
. - Mark the
ResponseInterface
constructor argument as deprecated.
- Create and use a
- Mark the
MarshalMiddlewareTrait
as deprecated, and modify it to use theMiddlewareFactory
instead. - Mark the
IsCallableInteropMiddlewareTrait
as deprecated.
History
- 2018-01-18: Initial creation
- 2018-01-24: Updated to remove path/pipeline decorator creation methods and instead propose the
MiddlewareFactory
/MiddlewareContainer
combination, and indicate all previous types except double-pass middleware will continue to be directly supported.