PSR-7 "attributes" on Response object - php

I'm developing using PSR-7 (with Zend Expressive). I figured out the method
ServerRequestInterface::withAttribute()
and I was wondering why the object Response doesn't have one.
I'd like to pass metadata through middlewares after processing, on "response side".
Is there somehow to pass "attributes" on Response for post-processing? What's is the best way, following the architecture guidelines, to achieve that?

Best practise is using the request object to pass data between Middleware. The response is what is going out to the client and you want to keep this clean. The request lives only on the server and you can add (sensitive data) attributes to pass around. In case something goes wrong or you return a response early before removing the custom data, then it doesn't matter since your response is "clean".
Also if you need to pass data around: The Middleware is always executed in the order it gets from the config. This way you can make sure the request object in MiddlewareX contains the data set by MiddlewareY.
UPDATE: An example on how to pass data with the a request.
Middleware 2 sets an messenger object which Middleware 4 can use to set data which is required on the way out again.
<?php
namespace Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Middleware2
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$messenger = new Messenger();
// Do something else before next middleware
if ($next) {
$response = $next($request->withAttribute(Messenger::class, $messenger), $response);
}
// Do something with the Response after it got back
// At this point the $messenger object contains the updated data from Middleware4
return $response->withHeader('Content-Language', $locale);
}
}
Middleware 4 grabs the messenger object and updates its values.
<?php
namespace Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Middleware4
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$messenger = $request->getAttribute(Messenger::class);
$messenger->info('going in');
// Do something else before next middleware
if ($next) {
$response = $next($request->withAttribute(FlashMessenger::class, $messenger), $response);
}
// Do something with the Response after it got back
$messenger->info('going out');
return $response->withHeader('Content-Language', $locale);
}
}

The PSR-7 specification defines attributes only for server requests. They are mainly use to store metadata deduced from the incoming request so that they could be used later when you reach your domain layer.
On the other hand, a response is usually created in the domain layer and traverses back all the middleware stack before being actually sent to the client. So metadata added to a response would have no place where they could actually be used.
I guess that if you want to pass data from a inner middleware to an outer one, the best way is to use response headers.

Not sure if this is "best practice" but another possibility is to simply inject your data object into the middlewares.
Middleware 2 has a messenger object injected and sets some data on it:
<?php
namespace Middleware;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Middleware2
{
private $messenger;
public function __construct(Messenger $messenger)
{
$this->messenger = $messenger;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->messenger->foo = 'bar';
$response = $handler->handle($request);
if ($this->messenger->foo = 'baz') {
return $response->withHeader('Really-Important-Header', 'Baz');
}
return $response;
}
}
Middleware 4 changes the data:
<?php
namespace Middleware;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Middleware4
{
private $messenger;
public function __construct(Messenger $messenger)
{
$this->messenger = $messenger;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->messenger->foo = 'baz';
return $handler->handle($request);
}
}
You might even use one of the middlewares as the messenger.
Caveat: You have to make sure that both classes get constructed with the same messenger object. But that seems to be the case with most dependency injection containers.

Related

Extended Request missing data when reaching controller with type-hint

A lot of pieces to this so here's the meat. Code very slightly tweaked for brevity.
Extended class:
<?php
namespace App\Http;
use Illuminate\Http\Request as LaravelRequest;
class Request extends LaravelRequest
{
}
Middleware:
<?php
namespace App\Http\Middleware;
use App\Http\Request as CustomizedRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
protected $app;
protected $customizedRequest;
public function __construct(Application $app, CustomizedRequest $customizedRequest){
$this->app = $app;
$this->customizedRequest = $customizedRequest;
}
public function handle(Request $request, Closure $next){
$this->app->instance(
'request',
Request::createFrom($request, $this->customizedRequest);
);
return $next($this->customizedRequest);
}
}
Routes:
Route::get('/books1/{id}',[BookController::class, 'frontend1']);
Route::get('/books2/{id}',[BookController::class, 'frontend2']);
Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Book;
class BookController extends Controller
{
public function frontend1(\Illuminate\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
public function frontend2(\App\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
}
The /books1/5?foo=bar and frontend1() path works. $request is populated as expected.
The /books2/5?foo=bar and frontend2() path is broken. $request has vast amounts of missing data, like it was instantiated with nothing.
Evidently if I type-hint my subclass instead of the more generic parent, it's causing some kind of broken instantiation. From an OO perspective I think this should be perfectly fine and I do specifically need my subclass being provided so prefer that type-hint. Is something deep within Laravel tripping this up? Is this some obscure PHP behavior I haven't seen before?
This is kind of tricky.
First of all, you need to be familiar with the service container and dependency injection. Here is the full doc: https://laravel.com/docs/8.x/container
When you type hint a class inside a controller method, Laravel will try to understand what it should do with it.
If nothing is registered inside the service container, it will try to make a new instance of it.
\Illuminate\Http\Request is bound as a singleton (https://laravel.com/docs/8.x/container#binding-a-singleton).
While a simple bind will return a new instance at each call, a singleton will always return the exact same instance.
Here is a quick demo:
\App\Models\User::class is a class that is not explicitly bound.
When you try to resolve it using the service container, it will not find it and will try to make a new instance:
$u1 = app(\App\Models\User::class);
// Searching \App\Models\User::class...
// Cannot find \App\Models\User::class...
// returning new \App\Models\User();
$u2 = app(\App\Models\User::class);
// same process again
$u3 = app(\App\Models\User::class);
// and again
// You can check these instances are indeed different by checking their hash:
dd(
spl_object_hash($u1), // 000000004af5213500000000220f0bc0 (52135)
spl_object_hash($u2), // 000000004af5213400000000220f0bc0 (52134)
spl_object_hash($u3) // 000000004af5213700000000220f0bc0 (52137)
);
But since \Illuminate\Http\Request::class is bound by Laravel, it follows a different path:
$r1 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it! Bound as a singleton.
// returning new \Illuminate\Http\Request() and storing the
// instance in case it is required again later;
$r2 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
$r3 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
// Their hash are the same
dd(
spl_object_hash($u1), // 0000000011f522cf0000000077704cd1
spl_object_hash($u2), // 0000000011f522cf0000000077704cd1
spl_object_hash($u3) // 0000000011f522cf0000000077704cd1
);
Now, what's happening?
Under the hood, when a new request is made to your app and before hitting the controller method, Laravel will do a lot of things to prepare the \Illuminate\Http\Request instance.
For instance, it will setup the route resolver inside Illuminate\Routing\Router:
/**
* Return the response for the given route.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Routing\Route $route
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function runRoute(Request $request, Route $route)
{
// here
$request->setRouteResolver(function () use ($route) {
return $route;
});
//
$this->events->dispatch(new RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
Each time Laravel internally call a method like this:
protected function method(Request $request){
// do something to $request
}
$request is always the same instance, because it is bound as a singleton.
We are now in your controller.
public function frontend1(\Illuminate\Http\Request $request){
// Searching \Illuminate\Http\Request::class...
// Found it and already called!
// Returning the stored instance that has been prepared through all
// Laravel core classes
dump($request);
dump($request->all()); //well prepared
dump($request->route('id')); //well setup
return Book::all();
}
public function frontend2(\App\Http\Request $request){
// Searching \App\Http\Request::class...
// Cannot find \App\Http\Request::class...
// returning new \App\Http\Request();
dump($request);
dump($request->all()); //nothing
dump($request->route('id')); //empty
return Book::all();
}
If you are still here, how to solve this problem?
The easiest way is to use a FormRequest, initially designed to handle form validation, but if you return an empty rules array, you should be able to do everything you did with your custom \App\Http\Request instance:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function rules()
{
return [];
}
}
Try again, everything should work fine, since this is a feature specially designed to replace the initial \Illuminate\Http\Request object.
The full doc is here: https://laravel.com/docs/8.x/validation#creating-form-requests

Slim 3 framework - Passing data from middleware to controller - action args

I'm using a controller/middleware build with slim 3 and i want from the middleware attached to a group, to pass some data to the $args parameter in my controller - action.
Here's some code:
class MyController
{
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function index(Request $request, Response $response, $args) {
return $this->container->get('renderer')->render($response, "index.html.twig", $args);
}
}
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
// do some stuff to inject further down to $args some data
return $next($request, $response);
}
}
$app->group('/group', function () use ($app){
//routes
})->add(new MyMiddleware());
My use case is to send stuff to all the views rendered by the actions of these controllers, so i'm also fine with other ways to do this :)
Thanks.
so you need just pass data from Middleware to Controller
what about
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
$request = $request->withAttribute('myMagicArgument', 42);
return $next($request, $response);
}
}
and then in controller
class MyController
{
//...
public function index(Request $request, Response $response) {
$yourAttributeFromMiddleware = $request->getAttribute('myMagicArgument');
//...
}
}
For completeness, I'm going to extend the excellent answer given #jDolba
Unfortunately, though it got me going in the right direction, it still took a little experimentation to get everything working.
Basically, as explained in the slim router docs
The route callback signature is determined by a route strategy. By
default, Slim expects route callbacks to accept the request, response,
and an array of route placeholder arguments. This is called the
RequestResponse strategy. However, you can change the expected route
callback signature by simply using a different strategy. As an
example, Slim provides an alternative strategy called
RequestResponseArgs that accepts request and response, plus each route
placeholder as a separate argument. Here is an example of using this
alternative strategy; simply replace the foundHandler dependency
provided by the default \Slim\Container:
$c = new \Slim\Container();
$c['foundHandler'] = function() {
return new \Slim\Handlers\Strategies\RequestResponseArgs();
};
$app = new \Slim\App($c);
$app->get('/hello/{name}', function ($request, $response, $name) {
return $response->write($name);
});
You can provide your own route strategy by implementing the
\Slim\Interfaces\InvocationStrategyInterface.
however, for the task of injecting some standardised data into the $args[] array, the default \Slim\Handlers\Strategies\RequestResponse class does everything it needs to minus injecting the data.
As such, I simply extended that class:
<?php
namespace MyProject\Handlers\Strategies;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use \Slim\Handlers\Strategies\RequestResponse;
class SomeNewInvocationStrategy extends RequestResponse
{
/**
* 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 $routeArguments The route's placholder arguments
*
* #return ResponseInterface|string The response from the callable.
*/
public function __invoke( callable $callable, ServerRequestInterface $request, ResponseInterface $response, array $routeArguments)
{
$routeArguments['test'] = 'testing testing 123';
return parent::__invoke( $callable, $request, $response, $routeArguments );
}
}
My container declaration looks like this:
<?php
use Slim\App;
return function (App $app) {
$container = $app->getContainer();
$container['foundHandler'] = function() {
return new MyProject\Handlers\Strategies\SomeNewInvocationStrategy();
};
}
Then in all of my controller actions I have access to $args['test']. Further, this can be passed straight through to any Twig views.
This is useful for tasks like access control where by I always want to load the roles of users before processing the request but I'm sure there'll be many other use-cases for it.
I Hope this helps somebody.

How to get the base url Programatically in zend expressive?

I am working on an API application that will be run in different domains: http://example.com/, http://sub.example.com/, http://example-another.com/. Part of the API responses needs to send out its base_url. So I am trying to find a way to dynamically collect the base_url and add it to my response.
I have a factory to initiate the action handler as follows:
class TestHandlerFactory
{
public function __invoke(ContainerInterface $container) : TestHandler
{
return new TestHandler();
}
}
Then my action handler is as follows:
class TestHandler implements RequestHandlerInterface
{
public function __construct()
{
...
}
public function handle(ServerRequestInterface $request) : ResponseInterface
{
...
}
}
I am new to Zend world and I found the https://github.com/zendframework/zend-http/blob/master/src/PhpEnvironment/Request.php probably a potential solution of my problem. However, I do not how to get that PHP-Environment object (or any other object that help me grab the base url) in the factory or handler class.
zend-http is not used in expressive, it's for zend-mvc. In expressive PSR-7 HTTP message interfaces are used and by default this is handled in zend-diactoros.
class TestHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request) : ResponseInterface
{
// Get request URI
$uri = $request->getUri();
// Reconstruct the part you need
$baseUrl = sprintf('%s://%s', $uri->getScheme(), $uri->getAuthority());
}
}
More info can be found here: https://github.com/zendframework/zend-diactoros/blob/master/src/Uri.php
EDIT: You cannot grab the request details in the factory itself. This can only be done in Middleware or Handlers (which are sort of a middleware).

Slim Controller Request, response and args not available

What I need is : Use a custom class to receive a HTTP Request and deal with it.
What I have so far:
$app->group('/user', function () use ($app) {
$app->post('/login', Auth::class . ':login');
}
And in my Auth Class:
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
// also tried: use Slim\Http\Request; use Slim\Http\Response;
class Auth {
protected $container;
public function __construct(&$c) {
$this->container = $c;
}
public function login(Request $request, Response $response, $args) {
// NEED TO GET GET/POST/PUT Params here
// When I Try to print $request, $response or $args, it returns empty
}
My code shows in the function login what I need : get the http params, and the request/response/args params. ( I want to implement many functions in one class, I mean, I don't want to use __invoke())
Another thing I don't understand is, if I do something like
return $response->withJson($somearraydata,200);
the variable $response actually works. Why?
Thanks for any help!
I think I have figured it out,
$args are never set (unless it is a get),but request and response are.
and, to get params, I could do:
$request->getParsedBody()['attribute']
Hope this helps someone. :)

Ho to Implement Slim like middleware mechanism

I decided to implement my own small framework to implement such stuff like dependency injection etc.
Now I'm stucking at my middleware implementation. I can add middleware to a route but I im wondering how slim loops through the attached middleware.
I'd like to do it the slim way, so in every middleware I can return a request or a response or the next middleware. But how do I have too iterate over my attached middleware.
Here is my stack I want to proceed
class MiddlewareStack
{
private $stack;
public function addMiddleware(Middleware $middleware)
{
$this->stack[] = $middleware;
}
public function processMiddleware(Request $request, Response $response)
{
}
}
and thats the middleware interface
public function __invoke(Request $request, Response $response, $next);
I want to
return $next($request,$response);
in my middleware classes or just a response or a request.
Here's how to create middlware callable in slim.
http://www.slimframework.com/docs/concepts/middleware.html#invokable-class-middleware-example
Slim 3 first adds itself to the stack which is the Slim\App#__invoke() which executes the route.
Then, when you add a middleware it does the following: (before this slim wrapps the callable (annonymous function/invokable class) inside a DeferredCallable which helps to execute both the function and class equally (See Slim\App#add()).
protected function addMiddleware(callable $callable) // $callable is a DeferredCallable
{
$next = $this->stack->top(); // when it the first middleware this would be the route execution
$this->stack[] = function (ServerRequestInterface $req, ResponseInterface $res) use ($callable, $next) {
$result = call_user_func($callable, $req, $res, $next);
return $result;
};
}
(This is only the simple code, for full code see: Slim\MiddlewareAwareTrait#addMiddleware())
So the middleware which is on top of the stack executes the other middleware as well, because it is provided in the next method.
Then when you want to execute the middleware, get the middleware which is on top of the stack and execute it.
$start = $this->stack->top();
$resp = $start($req, $res);
// $resp is now the final response.
(see Slim\MiddlewareAwareTrait#callMiddlewareStack())

Categories