[Expressive 3] How do pipe and lazy load double-pass-middlware-class

expressive
zend-stratigility

#1

My Middleware class:

class TestMiddleware {
    public function __construct($param) {}
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) {
         return $next($request, $response);
    }
}

In Expressive 2:

$app->pipe(TestMiddleware::class);

It’s OK!

In Expressive 3:

$app->pipe(TestMiddleware::class);

Actual results

Throw message:

Service "TestMiddleware" did not to resolve to a Psr\Http\Server\MiddlewareInterface instance; resolved to "TestMiddleware"

Expected results

Support __invoke($request, $response, $next) .
Or support $app->pipe(doublePassMiddleware(TestMiddleware::class));


#2

The docs for v3 are not ready yet, however the migration guide to v2.2 is online and it tells you how to do this: https://docs.zendframework.com/zend-expressive/v2/reference/migration-to-v2-2/#double-pass-middleware

If that middleware is your own code, you might as change it to PSR-15: https://www.php-fig.org/psr/psr-15/#22-psrhttpservermiddlewareinterface


#3

But most vendor’s middleware are not ready to upgrade to PSR 15.

(e.g. mtymek/blast-base-url )

Will provide a Decorator or change Zend\Expressive\Middleware\LazyLoadingMiddleware for compat DoublePassMiddlewareClass ?


#4

This is not terribly difficult; handle it in the factory for that middleware, and/or create a factory for it, and/or create a delegator factory for it.

Probably the easiest route is to create a single delegator factory for such middleware, and then map it to any services you have that currently produce double-pass middleware. It would likely look something like the following:

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use function Zend\Stratigility\doublePassMiddleware;

class DoublePassMiddlewareDelegator
{
    public function __invoke(Container $container, string $serviceName, callable $callback)
    {
        return doublePassMiddleware(
            $callback(),
            ($container->get(ResponseInterface::class))()
        );
    }
}

You would then attach it to the service within your configuration:

return [
    'dependencies' => [
        'delegators' => [
            \Blast\BaseUrl\BaseUrlMiddleware::class => [
                DoublePassMiddlewareDelegator::class,
            ],
        ],
        'factories' => [
            \Blast\BaseUrl\BaseUrlMiddleware::class => \Blast\BaseUrl\BaseUrlMiddlewareFactory::class,
        ],
    ],
];

The nice bit about this approach is you can re-use it for any service that normally returns double-pass middleware — and you’ll easily be able to grep through your configuration to identify double-pass middleware in order to determine dependencies you may want to re-think or replace.


#5

How about change Zend\Expressive\MiddlewareContainer.

  1. Add $responsePrototype param __construct(ContainerInterface $container, ResponseInterface $responsePrototype = null)
  2. Add if (is_callable($middleware)) in MiddlewareContainer::get()
class MiddlewareContainer implements ContainerInterface
{
    public function __construct(ContainerInterface $container, ResponseInterface $responsePrototype = null)
    {
        $this->container = $container;
        $this->responsePrototype = $responsePrototype ?? new Response;
    }

    //...

    public function get($service) : MiddlewareInterface
    {
        //..
        $middleware;

        // Compat­ible with callable middleware
        if (is_callable($middleware)) {
            $middleware = new DoublePassMiddlewareDecorator($middleware, $this->responsePrototype);
        }

        if (! $middleware instanceof MiddlewareInterface) {
            throw Exception\InvalidMiddlewareException::forMiddlewareService($service, $middleware);
        }

        return $middleware;
    }
}

#6

We want to encourage people to migrate to PSR-15 middleware, as it is a standard. If we make it difficult to use double-pass middleware, the hope is this will put pressure on middleware package authors to update.


#7

All right! Thanks for Matthew and ZF team.

I will create a library , and extend Zend\Expressive\MiddlewareContainer for compatible with callable middleware. I think this is minimal change for my project migration to v3.

This is full code:


use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Exception;
use Zend\Stratigility\Middleware\DoublePassMiddlewareDecorator;
use Zend\Stratigility\Middleware\RequestHandlerMiddleware;

class MiddlewareContainer extends \Zend\Expressive\MiddlewareContainer
{
    protected $container;
    protected $responsePrototype;

    public function __construct(ContainerInterface $container, ResponseInterface $responsePrototype = null)
    {
        parent::__construct($container);
        $this->container = $container;
        $this->responsePrototype = $responsePrototype;
    }

    public function get($service) : MiddlewareInterface
    {
        if (! $this->has($service)) {
            throw Exception\MissingDependencyException::forMiddlewareService($service);
        }

        $middleware = $this->container->has($service)
            ? $this->container->get($service)
            : new $service();

        if ($middleware instanceof RequestHandlerInterface) {
            $middleware = new RequestHandlerMiddleware($middleware);
        }

        if (is_callable($middleware)) {
            $middleware = new DoublePassMiddlewareDecorator($middleware, $this->responsePrototype);
        }

        if (! $middleware instanceof MiddlewareInterface) {
            throw Exception\InvalidMiddlewareException::forMiddlewareService($service, $middleware);
        }

        return $middleware;
    }
}

#8

Callable middleware compat library moln/expressive-callable-middleware-compat