How to call function (controller action) in PSR-15 middleware stack

798 views Asked by At

I have read PSR-15 from PHP-FIG (HTTP Server Request Handler) and wondering when an action is called (Controller action or Closure).

When processing through all middlewares the action should be called after passing all middlewares. After the action all middlewares are passed again from the inside to the outside (as described in the User Guide from Slim Framework).

I browsed the Code from Laravel on GitHub, but can't figure out how they call the action and then traverse all of the middlewares again.

This answer tells me that Slim adds itself to the middleware stack. I guess it then executes the action somehow. However, Slim is using double pass.

How does a middleware stack and execution look with single pass (as described in PSR-15) and call an action between all of the middlewares?

2

There are 2 answers

0
bhdzllr On

Here is an example code as I could imagine that could work. Of course, there are a lot of things missing like setting routes, resolving dependencies, PSR interfaces and other things that come with frameworks.

This codes adds middleware and action to the request handler and the request handler can execute both when needed.

<?php

interface RequestInterface {}
class Request implements RequestInterface {}

interface ResponseInterface {}
class Response implements ResponseInterface {}
class Response404 implements ResponseInterface {}

class FirstMiddleware {

    public function process(RequestInterface $request, RequestHandler $handler): ResponseInterface {
        echo ' First-Before ';

        $response = $handler->handle($request);

        echo ' First-After ';

        return $response;
    }

}

class SecondMiddleware {

    public function process(Request $request, RequestHandler $handler): Response {
        echo ' Second-Before ';

        $response = $handler->handle($request);

        echo ' Second-After ';

        return $response;
    }

}

class RequestHandler {

    private $middleware;
    private $callable;
    private $params;

    public function __construct(array $middleware = [], callable $callable, array $params = []) {
        $this->middleware = $middleware;
        $this->callable = $callable;
        $this->params = $params;
    }

    public function handle(RequestInterface $request): ResponseInterface {
        $middleware = current($this->middleware);
        next($this->middleware);

        if (!$middleware) {
            $response = ($this->callable)(...$this->params);

            return $response;
        }

        return $middleware->process($request, $this);
    }

}

class App {

    private $middleware = [];
    private $callable;

    public function setMiddleware(array $middleware) {
        $this->middleware = $middleware;
    }

    public function setCallable(callable $callable) {
        $this->callable = $callable;
    }

    public function run() {
        $handler = new RequestHandler($this->middleware, $this->callable, ['one', 'two']);
        $handler->handle(new Request());
    }

}

class Controller {

    public function action($a, $b) {
        echo ' Controller Action ';
        echo $a;
        echo $b;

        return new Response();
    }

}

$middleware = [
    new FirstMiddleware(),
    new SecondMiddleware(),
];

// Using closure ...
$callable = function ($a, $b) {
    echo ' Closure Action ';
    echo $a;
    echo $b;

    return new Response();
};

// Or an object method
$controller = new Controller();
$callable = array($controller, 'action');

/** Run */

$app = new App();

$app->setMiddleware($middleware);
$app->setCallable($callable);

$app->run();
0
Björn Tantau On

I started actually adding the controllers as middlewares. For that to work the RequestHandler has to be able to accept new middlewares to be added. And my router does the job of assigning the appropriate middleware. That way I may also assign more than one action per route.