Caution
The documentation you are viewing is for an older version of this component.
Switch to the latest (v3) version.
Migration
Migration from RC5 or earlier
RC6 introduced changes to the following:
- The routing middleware was split into separate middleware, one for routing, and one for dispatching.
- Due to the above change, we decided to remove auto-registration of routing middleware.
- The above change also suggested an alternative to the middleware pipeline configuration that simplifies it.
- Route result observers are deprecated, and no longer triggered for routing failures.
- Middleware configuration specifications now accept a
priority
key to guarantee the order of items. If you have defined your middleware pipeline in multiple files that are then merged, you will need to defined these keys to ensure order.
Routing and Dispatch middleware
Prior to RC6, the routing middleware:
- performed routing
- notified route result observers
- created a new request that composed the matched routing parameters as request attributes, and composed the route result instance itself as a request attribute.
- marshaled the middleware matched by routing
- dispatched the marshaled middleware
To provide a better separation of concerns, we split the routing middleware into
two distinct methods: routingMiddleware()
and dispatchMiddleware()
.
routingMiddleware()
performs the following duties:
- routing; and
- creating a new request that composes the matched routing parameters as request attributes, and composes the route result instance itself as a request attribute.
dispatchMiddleware()
performs the following duties:
- marshaling the middleware specified in the route result; and
- dispatching the marshaled middleware.
One reason for this split is to allow injecting middleware to operate between routing and dispatch. As an example, you could have middleware that determines if a matched route requires an authenticated identity:
public function __invoke($request, $response, $next)
{
$result = $request->getAttribute(RouteResult::class);
if (! in_array($result->getMatchedRouteName(), $this->authRequired)) {
return $next($request, $response);
}
if (! $this->authenticated) {
return $next($request, $response->withStatus(401), 'authentication
required');
}
}
The above could then be piped between the routing and dispatch middleware:
$app->pipeRoutingMiddleware();
$app->pipe(AuthenticationMiddleware::class);
$app->pipeDispatchMiddleware();
Since the routing middleware has been split, we determined we could no longer automatically pipe the routing middleware; detection would require detecting both sets of middleware, and ensuring they are in the correct order. Additionally, since one goal of splitting the middleware is to allow substitutions for these responsibilities, auto-injection could in some cases be undesired. As a result, we now require you to inject each manually.
Impact
This change will require changes in your application.
- If you are using Expressive programmatically (i.e., you are not using
a container and the
Zend\Expressive\Container\ApplicationFactory
), you are now required to callApplication::pipeRoutingMiddleware()
. Additionally, a new method,Application::pipeDispatchMiddleware()
exists for injecting the application with the dispatch middleware, this, too, must be called.
This has a fortunate side effect: registering routed middleware no longer
affects the middleware pipeline order. As such, you can register your
pipeline first or last prior to running the application. The only stipulation
is that unless you register the routing and dispatch middleware, your routed
middleware will not be executed! As such, the following two lines must
be added to your application prior to calling Application::run()
:
$app->pipeRoutingMiddleware();
$app->pipeDispatchMiddleware();
- If you are creating your
Application
instance using a container and theZend\Expressive\Container\ApplicationFactory
, you will need to update your configuration to list the routing and dispatch middleware. The next section details the configuration changes necessary.
ApplicationFactory configuration changes
As noted in the document summary, the middleware pipeline configuration was changed starting in RC6. The changes are done in such a way as to honor configuration from RC5 and earlier, but using such configuration will now prompt you to update your application.
RC5 and earlier defined the default middleware_pipeline
configuration as follows:
return [
'middleware_pipeline' => [
// An array of middleware to register prior to registration of the
// routing middleware
'pre_routing' => [
//[
// Required:
// 'middleware' => 'Name or array of names of middleware services and/or callables',
// Optional:
// 'path' => '/path/to/match',
// 'error' => true,
//],
[
'middleware' => [
Helper\ServerUrlMiddleware::class,
Helper\UrlHelperMiddleware::class,
],
],
],
// An array of middleware to register after registration of the
// routing middleware
'post_routing' => [
//[
// Required:
// 'middleware' => 'Name of middleware service, or a callable',
// Optional:
// 'path' => '/path/to/match',
// 'error' => true,
//],
],
],
];
The following changes have been made:
- The concept of
pre_routing
andpost_routing
have been deprecated, and will be removed starting with the 1.1 version. A single middleware pipeline is now provided, though any individual specification can also specify an array of middleware. - The routing and dispatch middleware must now be added to your configuration for them to be added to your application.
- Middleware specifications can now optionally provide a
priority
key, with 1 being the default. High integer priority indicates earlier execution, while low/negative integer priority indicates later execution. Items with the same priority are executed in the order they are registered. Priority is now how you can indicate the order in which middleware should execute.
Impact
While the configuration from RC5 and earlier will continue to work, it will raise deprecation notices. As such, you will need to update your configuration to follow the guidelines created with RC6.
RC6 and later change the configuration to remove the pre_routing
and
post_routing
keys. However, individual items within the array retain the same
format as middleware inside those keys, with the addition of a new key,
priority
:
[
// Required:
'middleware' => 'Name of middleware service, or a callable',
// Optional:
// 'path' => '/path/to/match',
// 'error' => true,
// 'priority' => 1, // integer
]
The priority
key is used to determine the order in which middleware is piped
to the application. Higher integer values are piped earlier, while
lower/negative integer values are piped later; middleware with the same priority
are piped in the order in which they are discovered in the pipeline. The default
priority used is 1.
Additionally, the routing and dispatch middleware now become items in the array; they (or equivalent entries for your own implementations) must be present in your configuration if you want your routed middleware to dispatch! This change gives you full control over the flow of the pipeline.
To specify the routing middleware, use the constant
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE
in place of
a middleware array; this has the value EXPRESSIVE_ROUTING_MIDDLEWARE
, if you
do not want to import the class. Similarly, for the dispatch middleware, use the
constant Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE
(value EXPRESSIVE_DISPATCH_MIDDLEWARE
) to specify the dispatch middleware.
As such, the default configuration now becomes:
return [
'middleware_pipeline' => [
// An array of middleware to pipe to the application.
// Each item is of the following structure:
// [
// // Required:
// 'middleware' => 'Name or array of names of middleware services and/or callables',
// // Optional:
// 'path' => '/path/to/match',
// 'error' => true,
// ],
[
'middleware' => [
Helper\ServerUrlMiddleware::class,
],
'priority' => PHP_INT_MAX,
],
// The following is an entry for:
// - routing middleware
// - middleware that reacts to the routing results
// - dispatch middleware
[
'middleware' => [
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
Helper\UrlHelperMiddleware::class,
Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],
'priority' => 1,
]
// The following is an entry for the dispatch middleware:
// Place error handling middleware after the routing and dispatch
// middleware, with negative priority.
// [
// 'middleware' => [
// ],
// 'priority' => -1000,
// ],
],
];
To update an existing application:
- Promote all
pre_routing
middleware up a level, and remove thepre_routing
key. Provide apriority
value greater than 1. We recommend having a single middleware specification with an array of middleware that represents the "pre routing" middleware. - Add the entries for
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE
andZend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE
immediately following anypre_routing
middleware, and before anypost_routing
middleware; we recommend grouping it per the above example. - Promote all
post_routing
middleware up a level, and remove thepost_routing
key. Provide apriority
value less than 1 or negative. - If you have
middleware_pipeline
specifications in multiple files, you will need to specifypriority
keys for all middleware in order to guarantee order after merging. We recommend having a single middleware specification with an array of middleware that represents the "post routing" middleware.
As an example, consider the following application configuration:
return [
'middleware_pipeline' => [
'pre_routing' => [
[
'middleware' => [
Zend\Expressive\Helper\ServerUrlMiddleware::class,
Zend\Expressive\Helper\UrlHelperMiddleware::class,
],
],
[ 'middleware' => DebugToolbarMiddleware::class ],
[
'middleware' => ApiMiddleware::class,
'path' => '/api',
],
],
'post_routing' => [
['middleware' => NotFoundMiddleware::class, 'error' => true],
],
],
];
This would be rewritten to the following to work with RC6 and later:
return [
'middleware_pipeline' => [
'always' => [
'middleware' => [
Zend\Expressive\Helper\ServerUrlMiddleware::class,
DebugToolbarMiddleware::class,
],
'priority' => PHP_INT_MAX,
],
'api' => [
'middleware' => ApiMiddleware::class,
'path' => '/api',
'priority' => 100,
],
'routing' => [
'middleware' => [
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
Zend\Expressive\Helper\UrlHelperMiddleware::class,
Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],
'priority' => 1,
],
'error' => [
'middleware' => [
NotFoundMiddleware::class,
],
'error' => true,
'priority' => -1000,
],
],
]
Note in the above example the various groupings. By grouping middleware by priority, you can simplify adding new middleware, particularly if you know it should execute before routing, or as error middleware, or between routing and dispatch.
Keys are ignored
The above example provides keys for each middleware specification. The factory will ignore these, but they can be useful for cases when you might want to specify configuration in multiple files, and merge specific entries together. Be aware, however, that the
middleware
key itself is an indexed array; items will be appended based on the order in which configuration files are merged. If order of these is important, create separate specifications with relevantpriority
values.
Route result observer deprecation
As of RC6, the following changes have occurred with regards to route result observers:
- They are deprecated for usage with
Zend\Expressive\Application
, and that class will not be a route result subject starting in 1.1. You will need to start migrating to alternative solutions. - The functionality for notifying observers has been moved from the routing
middleware into a dedicated
Application::routeResultObserverMiddleware()
method. This middleware must be piped separately to the middleware pipeline for it to trigger.
Impact
If you are using any route result observers, you will need to ensure your application notifies them, and you will want to migrate to alternative solutions to ensure your functionality continues to work.
To ensure your observers are triggered, you will need to adapt your application, based on how you create your instance.
If you are not using the ApplicationFactory
, you will need to pipe the
routeResultObserverMiddleware
to your application, between the routing and
dispatch middleware:
$app->pipeRoutingMiddleware();
$app->pipeRouteResultObserverMiddleware();
$app->pipeDispatchMiddleware();
If you are using the ApplicationFactory
, you may need to update your
configuration to allow injecting the route result observer middleware. If you
have not updated your configuration to remove the pre_routing
and/or
post_routing
keys, the middleware will be registered for you. If you have,
however, you will need to register it following the routing middleware:
[
'middleware_pipeline' => [
/* ... */
'routing' => [
'middleware' => [
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
Zend\Expressive\Container\ApplicationFactory::ROUTE_RESULT_OBSERVER_MIDDLEWARE,
Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],
'priority' => 1,
],
/* ... */
],
]
To make your observers forwards-compatible requires two changes:
- Rewriting your observer as middleware.
- Registering your observer as middleware following the routing middleware.
If your observer looked like the following:
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Router\RouteResultObserverInterface;
class MyObserver implements RouteResultObserverInterface
{
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function update(RouteResult $result)
{
$this->logger->log($result);
}
}
You could rewrite it as follows:
use Zend\Expressive\Router\RouteResult;
class MyObserver
{
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function __invoke($request, $response, $next)
{
$result = $request->getAttribute(RouteResult::class, false);
if (! $result) {
return $next($request, $response);
}
$this->logger->log($result);
return $next($request, $response);
}
}
You would then register it following the routing middleware. If you are building your application programmatically, you would do this as follows:
$app->pipeRoutingMiddleware();
$app->pipe(MyObserver::class);
$app->pipeDispatchMiddleware();
If you are using the ApplicationFactory
, alter your configuration:
[
'middleware_pipeline' => [
/* ... */
'routing' => [
'middleware' => [
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
MyObserver::class,
Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],
'priority' => 1,
],
/* ... */
],
]
Timeline for migration
The following features will be removed in version 1.1.0:
- Support for the
pre_routing
andpost_routing
configuration. - Support for route result observers.
Found a mistake or want to contribute to the documentation? Edit this page on GitHub!