.. include:: ../Includes.txt
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::
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*::
``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::
-Tag of the gravatar
*/
public function render($emailAddress) {
return '
';
}
}
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::
registerArgument('emailAddress', 'string',
'The email address to resolve the gravatar for', TRUE);
}
/**
* @return string the HTML
-Tag of the gravatar
*/
public function render() {
return '
';
}
}
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:
TODO:code
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 ``
``
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
-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):
````
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
``{post.author.emailAddress}``.
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
-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.