From b7f9da904be3830d4d2549fcf17efd964ce62559 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Wed, 16 Oct 2019 17:30:41 +0200 Subject: [PATCH 01/12] TASK: Fusion performance optimization (lazy Component props) --- .../FusionObjects/ComponentImplementation.php | 24 ++--- .../FusionObjects/Helpers/LazyProps.php | 92 +++++++++++++++++++ 2 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php diff --git a/Neos.Fusion/Classes/FusionObjects/ComponentImplementation.php b/Neos.Fusion/Classes/FusionObjects/ComponentImplementation.php index 8bf2c41fe2b..8243d1bd6c3 100644 --- a/Neos.Fusion/Classes/FusionObjects/ComponentImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/ComponentImplementation.php @@ -11,6 +11,7 @@ * source code. */ +use Neos\Fusion\FusionObjects\Helpers\LazyProps; /** * A Fusion Component-Object @@ -51,37 +52,32 @@ public function evaluate() * @param array $context * @return array */ - protected function prepare($context) + protected function prepare(array $context): array { - $context['props'] = $this->getProps(); + $context['props'] = $this->getProps($context); return $context; } /** * Calculate the component props * - * @return array + * @param array $context + * @return \ArrayAccess */ - protected function getProps() + protected function getProps(array $context): \ArrayAccess { $sortedChildFusionKeys = $this->sortNestedFusionKeys(); - $props = []; - foreach ($sortedChildFusionKeys as $key) { - try { - $props[$key] = $this->fusionValue($key); - } catch (\Exception $e) { - $props[$key] = $this->runtime->handleRenderingException($this->path . '/' . $key, $e); - } - } + $props = new LazyProps($this, $this->path, $this->runtime, $sortedChildFusionKeys, $context); return $props; } + /** * Evaluate the renderer with the give context and return * - * @param $context + * @param array $context * @return mixed */ - protected function render($context) + protected function render(array $context) { $this->runtime->pushContextArray($context); $result = $this->runtime->render($this->path . '/renderer'); diff --git a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php new file mode 100644 index 00000000000..8ad23749509 --- /dev/null +++ b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php @@ -0,0 +1,92 @@ +fusionObject = $fusionObject; + $this->parentPath = $parentPath; + $this->runtime = $runtime; + $this->keys = array_flip($keys); + $this->effectiveContext = $effectiveContext; + } + + public function offsetExists($offset) + { + return isset($this->keys[$offset]); + } + + public function offsetGet($offset) + { + return $this->fusionValue($offset); + } + + public function offsetSet($offset, $value) + { + // NOOP + } + + public function offsetUnset($offset) + { + // NOOP + } + + protected function fusionValue(string $path) + { + if (!isset($this->valueCache[$path])) { + $this->runtime->pushContextArray($this->effectiveContext); + try { + $this->valueCache[$path] = $this->runtime->evaluate($this->parentPath . '/' . $path, $this->fusionObject); + } finally { + $this->runtime->popContext(); + } + } + return $this->valueCache[$path]; + } +} From ad868a733040be46417936464320ec0d2e478321 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Wed, 16 Oct 2019 17:42:08 +0200 Subject: [PATCH 02/12] Inlined fusionValue method --- .../FusionObjects/Helpers/LazyProps.php | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php index 8ad23749509..e73b9f67ee6 100644 --- a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php +++ b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php @@ -57,27 +57,12 @@ public function __construct( $this->effectiveContext = $effectiveContext; } - public function offsetExists($offset) + public function offsetExists($path) { - return isset($this->keys[$offset]); + return isset($this->keys[$path]); } - public function offsetGet($offset) - { - return $this->fusionValue($offset); - } - - public function offsetSet($offset, $value) - { - // NOOP - } - - public function offsetUnset($offset) - { - // NOOP - } - - protected function fusionValue(string $path) + public function offsetGet($path) { if (!isset($this->valueCache[$path])) { $this->runtime->pushContextArray($this->effectiveContext); @@ -89,4 +74,14 @@ protected function fusionValue(string $path) } return $this->valueCache[$path]; } + + public function offsetSet($path, $value) + { + // NOOP + } + + public function offsetUnset($path) + { + // NOOP + } } From 0f86e147a6887947cc8906fbfeab6710ff591875 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Wed, 16 Oct 2019 17:50:27 +0200 Subject: [PATCH 03/12] Implement test to verify lazy rendering --- .../Tests/Functional/FusionObjects/ComponentTest.php | 10 ++++++++++ .../FusionObjects/Fixtures/Fusion/Component.fusion | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/ComponentTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/ComponentTest.php index 322fc98e8ba..d8fd1cfb30b 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/ComponentTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/ComponentTest.php @@ -56,4 +56,14 @@ public function componentSandboxRenderer() $view->setFusionPath('component/sandboxRenderer'); self::assertEquals('Hello ', $view->render()); } + + /** + * @test + */ + public function componentLazyRenderer() + { + $view = $this->buildView(); + $view->setFusionPath('component/lazyRenderer'); + self::assertEquals('Hello', $view->render()); + } } diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Component.fusion b/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Component.fusion index a6e38d68a3e..bd5cd931ebb 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Component.fusion +++ b/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Component.fusion @@ -40,3 +40,11 @@ component.sandboxRenderer = Neos.Fusion:Component { } } } + +component.lazyRenderer = Neos.Fusion:Component { + a = 'Hello' + // This prop is not evaluated, since it is not used - otherwise the test would fail + b = Neos.Fusion:NotImplemented + + renderer = ${props.a} +} From 4e61359ba0ae72611dcd6e4e27bc46f188099f75 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Thu, 17 Oct 2019 09:58:47 +0200 Subject: [PATCH 04/12] Implement Traversable for conversion to array --- .../FusionObjects/Helpers/LazyProps.php | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php index e73b9f67ee6..56701bd5732 100644 --- a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php +++ b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php @@ -8,7 +8,7 @@ /** * @Flow\Proxy(false) */ -final class LazyProps implements \ArrayAccess +final class LazyProps implements \ArrayAccess, \Iterator { /** @@ -67,7 +67,8 @@ public function offsetGet($path) if (!isset($this->valueCache[$path])) { $this->runtime->pushContextArray($this->effectiveContext); try { - $this->valueCache[$path] = $this->runtime->evaluate($this->parentPath . '/' . $path, $this->fusionObject); + $this->valueCache[$path] = $this->runtime->evaluate($this->parentPath . '/' . $path, + $this->fusionObject); } finally { $this->runtime->popContext(); } @@ -84,4 +85,33 @@ public function offsetUnset($path) { // NOOP } + + public function current() + { + $path = key($this->keys); + if ($path === null) { + return null; + } + return $this->offsetGet($path); + } + + public function next() + { + next($this->keys); + } + + public function key() + { + return key($this->keys); + } + + public function valid() + { + return current($this->keys) !== false; + } + + public function rewind() + { + reset($this->keys); + } } From aa168b7cabc422430b9668a8368e6a2980ffefe9 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Tue, 22 Oct 2019 12:07:15 +0200 Subject: [PATCH 05/12] Implement fix and test for apply with lazy props --- Neos.Fusion/Classes/Core/Runtime.php | 60 ++++++++++++++----- .../Functional/FusionObjects/ApplyTest.php | 10 ++++ .../Fixtures/Fusion/Apply.fusion | 11 ++++ 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 3fdc46beb5f..fed79f816b5 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -16,6 +16,7 @@ use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\Exception\StopActionException; use Neos\Flow\ObjectManagement\ObjectManagerInterface; +use Neos\Fusion\FusionObjects\Helpers\LazyProps; use Neos\Utility\Arrays; use Neos\Utility\ObjectAccess; use Neos\Utility\PositionalArraySorter; @@ -438,7 +439,11 @@ protected function evaluateInternal($fusionPath, $behaviorIfPathNotFound, $conte if ($this->evaluateIfCondition($fusionConfiguration, $fusionPath, $contextObject) === false) { return null; } - return $this->evaluateProcessors($currentProperties[$fusionPath]['value'], $fusionConfiguration, $fusionPath, $contextObject); + $value = $currentProperties[$fusionPath]['value']; + if (isset($currentProperties[$fusionPath]['lazy'])) { + $value = $value(); + } + return $this->evaluateProcessors($value, $fusionConfiguration, $fusionPath, $contextObject); } if (!$this->canRenderWithConfiguration($fusionConfiguration)) { @@ -830,11 +835,21 @@ protected function setPropertiesOnFusionObject(AbstractArrayFusionObject $fusion if (is_array($currentProperties)) { foreach ($currentProperties as $path => $property) { $key = $property['key']; - $valueAst = [ - '__eelExpression' => null, - '__objectType' => null, - '__value' => $property['value'] - ]; + if (isset($property['lazy'])) { + $valueAst = [ + '__eelExpression' => null, + // Mark this property as not having a simple value in the AST - + // the object implementation has to evaluate the key through the Runtime + '__objectType' => 'Neos.Fusion:Lazy', + '__value' => null + ]; + } else { + $valueAst = [ + '__eelExpression' => null, + '__objectType' => null, + '__value' => $property['value'] + ]; + } // merge existing meta-configuration to valueAst // to preserve @if, @process and @position informations @@ -941,17 +956,30 @@ protected function evaluateApplyValues($configurationWithEventualProperties, $fu $singleApplyPath .= '/expression'; } $singleApplyValues = $this->evaluateInternal($singleApplyPath, self::BEHAVIOR_EXCEPTION); - if ($this->getLastEvaluationStatus() !== static::EVALUATION_SKIPPED && is_array($singleApplyValues)) { - foreach ($singleApplyValues as $key => $value) { - // skip keys which start with __, as they are purely internal. - if ($key[0] === '_' && $key[1] === '_' && in_array($key, Parser::$reservedParseTreeKeys, true)) { - continue; + if ($this->getLastEvaluationStatus() !== static::EVALUATION_SKIPPED) { + if (is_array($singleApplyValues)) { + foreach ($singleApplyValues as $key => $value) { + // skip keys which start with __, as they are purely internal. + if ($key[0] === '_' && $key[1] === '_' && in_array($key, Parser::$reservedParseTreeKeys, + true)) { + continue; + } + + $combinedApplyValues[$fusionPath . '/' . $key] = [ + 'key' => $key, + 'value' => $value + ]; + } + } else if ($singleApplyValues instanceof LazyProps) { + for ($singleApplyValues->rewind(); ($key = $singleApplyValues->key()) !== null; $singleApplyValues->next()) { + $combinedApplyValues[$fusionPath . '/' . $key] = [ + 'key' => $key, + 'value' => function() use ($singleApplyValues, $key) { + return $singleApplyValues[$key]; + }, + 'lazy' => true + ]; } - - $combinedApplyValues[$fusionPath . '/' . $key] = [ - 'key' => $key, - 'value' => $value - ]; } } } diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php index aa531c909e7..146e2b18117 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php @@ -272,4 +272,14 @@ public function arrayWithPositionAndSpreadRendering() self::assertEquals('startmiddleModifiedendModified', $view->render() ); } + + /** + * @test + */ + public function rendererWithNestedPropsInApply() + { + $view = $this->buildView(); + $view->setFusionPath('apply/renderWithNestedProps'); + self::assertEquals('example', $view->render()); + } } diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion b/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion index 28c6704cc82..53a495b2385 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion +++ b/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion @@ -141,3 +141,14 @@ apply.renderArrayWithPositionAndSpread = Neos.Fusion:Array { first.@position = 'start' @apply.applyValues = ${{last: 'endModified', middle: 'middleModified'}} } + +apply.renderWithNestedProps = Neos.Fusion:Component { + applyValue = "example" + applyError = Neos.Fusion:NotImplemented + + renderer = Neos.Fusion:Component { + @apply.fromProps = ${props} + + renderer = ${props.applyValue} + } +} From db7c1112417abc904bf24dc257a26b032e1c2109 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Tue, 22 Oct 2019 17:33:10 +0200 Subject: [PATCH 06/12] Make StyleCI happy --- Neos.Fusion/Classes/Core/Runtime.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index fed79f816b5..7b926defbcf 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -970,11 +970,11 @@ protected function evaluateApplyValues($configurationWithEventualProperties, $fu 'value' => $value ]; } - } else if ($singleApplyValues instanceof LazyProps) { + } elseif ($singleApplyValues instanceof LazyProps) { for ($singleApplyValues->rewind(); ($key = $singleApplyValues->key()) !== null; $singleApplyValues->next()) { $combinedApplyValues[$fusionPath . '/' . $key] = [ 'key' => $key, - 'value' => function() use ($singleApplyValues, $key) { + 'value' => function () use ($singleApplyValues, $key) { return $singleApplyValues[$key]; }, 'lazy' => true From c652338000d7ed80825a8bbb9ed39b64c7067f1a Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Wed, 23 Oct 2019 13:34:19 +0200 Subject: [PATCH 07/12] Use Traversable and ArrayAccess instead of LazyProps --- Neos.Fusion/Classes/Core/Runtime.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 7b926defbcf..619d05a964a 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -16,7 +16,6 @@ use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\Exception\StopActionException; use Neos\Flow\ObjectManagement\ObjectManagerInterface; -use Neos\Fusion\FusionObjects\Helpers\LazyProps; use Neos\Utility\Arrays; use Neos\Utility\ObjectAccess; use Neos\Utility\PositionalArraySorter; @@ -957,7 +956,9 @@ protected function evaluateApplyValues($configurationWithEventualProperties, $fu } $singleApplyValues = $this->evaluateInternal($singleApplyPath, self::BEHAVIOR_EXCEPTION); if ($this->getLastEvaluationStatus() !== static::EVALUATION_SKIPPED) { - if (is_array($singleApplyValues)) { + if ($singleApplyValues === null) { + continue; + } elseif (is_array($singleApplyValues)) { foreach ($singleApplyValues as $key => $value) { // skip keys which start with __, as they are purely internal. if ($key[0] === '_' && $key[1] === '_' && in_array($key, Parser::$reservedParseTreeKeys, @@ -970,7 +971,7 @@ protected function evaluateApplyValues($configurationWithEventualProperties, $fu 'value' => $value ]; } - } elseif ($singleApplyValues instanceof LazyProps) { + } elseif ($singleApplyValues instanceof \Traversable && $singleApplyValues instanceof \ArrayAccess) { for ($singleApplyValues->rewind(); ($key = $singleApplyValues->key()) !== null; $singleApplyValues->next()) { $combinedApplyValues[$fusionPath . '/' . $key] = [ 'key' => $key, From 71b230d9f8d8d4e7e7e284b76aa1f3d93a5e28d4 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Tue, 26 Nov 2019 21:26:08 +0100 Subject: [PATCH 08/12] TASK: Make linter happy --- Neos.Fusion/Classes/Core/Runtime.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index bf5fdb1b890..9d5751eee34 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -841,11 +841,11 @@ protected function setPropertiesOnFusionObject(AbstractArrayFusionObject $fusion '__value' => null ]; } else { - $valueAst = [ - '__eelExpression' => null, - '__objectType' => null, - '__value' => $property['value'] - ]; + $valueAst = [ + '__eelExpression' => null, + '__objectType' => null, + '__value' => $property['value'] + ]; } // merge existing meta-configuration to valueAst From 0642115d529237bd30ae1502a78409e7739c4718 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Mon, 2 Dec 2019 12:35:51 +0100 Subject: [PATCH 09/12] Fix nested apply values by removing the stack --- Neos.Fusion/Classes/Core/Runtime.php | 75 ++++++++----------- .../FusionObjects/Helpers/LazyProps.php | 7 +- .../Functional/FusionObjects/ApplyTest.php | 2 +- .../Fixtures/Fusion/Apply.fusion | 21 +++++- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 9d5751eee34..f773c149159 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -86,19 +86,12 @@ class Runtime */ protected $currentContext = null; - /** - * Stack of evaluated "@apply" values - * - * @var array - */ - protected $applyValueStack = []; - /** * Reference to the current apply value * * @var array */ - protected $currentApplyValue = null; + protected $currentApplyValues = []; /** * Default context with helper definitions @@ -264,24 +257,11 @@ public function getCurrentContext() return $this->currentContext; } - /** - * @param null|array $values - * @return void - */ - public function pushApplyValues(?array $values) - { - $this->applyValueStack[] = $values; - $this->currentApplyValue = $values; - } - - /** - * @return null|array the topmost "@apply" values as associative array - */ - public function popApplyValues() + public function popApplyValues(array $paths): void { - $lastItem = array_pop($this->applyValueStack); - $this->currentApplyValue = end($this->applyValueStack); - return $lastItem; + foreach ($paths as $path) { + unset($this->currentApplyValues[$path]); + } } /** @@ -422,12 +402,12 @@ public function evaluate(string $fusionPath, $contextObject = null, string $beha // Check if the current "@apply" contain an entry for the requested fusionPath // in which case this value is returned after applying @if and @process rules - if (isset($this->currentApplyValue[$fusionPath])) { + if (isset($this->currentApplyValues[$fusionPath])) { if (isset($fusionConfiguration['__meta']['if']) && $this->evaluateIfCondition($fusionConfiguration, $fusionPath, $contextObject) === false) { return null; } - $appliedValue = $this->currentApplyValue[$fusionPath]['value']; - if (isset($this->currentApplyValue[$fusionPath]['lazy'])) { + $appliedValue = $this->currentApplyValues[$fusionPath]['value']; + if (isset($this->currentApplyValues[$fusionPath]['lazy'])) { $appliedValue = $appliedValue(); } if (isset($fusionConfiguration['__meta']['process'])) { @@ -460,26 +440,27 @@ public function evaluate(string $fusionPath, $contextObject = null, string $beha return null; } + $applyPathsToPop = []; try { - $needToPopApply = $this->prepareApplyValuesForFusionPath($fusionPath, $fusionConfiguration); + $applyPathsToPop = $this->prepareApplyValuesForFusionPath($fusionPath, $fusionConfiguration); $fusionObject = $this->instantiatefusionObject($fusionPath, $fusionConfiguration); $needToPopContext = $this->prepareContextForFusionObject($fusionObject, $fusionPath, $fusionConfiguration, $cacheContext); $output = $this->evaluateObjectOrRetrieveFromCache($fusionObject, $fusionPath, $fusionConfiguration, $cacheContext); } catch (StopActionException $stopActionException) { - $this->finalizePathEvaluation($cacheContext, $needToPopContext, $needToPopApply); + $this->finalizePathEvaluation($cacheContext, $needToPopContext, $applyPathsToPop); throw $stopActionException; } catch (SecurityException $securityException) { - $this->finalizePathEvaluation($cacheContext, $needToPopContext, $needToPopApply); + $this->finalizePathEvaluation($cacheContext, $needToPopContext, $applyPathsToPop); throw $securityException; } catch (RuntimeException $runtimeException) { - $this->finalizePathEvaluation($cacheContext, $needToPopContext, $needToPopApply); + $this->finalizePathEvaluation($cacheContext, $needToPopContext, $applyPathsToPop); throw $runtimeException; } catch (\Exception $exception) { - $this->finalizePathEvaluation($cacheContext, $needToPopContext, $needToPopApply); + $this->finalizePathEvaluation($cacheContext, $needToPopContext, $applyPathsToPop); return $this->handleRenderingException($fusionPath, $exception, true); } - $this->finalizePathEvaluation($cacheContext, $needToPopContext, $needToPopApply); + $this->finalizePathEvaluation($cacheContext, $needToPopContext, $applyPathsToPop); return $output; } @@ -560,17 +541,23 @@ protected function evaluateExpressionOrValueInternal($fusionPath, $fusionConfigu * * @param string $fusionPath * @param array $fusionConfiguration - * @return boolean + * @return array Paths to pop * @throws Exception * @throws RuntimeException * @throws SecurityException * @throws StopActionException */ - protected function prepareApplyValuesForFusionPath($fusionPath, $fusionConfiguration) + protected function prepareApplyValuesForFusionPath($fusionPath, $fusionConfiguration): array { $spreadValues = $this->evaluateApplyValues($fusionConfiguration, $fusionPath); - $this->pushApplyValues($spreadValues); - return true; + if ($spreadValues === null) { + return []; + } + + foreach ($spreadValues as $path => $entry) { + $this->currentApplyValues[$path] = $entry; + } + return array_keys($spreadValues); } /** @@ -618,17 +605,17 @@ protected function prepareContextForFusionObject(AbstractFusionObject $fusionObj * * @param array $cacheContext * @param boolean $needToPopContext - * @param boolean $needToPopApplyValues + * @param array $applyPathsToPop * @return void */ - protected function finalizePathEvaluation($cacheContext, $needToPopContext = false, $needToPopApplyValues = false) + protected function finalizePathEvaluation($cacheContext, $needToPopContext = false, array $applyPathsToPop = []) { if ($needToPopContext) { $this->popContext(); } - if ($needToPopApplyValues) { - $this->popApplyValues(); + if ($applyPathsToPop !== []) { + $this->popApplyValues($applyPathsToPop); } $this->runtimeContentCache->leave($cacheContext); @@ -829,8 +816,8 @@ protected function setPropertiesOnFusionObject(AbstractArrayFusionObject $fusion ObjectAccess::setProperty($fusionObject, $key, $value); } - if (is_array($this->currentApplyValue)) { - foreach ($this->currentApplyValue as $path => $property) { + if (is_array($this->currentApplyValues)) { + foreach ($this->currentApplyValues as $path => $property) { $key = $property['key']; if (isset($property['lazy'])) { $valueAst = [ diff --git a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php index 56701bd5732..e5add0afb0a 100644 --- a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php +++ b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php @@ -59,16 +59,15 @@ public function __construct( public function offsetExists($path) { - return isset($this->keys[$path]); + return array_key_exists($path, $this->keys); } public function offsetGet($path) { - if (!isset($this->valueCache[$path])) { + if (!array_key_exists($path, $this->valueCache)) { $this->runtime->pushContextArray($this->effectiveContext); try { - $this->valueCache[$path] = $this->runtime->evaluate($this->parentPath . '/' . $path, - $this->fusionObject); + $this->valueCache[$path] = $this->runtime->evaluate($this->parentPath . '/' . $path, $this->fusionObject); } finally { $this->runtime->popContext(); } diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php index 146e2b18117..e45504568aa 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/ApplyTest.php @@ -280,6 +280,6 @@ public function rendererWithNestedPropsInApply() { $view = $this->buildView(); $view->setFusionPath('apply/renderWithNestedProps'); - self::assertEquals('example', $view->render()); + self::assertEquals('::example::', $view->render()); } } diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion b/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion index 53a495b2385..07fbfd30c3d 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion +++ b/Neos.Fusion/Tests/Functional/FusionObjects/Fixtures/Fusion/Apply.fusion @@ -146,9 +146,26 @@ apply.renderWithNestedProps = Neos.Fusion:Component { applyValue = "example" applyError = Neos.Fusion:NotImplemented - renderer = Neos.Fusion:Component { + renderer = Neos.Fusion:TestNestedPropsA { + applyValue = ${props.applyValue} + applyError = ${props.applyError} + } +} + +prototype(Neos.Fusion:TestNestedPropsA) < prototype(Neos.Fusion:Component) { + applyValue = null + + renderer = Neos.Fusion:TestNestedPropsB { @apply.fromProps = ${props} + } +} + +prototype(Neos.Fusion:TestNestedPropsB) < prototype(Neos.Fusion:Component) { + applyValue = null - renderer = ${props.applyValue} + renderer = Neos.Fusion:Array { + item_0 = '::' + item_1 = ${props.applyValue} + item_2 = '::' } } From 78f0b14fe0a5ba2775fb19ffcf2777f4732018a6 Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Mon, 20 Apr 2020 14:01:04 +0200 Subject: [PATCH 10/12] Make LazyProps JSON serializable --- .../FusionObjects/Helpers/LazyProps.php | 7 +++- .../FusionObjects/Helpers/LazyPropsTest.php | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php diff --git a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php index e5add0afb0a..29e7c910fae 100644 --- a/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php +++ b/Neos.Fusion/Classes/FusionObjects/Helpers/LazyProps.php @@ -8,7 +8,7 @@ /** * @Flow\Proxy(false) */ -final class LazyProps implements \ArrayAccess, \Iterator +final class LazyProps implements \ArrayAccess, \Iterator, \JsonSerializable { /** @@ -113,4 +113,9 @@ public function rewind() { reset($this->keys); } + + public function jsonSerialize() + { + return iterator_to_array($this); + } } diff --git a/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php b/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php new file mode 100644 index 00000000000..3144f52da14 --- /dev/null +++ b/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php @@ -0,0 +1,36 @@ +getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); + $mockRuntime->expects($this->any())->method('evaluate')->withAnyParameters()->willReturnCallback(function($path) { + return $path; + }); + + $fusionObject = new \Neos\Fusion\FusionObjects\ValueImplementation($mockRuntime, 'test/path', 'Value'); + $lazyProps = new \Neos\Fusion\FusionObjects\Helpers\LazyProps($fusionObject, 'test/path', $mockRuntime, ['foo', 'bar'], ['value' => 42]); + + $serializedProps = json_encode($lazyProps); + $this->assertEquals('{"foo":"test\/path\/foo","bar":"test\/path\/bar"}', $serializedProps); + } +} From 1c61b5d4138f68812295918171e99d65e24e174a Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Mon, 20 Apr 2020 17:59:15 +0200 Subject: [PATCH 11/12] TASK: Add space that is required by styleci --- Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php b/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php index 3144f52da14..6277ec27ad7 100644 --- a/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php +++ b/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php @@ -24,7 +24,7 @@ public function jsonEncodeSerializesAllProps() /** @var Runtime $mockRuntime */ $mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); $mockRuntime->expects($this->any())->method('evaluate')->withAnyParameters()->willReturnCallback(function($path) { - return $path; + return $path; }); $fusionObject = new \Neos\Fusion\FusionObjects\ValueImplementation($mockRuntime, 'test/path', 'Value'); From 6982fb6b530d47ba945cbc19e1d131da5fbe68eb Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Mon, 20 Apr 2020 18:01:03 +0200 Subject: [PATCH 12/12] TASK: Add another space for styleCi --- Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php b/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php index 6277ec27ad7..94d041c1129 100644 --- a/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php +++ b/Neos.Fusion/Tests/Unit/FusionObjects/Helpers/LazyPropsTest.php @@ -23,7 +23,7 @@ public function jsonEncodeSerializesAllProps() { /** @var Runtime $mockRuntime */ $mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); - $mockRuntime->expects($this->any())->method('evaluate')->withAnyParameters()->willReturnCallback(function($path) { + $mockRuntime->expects($this->any())->method('evaluate')->withAnyParameters()->willReturnCallback(function ($path) { return $path; });