Slim 4 - Custom Request (extending the existing Request)

to keep the system as vanilla as possible to allow upgrading etc, is it possible to “extend” the current request object?

2 days later and so far the only thing that comes close would be to duplicate the internals (How to overwrite request in SLIM 4 - #5 by andrei.neneve) and add my single method i want to add to the request object to it and then break with every update type thing.

class CustomServerRequestFactory implements ServerRequestFactoryInterface {
public function __construct(?StreamFactoryInterface $streamFactory = null, ?UriFactoryInterface $uriFactory = null)
    {
        if (!isset($streamFactory)) {
            $streamFactory = new StreamFactory();
        }

        if (!isset($uriFactory)) {
            $uriFactory = new UriFactory();
        }

        $this->streamFactory = $streamFactory;
        $this->uriFactory = $uriFactory;
    }
 public static function createFromGlobals(): Request
    {
        $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
        $uri = (new UriFactory())->createFromGlobals($_SERVER);

        $headers = Headers::createFromGlobals();
        $cookies = Cookies::parseHeader($headers->getHeader('Cookie', []));

        // Cache the php://input stream as it cannot be re-read
        $cacheResource = fopen('php://temp', 'wb+');
        $cache = $cacheResource ? new Stream($cacheResource) : null;

        $body = (new StreamFactory())->createStreamFromFile('php://input', 'r', $cache);
        $uploadedFiles = UploadedFile::createFromGlobals($_SERVER);

        $request = new Request($method, $uri, $headers, $cookies, $_SERVER, $body, $uploadedFiles);
        $contentTypes = $request->getHeader('Content-Type') ?? [];

        $parsedContentType = '';
        foreach ($contentTypes as $contentType) {
            $fragments = explode(';', $contentType);
            $parsedContentType = current($fragments);
        }

        $contentTypesWithParsedBodies = ['application/x-www-form-urlencoded', 'multipart/form-data'];
        if ($method === 'POST' && in_array($parsedContentType, $contentTypesWithParsedBodies)) {
            return $request->withParsedBody($_POST);
        }

        return $request;
    }
}

just to be able to define a custom Request object that extends the base Request object. (new Request → new CustomRequest

im aware the options might be to add a setAttributes() with a closure but then its not accessible in my “out” middleware (cause response->getAttribute() isnt a thing).

so options:

  1. define a CustomRequestFactory that calls my CustomRequest that extends the Request. (with all the above post’s stuff)
  2. inject a dependency object to handle it for me

any other options?

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

The question is why do you need such a special object? Maybe there is another (better) option.

well i guess there is always a “better” way but its always going to involve more code or complicated work around for something i think should be rather simple.

for something like slim it makes sense to have an open system to allow devs to use whatever they prefer. but in my world there are 2 kinds of devs, those that just re use other libraries the whole time and those that dev themselves. (this opens a whole new debate about dont re invent the wheel, but if the wheel is a tractor tyre and you need a bicycle tyre… is it still a good idea to use the tractor tyre? and ive been burnt hard in the past by “dependencies” that i prefer keeping them to a bare minimum now in anycase)

im almost never going to replace my database / response / request stuff with externals unless theres an update or some such. in which case more would be “broken”.

Response

for my controllers i want to simply return the response like so

return $response->render("home.twig", $data); 

so im using a custom response object to set the template and data accordingly.

in a middleware i have a profiler that needs to be able to inject itself into the body as late as possible.
my responses can vary between a html rendered page or a json response. (just ?json=1 or call the page via ajax and its a json response)

i have it working at least. the current flow is return as per above, it sets a template and data attribute onto the response (and flag that the ->render method was used). i then have an output middleware that then checks if the flag is set it then outputs the response in whichever format i need it in (since template and data are still separate - and injects the profiler) this way i dont need the “output format” logic in the controllers to set the response body. it can be handled later on in the middleware, this includes errors etc.

class ResponseFactory implements ResponseFactoryInterface { and creates my custom Response (thats less than 20 lines long) and bingo. im happy.

Request

whilest you could do stuff like $id = isset($_GET['id'])? $_GET['id'] : null; or $id = isset($request->getQueryParams()['id'])? $request->getQueryParams()['id'] : null i really want to implement a $id = $request->get("GET.id"); method on the request.

i already have an object in the container that works for this use case so what im trying to do is ditch the reliance on having to inject it into every where im going to need it (which is pretty much everywhere theres a Request) so my hope here is to be able to add a get method onto the request that proxies to my getter object. (my getter object does more than just GET.id tho)

so my current “working” way is to set a container with my “System” object and then inject it into all my controllers (di-bridge) and then to $this->system->get(“GET.id”) or $this->system->get(“POST.username”) or $this->system->get(“CONFIG.storage.temp”) or $this->system->get(“PARAMS.id”) etc

(i know you gonna frown at this approach)

i JUST thought of another way… theres a routing strategy thing (i think thats what its called) that i read about. i could probably define my own strategy that injects “my” request into the route instead.

since i cant really use attributes on the request (since they dont bubble on to the out on the onion) the $request in my routes is rather “useless” (as in i cant really see any use case for it in my routes, i can just as easily replace the $request in the route callback with my getter thing)

When you have shard logic for the Response, e.g. rendering a template, a redirect, or creating a JSON response, you could try to use a Responder in your Action handler classes.

I don’t share your concern according to the Request query parameters etc… because I don’t see any issue/problem to solve here. For me, it’s more important to use a real standard interface then inventing my own “interface” for such a trivial thing. The configuration is also a completely other concern, and should also not be mixed together with the request etc… “Separation of concerns” is the key.

lol yeah. my “problems” im having at the moment stem from me being in the process of switching frameworks. i suspect somone thats been using a framework for a long time wouldnt have the “hmm i really like doing it that way” or the “i need x, how the hell do i do it in this framework”. me having gotten this far with my skeleton is at least a week of grind. sure i could just use an existing skeleton but i still need to be comfortable enough with the framework that i can “use it”.

wouldnt using a responder mean something like return (new Responder($request,$response))->setTemplate("....) ? as in i would have to pass the $response object to it from inside my route.

im just trying to get a “cleaner” working interface for the code than having a ton of stuff that has to be included just to get the system to work. you set middleware / skeleton etc once, but you work with the “app” part of the system many times a day.

sorry for my tone. im just frustrated.

We currently have something similar where we have a custom request handler that extends the slim/psr7 request so that we can have a function that has access to the request so $_POST values obtained via getQueryParams can be purified before used in code.

It has worked great and we import that class with " as Request" wherever we need it in controllers etc.

The issue we’ve run into recently, is that I am wanting to switch to nyholm/psr7 which can’t extend because it’s “final”

I thought using the RouteStrategy might be a solution. but its gona all ford on us “You can have the car in any colour as long as its black” Routing - Slim Framework routing strategy must be an interface that has the same pattern (request, response, args)

You can provide your own route strategy by implementing the Slim\Interfaces\InvocationStrategyInterface.
interface InvocationStrategyInterface
{
    /**
     * Invoke a route callable.
     *
     * @param callable               $callable       The callable to invoke using the strategy.
     * @param ServerRequestInterface $request        The request object.
     * @param ResponseInterface      $response       The response object.
     * @param array<mixed>           $routeArguments The route's placeholder arguments
     *
     * @return ResponseInterface The response from the callable.
     */
    public function __invoke(
        callable $callable,
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $routeArguments
    ): ResponseInterface;
}

Im not too sure how you could use that to supply your own strategy when all 3 the options are pretty much covered. args from params, args from attributes, args as an array/.

What benefits do you get for switching to nyholm/psr7? (Im interested since ive been told “you loose the benefit of psr7 if you customize anything” but to me if the psr follows the psr it should pretty much “do the same thing”. Unless each library then adds something like in my case setup a whole psr7 library to be able to inject the ->get() thing ive spoken about. in which case the first time you use that ->get() in code makes it that you “loose the benefits of psr7” so a catch 22. Or am i totally confusing things here?

the more i think about your use case the more i think this “could” be handled via a middleware and inject an attribute into the normal request

something along the lines of:
Middleware whatevers

public function __invoke(Request $request, RequestHandler $handler): Response {
        $post = array();
        foreach ((array)$request->getParsedBody() as $key=>$value){
            $post[$key]=funky_moneky_dance_santize($value);
        }
        $request->withAttribute("post",$post);
        return $handler->handle($request);
    }

or just return the post as an argument but that could get a bit confusing if you have a get and a post that are the same type thing hence why im separating them out into their own here post “namespace”.

then just in your route

 $request->getAttribute('post')['moonshine'] ?? null;

this way you dont need to extend any existing files etc.


edit: adding my motivation for the question in OP to its own post just to get my post count up (more like cause this post is about answering @dunkoh’s post whereas mine is more a general at the thread post)

the “why” im trying to do the above:

im trying to make it “cleaner” for myself that i dont need to do $request->getAttribute('post')['moonshine'] ?? null; i can simply do $request->get("POST.moonshine"); (a single "data fetcher entry point that i can make sure all inputs are santized and that if followed we wont ever have to worry about errors cause key doesnt exist (cause forgot the ?? part) and i can then “extend” it to return more stuff as well (like config values as bad as that may be) right now my intended data points:

  • GET
  • POST
  • SYSTEM (version / package)
  • SERVER (some values)
  • CONFIG

at the moment with the current tools unless above middleware approach is used, and “forced”, im pretty sure a quick $id = $request->getQueryParams()[‘id’] might slip through and thats not “sanitized” etc.

reason i really want ->get()… is causer its shorter and easier, so path of least resistance when coding, i feel because its easier i or any dev in the co using the skeleton wouldnt need to be sent warning letters if they dont use $id = (new sanitizer($_GET['id']))->clean()->toString(); they could simply say $id = $request->get("GET.id"); which is the path of least resistance therfore more likely to be used. and since it “makes sense” that its linked to the $request part since “i hereby request you perform this action, here is the information you require to perform your duties” makes sense to me.

im somewhat debating if setting up “attributes” like the above middleware for the post / get / system etc might not work as a simple object/array that i can do something like (this eg uses array)

__invoke($request,$response,$post,$get....){
    $moonshine = $post["moonshine];

but this again goes into the path of least resistance for coding,. easier to setup, but not as easy to use. and since you setup a skeleton once, and use the code a million times. makes sense to spend time on the skeleton to make the coding easier. i really hope this motivation is sufficient.

Not sure if you might fine this helpful
Custom request

on the face value that seems exactly what i need “perhaps to add helpful methods that keep our controllers clean”

but the good bits are locked behind a $12 per month paywall tho :frowning:


so what ive done to get i working (but this feels more like a total hack - and very dirty - than anythign else)

i duplicated the ServerRequestFactory.php from slim. slim\psr7\Factory\ServerRequestFactory.php and then just changed where its pointing the Request (my own) to and implemented my own Request that extends the slim Request (but as was pointed out, if the request is final this will break)

use Psr\Http\Message\ServerRequestInterface;
use Slim\Psr7\Request as BaseRequest;
class Request extends BaseRequest implements ServerRequestInterface {
    private $system_object;
    function setSystem($system){
        $this->system_object = $system;
    }
    function get($key){
        return $this->system_object->get($key);
    }
}

and in Application.php (the main index thing)

...
$request = ServerRequestFactory::createFromGlobals();
$request->setSystem($container->get(System::class));

$app->run($request);

My preferred way to add methods to a PSR-7 object is to create a decorator that implements the correct interface. An example for PSR-7 is Slim\Http which decorates a set of PSR-7 objects with methods familiar to Slim 3 users.

This pattern also allows you to swap out the PSR7 implementation much more easily for when you change it as @dunkoh is doing.

2 Likes

im actualy looking at this approach right now. in python decorators are mainstream, but ive never really used them or even thought about how its even possible to use them in php lol…

any chance you can give me a hint of how to (or where to)? as in at the moment im looking at setting up a routing strategy (so that i actually have a $request to work with) and then decorate that. or do should i go right to the heart and do it with $app->run()? (i know both “might” be possible - are there other options? - but which would you suggest?


edit: am i being thick with decorators in this case. blanking out hard

edit 2: im guessing this is like how the response factory thing works (is this even “correct”?)

class ResponseFactory implements ResponseFactoryInterface {
    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface {
        return (new CustomResponse())->withStatus($code, $reasonPhrase);
    }
}
$container->set(ResponseFactoryInterface::class, function () {
    return new ResponseFactory();
});

so im thinking

class RequestFactory implements ServerRequestFactoryInterface {
    protected $serverRequestFactory;
    public function __construct(ServerRequestFactoryInterface $serverRequestFactory) {
        $this->serverRequestFactory = $serverRequestFactory;
    }
    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface {
        $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $serverParams);
        return new CustomRequest($serverRequest);
    }
}

$app->run(new RequestFactory(??????));

but im stuck again with what to pass into it ServerRequestFactoryInterface $serverRequestFactor ( the ??? part)

please can you help me? :frowning:

slim-http isnt helping me much cause i can understand some of what its doing but i cant figure out where it even goes.

next possible working solution: (thanks to @FvsJson’s link)

composer require slim/http

create the Request,php file you want

notice where use Slim\Http\ServerRequest; points to


namespace System\Core;

use Slim\Http\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;

class Request extends ServerRequest {
   function get($key){
        return $key;
    }
}

and then set a custom InvocationStrategy in your main Application / Index page


class SystemRequestResponseArgs implements InvocationStrategyInterface {
    public function __invoke(
        callable $callable,
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $routeArguments
    ): ResponseInterface {
        return $callable(new Request($request), $response, ...array_values($routeArguments));
    }
}

new Request($request) here is the key bit. Request here is my custom Request class.

still trying to figure out how to get an object from the container in here tho…

this works by using slim-http which helps decorating the functions. as in slim-http is doing the duplication of methods for you.

im not sure if “use slim-http” was what @akrabat was suggesting or merely to duplicate how slim-http is doing it.

my dream solution however wouldn’t involve bringing in another package (dependency) that could at some point go out of sync. so im going back to pulling my hair out on how to do this with as little lines of code, best practices, and no “added” dependencies.

I would use it for inspiration to create your own classes.

i cant find a way to get past needing to implement the 30 something methods using this way tho.

been trying the whole __call() way but that doesnt return an object of type ServerRequestInterface and since the object must be of the type… it looks like the only way is to add the 30 odd methods in and all of them are simply function method_name(){ return call_user_func_array(array($this->request, $method), $args); type thing

is my use case (to extend the Request for use in routes) such an edge case?

im also not sure if setting an InvocationStrategy will let me reference the container? edit(well guess this is an easy answer - $routeCollector->setDefaultInvocationStrategy(new SystemRequestResponseArgs($container->get(xxx)));)

using slim-http for “inspiration” i get this. but it doesnt seem right to me?

Request.php (custom Request)

<?php

namespace System\Core;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use Slim\Http\ServerRequest;

class Request implements ServerRequestInterface {
    private $request;
    private $system;
    function __construct($request){
        $this->request = $request;
    }

    // my methods
    function setSystem($system){
        $this->system = $system;
        return $this;
    }
    function get($key){
        return $this->system->get($key);
    }

    // in-case the interface misses anything
    public function __call($method, $args) {
        if (!method_exists($this->request, $method)) {
            throw new \Exception("Undefined method $method");
        }

        return call_user_func_array(array($this->request, $method), $args);
    }

    // interface methods

    public function getServerParams() {
        return $this->request->getServerParams();
    }

    public function getCookieParams() {
        return $this->request->getCookieParams();
    }

    public function withCookieParams(array $cookies) {
        return $this->request->withCookieParams($cookies);
    }

    public function getQueryParams() {
        return $this->request->getQueryParams();
    }

    public function withQueryParams(array $query) {
        return $this->request->withQueryParams($query);
    }

    public function getUploadedFiles() {
        return $this->request->getUploadedFiles();
    }

    public function withUploadedFiles(array $uploadedFiles) {
        return $this->request->withUploadedFiles($uploadedFiles);
    }

    public function getParsedBody() {
        return $this->request->getParsedBody();
    }

    public function withParsedBody($data) {
        return $this->request->withParsedBody($data);
    }

    public function getAttributes() {
        return $this->request->getAttributes();
    }

    public function getAttribute($name, $default = null) {
        return $this->request->getAttribute($name,$default);
    }

    public function withAttribute($name, $value) {
        return $this->request->withAttribute($name, $value);
    }

    public function withoutAttribute($name) {
        return $this->request->withoutAttribute($name);
    }

    public function getProtocolVersion() {
        return $this->request->getProtocolVersion();
    }

    public function withProtocolVersion($version) {
        return $this->request->withProtocolVersion($version);
    }

    public function getHeaders() {
        return $this->request->getHeaders();
    }

    public function hasHeader($name) {
        return $this->request->hasHeader($name);
    }

    public function getHeader($name) {
        return $this->request->getHeader($name);
    }

    public function getHeaderLine($name) {
        return $this->request->getHeaderLine($name);
    }

    public function withHeader($name, $value) {
        return $this->request->withHeader($name, $value);
    }

    public function withAddedHeader($name, $value) {
        return $this->request->withAddedHeader($name, $value);
    }

    public function withoutHeader($name) {
        return $this->request->withoutHeader($name);
    }

    public function getBody() {
        return $this->request->getBody();
    }

    public function withBody(StreamInterface $body) {
        return $this->request->withBody($body);
    }

    public function getRequestTarget() {
        return $this->request->getRequestTarget();
    }

    public function withRequestTarget($requestTarget) {
        return $this->request->withRequestTarget($requestTarget);
    }

    public function getMethod() {
        return $this->request->getMethod();
    }

    public function withMethod($method) {
        return $this->request->withMethod($method);
    }

    public function getUri() {
        return $this->request->getUri();
    }

    public function withUri(UriInterface $uri, $preserveHost = false) {
        return $this->request->withUri($uri,$preserveHost);
    }
}

and Application.php

class SystemRequestResponseArgs implements InvocationStrategyInterface {
    protected $system;
    function __construct($system){
        $this->system = $system; // i need the System object from container in my method in Request
    }
    public function __invoke(
        callable $callable,
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $routeArguments
    ): ResponseInterface {
        $custom_request = (new Request($request))->setSystem($this->system);
        return $callable($custom_request, $response, ...array_values($routeArguments));
    }
}
$routeCollector = $app->getRouteCollector();
$routeCollector->setDefaultInvocationStrategy(new SystemRequestResponseArgs($container->get(System::class)));