You too can become a hero and support this Blog at GitHub Sponsors or Patreon.

More about support

What are Signals, Slots and Hooks?

As a developer I often need to alter the default behavior of TYPO3 at a specific event. Let's say I want to send an email each time a page is created in a certain area of the page tree. TYPO3 does not do that by default but it provides the possibility for the developer to hook into the process.

Until TYPO3 9LTS a developer has two options to intercept the code execution of the core and execute some custom code on a specific event. The old fashion way to do this are Hooks and the alternative are Signals and the corresponding Slots.

This article is valid up to TYPO3 9 LTS. With TYPO3 10 a new approach of intercepting and extending code has been introduced: PSR-14 Events. Extension authors are encouraged to migrate their code to the new Events whenever possible.

Top

Hooks

In the TYPO3 core hooks are all over the place. The DataHandler alone (despite being an extreme example) provides more than 20 hooks. As an example let's look at one of those hooks: In the method moveRecord() we find this code:

if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'])) {
  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] as $classRef) {
    $hookObj = GeneralUtility::getUserObj($classRef);
    if (method_exists($hookObj, 'moveRecord')) {
      $hookObj->moveRecord($table, $uid, $destPid, $propArr, $moveRec, $resolvedPid, $recordWasMoved, $this);
    }
  }
}

This code is pretty straight forward. If something was registered for this hook (registered means something was put in this ugly array with the key 'moveRecordClass') TYPO3 now tries to instantiate and call the method moveRecord() on it.

The core itself often uses its own hooks. So for example the sysext version registers for the 'moveRecordClass' hook in its ext_localconf.php:

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass']['version'] = \TYPO3\CMS\Version\Hook\DataHandlerHook::class;

In the moveRecordClass hook the method has to be named moveRecord and it gets passed the arguments $table, $uid, $destPid, $propArr, $moveRec, $resolvedPid, $recordWasMoved, $this. Some hooks may require the implementation of a specific interface. Some hooks don't determine the name of the method so you have to specify the name of the method in your registration as well (like e.g. 'MyClass->myHookfunction'). It also depends on the Hook whether your function has to return anything and what arguments you'll get. So the implementation details may differ slightly from hook to hook but the main procedure always stays the same:

Register in the ext_localconf.php by adding a class reference to an array and implement a public function with your code.

So what are signals and slots then?

Top

Signals & Slots

Originally developed for the Flow framework and backported to TYPO3s extbase in 2011 signals and slots are a more generic approach to the same issue. Roughly following the observer pattern, a class called SignalSlotDispatcher handles the dispatching of events to the observers. On any point in the code a signal can be emitted, triggering the execution of all registered slots.

I'll show you an example of a signal emitted in the core: In \TYPO3\CMS\Extensionmanager\Utility\InstallUtility we have several signals. One of them is the 'afterExtensionUninstall' signal which is emitted each time an extension is uninstalled in the extension manager.

The class \TYPO3\CMS\Extbase\SignalSlot\Dispatcher can be injected or instantiated with ObjectManager->get() or GeneralUtility::makeInstance(). I'll show that in a moment. Let's see how the signal is emitted first:

$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionUninstall', array($extensionKey, $this));

The method dispatch() takes three arguments. The 1st is the name of the class the signal is emitted from. The 2nd is the name of the signal and the 3rd argument is an array of variables that will be passed to any slot registered for this signal.

As we already saw with hooks the core uses its own signals quite frequently. A slot for the above example signal is registered in the ext_localconf.php of sysext core:

/** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */
$signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
$signalSlotDispatcher->connect(
  \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class,  // Signal class name
  'afterExtensionUninstall',                                  // Signal name
  \TYPO3\CMS\Core\Core\ClassLoadingInformation::class,        // Slot class name
  'dumpClassLoadingInformation'                               // Slot name
);

So instead of adding an item to an array as we did when registering for a hook we have to instantiate the SignalSlotDispatcher and call the method connect() when we want to register for a signal. The connect() method takes 5 arguments. The first is the class where the signal is emitted (line 4). The 2nd argument is the name of the signal emitted in that class that we want to register to (line 5). The 3rd argument is the class where our slot is located (line 6) and the 4th argument is the name of the mehtod that should be called on our slot (line 7).

So in short this says: When the signal 'afterExtensionUninstall' is emitted in the class InstallUtility, call dumpClassLoadingInformation() in the class ClassLoadingInformation.

The optional 5th argument would be a boolean that determines whether or not you want to receive additional information about the signal (EmitterClassName::signalName) in your slot implementation.

Top

So ... what should I use?

That depends. If you want to intercept the core or a TER extension at a specific event you have to go with whatever the code provides. Many popular extensions provide signals these days. The core however still has more hooks than signals wich makes sense in terms of staying backwards compatible. If you need a hook or a signal where noone is yet, don't hesitate to ask on forge or slack whether there could be one.

However if you want to provide a opportunity for developers to intercept the code of your own extension, I'd recommend to go for the signal/slot pattern as it is more likely futureproof. Remember that all you have to do is intanciate (or inject) the SignalSlotDispatcher and then call dispatch() wherever you want to emit signals.

Top

Further Information