Developing a custom ViewHelper 

This chapter demonstrates how to write a custom Fluid ViewHelper in TYPO3.

A "Gravatar" ViewHelper is created, which uses an email address as parameter and shows the picture from gravatar.com if it exists.

The official documentation of Fluid for writing custom ViewHelpers can be found within the Fluid documentation: Creating ViewHelpers.

Fluid 

The custom ViewHelper is not part of the default distribution. Therefore a namespace import is necessary to use this ViewHelper. In the following example, the namespace \MyVendor\MyExtension\ViewHelpers is imported with the prefix m. Now, all tags starting with m: are interpreted as ViewHelper from within this namespace. For further information about namespace import, see Import ViewHelper namespaces.

The ViewHelper should be given the name "gravatar" and take an email address and an optional alt-text as a parameters. The ViewHelper is called in the template as follows:

EXT:my_extension/Resources/Private/Templates/SomeTemplate.html
<html
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
    xmlns:m="http://typo3.org/ns/MyVendor/MyExtension/ViewHelpers"
    data-namespace-typo3-fluid="true"
>
    <m:gravatar emailAddress="username@example.org" alt="Gravatar icon of user" />
</html>
Copied!

AbstractViewHelper implementation 

Every ViewHelper is a PHP class. For the Gravatar ViewHelper, the fully qualified name of the class is \MyVendor\MyExtension\ViewHelpers\GravatarViewHelper.

Example 1: EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

final class GravatarViewHelper extends AbstractViewHelper
{
    protected $escapeOutput = false;

    public function initializeArguments(): void
    {
        // registerArgument($name, $type, $description, $required, $defaultValue, $escape)
        $this->registerArgument(
            'emailAddress',
            'string',
            'The email address to resolve the gravatar for',
            true,
        );
        $this->registerArgument(
            'alt',
            'string',
            'The optional alt text for the image',
        );
    }

    public function render(): string
    {
        $emailAddress = $this->arguments['emailAddress'];
        $altText = $this->arguments['alt'] ?? '';

        // this is improved with the TagBasedViewHelper (see below)
        return sprintf(
            '<img src="https://www.gravatar.com/avatar/%s" alt="%s">',
            md5($emailAddress),
            htmlspecialchars($altText),
        );
    }
}
Copied!

AbstractViewHelper 

line 9 extends AbstractViewHelper

Every ViewHelper must inherit from the class \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper .

A ViewHelper can also inherit from subclasses of \AbstractViewHelper , for example from \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper . Several subclasses are offering additional functionality. The \AbstractTagBasedViewHelper will be explained later on in this chapter in detail.

Disable escaping the output 

line 11 protected $escapeOutput = false;

By default, all output is escaped by htmlspecialchars() to prevent cross site scripting.

Setting the property $escapeOutput to false is necessary to prevent escaping of ViewHelper output.

By setting the property $escapeChildren to false, escaping of the tag content (its child nodes) can be disabled. If this is not set explicitly, the value will be determined automatically: If $escapeOutput: is true, $escapeChildren will be disabled to prevent double escaping. If $escapeOutput: is false, $escapeChildren will be enabled unless disabled explicitly.

Passing in children is explained in Prepare ViewHelper for inline syntax.

initializeArguments() 

line 13 public function initializeArguments(): void

The Gravatar ViewHelper must hand over the email address which identifies the Gravatar. An alt text for the image is passed as optional parameter.

ViewHelpers have to register (line 16, $this->registerArgument()) parameters. The registration happens inside method initializeArguments().

In the example above, the ViewHelper receives the argument emailAddress (line 17) of type string (line 18) which is mandatory (line 19). The optional argument alt is defined in lines 22-26.

These arguments can be accessed through the array $this->arguments, in method render().

render() 

line 29 public function render(): string

The method render() is called once the ViewHelper is rendered. Its return value can be directly output in Fluid or passed to another ViewHelper.

In line 30 an 31 we retrieve the arguments from the $arguments class property. alt is an optional argument and therefore nullable. Fluid ensures, the declared type is passed for non-null values. These arguments can contain user input.

When escapting is diabled, the render() method is responsible to prevent XSS attacks.

Therefore all arguments must be sanitized before they are returned.

Passing the email address through md5() ensures that we only have a hexadecimal number, it can contain no harmful chars.

The alt text is passed through htmlspecialchars(), therefore potentially harmful chars are escaped.

Creating HTML/XML tags with the AbstractTagBasedViewHelper 

For ViewHelpers which create HTML/XML tags, Fluid provides an enhanced base class: \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper . This base class provides an instance of \TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder that can be used to create HTML-tags. It takes care of the syntactically correct creation and, for example, escapes single and double quotes in attribute values.

line 11 protected $tagName = 'img' configures the name of the HTML/XML tag to be output.

All ViewHelpers extending \AbstractTagBasedViewHelper can receive arbitrary tag attributes which will be appended to the resulting HTML tag and escaped automatically. For example we do not have to declare or escape the alt argument as we did in Example 1.

Because the Gravatar ViewHelper creates an <img> tag the use of the \TagBuilder , stored in class property $this->tag is advised:

EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php (Example 2, tag-based)
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

final class GravatarViewHelper extends AbstractTagBasedViewHelper
{
    protected $tagName = 'img';

    public function initializeArguments(): void
    {
        parent::initializeArguments();
        $this->registerArgument(
            'emailAddress',
            'string',
            'The email address to resolve the gravatar for',
            true,
        );
        // The alt argument will be automatically registered
    }

    public function render(): string
    {
        $emailAddress = $this->arguments['emailAddress'];
        $this->tag->addAttribute(
            'src',
            'https://www.gravatar.com/avatar/' . md5($emailAddress),
        );
        return $this->tag->render();
    }
}
Copied!

line 32 $this->tag->render() creates the <img> tag with all explicitly added arguments (line 28-31 $this->tag->addAttribute()) and all arbitrary tag attributes passed to the ViewHelper when it is used.

AbstractTagBasedViewHelper 

line 6 class GravatarViewHelper extends AbstractTagBasedViewHelper

The ViewHelper does not inherit directly from \AbstractViewHelper but from \AbstractTagBasedViewHelper , which provides and initializes the \TagBuilder and passes on and escapes arbitrary tag attributes.

$tagName 

line 9 protected $tagName = 'img';

There is a class property $tagName which stores the name of the tag to be created ( <img>).

$this->tag->addAttribute() 

line 28 - 31 $this->tag->addAttribute(...)

The tag builder is available as class property $this->tag. It offers the method TagBuilder::addAttribute() to add new tag attributes. In our example the attribute src is added to the tag.

$this->tag->render() 

line 32 return $this->tag->render();

The GravatarViewHelper creates an img tag builder, which has a method named render(). After configuring the tag builder instance, the rendered tag markup is returned.

Insert optional arguments with default values 

An optional size for the image can be provided to the Gravatar ViewHelper. This size parameter will determine the height and width in pixels of the image and can range from 1 to 512. When no size is given, an image of 80px is generated.

The render() method can be improved like this:

EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

final class GravatarViewHelper extends AbstractTagBasedViewHelper
{
    public function initializeArguments(): void
    {
        $this->registerArgument(
            'emailAddress',
            'string',
            'The email address to resolve the gravatar for',
            true,
        );
        $this->registerArgument(
            'size',
            'integer',
            'The size of the gravatar, ranging from 1 to 512',
            false,
            80,
        );
    }

    public function render(): string
    {
        $emailAddress = $this->arguments['emailAddress'];
        $size = $this->arguments['size'];
        $this->tag->addAttribute(
            'src',
            sprintf(
                'http://www.gravatar.com/avatar/%s?s=%s',
                md5($emailAddress),
                urlencode($size),
            ),
        );
        return $this->tag->render();
    }
}
Copied!

With this setting of a default value and setting the fourth argument to false, the size attribute becomes optional.

Prepare ViewHelper for inline syntax 

So far, the Gravatar ViewHelper has focused on the tag structure of the ViewHelper. The call to render the ViewHelper was written with tag syntax, which seemed obvious because it itself returns a tag:

EXT:my_extension/Resources/Private/Templates/SomeTemplate.html
<m:gravatar emailAddress="{post.author.emailAddress}" />
Copied!

Alternatively, this expression can be written using the inline notation:

EXT:my_extension/Resources/Private/Templates/SomeTemplate.html
{m:gravatar(emailAddress: post.author.emailAddress)}
Copied!

One should see the Gravatar ViewHelper as a kind of post-processor for an email address and would allow the following syntax:

EXT:my_extension/Resources/Private/Templates/SomeTemplate.html
{post.author.emailAddress -> m:gravatar()}
Copied!

This syntax places focus on the variable that is passed to the ViewHelper as it comes first.

The syntax {post.author.emailAddress -> m:gravatar()} is an alternative syntax for <m:gravatar>{post.author.emailAddress}</m:gravatar>. To support this, the email address comes either from the argument emailAddress or, if it is empty, the content of the tag should be interpreted as email address.

This is typically used with formatting ViewHelpers. These ViewHelpers all support both tag mode and inline syntax.

Depending on the implemented method for rendering, the implementation is different:

To fetch the content of the ViewHelper the method renderChildren() is available in the \AbstractViewHelper . This returns the evaluated object between the opening and closing tag.

EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php (Example 3, with content arguments)
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

final class GravatarViewHelper extends AbstractTagBasedViewHelper
{
    protected $tagName = 'img';

    public function initializeArguments(): void
    {
        $this->registerArgument(
            'emailAddress',
            'string',
            'The email address to resolve the gravatar for',
            // The argument is optional now
        );
    }

    public function render(): string
    {
        $emailAddress = $this->renderChildren();

        // The children of the ViewHelper might be empty now
        if ($emailAddress === null) {
            throw new \Exception(
                'The Gravatar ViewHelper expects either the '
                . 'argument "emailAddress" or the content to be set. ',
                1726035545,
            );
        }
        // Or someone could pass a non-string value
        if (!is_string($emailAddress) || !filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
            throw new \Exception(
                'The Gravatar ViewHelper expects a valid ' .
                'e-mail address as input. ',
                1726035546,
            );
        }

        $this->tag->addAttribute(
            'src',
            'https://www.gravatar.com/avatar/' . md5($emailAddress),
        );

        return $this->tag->render();
    }

    public function getContentArgumentName(): string
    {
        return 'emailAddress';
    }
}
Copied!

Handle additional arguments 

All ViewHelpers implementing \AbstractTagBasedViewHelper can receive arbitrary tag attributes which will be appended to the resulting HTML tag. In the past, this was only possible for explicitly registered arguments.

The different render methods 

ViewHelpers can have one or more of the following methods for implementing the rendering. The following section will describe the differences between the implementations.

compile()-Method 

This method can be overwritten to define how the ViewHelper should be compiled. That can make sense if the ViewHelper itself is a wrapper for another native PHP function or TYPO3 function. In that case, the method can return the call to this function and remove the need to call the ViewHelper as a wrapper at all.

The compile() has to return the compiled PHP code for the ViewHelper. Also the argument $initializationPhpCode can be used to add further PHP code before the execution.

Example implementation:

EXT:my_extension/Classes/ViewHelpers/StrtolowerViewHelper.php
<?php

declare(strict_types=1);

namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

final class StrtolowerViewHelper extends AbstractViewHelper
{
    public function initializeArguments(): void
    {
        $this->registerArgument('string', 'string', 'The string to lowercase.', true);
    }

    public function compile(
        $argumentsName,
        $closureName,
        &$initializationPhpCode,
        ViewHelperNode $node,
        TemplateCompiler $compiler,
    ): string {
        return sprintf("strtolower(%s['string'])", $argumentsName);
    }
}
Copied!

render() method 

Most of the time, this method is implemented.

How to access classes in the ViewHelper implementation 

Custom ViewHelper implementations support Dependency injection.

You can, for example, inject the ConnectionPool to access the database by using the database abstraction layer DBAL.

Some objects depend on the current context and can be fetched from the rendering context:

Accessing the current Request in a ViewHelper implementation 

You can use a render() method in the ViewHelper implementation to get the current \ServerRequestInterface object from the RenderingContext :

EXT:my_extension/Classes/ViewHelpers/SomeViewHelper.php
public function render()
{
    $request = $this->renderingContext->getRequest();
    return 'Hello World!';
}
Copied!

Using stdWrap / fetching the current ContentObject in a ViewHelper implementation 

You can access the ContentObjectRenderer from the \ServerRequestInterface :

EXT:my_extension/Classes/ViewHelpers/SomeViewHelper.php
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;

public function render()
{
    $request = $this->renderingContext->getRequest();
    $cObj = $request->getAttribute('currentContentObject');
    return $cObj->stdWrap('Hello World', ['wrap' => '|!']);
}
Copied!

Deprecated since version 13.4

The class \TypoScriptFrontendController and its global instance $GLOBALS['TSFE'] , which were formerly used to fetch the ContentObjectRenderer, have been marked as deprecated. The class will be removed in TYPO3 v14. See TSFE for migration steps.