Search code examples
typo3fluidview-helperstypo3-7.6.x

TYPO3 ver. 7.6.2 - Condition ViewHelpers evaluated only once


Problem: I wrote a conditional VH (extending AbstractConditionViewHelper) and it works as usually, anyway I realized that in non-cached version it is evaluated only once. Initialy I thought that's my bug, but checked common <f:if> and the problem is identical :S

In general when I visit my page for the first time, condition is evaluated and valid result is given, but when I'll refresh the page VH isn't called anymore (checked by setting breakpoint inside the VH) and VH is always treated as FALSE. Only any change in view's code will cause that VH will be evaluated once, and again next refresh(es) won't call VH anymore.

typo3conf/ext/toolbox/Classes/ViewHelpers/IsFieldRequiredViewHelper.php:

<?php
namespace Vendor\Toolbox\ViewHelpers;

class IsFieldRequiredViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {

    /**
     * @param string $fieldName      Current field name
     * @param string $requiredFields List of required names separated by commas
     *
     * @return string the rendered string
     */
    public function render($fieldName, $requiredFields) {

        $requiredArray = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $requiredFields, true);

        return (in_array($fieldName, $requiredArray))
            ? $this->renderThenChild()
            : $this->renderElseChild();
    }
}

Usage:

{namespace toolbox=Vendor\Toolbox\ViewHelpers}

<toolbox:isFieldRequired fieldName="foo" requiredFields="foo, bar, baz">
    <f:then>TRUE</f:then>
    <f:else>FALSE</f:else>
</toolbox:isFieldRequired>

For the first hit I have TRUE but later only FALSE.

Any suggestions? Did I missed some important change in ViewHelpers API since 7.x- ?

Of course if extension is cached it will be not visible, as the first hit will be saved in cache with proper VH return.


Solution

  • The AbstractConditionViewHelper implements the TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface interface. This means that it implements a compile method that actually returns PHP code that will be stored in the compiled Fluid views.

    Have a look at this method in the source code:

     public function compile($argumentsVariableName, $renderChildrenClosureVariableName, &$initializationPhpCode, \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode, \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler)
     {
         foreach ($syntaxTreeNode->getChildNodes() as $childNode) {
             if ($childNode instanceof ViewHelperNode
                 && $childNode->getViewHelperClassName() === ThenViewHelper::class) {
                 $childNodesAsClosure = $templateCompiler->wrapChildNodesInClosure($childNode);
                 $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsVariableName, $childNodesAsClosure) . LF;
             }
             if ($childNode instanceof ViewHelperNode
                 && $childNode->getViewHelperClassName() === ElseViewHelper::class) {
                 $childNodesAsClosure = $templateCompiler->wrapChildNodesInClosure($childNode);
                 $initializationPhpCode .= sprintf('%s[\'__elseClosure\'] = %s;', $argumentsVariableName, $childNodesAsClosure) . LF;
             }
         }
    
         return sprintf('%s::renderStatic(%s, %s, $renderingContext)',
             get_class($this), $argumentsVariableName, $renderChildrenClosureVariableName);
     }
    

    Once compiled, the render() method will not be called anymore (it will on the first invocation, when the template is not yet compiled). Instead, the renderStatic() method will be called.

    Solution: You can either

    1. also override the renderStatic() method and implement your ViewHelper logic there (again)
    2. not implement the render() method and simply overwrite the static evaluateCondition($arguments) method. This method is actually designed to be overwritten -- the default implementations of both render() and renderStatic() call this method:

      This method decides if the condition is TRUE or FALSE. It can be overriden in extending viewhelpers to adjust functionality.

      static protected function evaluateCondition($arguments = null)
      {
          $requiredArray = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $arguments['requiredFields'], true);
          return (in_array($arguments['fieldName'], $requiredArray));
      }