diff --git a/examples/Resources/Private/Singles/Variables.html b/examples/Resources/Private/Singles/Variables.html index fc56707d7..829b07bf0 100644 --- a/examples/Resources/Private/Singles/Variables.html +++ b/examples/Resources/Private/Singles/Variables.html @@ -40,6 +40,12 @@ } }"/> + + + First argument + Second argument + + @@ -49,3 +55,8 @@ Received $array.xyz.foobar with value {array.xyz.foobar} Received $myVariable with value {myVariable} + + +Input argument "arg1" was: {arg1} +Input argument "arg2" was: {arg2} + diff --git a/src/Core/ViewHelper/ViewHelperVariableContainer.php b/src/Core/ViewHelper/ViewHelperVariableContainer.php index 5e60dc973..6a2d1493c 100644 --- a/src/Core/ViewHelper/ViewHelperVariableContainer.php +++ b/src/Core/ViewHelper/ViewHelperVariableContainer.php @@ -6,6 +6,7 @@ * See LICENSE.txt that was shipped with this package. */ +use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface; use TYPO3Fluid\Fluid\View\ViewInterface; /** @@ -29,6 +30,69 @@ class ViewHelperVariableContainer */ protected $view; + /** + * @var VariableProviderInterface[] + */ + protected $delegates = []; + + /** + * Push a new delegate variable container to a stack. + * + * If a ViewHelper requires a storage to collect variables which, for + * example, are filled by evaluating the child (tag content) closure, + * this method can be used to add a special delegate variable container + * stored in a stack. Once the variables you need to collect have been + * collected, calling `popDelegateVariableProvider` removes the delegate + * from the stack. + * + * The point of a stack is to avoid resetting a storage every time a + * ViewHelper is rendered. In the case of `f:render` it means one storage + * is created and filled for every one call to the ViewHelper. + * + * It is VITAL that you also "pop" any delegate you push to this stack! + * + * @param VariableProviderInterface $variableProvider + */ + public function pushDelegateVariableProvider(VariableProviderInterface $variableProvider) + { + $this->delegates[] = $variableProvider; + } + + /** + * Get the topmost delegate variable container that was previously pushed + * onto the stack by pushDelegateVariableContainer(). This method returns + * a reference to the storage that was last added to the stack without + * removing the variable provider from the stack. + * + * Is used in ViewHelpers that assign variables in variable providers in + * the stack - as a means to get the variable storage used by the "closest + * parent", e.g. when called in `f:argument` used inside `f:render`, will + * read the delegate variable provider inserted by that parent `f:render`. + * + * @return VariableProviderInterface|null + */ + public function getTopmostDelegateVariableProvider() + { + return end($this->delegates) ?: null; + } + + /** + * Return and REMOVE the topmost delegate variable provider. This method + * must be called after you finish sub-rendering with a delegated variable + * provider that was added with `pushDelegateVariableProvider`. Calling + * the method removes the delegate and returns the stack to the previous + * state it was in. + * + * To avoid removing from the stack, use `getTopmostDelegateVariableProvider`. + * + * @param string $viewHelperClassName + * @return VariableProviderInterface|null + */ + public function popDelegateVariableProvider() + { + return array_pop($this->delegates); + } + /** * Add a variable to the Variable Container. Make sure that $viewHelperName is ALWAYS set * to your fully qualified ViewHelper Class Name diff --git a/src/ViewHelpers/ArgumentViewHelper.php b/src/ViewHelpers/ArgumentViewHelper.php new file mode 100644 index 000000000..f286d1fee --- /dev/null +++ b/src/ViewHelpers/ArgumentViewHelper.php @@ -0,0 +1,68 @@ + + * Value1 + * Value2 + * + * + * Which is the equivalent of: + * + * + * + * But has the benefit that writing ViewHelper expressions or + * other more complex syntax becomes much easier because you + * can use tag syntax (tag content becomes argument value). + * + * @api + */ +class ArgumentViewHelper extends AbstractViewHelper +{ + use CompileWithContentArgumentAndRenderStatic; + + /** + * @return void + */ + public function initializeArguments() + { + $this->registerArgument('value', 'mixed', 'Value to assign. If not in arguments then taken from tag content'); + $this->registerArgument('name', 'string', 'Name of variable to create', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return null + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $delegateVariableProvider = $renderingContext->getViewHelperVariableContainer()->getTopmostDelegateVariableProvider(); + if ($delegateVariableProvider) { + $delegateVariableProvider->add($arguments['name'], $renderChildrenClosure()); + } + } + +} diff --git a/src/ViewHelpers/RenderViewHelper.php b/src/ViewHelpers/RenderViewHelper.php index ebcab45f4..18d2af408 100644 --- a/src/ViewHelpers/RenderViewHelper.php +++ b/src/ViewHelpers/RenderViewHelper.php @@ -87,7 +87,7 @@ class RenderViewHelper extends AbstractViewHelper { use CompileWithRenderStatic; - + /** * @var boolean */ @@ -121,7 +121,20 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl $delegate = $arguments['delegate']; /** @var RenderableInterface $renderable */ $renderable = $arguments['renderable']; + + // Prepare a delegate variable provider that will be possible to extract after rendering the child closure. + // Any variable defined therein gets used as argument and overrides any argument of the same name. + // Note: not using late static binding here is a conscious decision: if late static binding had been used + // then f:variable would not be able to reference this ViewHelper class' stack variable correctly. + $viewHelperVariableContainer = $renderingContext->getViewHelperVariableContainer(); + $collector = $renderingContext->getVariableProvider()->getScopeCopy($variables); + + $viewHelperVariableContainer->pushDelegateVariableProvider($collector); + $tagContent = $renderChildrenClosure(); + + $variables = $viewHelperVariableContainer->popDelegateVariableProvider()->getAll(); + if ($arguments['contentAs']) { $variables[$arguments['contentAs']] = $tagContent; } diff --git a/tests/Functional/ExamplesTest.php b/tests/Functional/ExamplesTest.php index e0659f1c2..c87a3f3d0 100644 --- a/tests/Functional/ExamplesTest.php +++ b/tests/Functional/ExamplesTest.php @@ -222,7 +222,9 @@ public function getExampleScriptTestValues() 'Received $array.printf with formatted string Formatted string, value: formatted', 'Received $array.baz with value 42', 'Received $array.xyz.foobar with value Escaped sub-string', - 'Received $myVariable with value Nice string' + 'Received $myVariable with value Nice string', + 'Input argument "arg1" was: First argument', + 'Input argument "arg2" was: Second argument', ] ], 'example_variableprovider.php' => [