Minimal example without using DI autowiring

While I understand the benefits of using DI and autowiring in a real application, I want to first write a minimal application without using Autowiring to learn the inner working of the framework.

I wrote the following code

<?php

use DI\Container;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
use Laminas\Stratigility\MiddlewarePipe;
use Mezzio\Application;
use Mezzio\Middleware\ErrorResponseGenerator;
use Mezzio\MiddlewareContainer;
use Mezzio\MiddlewareFactory;
use Mezzio\Router\RouteCollector;
use Slim\Psr7\Response;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Factory\ServerRequestFactory;
use Laminas\Diactoros\Response\TextResponse;
use Mezzio\Router\FastRouteRouter;

include __DIR__ . '/../../../composer/autoload.php';


$container = new MiddlewareContainer(new Container());
$pipeline = new MiddlewarePipe();

$collector = new RouteCollector(new FastRouteRouter());
$serverRequestFactory = function() {
    return ServerRequestFactory::createFromGlobals();
};
$errorResponseGenerator = function (Throwable $e) use($serverRequestFactory) {
    $generator = new ErrorResponseGenerator();
    return $generator($e, $serverRequestFactory(), new Response());
};
$runner = new RequestHandlerRunner($pipeline, new SapiEmitter(), $serverRequestFactory, $errorResponseGenerator);
$middlewareFactory = new MiddlewareFactory($container);
$app = new Application($middlewareFactory, $pipeline, $collector, $runner);
$pathMiddleware = function ( ServerRequestInterface $request, RequestHandlerInterface $handler) {
    $uri  = $request->getUri();
    $path = $uri->getPath();

    return new TextResponse('You visited ' . $path, 200, ['X-Path' => $path]);
};
$app->get('/', $pathMiddleware);
$app->run();

But it is failing by throwing the exception

Laminas\Stratigility\MiddlewarePipe cannot handle request; no middleware available to process the request

Why isn’t the router middleware working?

I am a bit confused after going through the source. The routes are added to $app. $app->run is proxied to $runner->run. But $app object is not passed to $runner, so how does it get access to the routes added to $app?

The problem is you’re not piping anything to your $pipeline, so there’s nothing to handle the request.

Yes, you are calling $app->get(), but that call only pushes a route into your Mezzio\Router\RouteCollector instance, which in turn pushes it into the router. In your example, however, the router is never called, because you never pipe middleware to route and dispatch the request.

You need to pipe the RouteMiddleware and the DispatchMiddleware to your $pipeline.

Make the following changes following your line that defines the $pipeline, and the line that creates the collector:

$router = new FastRouteRouter();
$routeMiddleware = new \Mezzio\Router\Middleware\RouteMiddleware($router);

$pipeline->pipe($routeMiddleware);
$pipeline->pipe(new \Mezzio\Router\Middleware\DispatchMiddleware());

$collector = new RouteCollector($router);

With those changes, you should have better success.

Thank you @matthew.

But it is still not working. Should I pipe the Route/Dispatch middlewares to $app or $pipeline?

Also, what I should pass as $handler while creating RequestHandlerRunner? The documentation for laminas-requesthandlerrunner is passing an instance of ApplicationRequestHandler, and says that I should pass a request handler representing my application.

<?php
use DI\Container;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
use Laminas\Stratigility\MiddlewarePipe;
use Laminas\Diactoros\Response\TextResponse;
use Mezzio\Application;
use Mezzio\Middleware\ErrorResponseGenerator;
use Mezzio\MiddlewareContainer;
use Mezzio\MiddlewareFactory;
use Mezzio\Router\RouteCollector;
use Mezzio\Router\FastRouteRouter;
use Mezzio\Router\Middleware\DispatchMiddleware;
use Mezzio\Router\Middleware\RouteMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
use Slim\Psr7\Factory\ServerRequestFactory;

include __DIR__ . '/../../../composer/autoload.php';

$container = new MiddlewareContainer(new Container());

$router = new FastRouteRouter();
$collector = new RouteCollector($router);

$pipeline = new MiddlewarePipe();
$pipeline->pipe(new RouteMiddleware($router));
$pipeline->pipe(new DispatchMiddleware());

$serverRequestFactory = function() {
    return ServerRequestFactory::createFromGlobals();
};

$errorResponseGenerator = function (Throwable $e) use($serverRequestFactory) {
    $generator = new ErrorResponseGenerator();
    return $generator($e, $serverRequestFactory(), new Response());
};

$middlewareFactory = new MiddlewareFactory($container);
$runner = new RequestHandlerRunner($pipeline, new SapiEmitter(), $serverRequestFactory, $errorResponseGenerator);
$app = new Application($middlewareFactory, $pipeline, $collector, $runner);

$app->get('/', function (ServerRequestInterface $request, RequestHandlerInterface $handler) {
    $uri  = $request->getUri();
    $path = $uri->getPath();

    return new TextResponse('You visited ' . $path, 200, ['X-Path' => $path]);
});

$app->run();

In the following code $app is passed as the first argument when creating RequestHandlerRunner, but it is using old classes from before migration to Laminas namespace. Also, I can create my application only after creating the RequestHandlerRunner, creating a chicken and egg situation.

And the following code is passing an implementation of MiddlewarePipeInterface. Hence I am passing $pipeline as the first argument.

I just tried locally, with some minimal changes:

  • I used laminas/laminas-diactoros for my PSR-7 implementation.
  • I used laminas/laminas-servicemanager for my PSR-11 container.
  • I added the handler by decorating it using the $middlewareFactory->callable() construct.
  • I reorganized a few declarations.

My composer.json looks like this:

{
    "require": {
        "mezzio/mezzio": "^3.2",
        "mezzio/mezzio-fastroute": "^3.0",
        "laminas/laminas-diactoros": "^2.2",
        "laminas/laminas-servicemanager": "^3.4"
    }
}

and my index.php looks like this:

<?php
use Laminas\Diactoros\Response;
use Laminas\Diactoros\Response\TextResponse;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
use Laminas\ServiceManager\ServiceManager;
use Laminas\Stratigility\MiddlewarePipe;
use Mezzio\Application;
use Mezzio\MiddlewareContainer;
use Mezzio\Middleware\ErrorResponseGenerator;
use Mezzio\MiddlewareFactory;
use Mezzio\Router\FastRouteRouter;
use Mezzio\Router\Middleware\DispatchMiddleware;
use Mezzio\Router\Middleware\RouteMiddleware;
use Mezzio\Router\RouteCollector;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

require './vendor/autoload.php';

$container         = new MiddlewareContainer(new ServiceManager([]));
$middlewareFactory = new MiddlewareFactory($container);
$router            = new FastRouteRouter();
$collector         = new RouteCollector($router);

$pipeline = new MiddlewarePipe();
$pipeline->pipe(new RouteMiddleware($router));
$pipeline->pipe(new DispatchMiddleware());

$serverRequestFactory   = Closure::fromCallable([ServerRequestFactory::class, 'fromGlobals']);
$errorResponseGenerator = function (Throwable $e) use($serverRequestFactory) {
    $generator = new ErrorResponseGenerator();
    return $generator($e, $serverRequestFactory(), new Response());
};

$runner = new RequestHandlerRunner(
    $pipeline,
    new SapiEmitter(),
    $serverRequestFactory,
    $errorResponseGenerator
);

$app = new Application($middlewareFactory, $pipeline, $collector, $runner);

$app->get(
    '/',
    $middlewareFactory->callable(
        function (ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface {
            $uri  = $request->getUri();
            $path = $uri->getPath();

            return new TextResponse('You visited ' . $path, 200, ['X-Path' => $path]);
        }
    )
);

$app->run();

I then started up the server running php -S 0:8080 -t . and hit the home page. I got a 200 response with a text/plain content-type and the text “You visited /”.

As you can see, the $pipeline is re-used between the Application and HttpHandlerRunner instances.

Give what I’ve done above a try!

2 Likes

Your code worked perfectly, and in fact mine too was working. I was hosting the code under a sub directory, hence the get route was not matching. Since the error message said

no middleware available to process the request

I assumed that the middleware was not being detected by the runner. :man_facepalming:

It would be useful to new users, if you could add a sample application code with Routing, Templating and Error Handling without using DIC to the project’s getting started section.

Thanks a lot for the help and the nice work you have done with the project.