Attention
TYPO3 v9 has reached its end-of-life September 30th, 2021 and is not maintained by the community anymore. Looking for a stable version? Use the version switch on the top left.
You can order Extended Long Term Support (ELTS) here: TYPO3 ELTS.
Developing a custom ViewHelper¶
Developing a custom ViewHelper is often necessary in extension development. This chapter will demonstrate how to write a custom ViewHelper for the blog example extension.
The official documentation of Fluid for writing custom ViewHelpers can be found within Fluid documentation at https://github.com/TYPO3/Fluid/blob/master/doc/FLUID_CREATING_VIEWHELPERS.md.
The Example ViewHelper¶
"Avatar" images are pictures or icons that for example are dedicated to the author of an article in blogs or on forums. The photos of blog authors and forum moderators are mostly stored on the appropriate server. With users that only want to ask a question or to comment a blog post, this is not the case. To allow them to supply their article with an icon, a service called gravatar.com is available. This online service makes sure that an email address is assigned to a certain avatar picture.
A web application that wants to check if an avatar picture exists for a given
email address has to send a checksum (with the hash function md5()
) of the
email address to the service and receives the picture for display.
This section explains how to write a ViewHelper that uses an email address as parameter and shows the picture from gravatar.com if it exists.
The custom ViewHelper is not part of the default distribution, therefore an own
namespace import is necessary to use this ViewHelper. In the following example
the namespace \MyVendor\BlogExample\ViewHelpers
is imported with the
prefix blog
. Now, all tags starting with blog:
are interpreted as
ViewHelper from within this namespace:
{namespace blog=MyVendor\BlogExample\ViewHelpers}
For further information about namespace import, see Importing namespaces.
The ViewHelper should be given the name "gravatar" and only take an email address as parameter. The ViewHelper is called in the template as follows:
<blog:gravatar emailAddress="username@example.org" />
See Global namespace import for information how to import namespaces globally.
The implementation¶
Every ViewHelper is a PHP class. For the Gravatar ViewHelper the name of the
class is \MyVendor\BlogExample\ViewHelpers\GravatarViewHelper
.
Following the naming conventions for Extbase extensions the ViewHelper skeleton
is created in the PHP file EXT:blog_example/Classes/ViewHelpers/GravatarViewHelper.php
:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class GravatarViewHelper extends AbstractViewHelper
{
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
// Implementation ...
}
}
Note
Prior to Fluid 2.4, the method renderStatic()
required a use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
at
the beginning of the ViewHelper class file and additionally a use CompileWithRenderStatic;
inside of the ViewHelper class.
Otherwise the method render()
would not be found. If you use the latest TYPO3 9 or higher, this should no longer be necessary.
Every ViewHelper must inherit from the class
\TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper
.
A ViewHelper can also inherit from subclasses of AbstractViewHelper
, e.g.
from \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper
.
Several subclasses are offering additional functionality. The
TagBasedViewHelper
will be explained later on in this chapter in detail.
In addition every ViewHelper needs a method renderStatic()
, which is called
once the ViewHelper will be displayed in the template. The return value of the
method is copied directly into the complete output. If the above example is
extended like the following:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class GravatarViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
return 'World';
}
}
And called like this in the template:
{namespace blog=MyVendor\BlogExample\ViewHelpers}
Hello <blog:gravatar />
The displayed result will be Hello World
.
Register arguments of ViewHelpers¶
The Gravatar
ViewHelper must hand over the email address on which identifies
the Gravatar.
This is the last remaining piece before the implementation can be completed.
All arguments of a ViewHelper must be registered. Every ViewHelper has to
declare explicitly which parameters are accepted. The registration happens
inside initializeArguments()
:
public function initializeArguments()
{
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
}
While this works for most use cases, there are some when arguments with a given
prefix should be allowed, e.g. data-
arguments. Therefore it's possible to
handle additional arguments. For further information see
Handle additional arguments.
This way the ViewHelper receives the argument emailAddress
of type string
.
The type is given by the second argument of the registerTagAttribute()
.
The arguments are registered via $this->registerArgument($name, $type,
$description, $required, $defaultValue)
. These arguments can be accessed
through the array $arguments
, which is passed into the method.
Tip
Sometimes arguments can take various types. In this case the type mixed
should be used.
Finally the output of the img
tag needs to be implemented:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class GravatarViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
public function initializeArguments()
{
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
return '<img src="http://www.gravatar.com/avatar/' .
md5($arguments['emailAddress']) .
'" />';
}
}
Escaping of Output¶
The above implementation still does not provide the expected result. The output for the following usage:
<blog:gravatar emailAddress="username@example.org" />
Does not result in:
<img src="http://www.gravatar.com/avatar/5f0efb20de5ecfedbe0bf5e7c12353fe" />
Instead the result is:
<img src="http://www.gravatar.com/avatar/5f0efb20de5ecfedbe0bf5e7c12353fe" />
By default all output is escaped by htmlspecialchars
to prevent cross site scripting.
To allow unescaped HTML output, escaping has to be explicitly disabled. This is done
by setting the class property $escapeOutput
to false
:
protected $escapeOutput = false;
To make the above implementation work, the property is set to false. The output
is no longer escaped and the <img>
-Tag is now rendered as expected.
If escaping of children is disabled, no nodes that are passed with inline
syntax or values used as tag content will be escaped. Note that
$escapeOutput
takes priority: if it is disabled, escaping of child nodes
is also disabled unless explicitly enabled.
protected $escapeChildren = false;
Passing in children is explained in Prepare ViewHelper for inline syntax.
Creating HTML/XML tags using TagBasedViewHelper¶
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.
Attention
Correctly escaping the attributes is mandatory as it affects security and prevents cross site scripting attacks.
The GravatarViewHelper
will now make use of the
TagBasedViewHelper
. Because the Gravatar ViewHelper creates an
img
tag the use of the
\TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder
is advised:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
class GravatarViewHelper extends AbstractTagBasedViewHelper
{
protected $tagName = 'img';
public function initializeArguments()
{
parent::initializeArguments();
$this->registerUniversalTagAttributes();
$this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
}
public function render()
{
$this->tag->addAttribute(
'src',
'http://www.gravatar.com/avatar/' . md5($this->arguments['emailAddress'])
);
return $this->tag->render();
}
}
What is different in this code?
First of all, the ViewHelper does not inherit directly from
AbstractViewHelper
but from TagBasedViewHelper
, which provides
and initializes the tag builder. Beyond that there is a class property
$tagName
which stores the name of the tag to be created. Furthermore the
tag builder is available at $this->tag
. It offers the method
addAttribute()
to add new tag attributes. In our example the attribute
src
is added to the tag, with the value assigned one line above it. Finally,
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.
Note
As $this->tag
is an instance variable, render()
is used to
generate the output. renderStatic()
would have no access. For further
information take a look at The different render methods.
Notice that the attribute $escapeOutput
is no longer necessary.
Furthermore the TagBasedViewHelper
offers assistance for ViewHelper
arguments that should recur directly and unchanged as tag attributes. These
should be output directly must be registered in initializeArguments()
with the method $this->registerTagAttribute($name, $type, $description, $required = false)
.
If support for the <img>
attribute alt
should be provided in the ViewHelper, this can be done by initializing this in
initializeArguments()
in the following way:
public function initializeArguments()
{
$this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}
For registering the universal attributes id, class, dir, style, lang, title,
accesskey and tabindex there is a helper method
registerUniversalTagAttributes()
available.
If support for universal attributes should be provided and in addition to the
alt
attribute in the Gravatar ViewHelper the following
initializeArguments()
method will be necessary:
public function initializeArguments()
{
parent::initializeArguments();
$this->registerUniversalTagAttributes();
$this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}
Insert optional arguments¶
An optional size for the image can be provided to the Gravatar ViewHelper. This size parameter will be used to 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:
public function initializeArguments()
{
$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()
{
$this->tag->addAttribute(
'src',
'http://www.gravatar.com/avatar/' .
md5($this->arguments['emailAddress']) .
'?s=' . urlencode($this->arguments['size'])
);
return $this->tag->render();
}
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:
<blog:gravatar emailAddress="{post.author.emailAddress}" />
Alternatively this expression can be written using inline notation:
{blog:gravatar(emailAddress: post.author.emailAddress)}
One should see the Gravatar ViewHelper as a kind of post processor for an email address and would allow the following syntax:
{post.author.emailAddress -> blog:gravatar()}
This syntax places focus on the variable that is passed to the ViewHelper as it comes first.
The syntax {post.author.emailAddress -> blog:gravatar()}
is an alternative
syntax for <blog:gravatar>{post.author.emailAddress}</blog: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 was in particular 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:
With renderStatic()
¶
To fetch the content of the ViewHelper the argument
$renderChildrenClosure
is available. This returns the evaluated object
between the opening and closing tag.
Lets have a look at the new code of the renderStatic()
method:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
class GravatarViewHelper extends AbstractViewHelper
{
use CompileWithContentArgumentAndRenderStatic;
protected $escapeOutput = false;
public function initializeArguments()
{
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for');
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$emailAddress = $renderChildrenClosure();
return '<img src="http://www.gravatar.com/avatar/' .
md5($emailAddress) .
'" />';
}
}
With render()
¶
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.
Lets have a look at the new code of the render()
method:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
class GravatarViewHelper extends AbstractTagBasedViewHelper
{
protected $tagName = 'img';
public function initializeArguments()
{
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', false, null);
}
public function render()
{
$emailAddress = $this->arguments['emailAddress'] ?? $this->renderChildren();
$this->tag->addAttribute(
'src',
'http://www.gravatar.com/avatar/' . md5($emailAddress)
);
return $this->tag->render();
}
}
Importing namespaces¶
Three different ways exist to import a ViewHelper namespace into Fluid. These three ways are explained in the following section.
Local namespace import via <>-Syntax¶
To import a ViewHelper namespace into Fluid, the following Syntax can be used:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:blog="http://typo3.org/ns/MyVendor/BlogExample/ViewHelpers"
data-namespace-typo3-fluid="true"
>
<!-- Content of Fluid Template -->
</html>
In above example blog
is the namespace available within the Fluid template and
MyVendor\BlogExample\ViewHelpers
is the PHP namespace to import into Fluid.
All ViewHelper which start with blog:
will be looked up within the PHP
namespace.
The <html>
-Tags will not be part of the output, due to
data-namespace-typo3-fluid="true"
attribute.
One benefit of this approach is auto completion within modern IDEs and the possibility to lint the Fluid files.
Local namespace import via {}-Syntax¶
To import a ViewHelper namespace into Fluid, the following Syntax can be used:
{namespace blog=MyVendor\BlogExample\ViewHelpers}
In above example blog
is the namespace available within the Fluid template and
MyVendor\BlogExample\ViewHelpers
is the PHP namespace to import into Fluid.
All ViewHelper which start with blog:
will be looked up within the PHP
namespace.
Each of the rows will result in an blank line. Multiple import statements can go into a single, or multiple lines.
Global namespace import¶
Fluid allows for registering namespaces. This is already done for
typo3/cms-fluid
and typo3fluid/fluid
ViewHelpers. Therefore they are always
available via the f
namespace.
Custom ViewHelpers, e.g. for a site package, can be registered the same way.
Namespaces are registered within
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']
in the form
of:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['blog'] = [
'MyVendor\BlogExample\ViewHelpers',
];
In above example blog
is the namespace within Fluid templates, which is
resolved to the PHP namespace \MyVendor\BlogExample\ViewHelpers
.
Handle additional arguments¶
If a ViewHelper allows further arguments then the configured one, see
Register arguments of ViewHelpers, the handleAdditionalArguments()
method can be implemented.
The \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper
makes use
of this, to allow setting any data-
argument for tag based ViewHelpers.
The method will receive an array of all arguments, which are passed in addition to the registered arguments. The array uses the argument name as key and the argument value as the value. Within the method these arguments can be handled.
E.g. the AbstractTagBasedViewHelper
implements the following behaviour:
public function handleAdditionalArguments(array $arguments)
{
$unassigned = [];
foreach ($arguments as $argumentName => $argumentValue) {
if (strpos($argumentName, 'data-') === 0) {
$this->tag->addAttribute($argumentName, $argumentValue);
} else {
$unassigned[$argumentName] = $argumentValue;
}
}
parent::handleAdditionalArguments($unassigned);
}
To keep the default behaviour, all unwanted arguments should be passed to the
parent method call parent::handleAdditionalArguments($unassigned);
, to
throw accordingly exceptions.
The different render methods¶
ViewHelper can have one or multiple of the following three methods for implementing the rendering. The following section will describe the differences between all three 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.
Note
The renderStatic()
method has still to be implemented for the non
compiled version of the ViewHelper. In the future, this should no longer be
necessary.
Example implementation:
<?php
namespace MyVendor\BlogExample\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
class StrtolowerViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
public function initializeArguments()
{
$this->registerArgument('string', 'string', 'The string to lowercase.', true);
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
return strtolower($arguments['string']);
}
public function compile(
$argumentsName,
$closureName,
&$initializationPhpCode,
ViewHelperNode $node,
TemplateCompiler $compiler
) {
return 'strtolower(' . $argumentsName. '[\'string\'])';
}
}
renderStatic()
-Method¶
Most of the time this method is implemented. It's the one that is called by default from within the compiled Fluid.
It is however not called on AbstractTagBasedViewHelper implementations - on such classes you still need to use the render() method since that is the only way you can access $this->tag which contains the tag builder that generates the actual XML tag.
As this method has to be static, there is no access to instance attributes, e.g.
$this->tag
within an subclass of AbstractTagBasedViewHelper
.
Note
This method can not be used when access to child nodes is necessary. This is
the case for ViewHelpers like if
or switch
which need to access their
children like then
or else
. In that case, render()
has to be used.
render()
-Method¶
This method is the slowest one. As the compiled version always calls
the renderStatic()
method, this will call further PHP code which, in the
end, will call the original render()
method.
By using this method, the surrounding logic does not get compiled.
Note
This way is planned to be removed in the future.