From b0118ef8efea43d0689703a02e19d9fe3d5e7032 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 25 Apr 2026 19:37:23 +0100 Subject: [PATCH] feat(core/events): add WebhookRegistering lifecycle event (HIGH) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebhookRegistering event exposes: - register(string $type, array $spec): add a webhook type to the registry - types(): array — queryable post-dispatch registry CoreServiceProvider dispatches the event at app boot and exposes the collected registry via webhookTypes() — matches the existing ApiRoutesRegistering / ConsoleBooting / ClientRoutesRegistering event-driven module pattern. Pairs with #1034 ofm.bot WebhookRegistrar (just landed) — that service can now also be wired through this event, allowing OTHER modules and external apps using Core to register webhook types via the standard Core lifecycle. Note: real Core lifecycle dispatcher lives in a sibling read-only framework checkout. CoreServiceProvider here is a local shim that mirrors the dispatch behaviour. Upstream patch needed when that sibling lands. Pest covers: instantiation + register, boot-time dispatch, post-boot registry lookup. Co-authored-by: Codex Closes tasks.lthn.sh/view.php?id=1013 --- php/CoreServiceProvider.php | 58 +++++++++++++ php/Events/WebhookRegistering.php | 67 +++++++++++++++ .../Unit/Events/WebhookRegisteringTest.php | 84 +++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 php/CoreServiceProvider.php create mode 100644 php/Events/WebhookRegistering.php create mode 100644 php/tests/Unit/Events/WebhookRegisteringTest.php diff --git a/php/CoreServiceProvider.php b/php/CoreServiceProvider.php new file mode 100644 index 0000000..b4e0b28 --- /dev/null +++ b/php/CoreServiceProvider.php @@ -0,0 +1,58 @@ +app->singleton(self::WEBHOOK_TYPES, static fn (): array => []); + } + + public function boot(): void + { + $this->app->booted(function (): void { + $this->app->instance(self::WEBHOOK_TYPES, static::fireWebhookRegistering()); + }); + } + + /** + * Fire WebhookRegistering and return the collected type registry. + * + * @return array> + */ + public static function fireWebhookRegistering(): array + { + $event = new WebhookRegistering; + event($event); + + return $event->types(); + } + + /** + * Get the webhook type registry captured during boot. + * + * @return array> + */ + public static function webhookTypes(): array + { + return app()->bound(self::WEBHOOK_TYPES) + ? app(self::WEBHOOK_TYPES) + : []; + } +} diff --git a/php/Events/WebhookRegistering.php b/php/Events/WebhookRegistering.php new file mode 100644 index 0000000..53259e4 --- /dev/null +++ b/php/Events/WebhookRegistering.php @@ -0,0 +1,67 @@ + 'onWebhookRegistering', + * ]; + * + * public function onWebhookRegistering(WebhookRegistering $event): void + * { + * $event->register('myapp.event.x', [ + * 'payload' => [ + * 'id' => 'string', + * ], + * ]); + * } + * ``` + */ +class WebhookRegistering extends LifecycleEvent +{ + /** @var array> Collected webhook type definitions keyed by type */ + protected array $types = []; + + /** + * Register a webhook type definition. + * + * Later registrations with the same type replace earlier definitions so an + * application can override defaults during boot. + * + * @param string $type Fully qualified webhook type name + * @param array $definition Payload shape or metadata for the type + */ + public function register(string $type, array $definition = []): void + { + $this->types[$type] = $definition; + } + + /** + * Get all registered webhook type definitions. + * + * @return array> + * + * @internal Used by the webhook ingress bootstrap + */ + public function types(): array + { + return $this->types; + } +} diff --git a/php/tests/Unit/Events/WebhookRegisteringTest.php b/php/tests/Unit/Events/WebhookRegisteringTest.php new file mode 100644 index 0000000..492ad52 --- /dev/null +++ b/php/tests/Unit/Events/WebhookRegisteringTest.php @@ -0,0 +1,84 @@ +register('myapp.event.x', [ + 'payload' => [ + 'id' => 'string', + ], + ]); + + expect($event->types())->toBe([ + 'myapp.event.x' => [ + 'payload' => [ + 'id' => 'string', + ], + ], + ]); +}); + +test('CoreServiceProvider_boot_Good_dispatches_webhook_registering', function (): void { + $received = false; + + Event::listen(WebhookRegistering::class, function (WebhookRegistering $event) use (&$received): void { + $received = true; + + $event->register('ofm.bot.message.received', [ + 'payload' => [ + 'message_id' => 'string', + ], + ]); + }); + + $this->app->register(CoreServiceProvider::class); + + expect($received)->toBeTrue(); +}); + +test('CoreServiceProvider_webhookTypes_Good_returns_registered_types_after_boot', function (): void { + Event::listen(WebhookRegistering::class, function (WebhookRegistering $event): void { + $event->register('ofm.bot.message.received', [ + 'payload' => [ + 'message_id' => 'string', + ], + ]); + + $event->register('ofm.bot.message.deleted', [ + 'payload' => [ + 'message_id' => 'string', + ], + ]); + }); + + $this->app->register(CoreServiceProvider::class); + + expect(CoreServiceProvider::webhookTypes())->toBe([ + 'ofm.bot.message.received' => [ + 'payload' => [ + 'message_id' => 'string', + ], + ], + 'ofm.bot.message.deleted' => [ + 'payload' => [ + 'message_id' => 'string', + ], + ], + ]); +});