Hooks

Note

Hooks are an outdated concept from Contao 2 and 3. If you are employing event-driven architecture within your own code, you should use the Symfony Event Dispatcher instead.

Hooks are entry points into the Contao core (and some of its extension bundles). Have a look at the hook reference for a list of all available hooks. You can register your own callable logic that will be executed as soon as a certain point in the execution flow of the core will be reached. Consider the following example.

if (isset($GLOBALS['TL_HOOKS']['activateAccount']) && \is_array($GLOBALS['TL_HOOKS']['activateAccount']))
{
    foreach ($GLOBALS['TL_HOOKS']['activateAccount'] as $callback)
    {
        $this->import($callback[0]);
        $this->{$callback[0]}->{$callback[1]}($objMember, $this);
    }
}

The hook activateAccount will be executed as soon as a user account is activated and all the callable functions registered to the particular hook are called in order of addition.

In order to be compatible with the method execution you need to consider the parameters that will be passed to your function.

$this->{$callback[0]}->{$callback[1]}($objMember, $this);

In this case an instance of Contao\MemberModel and an instance of Contao\ModuleRegistration will be passed as arguments to the hook.

Some hooks require its listener to return a specific value, that will be passed along. For example the compileFormFields needs you to return arrFields.

if (isset($GLOBALS['TL_HOOKS']['compileFormFields']) && \is_array($GLOBALS['TL_HOOKS']['compileFormFields']))
{
    foreach ($GLOBALS['TL_HOOKS']['compileFormFields'] as $callback)
    {
        $this->import($callback[0]);
        $arrFields = $this->{$callback[0]}->{$callback[1]}($arrFields, $formId, $this);
    }
}

Registering hooks

There are 3 different ways of subscribing to a hook. The recommended way is using PHP attributes together with invokable services. Which one you use depends on your setup. For example, if you still need to support PHP 7 you can use annotations.

Tip

Using attributes or annotations means it is only necessary to create one file for the respective adaptation when using Contao’s default way of automatically registering services under the App\ namespace within the src/ folder.

Contao implements PHP attributes with which you can tag your service to be registered as a hook.

// src/EventListener/ParseArticlesListener.php
namespace App\EventListener;

use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
use Contao\FrontendTemplate;
use Contao\Module;

class ParseArticlesListener
{
    #[AsHook('parseArticles', priority: 100)]
    public function onParseArticles(FrontendTemplate $template, array $newsEntry, Module $module): void
    {
        // Do something …
    }
}

The priority parameter is optional.

Contao also supports its own annotation formats via the Service Annotation Bundle.

// src/EventListener/ParseArticlesListener.php
namespace App\EventListener;

use Contao\CoreBundle\ServiceAnnotation\Hook;
use Contao\FrontendTemplate;
use Contao\Module;

class ParseArticlesListener
{
    /**
     * @Hook("parseArticles", priority=100)
     */
    public function onParseArticles(FrontendTemplate $template, array $newsEntry, Module $module): void
    {
        // Do something …
    }
}

The priority parameter is optional.

Hooks can also be registered using the contao.hook service tag directly.

# config/services.yaml
services:
    App\EventListener\ActivateAccountListener:
        tags:
            - { name: contao.hook, hook: activateAccount, method: onAccountActivation, priority: 100 }

The service tag can have the following options:

OptionTypeDescription
namestringMust be contao.hook.
hookstringThe name of the hook this service will listen to.
methodstringOptional: the method name in the service - otherwise infered from the hook (e.g. onActivateAccount).
priorityintegerOptional: priority of the hook. (Default: 0)
Info

When using the default priority, or a priority of 0 the hook will be executed according to the extension loading order, along side hooks that are using the legacy configuration via $GLOBALS['TL_HOOK']. With a priority that is greater than zero the hook will be executed before the legacy registered hooks. With a priority of lower than zero the hook will be executed after the legacy registered hooks.

Invokable Services

You can also use invokable classes for your services. If a service is tagged with contao.hook and no method name is given, the __invoke method will be called automatically. This also means that you can define the service annotation on the class, instead of a method:

// src/EventListener/ParseArticlesListener.php
namespace App\EventListener;

use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
use Contao\FrontendTemplate;
use Contao\Module;

#[AsHook('parseArticles')]
class ParseArticlesListener
{
    public function __invoke(FrontendTemplate $template, array $newsEntry, Module $module): void
    {
        // Do something …
    }
}
// src/EventListener/ParseArticlesListener.php
namespace App\EventListener;

use Contao\CoreBundle\ServiceAnnotation\Hook;
use Contao\FrontendTemplate;
use Contao\Module;

/**
 * @Hook("parseArticles")
 */
class ParseArticlesListener
{
    public function __invoke(FrontendTemplate $template, array $newsEntry, Module $module): void
    {
        // Do something …
    }
}
# config/services.yaml
services:
    App\EventListener\ParseArticlesListener:
        tags:
            - { name: contao.hook, hook: activateAccount }
// src/EventListener/ParseArticlesListener.php
namespace App\EventListener;

use Contao\FrontendTemplate;
use Contao\Module;

class ParseArticlesListener
{
    public function __invoke(FrontendTemplate $template, array $newsEntry, Module $module): void
    {
        // Do something …
    }
}