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

Experiment: FEATURE: Introduce SpecialResponsesSupport #3298

Draft
wants to merge 2 commits into
base: temporary-mvc-refactoring-target-branch
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 11 additions & 29 deletions Neos.Flow/Classes/Mvc/Controller/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
*/
abstract class AbstractController implements ControllerInterface
{
use SpecialResponsesSupport;

/**
* @var UriBuilder
*/
Expand Down Expand Up @@ -230,22 +232,6 @@ protected function forward(string $actionName, string $controllerName = null, st
$this->forwardToRequest($nextRequest);
}

/**
* Forwards the request to another action and / or controller.
*
* Request is directly transfered to the other action / controller
*
* @param ActionRequest $request The request to redirect to
* @throws ForwardException
* @see redirectToRequest()
* @api
*/
protected function forwardToRequest(ActionRequest $request): never
{
$nextRequest = clone $request;
throw ForwardException::createForNextRequest($nextRequest, '');
}

/**
* Redirects the request to another action and / or controller.
*
Expand Down Expand Up @@ -322,16 +308,12 @@ protected function redirectToRequest(ActionRequest $request, int $delay = 0, int
*/
protected function redirectToUri(string|UriInterface $uri, int $delay = 0, int $statusCode = 303): never
{
if ($delay === 0) {
if (!$uri instanceof UriInterface) {
$uri = new Uri($uri);
}
$this->response->setRedirectUri($uri, $statusCode);
} else {
$this->response->setStatusCode($statusCode);
$this->response->setContent('<html><head><meta http-equiv="refresh" content="' . (int)$delay . ';url=' . $uri . '"/></head></html>');
if (!$uri instanceof UriInterface) {
$uri = new Uri($uri);
}
throw StopActionException::createForResponse($this->response, '');

$response = $this->responseRedirectsToUri($uri, $delay, $statusCode, $this->response);
$this->throwStopActionWithResponse($response, '');
}

/**
Expand All @@ -343,20 +325,20 @@ protected function redirectToUri(string|UriInterface $uri, int $delay = 0, int $
* @param string $statusMessage A custom HTTP status message
* @param string $content Body content which further explains the status
* @throws StopActionException
* @api
* @deprecated Use SpecialResponsesSupport::responseThrowsStatus
* @see SpecialResponsesSupport::responseThrowsStatus
*/
protected function throwStatus(int $statusCode, $statusMessage = null, $content = null): never
{
$this->response->setStatusCode($statusCode);
if ($content === null) {
$content = sprintf(
'%s %s',
$statusCode,
$statusMessage ?? ResponseInformationHelper::getStatusMessageByCode($statusCode)
);
}
$this->response->setContent($content);
throw StopActionException::createForResponse($this->response, $content);

$this->responseThrowsStatus($statusCode, $content, $this->response);
}

/**
Expand Down
19 changes: 10 additions & 9 deletions Neos.Flow/Classes/Mvc/Controller/RestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,16 @@ protected function initializeUpdateAction()
*/
protected function redirectToUri(string|UriInterface $uri, int $delay = 0, int $statusCode = 303): never
{
// the parent method throws the exception, but we need to act afterwards
// thus the code in catch - it's the expected state
try {
parent::redirectToUri($uri, $delay, $statusCode);
} catch (StopActionException $exception) {
if ($this->request->getFormat() === 'json') {
$exception->response->setContent('');
}
throw $exception;
if (!$uri instanceof UriInterface) {
$uri = new Uri($uri);
}

$response = $this->responseRedirectsToUri($uri, $delay, $statusCode, $this->response);
if ($this->request->getFormat() === 'json') {
// send empty body on redirects for JSON requests
$response->setContent('');
}

$this->throwStopActionWithResponse($response, '');
}
}
88 changes: 88 additions & 0 deletions Neos.Flow/Classes/Mvc/Controller/SpecialResponsesSupport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
namespace Neos\Flow\Mvc\Controller;

use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Exception\ForwardException;
use Neos\Flow\Mvc\Exception\StopActionException;
use Psr\Http\Message\UriInterface;

/**
* provides helper methods to facilitate redirects, status throws, stop action and forwards
*/
trait SpecialResponsesSupport
{
/**
* Sends the specified HTTP status immediately.
*
* @param integer $statusCode The HTTP status code
* @param string $content Body content which further explains the status the body of a given response will be overwritten if this is not empty
* @param ActionResponse|null $response The response to use or null for an empty response with the given status and message or content
* @return never
* @throws StopActionException
*/
final protected static function responseThrowsStatus(int $statusCode, string $content = '', ?ActionResponse $response = null): never
{
$response = $response ?? new ActionResponse;

$response->setStatusCode($statusCode);
if ($content !== '') {
$response->setContent($content);
}

self::throwStopActionWithResponse($response, $content);
}

/**
* Redirects to another URI
*
* @param UriInterface $uri Either a string representation of a URI or a UriInterface object
* @param integer $delay (optional) The delay in seconds. Default is no delay.
* @param integer $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other"
* @param ActionResponse|null $response response that will have status, and location or body overwritten.
* @return ActionResponse
* @throws StopActionException
*/
final protected static function responseRedirectsToUri(UriInterface $uri, int $delay = 0, int $statusCode = 303, ?ActionResponse $response = null): ActionResponse
{
$nextResponse = $response !== null ? clone $response : new ActionResponse();

if ($delay < 1) {
$nextResponse->setRedirectUri($uri, $statusCode);
self::throwStopActionWithResponse($nextResponse, '');
}

$nextResponse->setStatusCode($statusCode);
$content = sprintf('<html lang="en"><head><meta http-equiv="refresh" content="%u;url=%s"/><title>Redirect to %s</title></head></html>', $delay, $uri, $uri);
$nextResponse->setContent($content);
return $nextResponse;
}

/**
* @param ActionResponse $response The response to be received by the MVC Dispatcher.
* @param string $details Additional details just for the exception, in case it is logged (the regular exception message).
* @return never
* @throws StopActionException
*/
final protected static function throwStopActionWithResponse(ActionResponse $response, string $details = ''): never
{
throw StopActionException::createForResponse($response, $details);
}

/**
* Forwards the request to another action and / or controller
* Request is directly transferred to the other action / controller
*
* NOTE that this will not try to convert any objects in the requests arguments,
* this can be a fine or a problem depending on context of usage.
*
* @param ActionRequest $request The request to redirect to
* @return never
* @throws ForwardException
*/
final protected static function forwardToRequest(ActionRequest $request): never
{
$nextRequest = clone $request;
throw ForwardException::createForNextRequest($nextRequest, '');
}
}
3 changes: 1 addition & 2 deletions Neos.Flow/Classes/Mvc/Exception/ForwardException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
* source code.
*/
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Controller\AbstractController;

/**
* This exception is thrown by a controller to stop the execution of the current
* action and return the control to the dispatcher for the special case of a forward.
*
* See {@see AbstractController::forward()} for more information.
* See {@see SpecialResponsesSupport::forwardToRequest()} for more information.
*
* Other control flow exceptions: {@see StopActionException}
*
Expand Down
4 changes: 2 additions & 2 deletions Neos.Flow/Classes/Mvc/Exception/StopActionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
*/

use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\AbstractController;

/**
* This exception is thrown by a controller to stop the execution of the current
* action and return the control to the dispatcher. The dispatcher catches this
* exception and - depending on the "dispatched" status of the request - either
* continues dispatching the request or returns control to the request handler.
*
* See {@see AbstractController::throwStatus()} or {@see AbstractController::redirectToUri()} for more information.
* See {@see SpecialResponsesSupport::throwStopActionWithResponse()}
* or {@see SpecialResponsesSupport::responseRedirectsToUri()} for more information.
*
* Other control flow exceptions: {@see ForwardException}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ abstract class AbstractWidgetController extends ActionController
* Handles a request. The result output is returned by altering the given response.
*
* @param ActionRequest $request The request object
* @return ActionResponse $response The response, modified by this handler
* @return ActionResponse The response, modified by this handler
* @throws WidgetContextNotFoundException
* @throws InvalidActionVisibilityException
* @throws InvalidArgumentTypeException
Expand Down
Loading