Skip to content

Commit

Permalink
[TASK] Remove renderStatic() from condition ViewHelpers
Browse files Browse the repository at this point in the history
Since `renderStatic()` is now deprecated, `AbstractConditionViewHelper` is
adjusted accordingly. Template compilation steps are left as-is except for the
ViewHelper call itself, which now also uses `ViewHelperInvoker`. The
functionality of `renderStatic()` is now integrated into the separate render
methods. Also, special handling for the internal closure arguments is added
to the class.
  • Loading branch information
s2b committed Aug 26, 2024
1 parent 5853226 commit c65ede6
Showing 1 changed file with 59 additions and 48 deletions.
107 changes: 59 additions & 48 deletions src/Core/ViewHelper/AbstractConditionViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@
* or as well use the "then" and "else" child nodes.
*
* @see \TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper for a more detailed explanation and a simple usage example.
* Make sure to NOT OVERRIDE the constructor.
*
* @api
* @todo add missing types with Fluid v5
*/
abstract class AbstractConditionViewHelper extends AbstractViewHelper
{
protected $escapeOutput = false;

private ?\Closure $thenClosure = null;
private ?\Closure $elseClosure = null;

/**
* @var bool
* @var array<array{'condition': \Closure, 'body': \Closure}>
*/
protected $escapeOutput = false;
private array $elseIfClosures = [];

/**
* Initializes the "then" and "else" arguments
Expand All @@ -53,7 +57,7 @@ public function initializeArguments()
* Method which only gets called if the template is not compiled. For static calling,
* the then/else nodes are converted to closures and condition evaluation closures.
*
* @return string the rendered string
* @return mixed
* @api
*/
public function render()
Expand All @@ -64,48 +68,6 @@ public function render()
return $this->renderElseChild();
}

/**
* @param array<string, mixed> $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return mixed
*/
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
{
$mainConditionVerdict = static::verdict($arguments, $renderingContext);
if ($mainConditionVerdict) {
// The condition argument evaluated to true. Return the "then" argument as string,
// execute f:then child or general body closure, or return empty string.
if (isset($arguments['then'])) {
return $arguments['then'];
}
if (isset($arguments['__then'])) {
return $arguments['__then']();
}
return '';
}
if (!empty($arguments['__elseIf'])) {
// The condition argument evaluated to false. For each "f:else if",
// evaluate its condition and return its executed body closure if verdict is true.
foreach ($arguments['__elseIf'] as $elseIf) {
if ($elseIf['condition']()) {
return $elseIf['body']();
}
}
}
if (isset($arguments['else'])) {
// The condition argument evaluated to false. If there
// is an else argument, return as string.
return $arguments['else'];
}
if (!empty($arguments['__else'])) {
// The condition argument evaluated to false. If there is
// an f:else body closure, return its executed body.
return $arguments['__else']();
}
return '';
}

/**
* Static method which can be overridden by subclasses. If a subclass
* requires a different (or faster) decision then this method is the one
Expand All @@ -130,10 +92,23 @@ public static function verdict(array $arguments, RenderingContextInterface $rend
*/
protected function renderThenChild()
{
// Prefer "then" ViewHelper argument if present
if ($this->hasArgument('then')) {
return $this->arguments['then'];
}

// Closure might be present if ViewHelper is called from a cached template
if ($this->thenClosure !== null) {
return ($this->thenClosure)();
}

// The following code can only be evaluated for uncached templates where the node structure
// is still available. If it's not, it has already been executed during compilation and we can
// assume that the condition wasn't met
if (!$this->viewHelperNode instanceof ViewHelperNode) {
return '';
}

$elseViewHelperEncountered = false;
foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
if ($childNode instanceof ViewHelperNode
Expand All @@ -158,15 +133,39 @@ protected function renderThenChild()
* If else attribute is not set, iterates through child nodes and renders ElseViewHelper.
* If else attribute is not set and no ElseViewHelper is found, an empty string will be returned.
*
* @return string rendered ElseViewHelper or an empty string if no ThenViewHelper was found
* @return mixed rendered ElseViewHelper or an empty string if no ThenViewHelper was found
* @api
*/
protected function renderElseChild()
{
// Closures are present if ViewHelper is called from a cached template
if ($this->elseIfClosures !== []) {
// Check each "f:else if" by evaluating its "condition" closure; evaluate and return
// the "body" closure if condition is met
foreach ($this->elseIfClosures as $elseIf) {
if ($elseIf['condition']()) {
return $elseIf['body']();
}
}
}

// Prefer "else" ViewHelper argument if present
if ($this->hasArgument('else')) {
return $this->arguments['else'];
}

// Closure might be present if ViewHelper is called from a cached template
if ($this->elseClosure !== null) {
return ($this->elseClosure)();
}

// The following code can only be evaluated for uncached templates where the node structure
// is still available. If it's not, it has already been executed during compilation and we can
// assume that the condition wasn't met
if (!$this->viewHelperNode instanceof ViewHelperNode) {
return '';
}

/** @var ViewHelperNode|null $elseNode */
$elseNode = null;
foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
Expand All @@ -186,6 +185,18 @@ protected function renderElseChild()
return $elseNode instanceof ViewHelperNode ? $elseNode->evaluate($this->renderingContext) : '';
}

/**
* Receives special ViewHelper arguments from compiled templates containing the
* individual renderChildrenClosures for the condition cases (then/elseif/else)
* and stores them in class properties for later use.
*/
public function handleAdditionalArguments(array $arguments): void
{
$this->thenClosure = $arguments['__then'] ?? null;
$this->elseIfClosures = $arguments['__elseIf'] ?? [];
$this->elseClosure = $arguments['__else'] ?? null;
}

/**
* Optimized version combining default convert() / compile() into one
* method: The condition VHs dissect children and looks for then, else
Expand Down Expand Up @@ -297,7 +308,7 @@ final public function convert(TemplateCompiler $templateCompiler): array
$accumulatedArgumentInitializationCode . chr(10) .
$argumentInitializationCode,
'execution' => sprintf(
'%s::renderStatic(%s, static fn() => \'\', $renderingContext)' . chr(10),
'$renderingContext->getViewHelperInvoker()->invoke(%s::class, %s, $renderingContext)' . chr(10),
get_class($this),
$argumentsVariableName,
),
Expand Down

0 comments on commit c65ede6

Please sign in to comment.