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.
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”.
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)
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)
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.
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
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);
}
}
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.
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)
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 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)));
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..
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.
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)