Files
gh-netresearch-claude-code-…/skills/typo3-conformance/references/hooks-and-events.md
2025-11-30 08:43:13 +08:00

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 processing
  • processDatamap_postProcessFieldArray() - After field processing
  • processDatamap_afterDatabaseOperations() - After DB operations
  • processCmdmap_preProcess() - Before command processing
  • processCmdmap_postProcess() - After command processing
  • processCmdmap_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 RTE
  • transform_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:

  • AfterUserLoggedInEvent
  • BeforeUserLogoutEvent
  • AfterUserLoggedOutEvent

Backend Events:

  • ModifyButtonBarEvent
  • ModifyDatabaseQueryForContentEvent
  • BeforePagePreviewUriGeneratedEvent

DataHandler Events:

  • AfterDataInsertedEvent
  • AfterDataUpdatedEvent
  • AfterRecordDeletedEvent

Page Events:

  • AfterPageTreeItemsPreparedEvent
  • ModifyPageLayoutContentEvent

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)

Resources