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
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 = []
): voidParameters
- 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).
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
// 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).
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.
// 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)
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
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
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:
*Add trait to Filters class:
phpuse DataMachineCoreStepsHandlerRegistrationTrait; class MyHandlerFilters { use HandlerRegistrationTrait; }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' => [/* ... */], ], ]; }Remove redundant filter registration code from *Filters.php file.
File Organization
*Add trait to Filters class:
use DataMachineCoreStepsHandlerRegistrationTrait;
class MyHandlerFilters {
use HandlerRegistrationTrait;
}Benefits
Code Reduction
Replace manual filter calls with single registerHandler() call:
// 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:
// 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
$authProviderKeytoregisterHandler()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
Related Documentation
- Core owns
HandlerRegistrationTrait,datamachine_handlers,datamachine_handler_settings,datamachine_auth_providers, and deferred_handler_callableresolution. - 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_typesonly 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.