2

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?

tornellas
  • 21
  • 3

3 Answers3

3

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);
    }
}
BenMorel
  • 34,448
  • 50
  • 182
  • 322
xtreamwayz
  • 1,285
  • 8
  • 10
  • Assume the following: Middleware1 -> Middleware2 -> Middleware3 -> Middleware4 Let's say the Middleware 4 has some metadata to pass and the Middleware2 will use it. I can't see how to do this without "attributes". This would be the "best and cleaner" way, IMHO. Using "X-Header" is possible, but has drawbacks, like removing it along the opposite flow. Do you know what I mean? – tornellas Jul 18 '16 at 17:33
  • 1
    To pass data from middleware4 to middleware2 you need to change the load order and make sure middleware4 is loaded before middleware2. Or you can make an object available in the request with middleware2. Middleware4 gets that object from the request and sets the required data. Then when the response is on its way back the data is set/updated in middleware2 and it can modify the response accordingly. You can even update the data when the response is going out. – xtreamwayz Jul 19 '16 at 19:03
  • @tornellas Added an example on how to use an object to pass data around. – xtreamwayz Jul 19 '16 at 19:21
2

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.

marcosh
  • 8,780
  • 5
  • 44
  • 74
  • 1
    That is what I'm doing, but I think would be better if we have something like "attributes", so wouldn't be necessary remove those custom headers from response before sent it. – tornellas Jul 11 '16 at 13:01
0

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.

Björn Tantau
  • 1,564
  • 14
  • 13