8.4 KiB
TYPO3 Hooks and PSR-14 Events
Source: TYPO3 Core API Reference - Hooks, Events, and Signals Purpose: Understanding TYPO3 hook system, PSR-14 events, and migration strategies
SC_OPTIONS Hooks Status in TYPO3 13
⚠️ Common Misconception
INCORRECT: "SC_OPTIONS hooks are deprecated in TYPO3 13"
CORRECT: SC_OPTIONS hooks are NOT deprecated in TYPO3 13. They remain the official pattern for specific use cases.
SC_OPTIONS Hooks That Are Still Official
The following SC_OPTIONS hooks remain the official TYPO3 13 pattern:
DataHandler Hooks (Still Official)
// Configuration/Services.yaml
Vendor\Extension\Database\MyDataHandlerHook:
public: true
tags:
- name: event.listener
identifier: 'my-extension/datahandler-hook'
method: 'processDatamap_postProcessFieldArray'
Still Official in ext_localconf.php:
<?php
// TYPO3 13+ DataHandler hooks remain official pattern
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] =
\Vendor\Extension\Database\MyDataHandlerHook::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] =
\Vendor\Extension\Database\MyDataHandlerHook::class;
Key DataHandler Hook Methods (TYPO3 13+):
processDatamap_preProcessFieldArray()- Before field processingprocessDatamap_postProcessFieldArray()- After field processingprocessDatamap_afterDatabaseOperations()- After DB operationsprocessCmdmap_preProcess()- Before command processingprocessCmdmap_postProcess()- After command processingprocessCmdmap_afterFinish()- After all commands finished
RTE Transformation Hooks (Still Official)
<?php
// TYPO3 13+ RTE transformation hooks remain official pattern
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][] =
\Vendor\Extension\RteTransformation\MyRteTransformationHook::class;
Required Methods:
transform_rte()- Transform content from database to RTEtransform_db()- Transform content from RTE to database
When to Use SC_OPTIONS vs PSR-14 Events
| Scenario | Use SC_OPTIONS Hook | Use PSR-14 Event |
|---|---|---|
| DataHandler field processing | ✅ Yes (official) | ❌ No event available |
| RTE content transformation | ✅ Yes (official) | ❌ No event available |
| Backend user authentication | ❌ Migrated | ✅ Use PSR-14 events |
| Frontend rendering | ❌ Migrated | ✅ Use PSR-14 events |
| Page generation | ❌ Migrated | ✅ Use PSR-14 events |
| Cache clearing | ❌ Migrated | ✅ Use PSR-14 events |
PSR-14 Event Listeners (Preferred)
For most scenarios, PSR-14 events are the modern TYPO3 13+ approach.
Event Listener Configuration
# Configuration/Services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\Extension\:
resource: '../Classes/*'
# PSR-14 Event Listener
Vendor\Extension\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/my-event-listener'
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
Event Listener Implementation
<?php
declare(strict_types=1);
namespace Vendor\Extension\EventListener;
use TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent;
/**
* PSR-14 Event Listener for user login.
*/
final class MyEventListener
{
public function __invoke(AfterUserLoggedInEvent $event): void
{
$user = $event->getUser();
// Your logic here
}
}
Common TYPO3 13 Events
Authentication Events:
AfterUserLoggedInEventBeforeUserLogoutEventAfterUserLoggedOutEvent
Backend Events:
ModifyButtonBarEventModifyDatabaseQueryForContentEventBeforePagePreviewUriGeneratedEvent
DataHandler Events:
AfterDataInsertedEventAfterDataUpdatedEventAfterRecordDeletedEvent
Page Events:
AfterPageTreeItemsPreparedEventModifyPageLayoutContentEvent
Complete Reference: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html
Migration Strategy
Step 1: Identify Hook Type
// Check if hook is in ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['...']['...'][]
Step 2: Check Official Documentation
- DataHandler hooks: Still official, keep using SC_OPTIONS
- RTE transformation: Still official, keep using SC_OPTIONS
- Other hooks: Check if PSR-14 event exists
Step 3: Migrate or Modernize
If PSR-14 event exists:
// OLD: ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][]
= \Vendor\Extension\Hook\MyHook::class;
// NEW: Configuration/Services.yaml + EventListener class
Vendor\Extension\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/after-login'
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
If no PSR-14 event exists (DataHandler, RTE):
// KEEP: Still official in TYPO3 13+
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]
= \Vendor\Extension\Database\MyDataHandlerHook::class;
// MODERNIZE: Add dependency injection
// Configuration/Services.yaml
Vendor\Extension\Database\MyDataHandlerHook:
public: true
arguments:
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
$context: '@TYPO3\CMS\Core\Context\Context'
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
Best Practices
1. Constructor Dependency Injection
Even for SC_OPTIONS hooks, use constructor injection (TYPO3 13+):
<?php
declare(strict_types=1);
namespace Vendor\Extension\Database;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Resource\ResourceFactory;
/**
* DataHandler hook with dependency injection.
*/
final class MyDataHandlerHook
{
public function __construct(
private readonly ResourceFactory $resourceFactory,
private readonly Context $context,
private readonly LogManager $logManager,
) {}
public function processDatamap_postProcessFieldArray(
string $status,
string $table,
string $id,
array &$fieldArray,
\TYPO3\CMS\Core\DataHandling\DataHandler &$dataHandler,
): void {
// Use injected dependencies
$file = $this->resourceFactory->getFileObject($fileId);
}
}
2. Avoid GeneralUtility::makeInstance()
// ❌ BAD: Using makeInstance (legacy pattern)
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
// ✅ GOOD: Constructor injection (TYPO3 13+ pattern)
public function __construct(
private readonly ResourceFactory $resourceFactory,
) {}
3. Configure Services Explicitly
# Configuration/Services.yaml
services:
Vendor\Extension\Database\MyDataHandlerHook:
public: true # Required for SC_OPTIONS hooks
arguments:
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
$context: '@TYPO3\CMS\Core\Context\Context'
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
Acceptable $GLOBALS Usage
Even in TYPO3 13+, certain $GLOBALS usage is acceptable:
✅ Acceptable $GLOBALS
// TCA access (no alternative available)
$GLOBALS['TCA']['tt_content']['columns']['bodytext']
// Current request (framework-provided)
$GLOBALS['TYPO3_REQUEST']
// Backend user context (framework-provided)
$GLOBALS['BE_USER']
// Frontend user context (framework-provided)
$GLOBALS['TSFE']
❌ Avoid $GLOBALS
// Database connection (use ConnectionPool)
$GLOBALS['TYPO3_DB']
// Extension configuration (use ExtensionConfiguration)
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['my_ext']
// Object instantiation (use dependency injection)
GeneralUtility::makeInstance(SomeClass::class)