Developing a custom ViewHelper
Deprecated since version Fluid v2.15 (TYPO3 v12.4)
The traits
\TYPO3Fluid\
and
\TYPO3Fluid\
are deprecated. See section migration.
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.
Contents of this page
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 \My
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:
AbstractViewHelper implementation
Every ViewHelper is a PHP class. For the Gravatar ViewHelper, the fully
qualified name of the class is
\My
.
<?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),
);
}
}
AbstractViewHelper
line 9 extends Abstract
Every ViewHelper must inherit from the class
\TYPO3Fluid\
.
A ViewHelper can also inherit from subclasses of
\Abstract
, for example
from \TYPO3Fluid\
.
Several subclasses are offering additional functionality. The
\Abstract
will be explained later on in this chapter in detail.
Disable escaping the output
line 11 protected $escape
By default, all output is escaped by htmlspecialchars
to prevent cross
site scripting.
Setting the property $escape
to false is necessary to prevent
escaping of ViewHelper output.
By setting the property $escape
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 $escape
: is true,
$escape
will be disabled to prevent double escaping. If
$escape
: is false, $escape
will be enabled unless
disabled explicitly.
Passing in children is explained in Prepare ViewHelper for inline syntax.
initializeArguments()
line 13 public function initialize
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->register
) parameters.
The registration happens inside method initialize
.
In the example above, the ViewHelper receives the argument email
(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
.
Tip
Sometimes arguments can take various types. In this case, the type mixed
should be used.
render()
line 29 public function render
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.
Attention
Prevent XSS (Cross-Site Scripting) attacks.
The returned string is displayed raw, without being passed through
htmlspecialchars
as we have
disabled escaping.
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.
Hint
The render
method usually returns a string that should be displayed
directly in the Fluid template. But it is also possible to return another
type and use it as argument for another ViewHelper which expects that type.
Creating HTML/XML tags with the AbstractTagBasedViewHelper
Changed in version Fluid Standalone v2.12 / TYPO3 v12.4
All TagBasedViewHelpers (such as <f:
or <f:
) can now receive
arbitrary tag attributes which will be appended to the resulting HTML tag. In the past,
this was only possible for a small list of tag attributes, like class, id or lang.
See also Migration: Remove registerUniversalTagAttributes and registerTagAttribute.
For ViewHelpers which create HTML/XML tags, Fluid provides an enhanced base
class: \TYPO3Fluid\
. This
base class provides an instance of
\TYPO3Fluid\
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 $tag
configures the name of the HTML/XML tag
to be output.
All ViewHelpers extending \Abstract
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
\Tag
, stored in class property
$this->tag
is advised:
<?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();
}
}
line 32 $this->tag->render
creates the <img>
tag with all
explicitly added arguments (line 28-31 $this->tag->add
) and all
arbitrary tag attributes passed to the ViewHelper when it is used.
AbstractTagBasedViewHelper
line 6 class Gravatar
The ViewHelper does not inherit directly from
\Abstract
but
from \Abstract
,
which provides and initializes the
\Tag
and passes on and
escapes arbitrary tag attributes.
$tagName
line 9 protected $tag
There is a class property $tag
which stores the name of the tag to be
created (<img>
).
$this->tag->addAttribute()
line 28 - 31 $this->tag->add
The tag builder is available as class property $this->tag
. It offers
the method Tag
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.
$this->registerTagAttribute()
Deprecated since version Fluid Standalone v2.12 / TYPO3 v13.2
The methods php:$this->register
and
register
have been deprecated. They can be
removed on dropping TYPO3 v12.4 support.
Migration: Remove registerUniversalTagAttributes and registerTagAttribute
public function initializeArguments(): void
{
parent::initializeArguments();
+ $this->registerUniversalTagAttributes();
+ $this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}
When removing the call, attributes registered by the call are now available in
$this->additional
, and no longer in $this->arguments
.
This may need adaption within single ViewHelpers, if they handle such
attributes on their own.
If you need to support both TYPO3 v12.4 and v13, you can leave the calls in until dropping TYPO3 v12.4 support.
public function initializeArguments(): void
{
parent::initializeArguments();
+ // TODO: Remove registerUniversalTagAttributes and registerTagAttribute
+ // On dropping TYPO3 v12.4 support.
$this->registerUniversalTagAttributes();
$this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}
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:
<?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();
}
}
With this setting of a default value and setting the fourth argument to false
,
the size
attribute becomes optional.
Prepare ViewHelper for inline syntax
Deprecated since version Fluid v2.15 (TYPO3 v13.3 / TYPO3 v12.4)
In former versions this was done by using the now deprecated trait
\Compile
.
See section migration.
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:
<m:gravatar emailAddress="{post.author.emailAddress}" />
Alternatively, this expression can be written using the inline notation:
{m: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 -> m:gravatar()}
This syntax places focus on the variable that is passed to the ViewHelper as it comes first.
The syntax {post.
is an alternative
syntax for <m:
. To
support this, the email address comes either from the argument email
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 render
is
available in the \Abstract
.
This returns the evaluated object between the opening and closing tag.
<?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';
}
}
Handle additional arguments
Changed in version Fluid Standalone v2.12 / TYPO3 v12.4
All ViewHelpers implementing
\Abstract
can now receive arbitrary tag attributes which will be appended to the
resulting HTML tag. In the past, this was only possible for
explicitly registered arguments.
See also Migration: Remove registerUniversalTagAttributes and registerTagAttribute.
If a ViewHelper allows further arguments which have not been explicitly
configured, the handle
method can be implemented.
ViewHelper implementing
\Abstract
do
not need to use this as all arguments are passed on automatically.
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 $initialization
can be used to add further PHP
code before the execution.
Example implementation:
<?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);
}
}
renderStatic()
method
Deprecated since version Fluid v2.15 (TYPO3 v12.4)
The trait
\Compile
,
which is responsible for calling render
is deprecated.
See section migration.
render()
method
Most of the time, this method is implemented.
Migration: Remove deprecated compliling traits
Migration: Remove deprecated trait CompileWithRenderStatic
To remove the deprecated trait
\TYPO3Fluid\
switch
to use the render
method instead of the render
.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\ViewHelpers;
-use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
-use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
final class GravatarViewHelper extends AbstractViewHelper
{
- use CompileWithRenderStatic;
-
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);
}
- public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string {
+ public function render(): string {
- $emailAddress = $arguments['emailAddress'];
+ $emailAddress = $this->arguments['emailAddress'];
return sprintf('<img src="https://www.gravatar.com/avatar/%s">', md5($emailAddress));
}
}
- line 13
- Remove the trait
\Compile
.With Render Static - lines 23, 24
- Switch the render method from
render
toStatic () render
.() - lines 25, 26
- Fetch the arguments from the class property instead method argument.
Migration: Remove deprecated trait CompileWithContentArgumentAndRenderStatic
If \TYPO3Fluid\
was also used in your ViewHelper implementation, further steps are needed:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\ViewHelpers;
-use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
-use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic;
final class GravatarViewHelper extends AbstractViewHelper
{
- use CompileWithContentArgumentAndRenderStatic;
-
protected $escapeOutput = false;
public function initializeArguments(): void
{
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for');
}
- public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string {
+ public function render(): string {
- $emailAddress = $renderChildrenClosure();
+ $emailAddress = $this->renderChildren();
return sprintf('<img src="https://www.gravatar.com/avatar/%s" />', md5($emailAddress));
}
public function getContentArgumentName(): string
{
return 'emailAddress';
}
}
- line 13
- Remove the trait
\Compile
.With Content Argument And Render - lines 22, 23
- Switch the render method from
render
toStatic () render
.() - lines 24, 25
- Use the non-static method
$this->render
instead of the closureChildren () $render
.Children Closure ()
Remove calls to removed renderStatic()
method of another ViewHelper
If you called a now removed render
method from within another
ViewHelper's render
method you can replace the code like this:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\ViewHelpers;
-use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
-use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
final class GravatarViewHelper extends AbstractViewHelper
{
- use CompileWithRenderStatic;
-
protected $escapeOutput = false;
public function initializeArguments(): void
{
$this->registerArgument('emailAddress', 'string', 'The email address to resolve the gravatar for', true);
}
- public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
+ public function render(): string
{
- $emailAddress = $arguments['emailAddress'];
+ $emailAddress = $this->arguments['emailAddress'];
- $gravatorUrl = GravatarUrlViewHelper::renderStatic(['email', $emailAddress], $renderChildrenClosure, $renderingContext);
+ $gravatarUrl = $this->renderingContext->getViewHelperInvoker()->invoke(
+ GravatarUrlViewHelper::class,
+ ['email', $emailAddress],
+ $this->renderingContext,
+ $this->renderChildren(),
+ );
return sprintf('<img src="%s" />', $gravatarUrl);
}
}
- line 27, 28ff
-
Replace the static call to the
render
method of another ViewHelper by callingStatic () $this->rendering
instead.Context->get View Helper Invoker ()->invoke () See also
\TYPO3Fluid\
.Fluid\ Core\ View Helper\ View Helper Invoker