From 04356011a0438ddf9924e5b79e04e672f102b1a6 Mon Sep 17 00:00:00 2001 From: Claus Due Date: Thu, 14 Feb 2019 17:21:43 +0100 Subject: [PATCH] [FEATURE] Allow defining arguments for f:render with f:argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch allows you to define arguments that get passed to f:render by using f:argument inside the tag contents. ```xml Special value for arg1 variable Special value for arg2 variable ``` Any argument specified with f:variable this way will override the argument of the same name if it was passed in the “arguments” array as well. The combined result will be used as variables for the sub-rendering call. References: #427 --- .../Resources/Private/Singles/Variables.html | 11 +++ .../ViewHelperVariableContainer.php | 64 +++++++++++++++++ src/ViewHelpers/ArgumentViewHelper.php | 68 +++++++++++++++++++ src/ViewHelpers/RenderViewHelper.php | 15 +++- tests/Functional/ExamplesTest.php | 4 +- 5 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/ViewHelpers/ArgumentViewHelper.php 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' => [