Possible to pass an argument to invokable Middleware?

I was wondering if there’s a way to pass arguments to an invokable middleware.
This is a route example I have:

$app->get('/myroute', MyController::class . ':myAction')
        ->add(MyMiddleware1::class)
        ->add(new MyMiddleware2(["hello", 123]));

And I would like the last line to be something close to this:

        ->add(MyMiddleware2::class, ["hello", 123]);

So is there a way to keep the invokable format for Middleware2 from the DI Container? I can’t seem to be able to retrieve it from the DI Container and give it an argument.

Well, not super clean, but the closest I’ve gotten to is the following:

$app->get('/myroute', MyController::class . ':myAction')
        ->add(MyMiddleware1::class)
        ->add($this->get(MyMiddleware2::class)->setArgs(["hello", 123]));

where setArgs() is something like this:

function setArgs(Array $args){
    $this->args = $args;
    return $this;
}

Why do not pass the options via constructor within the DI container?

The values of those arguments change based on the route this middleware is attached to.
One route might be

$app->get('/myroute', MyController::class . ':myAction')
    ->add($this->get(MyMiddleware2::class)->setArgs(["hello", 123]));

while the next could be:

$app->get('/myotherroute', MyController::class . ':myAction')
    ->add($this->get(MyMiddleware2::class)->setArgs(["okay", 456]));

This is what I could find from Slim’s v2 Github issues:

$member = function ($role) {
    return function ($req, $res, $next) {
        return $next($req, $res);
    };
};

$app->get()->add($member('member'));

Just not sure how or where to apply this code for v4.

Edit

This is what I’ve got (not a fan of this either, as I still have to implement the setArgs() function):

$Middleware2 = function ($params){
    return function ($req, $handler) use ($params) {
         $mw2 = $this->get(Middleware2::class)->setArgs($params);
         return $mw2->__invoke($req, $handler);
     };
};

...

$app->get('/myroute', MyController::class . ':myAction')
    ->add($MyMiddleware2(["hello", 123]))

This is what I ended up doing:

#class MyMiddleware2

function __invoke($request, $handler, $my_args = []){
    //...
}

static function withArgs($params){
    return function ($request, $handler) use ($params) {
        return $this->get(self::class)->__invoke($request, $handler, $params);
    };
}
#routes
$app->get('/myroute', MyController::class . ':myAction')
    ->add(MyMiddleware2::withArgs(["hello", 123]))

This way it looks clean and I also don’t have to pass around the anonymous functions to each route group with use($params). Also, this way we’re not adding a setter, we’re instead directly injecting the args in the invoke call.

Any feedback / comments?

To pass parameters between middlewares, you could use the request attributes like this:

$request = $request->withAttribute('my-key', 'my-value');

To read this value, use the getAttribute method:

// 'my-value'
$value = $request->getAttribute('my-key');

Yeah, I thought of that approach but I really wanted to be able to see the parameters being passed from a higher level, not from inside each controller/action.

Also, I’m already using the $request->withAttribute() for other reasons, and it might cause naming conflicts.