Skip to content

Fluid Compilable - Speed it up

Using your handcrafted viewhelper in a long for-loop can easily lead to massive speed problems. Leveraging the power of the Fluid CompilableInterface might be your way to go then.

You may have noticed already that the TYPO3 CMS Core got a nice improvement recently. A big amount of Fluid ViewHelpers have been changed to use the \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface.

But what's so special about this?

The problem

Lets look at a simple example:

<div xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:rx="http://typo3.org/ns/Reelworx/MyExt/ViewHelpers">
<f:for each="{products}" as="product">
<rx:product.format product="{product}" />
</f:for>
</div>

What we do here is nothing really special. We render a list of products by using a custom viewhelper of ours.

The viewhelper might look like:

<?php
namespace Reelworx\MyExt\ViewHelpers\Product;

...

class FormatViewHelper extends AbstractViewHelper {
public function render($product) {
    return htmlspecialchars(substr($product->getCode(), 3));
  }
}

We just do some basic string manipulation and return the result (of course escaped!) back to the output.

 

When the Fluid engine now renders the template, it stumbles over the viewhelper. The viewhelper-class is resolved and a new instance of that class is created. After some initialization, the render method is finally called. For the sake of performance the original template is compiled to a single "optimized" PHP file and stored in the cache.

Nothing really wrong so far, right? Should work pretty fast, it isn't that much code in the end.

.... well, until your amount of products starts to grow. Running this tiny piece of code for 10000 products could easily lead you to a point where you start asking DevOps if the server was replaced by a Raspberry Pi. (Disclaimer: Raspberries are great - but maybe not for hosting your major product store)

What actually happens in the background might not be clear to the non-involved integrator/programmer. The optimized template code still creates a new instance of the viewhelper for every product we render. The number of viewhelper objects therefore scales with O(n), n being the number of products.

While PHP is really good at juggling huge amounts of objects, at some point there is a limit (even if that is RAM).

We can do better!

CompilableInterface

Fluid ships a rather unknown interface, which can be used for viewhelpers. To be honest, the name doesn't really reveal what eventually happens when it is used.

While some readers may have heard already about "a template being compiled", it has to be clarified that this is a different "compile" process than the cached and compiled templates. Viewhelpers implementing the CompilableInterface are required to provide a static render method next to the normal method. Having this static method allows to avoid the creation of a dedicated object. Having the viewhelper compilable implicitly means that it is stateless, hence there is no need to really create an object.

When the Fluid engine is compiling a template and finds a viewhelper which implements this very interface, it generates a different code for the cached template. It leverages the knowledge about the viewhelper being stateless and only calls the static render method from within the template.

With this nice trick we win a lot in our sample case. Instead of creating 10000 instances of our viewhelper class, we only call the static method. Quite a time/memory saver, especially if you use more than only one viewhelper.

This is how the improved viewhelper code from above looks like

<?php
namespace Reelworx\MyExt\ViewHelpers\Product;

...

class FormatViewHelper extends AbstractViewHelper implements CompilableInterface {

public function render($product) {
return static::renderStatic(
            array( 'product' => $product ),
            $this->buildRenderChildrenClosure(),
            $this->renderingContext
        );
    }

static public function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) {
       return htmlspecialchars(substr($arguments['product']->getCode(), 3));
    }
}

Rationale

Take a look at your Fluid templates and the viewhelpers you use. If you conclude that a viewhelper is used more often, for instance in a loop, consider changing this viewhelper to implement the \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface in order to significantly improve the performance of your template.

 

This information applies to TYPO3 CMS 6.2 and 7.4