2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00
2026-04-26 23:23:59 +03:00

Latch

A cross-package hook/filter registry system for PHP. Apps register themselves as "hook sources" and declare typed extension points. Other packages attach handlers to those points. The source app queries the registry at runtime to collect, transform, or broadcast through those handlers.

Requirements

  • PHP ^8.2

Installation

composer require chenasraf/latch

Quick Start

use Latch\HookRegistry;

$registry = new HookRegistry();

// 1. Source declares extension points
$cms = $registry->registerSource('cms')
    ->filter('render-html', RenderPayload::class)
    ->action('page-published', PageEvent::class)
    ->collect('nav-items', NavContext::class);

// 2. Register a named handler and attach to those points
$seo = $registry->registerHandler('seo');

$seo->hook('cms', 'render-html')
    ->priority(5)
    ->handle(fn (RenderPayload $p) => $p->withHtml(minify($p->html)));

$seo->hook('cms', 'nav-items')
    ->when(fn (NavContext $ctx) => $ctx->user->isAdmin())
    ->handle(fn (NavContext $ctx) => [new NavItem('Admin', '/admin')]);

// 3. Source invokes at runtime
$payload = $cms->apply('render-html', new RenderPayload($html));
$cms->dispatch('page-published', new PageEvent($page, $user));
$navItems = $cms->collectFromHandlers('nav-items', new NavContext($user));

Hook Types

Type Source invokes Handler receives Handler returns
Filter apply() The payload object A modified payload (chained)
Action dispatch() The payload object Nothing (return value ignored)
Collect collectFromHandlers() An optional context object An array of items (merged flat)

Handler Options

$handler = $registry->registerHandler('my-plugin');

$handler->hook('source', 'point')
    ->priority(5)           // Lower runs first (default: 10)
    ->exclusive()           // Short-circuits remaining handlers after this one
    ->when(fn ($p) => ...)  // Skip handler if condition returns false
    ->tag('admin', 'ui')   // Additional tags for introspection and filtering
    ->handle(fn ($p) => ...);

All hooks are automatically tagged with handler:{name}. Sources can target specific handlers:

$cms->dispatch('page-published', $event, ['handler:seo']);

Each handler name can only be registered once. Pass the HookHandler instance via DI to reuse it across your package.

Capability Discovery

Both sources and handlers can declare capability tags at registration. The registry can then be queried to find either side by capability — without knowing names in advance.

// Sources declare what they provide
$registry->registerSource('cms', tags: ['content-management', 'publishing']);

// Handlers declare what they provide
$registry->registerHandler('seo', ['content-enhancement']);
$registry->registerHandler('minifier', ['content-enhancement']);
$registry->registerHandler('analytics', ['tracking']);

// Discover handlers by capability
$enhancers = $registry->handlersByTag('content-enhancement');
// → [HandlerInfo('seo'), HandlerInfo('minifier')]

// Discover sources by capability
$cmsSources = $registry->sourcesByTag('content-management');
// → [SourceStore('cms')]

This enables a discovery pattern where either side can find compatible counterparts at runtime, present them to the user, and target the chosen one.

Existence Checks

$registry->hasSource('cms');          // Does this source exist?
$cms->hasHandlers('render-html');    // Does anyone handle this point?

Framework Integration

Framework Setup
Laravel Auto-discovered — inject HookRegistryInterface anywhere
Nextcloud Each app bundles Latch; use LatchBootstrap::registry() for cross-app hooks via NC events
Plain PHP HookRegistry::getInstance() for shared singleton, or new HookRegistry()

Documentation

  • Guide — Full API reference for sources, handlers, introspection, and error handling
  • Examples — End-to-end examples and framework integration walkthroughs

Development

make install       # Install dependencies
make install-hooks # Set up lefthook git hooks
make test          # Run tests (Pest)
make analyze       # Static analysis (PHPStan level 8)
make fix           # Code style (Laravel Pint)

License

MIT

Description
No description provided
Readme MIT 97 KiB
Languages
PHP 99.3%
Makefile 0.7%