Migration to Expressive 2.0

Expressive 2.0 should not result in many upgrade problems for users. However, starting in this version, we offer a few changes affecting the following that you should be aware of, and potentially update your application to adopt:

Signature changes

The following signature changes were made that could affect class extensions:

Additionally, a number of signatures change due to updating Expressive to support PSR-11 instead of container-interop (which was the basis for PSR-11). Essentially, these were a matter of updating typehints on Interop\Container\ContainerInterface to Psr\Container\ContainerInterface. Signatures affected include:

In each of the above cases, updating your import statements from Interop\Container\ContainerInterface to Psr\Container\ContainerInterface will make your code work again.

The following exceptions now implement PSR-11 exception interfaces instead of container-interop variants:

In the above case, if you were previously catching the container-interop exception on which it was based, your code should still work so long as you have container-interop installed. You should likely update it to catch the more general Psr\Container\ContainerExceptionInterface instead, however.

Removed functionality

The following classes and/or methods were removed for the Expressive 2.0 release:

Deprecated functionality

PSR-11 support

In previous versions of Expressive, we consumed container-interop, which provides Interop\Container\ContainerInterface, a shared interface for dependency injection containers. container-interop served as a working group for the PSR-11 specification.

In the weeks prior to the Expressive 2.0 release, PSR-11 was formally accepted, and the package psr/container was released. As such, we have updated Expressive to consume the interfaces PSR-11 exposes.

No supported implementations currently directly implement PSR-11, however. Fortunately, the container-interop 1.2.0 release acts as a forwards-compatibility measure by altering every interface it exposes to extend those from PSR-11, making existing container-interop implementations de facto PSR-11 implementations!

The result is a (mostly) transparent upgrade for users of Expressive. As newer versions of container implementations are released supporting PSR-11 directly, you will be able to upgrade immediately; we will also periodically update the skeleton to pick up these new versions when present. (The one caveat to upgrading is signature changes within Expressive classes based on the new psr/container interface names.)

As long as you have container-interop 1.2.0 installed, your existing factories that typehint against its interface will continue to work. However, we recommend updating them to instead typehint against PSR-11, which will allow you to drop the container-interop requirement once your chosen container implementation no longer requires it.

Do not update blindly!

If you are implementing interfaces from other packages in your factory implementations, be sure to check and see if those interfaces update to PSR-11 before making changes.

As an example, zend-servicemanager v3 does not update Zend\ServiceManager\Factory\FactoryInterface and siblings to typehint against PSR-11, as doing so would break backwards compatibility.

http-interop

Stratigility 2.0 provides the ability to work with http-interop middleware 0.4.1.

This specification, which is being developed as the basis of PSR-15, defines what is known as lambda or single-pass middleware, vs the double-pass middleware traditionally used by Stratigility and Expressive.

Double-pass refers to the fact that two arguments are passed to the delegation function $next: the request and response. Lambda or single-pass middleware only pass a single argument, the request.

Stratigility 2.0 provides support for dispatching either style of middleware.

Specifically, your middleware can now implement:

Both styles of middleware may be piped directly to the middleware pipeline or as routed middleware within Expressive. In each case, you can invoke the next middleware layer using $delegate->process($request).

In Expressive 2.0, Application will continue to accept the legacy double-pass signature, but will require that you either:

If you use Zend\Expressive\Container\ApplicationFactory to create your Application instance, a response prototype will be injected for you from the outset.

We recommend that you begin writing middleware to follow the http-interop standard at this time. As an example:

namespace App\Middleware;

use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;

class XClacksOverheadMiddleware implements MiddlewareInterface
{
    /**
     * {@inheritDoc}
     */
    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $response = $delegate->process($request);
        return $response->withHeader('X-Clacks-Overhead', 'GNU Terry Pratchett');
    }
}

Alternately, you can write this as a callable:

namespace App\Middleware;

use Interop\Http\ServerMiddleware\DelegateInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class XClacksOverheadMiddleware
{
    /**
     * @param ServerRequestInterface $request
     * @param DelegateInterface $delegate
     * @return ResponseInterface
     */
    public function __invoke(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $response = $delegate->process($request);
        return $response->withHeader('X-Clacks-Overhead', 'GNU Terry Pratchett');
    }
}

Original messages

In the migration to version 1.1 guide, we detail the fact that Stratigility 1.3 deprecated its internal request and response decorators. Stratigility 2.0, on which Expressive 2.0 is based, removes them entirely.

If your code relied on the various getOriginal*() methods those decorators exposed, you will need to update your code in two ways:

To address the first point, see the Expressive 1.1 migration documentation, which details how to update your configuration or programmatic pipeline.

For the second point, we provide a tool via the zendframework/zend-expressive-tooling package which will help you in this latter part of the migration. Install it as a development requirement via composer:

$ composer require --dev zendframework/zend-expressive-tooling

And then execute it via:

$ ./vendor/bin/expressive-migrate-original-messages

This tool will update calls to getOriginalRequest() and getOriginalUri() to instead use the new request attributes that the OriginalMessages middleware injects:

In both cases, $request will be replaced with whatever variable name you used for the request instance.

For getOriginalResponse() calls, which happen on the response instance, the tool will instead tell you what files had such calls, and detail how you can update those calls to use the originalResponse request attribute.

Error handling

As noted in the Expressive 1.1 migration docs, Stratigility 1.3 introduced the ability to tell it to no longer catch exceptions internally, paving the way for middleware-based error handling. Additionally, it deprecated its own ErrorMiddlewareInterface and duck-typed implementations of the interface in favor of middleware-based error handling. Finally, it deprecated the $e/$error argument to "final handlers", as that argument would be used only when attempting to invoke ErrorMiddlewareInterface instances.

Stratigility 2.0, on which Expressive 2.0 is based, no longer catches exceptions internally, removes the ErrorMiddlewareInterface entirely, and thus the $e/$error argument to final handlers.

As such, you MUST provide your own error handling with Expressive 2.0.

Error handling middleware will typically introduce a try/catch block:

function (
    ServerRequestInterface $request,
    ResponseInterface $response,
    callable $next
) {
    try {
        $response = $next($request, $response);
        return $response;
    } catch (\Throwable $exception) {
        // caught PHP 7 throwable
    } catch (\Exception $exception) {
        // caught PHP 5 exception
    }

    // ... 
    // do something with $exception and generate a response
    // ...

    return $response;
}

Additionally, you will need middleware registered as your innermost layer that is guaranteed to return a response. Generally, if you hit that layer, no other middleware is capable of handling the request, indicating a 400 (Bad Request) or 404 (Not Found) HTTP status. With the combination of an error handler at the outermost layer, and a "not found" handler at the innermost layer, you can handle any error in your application.

Stratigility 1.3 and 2.0 provide an error handler implementation via Zend\Stratigility\Middleware\ErrorHandler. In addition to the try/catch block, it also sets up a PHP error handler that will catch any PHP error types in the current error_reporting mask; the error handler will raise exceptions of the type ErrorException with the PHP error details.

ErrorHandler allows injection of an "error response generator", which allows you to alter how the error response is generated based on the current environment. Error response generators are callables with the signature:

function (
    Throwable|Exception $e,
    ServerRequestInterface $request,
    ResponseInterface $response
) : ResponseInterface

Expressive 2.0 provides the following functionality to assist with your error handling needs:

Error handler configuration example

If you are using configuration-driven middleware, your middleware pipeline configuration may look like this in order to make use of the new error handling facilities:

// config/autoload/middleware-pipeline.global.php

use Zend\Expressive\Application;
use Zend\Expressive\Container;
use Zend\Expressive\Helper;
use Zend\Expressive\Middleware;
use Zend\Stratigility\Middleware\ErrorHandler;
use Zend\Stratigility\Middleware\OriginalMessages;

return [
    // Add the following section to enable the new error handling:
    'zend-expressive' => [
        'raise_throwables' => true,
    ],

    'dependencies' => [
        'invokables' => [
            // See above section on "Original messages":
            OriginalMessages::class => OriginalMessages::class,
        ],
        'factories' => [
            Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class,
            Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class,

            // Add the following three entries:
            ErrorHandler::class => Container\ErrorHandlerFactory::class,
            Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class,
            Middleware\NotFoundHandler::class => Container\NotFoundHandlerFactory::class,
        ],
    ],

    'middleware_pipeline' => [
        'always' => [
            'middleware' => [
                OriginalMessages::class,
                Helper\ServerUrlMiddleware::class,
                ErrorHandler::class,
                /* ... */
            ],
            'priority' => 10000,
        ],

        'routing' => [
            'middleware' => [
                Application::ROUTING_MIDDLEWARE,
                Helper\UrlHelperMiddleware::class,
                Application::DISPATCH_MIDDLEWARE,
            ],
            'priority' => 1,
        ],

        'not-found' => [
            'middleware' => Middleware\NotFoundHandler::class,
            'priority' => 0,
        ],

        // Remove the section "error""
    ],
];

If you are defining a programmatic pipeline (see more below on this), the pipeline might look like:

$app->pipe(OriginalMessages::class);
$app->pipe(Helper\ServerUrlMiddleware::class);
$app->pipe(ErrorHandler::class);
$app->pipeRoutingMiddleware();
$app->pipe(Helper\UrlHelperMiddleware::class);
$app->pipeDispatchMiddleware();
$app->pipe(Middleware\NotFoundHandler::class);

Error handling and PHP errors

As noted above, Zend\Stratigility\Middleware\ErrorHandler also creates a PHP error handler that casts PHP errors to ErrorException instances. More specifically, it uses the current error_reporting value to determine which errors it should cast this way.

This can be problematic when deprecation errors are triggered. If they are cast to exceptions, code that would normally run will now result in error pages.

We recommend adding the following line to your public/index.php towards the top of the file:

error_reporting(error_reporting() & ~E_USER_DEPRECATED);

This will prevent the error handler from casting deprecation notices to exceptions, while keeping the rest of your error reporting mask intact.

Removing legacy error middleware

Stratigility version 1-style error middleware (middleware implementing Zend\Stratigility\ErrorMiddlewareInterface, or duck-typing its signature, which included an $error argument as the first argument to the middleware) is no longer supported with Stratigility version 2 and Expressive 2.0. You will need to find any instances of them in your application, or cases where your middleware invokes error middleware via the third argument to $next().

We provide a tool to assist you with that via the package zendframework/zend-expressive-tooling: vendor/bin/expressive-scan-for-error-middleware. Run the command from your project root, optionally passing the help, --help, or -h commands for usage. The tool will detect each of these for you, flagging them for you to update or remove.

Final handlers become default delegates

One ramification of supporting http-interop middleware is that the concept of "final handlers" changes. In Stratigility 1.X and Expressive 1.X, a "final handler" was invoked when the middleware pipeline was exhausted; however, due to how Stratigility caught exceptions, this also meant that the final handler often acted as the application error handler, reporting errors to end users.

With the error handling changes noted above, error handling is moved to dedicated middleware. However, there is still a need to have something that can execute once the middleware pipeline is exhausted. Such a situation typically indicates no middleware was able to handle the request, or that the request was somehow malformed.

In Expressive 2.0, we have removed final handlers, and replaced them with the concept of "default delegates". Delegates are Interop\Http\ServerMiddleware\DelegateInterface instances, which are invoked by middleware when they wish to delegate processing of the request to something else. Internally, Stratigility 2.0 and Expressive 2.0 use a delegate to iterate through the middleware pipeline. For Expressive 2.0, a default delegate is a delegate executed when the application's internal middleware pipeline is exhausted.

The ramifications for end users are as follows:

If you use the vendor/bin/expressive-pipeline-from-config tool to migrate your application to programmatic pipelines, as described below, the DefaultDelegate service will be mapped to Zend\Expressive\Container\NotFoundDelegateFactory, which will provide an instance of Zend\Expressive\Delegate\NotFoundDelegate. This new class will produce a 404 response, using a template if the Zend\Expressive\Template\TemplateRendererInterface service is present, but otherwise producing a plain text response.

Application's built using the 2.0 version of the skeleton application will have these features enabled by default.

NotFoundDelegate and NotFoundHandler

Zend\Expressive\Middleware\NotFoundHandler, which is intended as innermost middleware for producing a 404 response, composes and proxies to a NotFoundDelegate instance to produce its response.

Programmatic middleware pipelines

Starting with Expressive 1.1, we recommended programmatic creation of pipelines and routing; the Expressive 1.1 migration guide provides more detail.

With Expressive 2.0, this is now the default option shipped in the skeleton.

If you are upgrading from version 1 and are not currently using programmatic pipelines, we provide a migration tool that will convert your application to do so. The tool is available via the package zendframework/zend-expressive-tooling. You may install this package in one of the following ways:

$ ./vendor/bin/expressive-tooling        # install
$ ./vendor/bin/expressive-tooling remove # uninstall
$ composer require --dev zendframework/zend-expressive-tooling # install
$ composer remove --dev zendframework/zend-expressive-tooling  # uninstall

Once installed, you will use the vendor/bin/expressive-pipeline-from-config command.

This command does the following:

Your middleware_pipeline and routes configuration are not removed at this time, to allow you to test and verify your application first; however, due to the configuration in config/autoload/programmatic-pipeline.global.php, these are now ignored.

If you wish to use Whoops in your development environment, you may add the following to a local configuration file (e.g., config/autoload/local.php):

use Zend\Expressive\Container\WhoopsErrorResponseGeneratorFactory;
use Zend\Expressive\Middleware\ErrorResponseGenerator;

return [
    'dependencies' => [
        'factories' => [
            ErrorResponseGenerator::class => WhoopsErrorResponseGeneratorFactory::class,
        ],
    ],
];

Other things you may want to do:

Handling HEAD and OPTIONS requests

Prior to 2.0, it was possible to route middleware that could not handle HEAD and/or OPTIONS requests. Per RFC 7231, section 4.1, "all general-purpose servers MUST support the methods GET and HEAD. All other methods are OPTIONAL." Additionally, most servers and implementors agree that OPTIONS should be supported for any given resource, so that consumers can determine what methods are allowed for the given resource.

To make this happen, the Expressive project implemented several features.

First, zend-expressive-router 1.3.0 introduced several features in both Zend\Expressive\Router\Route and Zend\Expressive\Router\RouteResult to help consumers implement support for HEAD and OPTIONS in an automated way. The Route class now has two new methods, implicitHead() and implicitOptions(); these each return a boolean true value if support for those methods is implicit — i.e., not defined explicitly for the route. The RouteResult class now introduces a new factory method, fromRoute(), that will create an instance from a Route instance; this then allows consumers of a RouteResult to query the Route to see if a matched HEAD or OPTIONS request needs automated handling. Each of the supported router implementations were updated to use this method, as well as to return a successful routing result if HEAD and/or OPTIONS requests are submitted, but the route does not explicitly support the method.

Within Expressive itself, we now offer two new middleware to provide this automation:

If you want to support these methods automatically, each of these should be enabled between the routing and dispatch middleware. If you use the expressive-pipeline-from-config tool as documented in the programmatic pipeline migration section, entries for each will be injected into your generated pipeline.

Please see the chapter on the implicit methods middleware for more information on each.

Router interface changes

Expressive 2.0 uses zendframework/zend-expressive-router 2.1+. Version 2.0 of that package introduced a change to the Zend\Expressive\Router\RouterInterface::generateUri() method; it now accepts an additional, optional, third argument, array $options = [], which can be used to pass router-specific options when generating a URI. As an example, the implementation that uses zendframework/zend-router might use these options to pass a translator instance in order to translate a path segment to the currently selected locale.

For consumers, his represents no backwards-incompatible change; consumers may opt-in to the new argument at will. For those implementing the interface, upgrading will require updating your router implementation's signature to match the new interface:

public function generateUri(
    string $name,
    array $substitutions = [],
    array $options = []
) : string

URL helper changes

Expressive 2.0 uses zendframework/zend-expressive-helpers version 3.0+. This new version updates the signature of the Zend\Expressive\Helper\UrlHelper from:

function (
    $routeName,
    array $routeParams = []
) : string

to:

function (
    $routeName,
    array $routeParams = [],
    $queryParams = [],
    $fragmentIdentifier = null,
    array $options = []
) : string

For consumers, this should represent a widening of features, and will not require any changes, unless you wish to opt-in to the new arguments. See the UrlHelper documentation for information on each argument.

For any users who were extending the class, you will need to update your extension accordingly.

zend-view renderer changes

Expressive 2.0 will use zend-expressive-zendviewrenderer 1.3+ if that renderer is chosen. Starting with 1.3.0 of that renderer, you may now pass a boolean false value for the layout variable when calling either addDefaultParam() or render() on the renderer instance in order to disable the layout.

Twig renderer changes

Expressive 2.0 will use zend-expressive-twigrenderer 1.3+ if that renderer is chosen. Starting with 1.3.0 of that renderer, Twig 2.1+ is now supported.