Developing a custom ViewHelper

The development of an own ViewHelper is much asked for in practice and is part of the base repertoire of the extension development. We will guide you step by step through a simple example from the blog example and describe enhanced techniques afterwards.

The Gravatar-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 to display. Therefore the use of gravatar.com introduces no security risk because the user of the blog only see the checksums of the email address and not the email address itself. This is possible as no efficient possibility is known to get the original data reconstructed from the checksum.

In this section we show you how to write your own ViewHelper that uses an email address as parameter and shows the picture from gravatar.com if it exists.

Preliminary considerations

The first step should be thinking about how to use the ViewHelper later on in the template, in order to get a clear view about the arguments of the ViewHelper. We take the point of view of a template author who wants to use our ViewHelper later on, without knowledge of the internal operations.

First of all, think about how the ViewHelper should be called inside the template: The ViewHelper is not part of the default distribution, therefore we need an own namespace import to use the ViewHelper. We import the namespace MyVendor\BlogExample\ViewHelpers with the token blog. Now, all tags starting with blog: are interpreted as ViewHelper:

{namespace blog=MyVendor\BlogExample\ViewHelpers}

Our ViewHelper should get the name gravatar and only get an email address as parameter. We will call the ViewHelper in the template as follows:

<blog:gravatar emailAddress="sebastian@typo3.org" />

After this preliminary considerations we will start with the implementation.

Now implementing!

Every ViewHelper is a PHP class whose name is derived from the namespace import and the name of the XML element. The classname consists of the following three parts:

  • full namespace (in our example \MyVendor\BlogExample\ViewHelpers)
  • the name of the ViewHelper in UpperCamelCase writing (in our example Gravatar)
  • the ending ViewHelper

For the Gravatar ViewHelper the name of the class is \MyVendor\BlogExample\ViewHelpers\GravatarViewHelper.

Following the naming conventions for Extbase extensions we create the ViewHelper skeleton in the PHP file EXT:blog_example/Classes/ViewHelpers/GravatarViewHelper.php:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

class GravatarViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
public function render() {
}
}

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

Tip

A ViewHelper can also inherit from subclasses of AbstractViewHelper, e.g. from \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper. Several subclasses are offering additional functionality. We will talk about the just addressed TagBasedViewHelper later on in this chapter in detail in "Creating XML tags using TagBasedViewHelper".

In addition every ViewHelper needs a method render(), which is called once the ViewHelper is to be displayed in the template. The return value of the method is copied directly into the complete output. If we enhanced our ViewHelper from above as follows:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

class GravatarViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
        public function render() {
                return 'World';
        }
}

and we insert it in the template like this:

{namespace blog=MyVendor\BlogExample\ViewHelpers}
Hello <blog:gravatar />

Hello World should be displayed.

Register arguments of ViewHelpers

Our Gravatar ViewHelper must hand over the email address it should work on. This is the last needed building block, before we can implement our needed functionality.

All arguments of a ViewHelper must be registerd. Every ViewHelper has to declare explicit which parameters are accepted.

The easiest alternative to register these arguments is to enhance the render() method. All method arguments of the render() method are automatically arguments of the ViewHelpers. In our example it looks like this:

/**
* @param string $emailAddress
*/
public function render($emailAddress) {
}

With this the ViewHelper gets the argument emailAddress, which is of the type string. You see that the annotation of the method in the PHPDoc block is important, because the type of the parameter is based on this by Fluid.

Warning

If you forget to specify the type of a parameter, an error message will be displayed. Check at all times that the PHPDoc block is complete and syntactical correct. For example, if you forget the @ in front of the param, the type of the parameter is not identified.

Tip

Sometimes arguments should get different types. In this case you should use the type mixed in the PHPDoc. With the line @param mixed $emailAddress any type of object can be given as parameter emailAddress, e.g. arrays, strings or integer values.

At the end we implement the output as img tag:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

class GravatarViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* @param string $emailAddress The email address to resolve the gravatar for
* @return string the HTML <img>-Tag of the gravatar
*/
public function render($emailAddress) {
return '<img src="http://www.gravatar.com/avatar/' . md5($emailAddress) . '" />';
}
}

Congratulation on creating your first ViewHelper! In the following sections we will show you some enhancements and tricks for implementing ViewHelpers.

Register Arguments with initializeArguments()

Initializing the ViewHelper arguments directly at the render() method is extreme handy, when you don't have to much arguments. But sometimes you'll build a complex inheritance hierarchy with the ViewHelper, where different level of the inheritance structure should register additional arguments. Fluid itself does this for example with the form ViewHelpers.

Because method parameter and annotations are not inheritable, there must be an additional way to register the arguments of a ViewHelper. Fluid provides the method initializeArguments for this. In this method you can register additional arguments by calling $this->registerArgument($name, $type, $description, $required, $defaultValue). You can access these arguments through the array $this->arguments.

The above example could be changed in the following way and would function identical:

<?php
namespace MyVendor\BlogExample\ViewHelpers;

class GravatarViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* Arguments Initialization
*/
public function initializeArguments() {
$this->registerArgument('emailAddress', 'string',
'The email address to resolve the gravatar for', TRUE);
}

/**
* @return string the HTML <img>-Tag of the gravatar
*/
public function render() {
return '<img src="http://www.gravatar.com/avatar/' .
md5($this->arguments['emailAddress']) . '" />';
}
}

In this example the usage of initializeArguments is not particular meaningful, because the method only requires one parameter. When working with complex ViewHelpers which have a multilevel inheritance hierarchy, it is sometimes more readable to register the arguments with initializeArguments().

Creating XML tags using TagBasedViewHelper

For ViewHelper that create XML tags Fluid provides an enhanced baseclass: the \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper. This ViewHelper provides a Tag-Builder that can be used to create tags in a simple way. It takes care about the syntactical correct creation of the tag and escapes for example single and double quote in attributes.

Tip

With the correct escaping of the attributes the system security is enhanced, because it prevents cross site scripting attacks that would break out of the attributes of XML tags.

In the next step we modify the just created GravatarViewHelper a bit and use the TagBasedViewHelper. Because the Gravatar-ViewHelper creates an img tag the use of the Tag-Builder is advised.

Lets have a look how we change the ViewHelper:

<remark>TODO:code</remark>

What has changed? First of all, the ViewHelper inherits not directly from AbstractViewHelper but from TagBasedViewHelper, which provides and initializes the Tag-Builder. Beyond that there is a class variable $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 (Attribute, Value) to add new tag attributes. In our example we add the attribute src to the tag, with the value assigned one line above it. Finally the Tag-Builder offers a method render() which generates and returns the tag which than is given back, because we want to insert it in the template.

Tip

You may ask why this code is better even though it is much longer. It communicates the meaning much better and therefore it is preferred to the first example, where the gravatar URL and the creating of the img tag was mixed.

The base class TagBasedViewHelper allows you to implement ViewHelpers which returns a XML tag easier and cleaner and help to concentrate at the essential.

Furthermore the TagBasedViewHelper offers assistance for ViewHelper arguments that should recur direct and unchanged as tag attributes. These could be registerd in initializeArguments() with the method $this->registerTagAttribute($name, $type, $description, $required = FALSE). If we want to support the <img> attribure alt in our ViewHelper, we can initialize 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 we want to support the universal attributes and the alt attribute in our Gravatar ViewHelper we need the following initializeArguments() method:

public function initializeArguments() {
parent::initializeArguments();
$this->registerUniversalTagAttributes();
$this->registerTagAttribute('alt', 'string', 'Alternative Text for the image');
}

Insert optional arguments

All ViewHelper arguments we have registered so far were required. By setting a default value for an argument in the method signature, the argument is automatically optional. When registering the arguments through initializeArguments() the according parameter has to be set to FALSE.

Back to our example: We can add a size parameter for the picture in the Gravatar ViewHelper. This size parameter will be used to determine the height and width of the image in pixels and can range from 1 to 512. When no size is given, an image of 80px is generated.

We can enhance the render() method like this:

/**
* @param string $emailAddress The email address to resolve the gravatar for
* @param string $size The size of the gravatar, ranging from 1 to 512
* @return string the HTML <img>-Tag of the gravatar
*/
public function render($emailAddress, $size = '80') {
$gravatarUri = 'http://www.gravatar.com/avatar/' . md5($emailAddress) . '?s=' . urlencode($size);
$this->tag->addAttribute('src', $gravatarUri);
return $this->tag->render();
}
}

With this setting of a default value we have made the size attribute optional.

Prepare ViewHelper for inline syntax

So far with our gravatar ViewHelper we have focussed on the tag structure of the ViewHelper. We have used the ViewHelper only with the tag syntax (because it returns a tag as well):

<blog:gravatar emailAddress="{post.author.emailAddress}" />

Alternatively we can rewrite this sample in the inline notation:

{blog:gravatar(emailAddress: post.author.emailAddress)}

With this, the tag concept of the ViewHelper is mostly gone. 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()}

Here the email address has the focus and we see the gravatar ViewHelper as a converting step based on the email address.

We want to show you now what a ViewHelper has to do, to support this syntax. The syntax {post.author.emailAddress -> blog:gravatar()} is an alternative writing for <blog:gravatar>{post.author.emailAddress}</blog:gravatar>. To support this we have to use the email address either from the argument emailAddress or, if it is empty, we should interpret the content of the tag as email address.

How did we get the content of a ViewHelper tag? For this a helper 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:

/**
* @param string $emailAddress The email address to resolve the gravatar for
* @param string $size The size of the gravatar, ranging from 1 to 512
* @return string the HTML <img>-Tag of the gravatar
*/
public function render($emailAddress = NULL, $size = '80') {
if ($emailAddress === NULL) {
$emailAddress = $this->renderChildren();
}

$gravatarUri = 'http://www.gravatar.com/avatar/' . md5($emailAddress) . '?s=' . urlencode($size);
$this->tag->addAttribute('src', $gravatarUri);
return $this->tag->render();
}
}

This code section has the following effect: First we have made the ViewHelper attribute emailAddress optional. If no emailAddress attribuite is given, we interpret the content of the tag as email address. The rest of the code in unchanged.

Tip

This trick was specially used at the format ViewHelpers. Every ViewHelper supports both writings there.