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

!!! FEATURE: ViewInterface returns PSR StreamInterface #3286

Merged
14 changes: 8 additions & 6 deletions Neos.Flow/Classes/Error/AbstractExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,14 @@ protected function buildView(\Throwable $exception, array $renderingOptions): Vi
$request->setControllerPackageKey('Neos.Flow');
$uriBuilder = new UriBuilder();
$uriBuilder->setRequest($request);
$view->setControllerContext(new ControllerContext(
$request,
new ActionResponse(),
new Arguments([]),
$uriBuilder
));
if (method_exists($view, 'setControllerContext')) {
$view->setControllerContext(new ControllerContext(
$request,
new ActionResponse(),
new Arguments([]),
$uriBuilder
));
}

if (isset($renderingOptions['variables'])) {
$view->assignMultiple($renderingOptions['variables']);
Expand Down
11 changes: 10 additions & 1 deletion Neos.Flow/Classes/Error/DebugExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Http\Helper\ResponseInformationHelper;
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
use Psr\Http\Message\ResponseInterface;

/**
* A basic but solid exception handler which catches everything which
Expand Down Expand Up @@ -76,7 +77,15 @@ protected function echoExceptionWeb($exception)
}

try {
echo $this->buildView($exception, $this->renderingOptions)->render();
$stream = $this->buildView($exception, $this->renderingOptions)->render();
if ($stream instanceof ResponseInterface) {
/**
* The http status code will already be sent, and we are only currently interested in the content stream
* Thus, we unwrap the repose here:
*/
$stream = $stream->getBody();
}
ResponseInformationHelper::sendStream($stream);
} catch (\Throwable $throwable) {
$this->renderStatically($statusCode, $throwable);
}
Expand Down
11 changes: 10 additions & 1 deletion Neos.Flow/Classes/Error/ProductionExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Http\Helper\ResponseInformationHelper;
use Psr\Http\Message\ResponseInterface;

/**
* A quite exception handler which catches but ignores any exception.
Expand All @@ -39,7 +40,15 @@ protected function echoExceptionWeb($exception)
try {
if ($this->useCustomErrorView()) {
try {
echo $this->buildView($exception, $this->renderingOptions)->render();
$stream = $this->buildView($exception, $this->renderingOptions)->render();
if ($stream instanceof ResponseInterface) {
/**
* The http status code will already be sent, and we are only currently interested in the content stream
* Thus, we unwrap the repose here:
*/
$stream = $stream->getBody();
}
mhsdesign marked this conversation as resolved.
Show resolved Hide resolved
ResponseInformationHelper::sendStream($stream);
} catch (\Throwable $throwable) {
$this->renderStatically($statusCode, $throwable);
}
Expand Down
12 changes: 12 additions & 0 deletions Neos.Flow/Classes/Http/Helper/ResponseInformationHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Neos\Flow\Http\CacheControlDirectives;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
* Helper to extract various information from PSR-7 responses.
Expand Down Expand Up @@ -242,4 +243,15 @@ public static function makeStandardsCompliant(ResponseInterface $response, Reque

return $response;
}

public static function sendStream(StreamInterface $stream): void
{
$body = $stream->detach() ?: $stream->getContents();
if (is_resource($body)) {
fpassthru($body);
fclose($body);
} else {
echo $body;
}
}
}
8 changes: 1 addition & 7 deletions Neos.Flow/Classes/Http/RequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,6 @@ protected function sendResponse(ResponseInterface $response)
ob_end_flush();
}

$body = $response->getBody()->detach() ?: $response->getBody()->getContents();
if (is_resource($body)) {
fpassthru($body);
fclose($body);
} else {
echo $body;
}
ResponseInformationHelper::sendStream($response->getBody());
}
}
17 changes: 4 additions & 13 deletions Neos.Flow/Classes/Mvc/Controller/ActionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ public function processRequest(ActionRequest $request, ActionResponse $response)
}
if ($this->view !== null) {
$this->view->assign('settings', $this->settings);
$this->view->setControllerContext($this->controllerContext);
$this->view->assign('request', $this->request);
if (method_exists($this->view, 'setControllerContext')) {
$this->view->setControllerContext($this->controllerContext);
}
$this->initializeView($this->view);
}

Expand Down Expand Up @@ -820,25 +823,13 @@ protected function renderView()
{
$result = $this->view->render();

if (is_string($result)) {
$this->response->setContent($result);
}

if ($result instanceof ActionResponse) {
$result->mergeIntoParentResponse($this->response);
}

if ($result instanceof ResponseInterface) {
$this->response->replaceHttpResponse($result);
if ($result->hasHeader('Content-Type')) {
$this->response->setContentType($result->getHeaderLine('Content-Type'));
}
}

if (is_object($result) && is_callable([$result, '__toString'])) {
$this->response->setContent((string)$result);
}

if ($result instanceof StreamInterface) {
$this->response->setContent($result);
}
Expand Down
33 changes: 11 additions & 22 deletions Neos.Flow/Classes/Mvc/View/AbstractView.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ abstract class AbstractView implements ViewInterface

/**
* @var ControllerContext
* @deprecated if you absolutely need access to the current request please assign a variable.
* when using the action controller the request is directly available at "request"
*/
protected $controllerContext;

/**
* Factory method to create an instance with given options.
*
* @param array $options
* @return ViewInterface
* @return static
*/
public static function createWithOptions(array $options)
public static function createWithOptions(array $options): self
{
return new static($options);
}
Expand Down Expand Up @@ -141,10 +143,10 @@ public function setOption($optionName, $value)
*
* @param string $key Key of variable
* @param mixed $value Value of object
* @return AbstractView an instance of $this, to enable chaining
* @return $this for chaining
* @api
*/
public function assign($key, $value)
public function assign(string $key, mixed $value): self
{
$this->variables[$key] = $value;
return $this;
Expand All @@ -154,10 +156,10 @@ public function assign($key, $value)
* Add multiple variables to $this->variables.
*
* @param array $values array in the format array(key1 => value1, key2 => value2)
* @return AbstractView an instance of $this, to enable chaining
* @return $this for chaining
* @api
*/
public function assignMultiple(array $values)
public function assignMultiple(array $values): self
{
foreach ($values as $key => $value) {
$this->assign($key, $value);
Expand All @@ -168,26 +170,13 @@ public function assignMultiple(array $values)
/**
* Sets the current controller context
*
* @param ControllerContext $controllerContext
* @deprecated if you absolutely need access to the current request please assign a variable.
* when using the action controller the request is directly available at "request"
* @param ControllerContext $controllerContext Context of the controller associated with this view
* @return void
* @api
*/
public function setControllerContext(ControllerContext $controllerContext)
{
$this->controllerContext = $controllerContext;
}

/**
* Tells if the view implementation can render the view for the given context.
*
* By default we assume that the view implementation can handle all kinds of
* contexts. Override this method if that is not the case.
*
* @param ControllerContext $controllerContext
* @return boolean true if the view has something useful to display, otherwise false
*/
public function canRender(ControllerContext $controllerContext)
{
return true;
}
}
23 changes: 12 additions & 11 deletions Neos.Flow/Classes/Mvc/View/JsonView.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@
* source code.
*/

use GuzzleHttp\Psr7\Response;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Controller\ControllerContext;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Http\Factories\StreamFactoryTrait;
use Neos\Utility\ObjectAccess;
use Neos\Utility\TypeHandling;
use Psr\Http\Message\ResponseInterface;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deprecate

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we deprecated the json view ... what is our answer to code like this? :O

/**
 * @var array
 */
protected $viewFormatToObjectNameMap = [
    'html' => FusionView::class,
    'json' => JsonView::class,
];

do we need a new simple json view? :P

cc @kitsunet


/**
* A JSON view
*
* @api
* @deprecated please use json_encode instead
*/
class JsonView extends AbstractView
{
use StreamFactoryTrait;

/**
* Supported options
* @var array
Expand All @@ -50,11 +54,6 @@ class JsonView extends AbstractView
*/
const EXPOSE_CLASSNAME_UNQUALIFIED = 2;

/**
* @var ControllerContext
*/
protected $controllerContext;

/**
* Only variables whose name is contained in this array will be rendered
*
Expand Down Expand Up @@ -195,15 +194,17 @@ public function setConfiguration(array $configuration)
* array represantion using a YAML view configuration and JSON encodes
* the result.
*
* @return string The JSON encoded variables
* @return ResponseInterface The JSON encoded variables
* @api
*/
public function render()
public function render(): ResponseInterface
{
$this->controllerContext->getResponse()->setContentType('application/json');
$response = new Response();
$response = $response->withHeader('Content-Type', 'application/json');
$propertiesToRender = $this->renderArray();
$options = $this->getOption('jsonEncodingOptions');
return json_encode($propertiesToRender, JSON_THROW_ON_ERROR | $options);
$value = json_encode($propertiesToRender, JSON_THROW_ON_ERROR | $options);
return $response->withBody($this->createStream($value));
}

/**
Expand Down
11 changes: 8 additions & 3 deletions Neos.Flow/Classes/Mvc/View/SimpleTemplateView.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* source code.
*/

use Neos\Http\Factories\StreamFactoryTrait;
use Neos\Utility\ObjectAccess;
use Psr\Http\Message\StreamInterface;

/**
* An abstract View
Expand All @@ -20,6 +22,8 @@
*/
class SimpleTemplateView extends AbstractView
{
use StreamFactoryTrait;

/**
* @var array
*/
Expand All @@ -31,19 +35,20 @@ class SimpleTemplateView extends AbstractView
/**
* Renders the view
*
* @return string The rendered view
* @api
*/
public function render()
public function render(): StreamInterface
{
$source = $this->getOption('templateSource');
$templatePathAndFilename = $this->getOption('templatePathAndFilename');
if ($templatePathAndFilename !== null) {
$source = file_get_contents($templatePathAndFilename);
}

return preg_replace_callback('/\{([a-zA-Z0-9\-_.]+)\}/', function ($matches) {
$content = preg_replace_callback('/\{([a-zA-Z0-9\-_.]+)\}/', function ($matches) {
return ObjectAccess::getPropertyPath($this->variables, $matches[1]);
}, $source);

return $this->createStream($content);
}
}
Loading
Loading