The voting from August was unambiguous, so let’s get a grip on this one
I talked to @christianm in length about this and every time I forget half of what we discussed before.
So let me try to summarize what we came up with so far1:
TL;DR:
This post wasn’t meant to get that long, but there is no shorter way. So if it’s too long: Just don’t read it
The current architecture
http://yuml.me/edit/6fe5eb0e
Response
http://yuml.me/edit/710be027
The new architecture
Request
http://yuml.me/edit/e1e96034
Response
http://yuml.me/edit/18d4cfb1
Consequences
Http\Request
Will be removed without replacement. Instead we should code against Psr\Http\Message\ServerRequestInterface
everywhere we talk about the incoming HTTP request and against Psr\Http\Message\RequestInterface
for outgoing requests.
For the implementation we could just use one of the existing packages, for example guzzlehttp/psr7
.
ActionRequest
…is no longer related to the HTTP Request directly as that led to lots of confusion and inter-dependencies like the above.
Instead it should get everything it needs (controllerName
, controllerActionName
, arguments
, format
…) at construction time.
To be discussed: Can we get rid of nested ActionRequests and instead add something like ActionRequest::withArgumentNamespace(string $namespace): ActionRequest
?
For the usual action handling it should not be required to access the Main (or HTTP) request. If that’s needed, it should be done via the RequestHandler
(see below)
The ActionRequest
can probably be immutable in the future
CommandRequest
(formerly Cli\Request
)
Same as ActionRequest
, but they definitely don’t need a common interface and command requests definitely don’t need to be nested
ActionResponse
(formerly Mvc\Response
)
Currently the Mvc\Response
is not used at all(!). Instead actions are dispatched with the main Http\Response
via the DispatchComponent
(and by creating a new “sub-Http\Response” for sub requests like plugins, widgets, form, …)
The ActionResponse
has to stay mutable to respect the whole dispatch architecture of our MVC framework (that changes the response until its dispatched
flag is set).
If we re-introduce a dedicated ActionResponse
it should never mutate the HTTP Response itself. Instead it’s the dispatchers responsibility to apply changes to the HTTP response (see example below)
IMO the ActionResponse
should be specific to MVC, but probably needs some setters for convenience. For example:
final class ActionResponse
{
public function setContent(string $content): void
public function setContentType(string $contentType): void
public function setRedirectUri(UriInterface $uri, int $statusCode = 303): void
public function setStatusCode(int $statusCode): void
}
To be discussed: Are there more properties of the ActionResponse
that have to be mutable from the action, like setHeader($name, $value)
?
HttpRequestHandlerInterface
Not directly related to PSR-7 but due to the consequences mentioned above, we would have to change the interface from
interface HttpRequestHandlerInterface extends RequestHandlerInterface
{
public function getHttpRequest();
public function getHttpResponse();
}
to something like:
interface HttpRequestHandlerInterface extends RequestHandlerInterface
{
public function getComponentContext(): ComponentContext;
}
That would finally give us an API way to get hold of HTTP Request & Response and to “mutate” them if really needed:
class SomeController extends ActionController
{
/**
* @Flow\Inject
* HttpRequestHandlerInterface
*/
protected $requestHandler;
public function someAction()
{
$componentChain = $this->requestHandler->getComponentContext();
$httpResponse = $componentChain->getHttpResponse()->withHeader('X-Foo', 'Bar');
$componentChain->replaceHttpResponse($httpResponse);
}
}
Example Dispatch
This is how I could imagine the DispatchComponent
to look after the rework:
// ...
$actionRequest = ActionRequest::fromArguments($arguments);
$actionResponse = new ActionResponse();
$this->dispatcher->dispatch($actionRequest, $actionResponse);
$httpResponse = $actionResponse->mergeToHttpResponse($componentContext->getHttpResponse());
$componentContext->replaceHttpResponse($httpResponse);
Sub Request
// ...
$pluginRequest = $actionRequest->withArgumentNamespace($pluginNamespace)->withArgument($pluginArguments);
$pluginResponse = new ActionResponse();
$this->dispatcher->dispatch($pluginRequest, $pluginResponse);
$actionResponse->mergeActionResponse($pluginResponse);
1 it’s based on what I discussed with Christian, but I added some new suggestions that we didn’t talk about yet