Handler Registration Trait

Standardized handler registration trait introduced in v0.2.2 that eliminates ~70% of boilerplate code across all handlers.

Overview

HandlerRegistrationTrait provides a single registerHandler() method that automatically registers handlers with the core WordPress filters Data Machine uses for handler discovery, settings, auth providers, and deferred handler tools.

Architecture

  • Location: /inc/Core/Steps/HandlerRegistrationTrait.php
  • Purpose: Centralize handler registration logic
  • Benefits:
    • Reduces code duplication by ~70%
    • Ensures consistent registration patterns
    • Centralizes filter registration logic
    • Auto-handles conditional registration

Method Signature

php
protected static function registerHandler(
    string $handler_slug,
    string $handler_type,
    string $handler_class,
    string $label,
    string $description,
    bool $requires_auth = false,
    ?string $auth_class = null,
    ?string $settings_class = null,
    ?callable $tools_callback = null,
    ?string $authProviderKey = null,
    array $meta = []
): void

Parameters

  • Reduces code duplication by ~70%
  • Ensures consistent registration patterns
  • Centralizes filter registration logic
  • Auto-handles conditional registration

Filters Registered

The trait automatically registers handlers with the following WordPress filters:

1. datamachine_handlers

Handler metadata registration (always registered).

php
add_filter('datamachine_handlers', function($handlers, $step_type = null) {
    if (null !== $step_type && $step_type !== $handler_type) {
        return $handlers;
    }

    $handlers[$handler_slug] = [
        'type' => $handler_type,
        'class' => $handler_class,
        'label' => $label,
        'description' => $description,
        'requires_auth' => $requires_auth,
        'auth_provider_key' => $requires_auth ? $provider_key : null,
        'meta' => $meta,
    ];
    return $handlers;
}, 10, 2);

2. datamachine_auth_providers

Authentication provider registration (conditional on requires_auth=true).

Auth providers are keyed by provider key, not necessarily the handler slug.

  • Reduces code duplication by ~70%
  • Ensures consistent registration patterns
  • Centralizes filter registration logic
  • Auto-handles conditional registration
php
// Only registered if requires_auth is true
add_filter('datamachine_auth_providers', function($providers, $step_type = null) use ($provider_key) {
    if (null === $step_type || $step_type === $handler_type) {
        $providers[$provider_key] = $providers[$provider_key] ?? new $auth_class();
    }
    return $providers;
}, 10, 2);

Portable bundles may export credential-bearing handler config as auth_ref. Publish and upsert base classes resolve that reference at runtime with AuthRefHandlerConfig::resolve_runtime_config() before calling the handler-specific execution method. The registration trait only registers the provider and advertises auth_provider_key; it does not resolve credentials itself.

3. datamachine_handler_settings

Settings class registration (always registered if settings_class provided).

php
add_filter('datamachine_handler_settings', function($all_settings, $handler_slug_param = null) {
    if (null === $handler_slug_param || $handler_slug_param === $handler_slug) {
        $all_settings[$handler_slug] = new $settings_class();
    }
    return $all_settings;
}, 10, 2);

4. datamachine_tools (handler tools)

AI tool registration via callback (conditional on tools_callback provided). The trait wires the callback into the unified datamachine_tools registry as a deferred _handler_callable entry. ToolPolicyResolver::gatherPipelineTools() resolves it at pipeline execution time using the runtime handler config and engine data from the adjacent step.

php
// Only registered if tools_callback is provided
add_filter('datamachine_tools', function($tools) use ($slug, $tools_callback) {
    $tools['__handler_tools_' . $slug] = [
        '_handler_callable' => $tools_callback,
        'handler'           => $slug,
        'modes'             => ['pipeline'],
        'access_level'      => 'admin',
    ];
    return $tools;
});

The preferred direct-style callback receives (string $handler_slug, array $handler_config, array $engine_data) and returns ['tool_name' => $tool_definition] (empty array to opt out).

Existing filter-style callbacks are also supported. ToolManager::resolveHandlerTools() invokes callbacks with ($tools, $handler_slug, $handler_config, $engine_data) when reflection detects four or more parameters, or a first parameter named $tools or $all_tools.

Handler tool matching is exact by default: a registry entry with 'handler' => 'wordpress_publish' is resolved only for adjacent steps whose handler slug is exactly wordpress_publish. Cross-cutting tools can use 'handler_types' => ['fetch', 'event_import']; those are matched against datamachine_handlers metadata.

_handler_callable entries are deferred registry entries. They are not directly executable tools and should not include normal tool parameters at the wrapper level. The callback returns the executable tool definitions.

Usage Example

Basic Usage (Publish Handler)

php
use DataMachineCoreStepsHandlerRegistrationTrait;

class TwitterFilters {
    use HandlerRegistrationTrait;

    public static function register(): void {
        self::registerHandler(
            'twitter',
            'publish',
            Twitter::class,
            __('Twitter', 'datamachine'),
            __('Post content to Twitter with media support', 'datamachine'),
            true,  // Requires OAuth
            TwitterAuth::class,
            TwitterSettings::class,
            function($handler_slug, $handler_config, $engine_data) {
                return [
                    'twitter_publish' => datamachine_get_twitter_tool($handler_config),
                ];
            }
        );
    }
}

// Auto-execute at file load
function datamachine_register_twitter_filters() {
    TwitterFilters::register();
}
datamachine_register_twitter_filters();

Fetch Handler Example

php
use DataMachineCoreStepsHandlerRegistrationTrait;

class RSSFilters {
    use HandlerRegistrationTrait;

    public static function register(): void {
        self::registerHandler(
            'rss',
            'fetch',
            RSS::class,
            __('RSS Feed', 'datamachine'),
            __('Fetch content from RSS/Atom feeds', 'datamachine'),
            false,  // No auth required
            null,
            RSSSettings::class,
            null  // No AI tools for fetch handlers
        );
    }
}

function datamachine_register_rss_filters() {
    RSSFilters::register();
}
datamachine_register_rss_filters();

Upsert Handler Example

php
use DataMachineCoreStepsHandlerRegistrationTrait;

class WordPressUpdateFilters {
    use HandlerRegistrationTrait;

    public static function register(): void {
        self::registerHandler(
            'wordpress_update',
            'upsert',
            WordPressUpdate::class,
            __('WordPress Update', 'datamachine'),
            __('Update existing WordPress content', 'datamachine'),
            false,
            null,
            WordPressUpdateSettings::class,
            function($handler_slug, $handler_config, $engine_data) {
                return [
                    'wordpress_update' => datamachine_get_wordpress_update_tool($handler_config),
                ];
            }
        );
    }
}

function datamachine_register_wordpress_update_filters() {
    WordPressUpdateFilters::register();
}
datamachine_register_wordpress_update_filters();

Migration Guide

Custom handlers should adopt this pattern by:

  1. *Add trait to Filters class:

    php
    use DataMachineCoreStepsHandlerRegistrationTrait;
    
    class MyHandlerFilters {
        use HandlerRegistrationTrait;
    }
    
  2. Replace manual filter calls with single registerHandler() call:

    php
    // Before (manual registration)
    add_filter('datamachine_handlers', function($handlers) { /* ... */ });
    add_filter('datamachine_auth_providers', function($providers) { /* ... */ });
    add_filter('datamachine_handler_settings', function($settings) { /* ... */ });
    add_filter('datamachine_tools', function($tools) { /* ... */ });  // manual _handler_callable wiring
    
    // After (trait registration)
    self::registerHandler(
        'my_handler',
        'publish',
        MyHandler::class,
        __('My Handler', 'textdomain'),
        __('Handler description', 'textdomain'),
        true,
        MyHandlerAuth::class,
        MyHandlerSettings::class,
        $tools_callback
    );
    
  3. Move tool registration to callback parameter:

    php
     // Receives (handler_slug, handler_config, engine_data) and returns
     // [tool_name => definition]. The trait handles exact-slug routing to the adjacent
    // step's handler — no manual slug check needed.
    function($handler_slug, $handler_config, $engine_data) {
        return [
            'my_tool' => [
                'class' => MyHandler::class,
                'method' => 'handle_tool_call',
                'handler' => 'my_handler',
                'description' => 'Tool description',
                'parameters' => [/* ... */],
            ],
        ];
    }
    
  4. Remove redundant filter registration code from *Filters.php file.

File Organization

*Add trait to Filters class:

php
use DataMachineCoreStepsHandlerRegistrationTrait;

class MyHandlerFilters {
    use HandlerRegistrationTrait;
}

Benefits

Code Reduction

Replace manual filter calls with single registerHandler() call:

php
// Before (manual registration)
add_filter('datamachine_handlers', function($handlers) { /* ... */ });
add_filter('datamachine_auth_providers', function($providers) { /* ... */ });
add_filter('datamachine_handler_settings', function($settings) { /* ... */ });
add_filter('datamachine_tools', function($tools) { /* ... */ });  // manual _handler_callable wiring

// After (trait registration)
self::registerHandler(
    'my_handler',
    'publish',
    MyHandler::class,
    __('My Handler', 'textdomain'),
    __('Handler description', 'textdomain'),
    true,
    MyHandlerAuth::class,
    MyHandlerSettings::class,
    $tools_callback
);

Move tool registration to callback parameter:

php
// Receives (handler_slug, handler_config, engine_data) and returns
 // [tool_name => definition]. The trait handles exact-slug routing to the adjacent
// step's handler — no manual slug check needed.
function($handler_slug, $handler_config, $engine_data) {
    return [
        'my_tool' => [
            'class' => MyHandler::class,
            'method' => 'handle_tool_call',
            'handler' => 'my_handler',
            'description' => 'Tool description',
            'parameters' => [/* ... */],
        ],
    ];
}

Consistency

  • $handler_slug: Unique identifier for the handler (e.g., ‘twitter’, ‘rss’)
  • $handler_type: Handler type ('fetch', 'publish', or 'upsert')
  • $handler_class: Fully qualified class name for the handler
  • $label: Display name for admin interface (translatable)
  • $description: Handler description for admin interface (translatable)
  • $requires_auth: Whether handler requires OAuth authentication
  • $auth_class: Fully qualified auth class name (required if requires_auth=true)
  • $settings_class: Fully qualified settings class name
  • $tools_callback: Callback for AI tool registration
  • $authProviderKey: Optional auth provider key override for shared authentication. When set, the handler metadata includes auth_provider_key, and the auth provider is registered under this key.
  • $meta: Optional arbitrary metadata passed through to handler discovery APIs, for example character limits or media limits.

Maintainability

  • Default: provider_key === handler_slug
  • Shared auth: pass $authProviderKey to registerHandler() and use that as the provider key

Type Safety

  • Ensures all handlers follow identical registration patterns
  • Prevents missing or incorrectly configured filters
  • Centralizes validation logic for handler metadata

Core vs Extension Boundaries

  • Single location for registration logic updates
  • Easy to add new registration requirements
  • Simplified debugging of handler registration issues

See Also

  • Method signature provides clear parameter requirements
  • IDE autocomplete support for all parameters
  • PHP type hints prevent registration errors
  • Core owns HandlerRegistrationTrait, datamachine_handlers, datamachine_handler_settings, datamachine_auth_providers, and deferred _handler_callable resolution.
  • Extensions own concrete handler classes, settings classes, auth provider classes, and executable tool definitions.
  • Extensions should register exact handler slugs. Core does not perform fuzzy slug matching.
  • Extensions should use handler_types only for cross-cutting tools that intentionally apply to every handler of a registered type.
  • Extensions can export portable auth config as auth_ref; core resolves it at runtime before fetch, publish, and upsert handler execution.