Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DISCUSSION: Replace ActionResponse with ResponseInterface? #3289

Open
mhsdesign opened this issue Jan 29, 2024 · 13 comments
Open

DISCUSSION: Replace ActionResponse with ResponseInterface? #3289

mhsdesign opened this issue Jan 29, 2024 · 13 comments

Comments

@mhsdesign
Copy link
Member

mhsdesign commented Jan 29, 2024

while working on #3232 and #2492

I stumbled over the fact that the whole ActionResponse abstraction is "broken".

In #3294 i experimented with returning a psr response directly from a controller:

public function processRequest(ActionRequest $request): ResponseInterface;
@mhsdesign
Copy link
Member Author

christian wrote

I am personally not totally opposed to this, BUT it was always meant to be a level of separation just like the ActionRequest is. Granted I see way less reason for the response to be that, especially the way it exists here, but I am sure this will be in question.

@mhsdesign
Copy link
Member Author

Yes i know. I also thought a bit about this before i wanted to experiment with this.

The way i see it is that the current ActionResponse is since flow 7 broken, as it allows to "replace" the http reponse. This behaviour is in many cases currently undefined and behaves unexpectedly: #2492

But even before Flow 7 and prs 7, the ActionResponse was at least partially inconsistent and the abstraction broken.
The response had these properties:

  • content StreamInterface
  • redirectUri UriInterface
  • statusCode int
  • contentType string
  • cookies Cookie[]
  • headers array<string, array>

the first 5 are a well fit abstraction, but the headers are an escape hatch to the lower level with possibly undeclared behaviour:

What if i actually set cookies AND a Set-Cookie header? Will getCookie understand this? What if i set a custom Location header AND redirect uri, what will win and what will getHeader return? And most importantly contentType vs Content-Type.

After reading the code, i can say that the "headers" functionality is just an array carried around with no validation or transformation. So that will cause conflicts one would ran into with duplicated headers set on the final result.

Additionally the mutability of the ActionResponse is completely unpredictable to work with. We might offer this as a legacy behaviour in the action controller, but we should prevent leaking this mutable action response anywhere far outside.

The ViewInterface must support the usecase to render a psr message (see fusion view) and it doesnt really make sense to transform a lower level abstraction to a higher one as this might not always even be possible and features might not be supported.

in our case transforming the ResponseInterface to an actual ActionResponse is actually possible (without using the httpRequest hack) as the action response allows the lower level headers feature already so that could be a way #3287

but im clearly in favour of working with the pretty immutable ResponseInterface directly.

For the ActionRequest it seems to be a different case, as we need the additional controller information and subrequest details.

@mhsdesign
Copy link
Member Author

christian wrote

For the ActionRequest it seems to be a different case, as we need the additional controller information and subrequest details.

I did actually think about replacing it with \Psr\Http\Message\ServerRequestInterface::withAttribute at some point, there is still some stuff to be considered, especially nesting behavior for subrequests (that I think we need for some functionality) but in general we could store our special stuff in custom attributes.

@mhsdesign
Copy link
Member Author

mhsdesign commented Jan 29, 2024

@bwaidelich mentioned once:

I remember that this kind of logic holes were the reason why I was in favor of a response abstraction that is not so closely related to the HTTP response.

but im not sure that we can ever found a well fit response abstraction and then we will always still have the problem that a view might return a psr response which is too low level for us to handle, if were not that low level ourselves.


It seems this ActionResponse::setHttpHeader is not that ooold. It was introduce with Flow 7, and previously one would use a SetHeader component: #2219

So it seems the abstraction of the response was intact before psr 7.

Now we just need to find out what to do :D

Either we remove httpResponse and headers from the ActionResponse, which will raise the question how to set any of the two, or we remove the whole ActionResponse (at least from the dispatcher, it might be still used internally)

@kitsunet
Copy link
Member

Given that we haven't had that many complains about it in the last years I feel "broken" over-dramatizes this 😆 it certainly has many problems but broken it is not, as it demonstratively works in many common usecases.

@kitsunet
Copy link
Member

Yeah the setHeader was introduced on pressure of "ease of use" from people. I liked the SetHeader thing. it was clearly marked as escape hatch

@mhsdesign mhsdesign changed the title DISCUSSION: ActionResponse abstraction is broken since Psr7 DISCUSSION: ActionResponse abstraction is inconsistent since Psr7 Jan 29, 2024
@mhsdesign
Copy link
Member Author

mhsdesign commented Jan 29, 2024

But when seeing that all these steps taken to make the ActionResponse more and more coupled to the http response, that far than one could transform the one to the other, it seems only natural to take the next step and replace it completely with the psr ResponseInterface? As our abstraction is not necessary anymore as we want "ease of use", and that seems only fine?

@mhsdesign
Copy link
Member Author

And it seems we need easy of use.
I have seen now way to many times people doing this:

public function myAction...
    hearder(...
    echo ...
    die();

i hope that once we allow to directly return a psr response interface from the action (which doesnt work currently!) and once we use this ourselves instead of manipulating $this->response people will see and understand. Hopefully.

public function myAction...
    return new Response()

@bwaidelich
Copy link
Member

What about supporting both, or even more return types?

@kitsunet
Copy link
Member

We need an anti corruption layer though, an action might return more, but then what about the processRequest interface method which is the actual public border for controllers? that would already be somewhat nasty to be multi return, next step would be dispatcher and at that point I would really like to know what I get from my controller. After that we are in the middlewares which definitely want PSR.

@mhsdesign
Copy link
Member Author

mhsdesign commented Jan 29, 2024

id say inside the "historic" action controller we can allow all kinds of things, like we already do from views string|ActionResponse|ResponseInterface|StreamInterface|\Stringable.

In an *Action of such an ActionController we can currently return a value, which will be set as content of our response and must be either a StreamInterface or anything else Utils::streamFor accepts: resource|string|int|float|bool|callable|\Iterator|\Stringable

Actually returning an ActionResponse or a ResponseInterface is both not supported, but i have no preservations in allowing both, or at least the ResponseInterface.

The only way to mutate the response currently is via $this->response, but id like to deprecate that in favour of returning.


The new SimpleActionController, might already just support the ResponseInterface internally and return it directly. (Especially since using ActionResponse->buildHttpResponse() is that easy if one gets ahold of an action response somehow).


For the public api of the controller \Neos\Flow\Mvc\Controller\ControllerInterface::processRequest
And the dispatcher \Neos\Flow\Mvc\Dispatcher::dispatch i would very like to make them operate on the psr ResponseInterface directly with no conditional return type, so its obvious what to expect.

class Dispatcher
{
    public function dispatch(ActionRequest $request): ResponseInterface;
}

interface ControllerInterface
{
    public function processRequest(ActionRequest $request): ResponseInterface;
}

class MyController extends ActionController
{
    public function someAction()
    {
        return new \GuzzleHttp\Psr7\Response(body: 'hi');
    }

    public function someOtherAction()
    {
        // legacy but will still work
        $this->response->setCookie(new Cookie(...));
        return "my body";
    }
}

@mhsdesign
Copy link
Member Author

mhsdesign commented Jan 29, 2024

Ha it seems flow went through much more than i could have imagined. The ActionResponse was actually a new abstraction introduced in 5.3 in preparation for psr7 #1531
While looking into tooling for headers - see #3291 - my thoughs were only confirmed. Im completely blown away how much work was put into psr7 AND psr15 refactorings. And flow actually looks in that parts quite shiny now.

Given that we haven't had that many complains about it in the last years I feel "broken" over-dramatizes this 😆 it certainly has many problems but broken it is not, as it demonstratively works in many common usecases.

So @kitsunet please forgive me from calling it broken. I was absolutely in the wrong of doing so ^^. Everything was super well discussed and happened exactly as it should :D Thank you all for the effort.

But back to why the ActionResponse knows its headers. I found an explanation when reading through @bwaidelich extensive (thank you) comment with the discussion how to move to psr15 #2019 (comment). Its absolutely understandable that the behaviour of the ActionResponse was changed to its current state. The initial SetHeaderComponent and ReplaceHttpResponseComponent can just not be implemented differently in psr15 world:

[...] the fact that ActionResponse::setComponentParameter() won't work with the middleware approach since it is only possible to pass information (other than the HTTP response) down into the middleware chain, but the Dispatch MW has to be the inner most.

So its only natural that the response needs to now its headers.

@mhsdesign mhsdesign changed the title DISCUSSION: ActionResponse abstraction is inconsistent since Psr7 DISCUSSION: Replace ActionResponse with ResponseInterface? Jan 29, 2024
@mhsdesign
Copy link
Member Author

Funnily the idea to use Psr response is not that new ^^

In Flow 5.3 the ActionResponse actually implemented for backwards compatibility the psr ResponseInterface.

$this->dispatcher->dispatch($actionRequest, $actionResponse);
// TODO: This should change in next major when the action response is no longer a HTTP response for backward compatibility.
$componentContext->replaceHttpResponse($actionResponse);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants