diff --git a/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php b/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php index 57c06ff3006..60040ff33f0 100644 --- a/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php +++ b/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php @@ -9,7 +9,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** - * Serializer for Fusion's [at]cache.context values + * Serializer for Fusion's \@cache.context values * * Uses the Flows's property mapper as implementation. * It relies on a converter being available from the context value type to string and reverse. diff --git a/Neos.Fusion/Classes/Core/FusionGlobals.php b/Neos.Fusion/Classes/Core/FusionGlobals.php index 3c1e26ca277..0b81b7f27bb 100644 --- a/Neos.Fusion/Classes/Core/FusionGlobals.php +++ b/Neos.Fusion/Classes/Core/FusionGlobals.php @@ -5,21 +5,31 @@ namespace Neos\Fusion\Core; /** - * Fusion allows to add variable to the context either via - * \@context.foo = "bar" or by leveraging the php api {@see Runtime::pushContext()}. + * Fusion differentiates between dynamic context variables and fixed Fusion globals. * - * Those approaches are highly dynamic and don't guarantee the existence of variables, + * Context variables are allowed to be set via Fusion's \@context.foo = "bar" + * or by leveraging the php api {@see Runtime::pushContext()}. + * + * Context variables are highly dynamic and don't guarantee the existence of a specific variables, * as they have to be explicitly preserved in uncached \@cache segments, * or might accidentally be popped from the stack. * - * The Fusion runtime is instantiated with a set of global variables which contain the EEL helper definitions - * or functions like FlowQuery. Also, variables like "request" are made available via it. + * The Fusion globals are immutable and part of the runtime's constructor. + * A fixed set of global variables which might contain the EEL helper definitions + * or functions like FlowQuery can be passed this way. + * + * Additionally, also special variables like "request" are made available. * - * The "${request}" special case: To make the request available in uncached segments, it would need to be serialized, - * but we don't allow this currently and despite that, it would be absurd to cache a random request. + * The speciality with "request" and similar is that they should be always available but never cached. + * Regular context variables must be serialized to be available in uncached segments, + * but the current request must not be serialized into the cache as it contains user specific information. * This is avoided by always exposing the current action request via the global variable. * * Overriding Fusion globals is disallowed via \@context and {@see Runtime::pushContext()}. + * + * Fusion globals are case-sensitive, though it's not recommend to leverage this behaviour. + * + * @internal The globals will be set inside the FusionView as declared */ final readonly class FusionGlobals { @@ -45,8 +55,13 @@ public static function fromArray(array $variables): self } /** - * You can access the current request like via this getter: - * `$runtime->fusionGlobals->get('request')` + * Access the possible current request or other globals: + * + * $actionRequest = $this->runtime->fusionGlobals->get('request'); + * if (!$actionRequest instanceof ActionRequest) { + * // fallback or error + * } + * */ public function get(string $name): mixed { diff --git a/Neos.Fusion/Classes/Core/RuntimeFactory.php b/Neos.Fusion/Classes/Core/RuntimeFactory.php index 24522dbb6d8..33ed7dfab64 100644 --- a/Neos.Fusion/Classes/Core/RuntimeFactory.php +++ b/Neos.Fusion/Classes/Core/RuntimeFactory.php @@ -44,16 +44,15 @@ public function create(array $fusionConfiguration, ControllerContext $controller $defaultContextVariables = EelUtility::getDefaultContextVariables( $this->defaultContextConfiguration ?? [] ); - $runtime = new Runtime( + return new Runtime( FusionConfiguration::fromArray($fusionConfiguration), FusionGlobals::fromArray( [ + ...$defaultContextVariables, 'request' => $controllerContext?->getRequest() ?? ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()), - ...$defaultContextVariables ] ) ); - return $runtime; } public function createFromConfiguration( diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 9c916a6a471..b23fad4f38a 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -16,18 +16,16 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** - * Serializer for Fusion's [at]cache.context values + * Serializer for Fusion's \@cache.context values * * Implements special handing for serializing {@see Node} objects in fusions cache context: * - * ``` - * [at]cache { - * mode = 'uncached' - * context { - * 1 = 'node' - * } - * } - * ``` + * \@cache { + * mode = 'uncached' + * context { + * 1 = 'node' + * } + * } * * The property mapper cannot be relied upon to serialize nodes, as this is willingly not implemented. * diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index d86ef54c240..ecd9d88bb59 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -18,6 +18,8 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Flow\Mvc\ActionRequest; +use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\View\AbstractView; use Neos\Flow\Security\Context; use Neos\Fusion\Core\FusionGlobals; @@ -52,6 +54,12 @@ class FusionView extends AbstractView #[Flow\Inject] protected RenderingModeService $renderingModeService; + /** + * Via {@see assign} request using the "request" key, + * will be available also as Fusion global in the runtime. + */ + protected ?ActionRequest $assignedActionRequest = null; + /** * Renders the view * @@ -196,10 +204,10 @@ protected function getFusionRuntime(Node $currentSiteNode) $renderingMode = $this->renderingModeService->findByName($this->getOption('renderingModeName')); - $fusionGlobals = FusionGlobals::fromArray([ - 'request' => $this->controllerContext->getRequest(), + $fusionGlobals = FusionGlobals::fromArray(array_filter([ + 'request' => $this->assignedActionRequest, 'renderingMode' => $renderingMode - ]); + ])); $this->fusionRuntime = $this->runtimeFactory->createFromConfiguration( $fusionConfiguration, $fusionGlobals @@ -220,7 +228,30 @@ protected function getFusionRuntime(Node $currentSiteNode) */ public function assign($key, $value): AbstractView { + if ($key === 'request') { + // the request cannot be used as "normal" fusion variable and must be treated as FusionGlobal + // to for example not cache it accidentally + // additionally we need it for special request based handling in the view + $this->assignedActionRequest = $value; + return $this; + } $this->fusionRuntime = null; return parent::assign($key, $value); } + + /** + * Legacy layer to set the request for this view if not set already. + * + * Please use {@see assign} with "request" instead + * + * $view->assign('request"', $this->request) + * + * @deprecated with Neos 9 + */ + public function setControllerContext(ControllerContext $controllerContext) + { + if (!$this->assignedActionRequest) { + $this->assignedActionRequest = $controllerContext->getRequest(); + } + } }