Type hinting $request->getAttribute("woof") / data from middleware

what is the preferred way to pass data from the middleware down to the route handler?

the 3 options i can think of:

  1. middleware+ ->withAttribute("woof",new WoofObject(...)) and in route /** @var $woof WoofObject */ $woof->getAttribute("woof");
    (ie type hinting the variable holding ->getAttribute wherever its used with /** @var - its usually not just a simple new object, im just using it as an eg here)

  2. not use the middleware in a traditional sense but to have an object that accepts the full response and returns the desired object.

  3. use the middleware to load up a container item then use DI in the controller to retrieve it

options 2 and 3 dont seem very nice to me. but im including them as options

what is you guy’s preferred way to pass down objects from middleware to controller? and how do you generally get type hinting on it?

(im currently using option 1, but just wondering if theres a better way to do it)

I would say for simple values (pure data), the $request->withAttribute(…) method is fine. For other, complexer “scenarios”, dependency injection would be a good approach. It always depends on the context.

1 Like

for instance wanting to add a middleware like ServiceMiddleware

class ServiceMiddleware {
    public function __construct(
        protected ServicesRepository $servicesRepository
    ) { }

    public function __invoke(Request $request, RequestHandler $handler): Response {
        $id = $request->getAttribute("PARAMS.service_id");
        if ($id) {
            $request = $request->withAttribute("SERVICE", $this->servicesRepository->get($id));
        }
        
        return $handler->handle($request);

    }
}

(there are more complex ones)

this way every route that needs access to the service just needs a @service_id and middleware and this will be applied.

is “dynamically” adding to the DI container an advised approach? like in the route / groups middleware get the container and add service to it?

im mostly using the container for sort of “global” items that need initializing like the user / tenant / db / config etc.

the getAttribute with a @var above it is pretty solid i think. was just wondering how the rest of the community / pro’s handle it.

The DI container belongs to the outer layer of the application and is “invisible” to your core application (services classes). A middleware belongs to the HTTP layer and usually performs only HTTP-specific tasks.

According to your example, it’s not clear for me what this “service” is actually responsible for. Can you add more context please.

Doesn’t Slim or usage of PHP_DI inject request attributes into the handler?

So, you’d have WoofObject $woof in the signature, and make it nullable if you’re not sure it would be set.

I use thus method, but the again, I’m still on 7.2 fir the project that is using Slim 4 and haven’t played with the type options heavily.

sorry, not too clear on what you mean.

just incase its the DI stuff then sure…

function __construct(
        protected System $system,
        protected Config $config,
        protected Profiler $profiler,
        protected Responder $responder,
        protected JobsRepository $jobsRepository,
        protected HandlersRepository $handlersRepository
    ) {
    }


    public function __invoke(Request $request, Response $response): Response {
        /** @var $job JobModel */
        $job = $request->getAttribute("JOB");
...

using DI either loads up a container instance or creates a new instance if its not in the container for all those items in the __constructor

if you add the “JOB” attribute in via a middleware:

class JobMiddleware {
    public function __construct(
        protected Profiler $profiler,
        protected System $system,
        protected JobsRepository $jobsRepository
    ) {
    }
    public function __invoke(Request $request, RequestHandler $handler): Response {
        $id = $request->getAttribute("PARAMS.job_id");
        if ($id) {
            $request = $request->withAttribute("JOB", $this->jobsRepository->get($id));
        }
        return $handler->handle($request);
    }
}

you arent really looking for either of those.

my OP was more a case of how do others use it?

 /** @var $job JobModel */
 $job = $request->getAttribute("JOB");

does work nicely. was just wondering if there was some super global dockblock or put something in the middleware to auto define type or whatever.

“typing” is definitely <3<3<3<3 php is a whole other language with it in.

Consider this slightly modified middleware from the last comment.

class JobMiddleware {
    public function __construct(
        protected Profiler $profiler,
        protected System $system,
        protected JobsRepository $jobsRepository
    ) {
    }
    public function __invoke(Request $request, RequestHandler $handler): Response {
        $id = $request->getAttribute("PARAMS.job_id");
        if ($id) {
            $request = $request->withAttribute("job", $this->jobsRepository->get($id));
        }
        return $handler->handle($request);
    }
}

Then the controller can be modified as

function __construct(
        protected System $system,
        protected Config $config,
        protected Profiler $profiler,
        protected Responder $responder,
        protected JobsRepository $jobsRepository,
        protected HandlersRepository $handlersRepository
    ) {
    }


    public function __invoke(Request $request, Response $response, JobModel $job): Response {
        // do stuff
        return $response;
     }
...

In the same way that route variables can be injected into the controller method, request attributes can also be injected into the controller method.

does that even work? that would be SOOO cool if it did. but as far as i know the 3rd item in the controller callback is the “params” array.

App\Pages\Jobs\Controllers\JobsController::__invoke(): Argument #3 ($job) must be of type App\Domain\Job\Models\JobModel, array given, called in W:\Projects\portal-admin\api\vendor\slim\slim\Slim\Handlers\Strategies\RequestResponse.php on line 38

hmmmmmmmmmmm which got me digging… theres a RequestResponseNamedArgs strategy… let me try this


edit… php-di slim-bridge seems to be the answer

riiiiiiiight… so i was a total dumbass it seems.

for anyone else… USE THE BRIDGE (if using php-DI)

and my setup… (because knowledge is power right)

Application.php


$containerBuilder = new ContainerBuilder();

$containerBuilder->addDefinitions(__DIR__ . '/Container.php');
$containerBuilder->addDefinitions(__DIR__ . '/Config.php');

$containerBuilder->useAutowiring(true);
$containerBuilder->useAnnotations(false);

$container = $containerBuilder->build();

$app = \DI\Bridge\Slim\Bridge::create($container);

$container->set(ResponseFactoryInterface::class,$app->getResponseFactory());
$container->set(RouteParserInterface::class,$app->getRouteCollector()->getRouteParser());
$container->set(RouteCollectorInterface::class,$app->getRouteCollector());

in the past i had the

App::class => function(
        ContainerInterface $container
    ) {
        AppFactory::setContainer($container);

        return AppFactory::create();
    },

inside the container and in Application.php just $app = $container->get(App::class); but it didnt seem to wanna work… so now ive moved the entire “setup” of slim to the Application.php file and setting up the slim containers here to.

and thats pretty much it… you’re setup and ready for the awesome.

in the middleware

$request = $request->withAttribute("myvariable", new MyVariableObject());

and in the route’s controller

 public function __invoke(Request $request, Response $response, MyVariableObject $myvariable): Response {

somethings to note:

  • the $variable you use in the controller must match the withAttribute("variable"...
  • the typing part doesnt matter other than ide type hinting. so if you did $request = $request->withAttribute("strr", "abc"); you can do public function __invoke(Request $request, Response $response, $strr): Response {. in this case $strr would have the value of “abc”
  • this works with “parameters” as well. so $group->get("[/{job_id}]", Controllers\JobsController::class) and public function __invoke(Request $request, Response $response, $job_id): Response { would give you the @job_id in the $job_id variable

mr @reuben.helms you sir… deserve a beer!

@odan i think you were pointing me in this direction, but i failed to understand it. sorry :frowning:

and i know its probably obvious to others how to use the above and all… oops :frowning: thanks for the help!

Awesome.

The bridge is a part of the furniture for me. I didn’t consider that it might not be installed.