PSR-7 “attributes” on Response object

2019-06-08 02:58发布

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?

3条回答
Explosion°爆炸
2楼-- · 2019-06-08 03:29

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 messanger 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);
    }
}
查看更多
beautiful°
3楼-- · 2019-06-08 03:38

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.

查看更多
霸刀☆藐视天下
4楼-- · 2019-06-08 03:47

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.

查看更多
登录 后发表回答