zend-stratigility has had a number of refactors put into place for its upcoming version 3. Among these:
- It will only accept PSR-15
MiddlewareInterfaceinstances toMiddlewarePipe::pipe. -
MiddlewarePipe::pipeno longer accepts a path argument. - The
MiddlewarePipeitself has been markedfinal. - It now provides a number of decorators:
-
PathMiddlewareDecoratorallows providing path-segregated middleware, and accepts a string path and aMiddlewareInterfacemiddleware to its constructor. Apath()utility function acts as a convenience factory for these instances. -
CallableMiddlewareDecoratorallows 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. -
DoublePassMiddlewareDecoratorallows 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
Applicationclass will now implement the PSR-15MiddlewareInterfaceandRequestHandlerInterface, and compose aMiddlewarePipeinstance internally. -
Application::pipe()will accept only a$middlewareargument, and no longer do path segregation directly. - The
$middlewareargument 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, andApplicationand other classes will no longeruseit. More on this below. - We will add two new classes:
-
MiddlewareContainerwill 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
MiddlewareInterfaceinstance
-
MiddlewareFactorywill compose aMiddlewareContainerand provide methods for decorating middleware:callable(callable $middleware) : CallableMiddlewareDecorator- `lazy(string $middleware) : LazyLoadingMiddleware
-
prepare($middleware) : MiddlewareInterfacewill accept a string, callable, array, orMiddlewareInterfaceand return aMiddlewareInterfacebased on the argument type. -
pipeline(...$middleware) : MiddlewarePipewill create aMiddlewarePipe, piping each argument after first passing it toprepare(). -
path(string $path, $middleware) : PathMiddlewareDecoratorwill create aPathMiddlewareDecoratorafter first passing$middlewaretoprepare().
-
-
Applicationwill create aMiddlewareFactoryfrom the container it composes, and use that container to resolve all middleware passed topipe()or one of the routing methods. -
Applicationwill expose the composedMiddlewareFactoryvia 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
MiddlewareFactoryandMiddlewareContainer, but update theMiddlewareFactoryto differentiate between interop and double-pass middleware. When decorating double-pass middleware, emit anE_USER_DEPRECATED, urging developers to decorate usingdoublePassMiddleware()orDoublePassMiddlewareDecoratorinstances. The factory will also require aResponseInterfaceinstance in order to generateDoublePassMiddlewareDecoratorinstances. - Update
Applicationto use theMiddlewareFactoryfor marshaling middleware. - Continue accepting the path argument to
Application::pipe, but:- trigger an
E_USER_DEPRECATEDurging developers to usepath()or thegetMiddlewareFactory()->path()functionality. - Use the
MiddlewareFactoryto 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
RouterInterfaceservice, and aResponseInterfaceservice. - trigger an
E_USER_DEPRECATEDurging developers to simply pipe theRouteMiddlewareservice. - pull the
RouteMiddlewareservice 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
RouterInterfaceservice, aResponseInterfaceservice, and the container itself. (In v3, it would only accept the container.) - trigger an
E_USER_DEPRECATEDurging developers to simply pipe theDispatchMiddlewareservice. - pull the
DispatchMiddlewareservice from the container and pipe it.
- Provide a factory for the routing middleware that consumes the same
- Update
LazyLoadingMiddleware:- Create and use a
MiddlewareFactoryinternally instead of using theMarshalMiddlewareTrait. - Mark the
ResponseInterfaceconstructor argument as deprecated.
- Create and use a
- Mark the
MarshalMiddlewareTraitas deprecated, and modify it to use theMiddlewareFactoryinstead. - Mark the
IsCallableInteropMiddlewareTraitas deprecated.
History
- 2018-01-18: Initial creation
- 2018-01-24: Updated to remove path/pipeline decorator creation methods and instead propose the
MiddlewareFactory/MiddlewareContainercombination, and indicate all previous types except double-pass middleware will continue to be directly supported.
