Plugin Hook Registry
Contract for accumulating plugin hook handler registrations and resolving them by hook name, type, and priority.
Overview
App\Sdk\Hooks\PluginHookRegistry is the in-memory registry of plugin hook handlers. Registrations accumulate at plugin boot and are consumed by App\Sdk\Hooks\PluginHookDispatcher.
The registry stores ordered PluginHookRegistration value objects. It does not invoke handlers, isolate failures, or soft-disable plugins — those behaviours live on the dispatcher.
Public surface
| Method | Purpose |
|---|---|
register() |
Append one PluginHookRegistration |
for() |
Return matching registrations for a hook name and HookType, priority ascending |
hookNames() |
Return distinct hook names that have at least one registration of the given type |
all() |
Return every accumulated registration in append order |
flush() |
Clear all registrations |
Accepted registration shape
Each entry is an immutable App\Sdk\Hooks\PluginHookRegistration:
new PluginHookRegistration(
package: 'signals/xero-sync',
hookName: 'account.updated',
type: HookType::Event,
priority: 50,
handler: $callable,
)
App\Sdk\Hooks\HookType cases:
| Case | Value | Dispatcher method |
|---|---|---|
Event |
event |
dispatchEvent() |
Filter |
filter |
applyFilters() |
Validator |
validator |
runValidators() |
Decorator |
decorator |
applyDecorators() |
There is no keyed identity and no duplicate rejection. Multiple packages may register the same hook name and type; register() always appends.
Ordering and lookup behavior
for($hookName, $type) filters by exact hook name and type, then sorts by priority ascending (lower first). Equal priorities retain relative order from the filtered list after usort.
hookNames($type) returns distinct hook names that have at least one registration of that type, in first-seen append order.
all() returns the raw append-order list. flush() empties the registry (used between boots / tests).
Runtime consumption and failure isolation
PluginHookDispatcher is the runtime consumer:
- It resolves handlers through
for($hook, $type)before each dispatch path - A throwing handler is logged and skipped so core continues
- Consecutive failures are counted per package in cache key
plugin-hooks:failures:{package} - After
config('plugins.hook_failure_threshold')(default10) the package is soft-disabled - Success resets the counter; slow handlers above
plugins.hook_timeout_secondslog a warning but are not interrupted
The registry itself never catches exceptions or mutates plugin status.
Worked example
This is the current accumulation and lookup shape:
$registry->register(new PluginHookRegistration(
package: 'signals/xero-sync',
hookName: 'account.updated',
type: HookType::Event,
priority: 10,
handler: $handler,
));
$registry->for('account.updated', HookType::Event); // priority-ascending list
$registry->hookNames(HookType::Event); // ['account.updated', ...]
PluginHookDispatcher::dispatchEvent('account.updated', ...$payload) walks that for() result and invokes each handler under failure isolation.