Slim 4 - Custom Request (extending the existing Request)

Busy trying this approach out as i wanted a way to “trigger” an error so wanted to do it on the $response (but trying to take in as much as you guys have said as possible). So now with the responder pattern for response instead of decorator as per akrabat’s suggestion.

Dow does everyone solve the “different output types” thing? As in “if the query string has a ?format=json” then output the content as json (just the “data”). How i did it previously was to setup a custom method(s) on the response object to set a template and data on its object then with a middleware. If those are set to work out what the desired output should be then output it as that format. (data and template are still separate so this is possible, the middleware then sets the body on the response).

With responder that moves into the responder object. but the responder needs access to the request to be able to decide what it needs to output. so either its a

// using container di stuff
return $this->responder($request,$response,template,data);

or its a

$responder = new Responder($request,$response);

return $responder->render(template,data)

The 2nd option seems a bit more preferential except when it comes to adding in container deps like loggers, templater etc in which case the constructor might get a bit big.

motivation:
My application front ends use the same routes as the ajax routes. So if you for instance go to /page in a normal window it will render the pages data and template together and hand you the content as html. If you click an ajax button in the page to “reload content” then it will hit the page with a XMLHttpRequest in which case it outputs just the pages data as json. (You can also override the behavior by specifying type - defaults to html). - Im very reluctant to have separate routes for json (ajax) vs html.

Part of the issue i face is that this needs to work for errors as well. So the error handler will basically set a template (if the error is in html) and data array for json. So the new Responder() way seems to be a viable option since i can easily implement that, but the error handler doesnt have a request / response as the routes have.

So i can either “decide” on content type in the route handler, or in a middleware. The middleware way seems preferential to me but my route still needs to return a ResponseInterface object (i could duplicate the methods and return a ResponseInterface type with my responder).

opdan’s responder passes the $response object into the ->withTemplate(response,template,data) method, but if for instance i wanted to “respond with an error” as in ->error(404,“page cant be found”), using slims internal throw new \Slim\Exception\HttpNotFoundException($request, "page cant be found"); requires the $request. Or if i need to interrogate the request to figure out in what format i need to pass the data back as i’ll need the request.

Im sure others have elegant solutions to these problems im facing. Care to share?

Generally, I would NOT recommend extending the Request (and Response) object because you will lose all the advantages of the PSR-7 standard interfaces..

Edit: I think i found a post that sort of explains how to do it but i could use some help there trying to figure out my usecase: Type hinting $request->getAttribute("woof") / data from middleware - #10 by reuben.helms

This is an old topic, but I am also trying to implement a custom request. I want autocomplete in my IDE for the data that is being sent with the specific request. So I wlll have a LoginUserRequest, SaveInvoiceRequest, etc.

All examples simplified ofcourse.

I want my controller to look something like this:

public function __invoke(
    LoginUserRequest $request, // instead of ServerRequestInterface
    ResponseInterface $response
): ResponseInterface {

    $user = ($this->LoginUserAction)($request);

    return $response->withJson([
            'success' => true,
            'data' => [
                'token' => $token,
            ],
        ]);
}

and my LoginUserAction something like this:

    public function __invoke(LoginUserRequest $data): User
    {
        return $this->userModel->findByEmail($loginData->email);
    }

But how do i go about creating that custom request class? Is there any way in Slim? I tried finding a solution but havent found any yet.

And the custom LoginUserRequest something like this but i have no idea what to extend, implement or use to get there:

class LoginUserRequest
{
    public function __construct(
        public string $email,
        public string $password,
    ) {}

    public function __invoke($request) {
        $parsedBody = $request->getParsedBody();

        $this->email = $parsedBody['email'];

        $this->password = $parsedBody['password'];

        return $this;
    }
}

messing with the request object (or response) is a recipe for a HUGE headache later on. its not the big things that get you…

suggest you leave those alone. move the user to the container since it will be used A LOT. then each route can use the __constructor to retrieve it

container

User::class = > function(){
    return new User()
}

application middleware

UserMiddleware {
    public function __construct(
        protected User $user
    ) {}
   public function __invoke(
        Request $request,
        RequestHandler $handler
    ): Response {
    // if your container doesnt pick up the user and you need to late bind it in then 
    // update the containers user object
    $user = $this->user->update();

   // incase you really really want to ise it as $request->getAttribute("user) or public function __invoke($request, $response, $user) {


   $request->setAttribute("user",$user);

   return $handler->handle($request);

}

you might find this useful. i have an InputsMiddleware



final class InputsMiddleware {
    
    public function __construct(
        protected Config $config,
    ) {}
    
    public function __invoke(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $routeContext = RouteContext::fromRequest($request);
        $route = $routeContext->getRoute();
        
        
        // TODO: sanitize the inputs
        foreach ( (array)$request->getQueryParams() as $key => $value ) {
            $request = $request->withAttribute("GET." . $key, $value);
        }
        foreach ( (array)$request->getParsedBody() as $key => $value ) {
            $request = $request->withAttribute("POST." . $key, $value);
        }
        
        foreach ( (array)$route->getArguments() as $key => $value ) {
            $request = $request->withAttribute("PARAM." . $key, $value);
        }
        
        return $handler->handle($request);
    }
}

this way when i get to the controller its a case of
https://something/page?id=123 -
$id = $request->getAttribute("GET.id"); // 123

same for post
$posted_name= $request->getAttribute("POST.name");

but also the route parameters
$group->group("/{ACCOUNT}",
$account_param= $request->getAttribute("PARAM.ACCOUNT");
(i name my route parameters in caps so that it doesnt mess with my set slim bridge ->setAttributes in my middleware)

1 Like

GitHub - slimphp/Slim-Http: A set of PSR-7 object decorators providing useful convenience methods the decorator is probs a better option for it