webware / webware-event
Event system for Webware
Requires
- php: ~8.4.1 || ~8.5.0
- phly/phly-event-dispatcher: ^1.5.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- infection/infection: ^0.35.0
- laminas/laminas-servicemanager: ^4.5
- phpbench/phpbench: ^1.7
- phpunit/phpunit: ^13.3.0
- roave/backward-compatibility-check: ^8.21.0
- roave/security-advisories: dev-master
- webware/webware-tools: ^1.0.0-beta.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- 1.0.x-dev
- 1.0.0-alpha.2
- 1.0.0-alpha.1
- 0.1.x-dev
- 0.1.0
- dev-chore/pin-webware-tools-1.0.0-beta.2
- dev-chore/pin-webware-tools-1.0.0-beta.1
- dev-fix/fail-loud-on-unresolvable-listener
- dev-chore/webware-tools-alignment
- dev-chore/psr15-http-move
- dev-fix/event-aware-contracts
- dev-chore/bump-webware-core-1.0.0-alpha.1
This package is auto-updated.
Last update: 2026-09-20 03:13:17 UTC
README
PSR-14 event system for the Mezzio framework — declarative listener wiring, delegator-based dispatcher injection, and PSR-15 middleware integration.
Installation
composer require webware/webware-event
Quick Start
1. Register the config provider
Merge Webware\Event\ConfigProvider into your application config (standard Laminas/Mezzio pattern).
2. Register listeners in app config
return [ 'listeners' => [ OrderPlaced::class => [ ['listener' => UpdateInventory::class, 'priority' => 100], ], ], 'listener_providers' => [ CustomListenerProvider::class, ], ];
3. Dispatch events from your services
use Webware\Event\EventDispatcherAwareInterface; use Webware\Event\EventDispatcherAwareTrait; class OrderService implements EventDispatcherAwareInterface { use EventDispatcherAwareTrait; public function placeOrder(Order $order): void { // ...business logic... $this->eventDispatcher->dispatch(new Event('order.placed', $this, [ 'order_id' => $order->id, ])); } }
To inject the dispatcher, wire the EventDispatcherAwareDelegator for each service that implements EventDispatcherAwareInterface:
return [ 'dependencies' => [ 'delegators' => [ OrderService::class => [ EventDispatcherAwareDelegator::class, ], ], ], ];
4. Access the dispatcher in HTTP handlers
class OrderHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { $dispatcher = $request->getAttribute(EventDispatcherInterface::class); $dispatcher->dispatch(new Event('handler.invoked', $this)); // ... } }
The EventDispatcherMiddleware attaches the dispatcher as a request attribute. You must register it in your middleware pipeline to make the dispatcher available to all downstream Middleware/handlers.
Configuration
ConfigProvider keys
| Key | Type | Description |
|---|---|---|
dependencies |
array |
Container aliases & factories for the dispatcher, aggregate, and middleware. |
listeners |
array<class-string, array> |
Event class → listener specs. Merged with app config. |
listener_providers |
array<class-string> |
Additional ListenerProviderInterface FQCNs to attach to the aggregate. |
Listener spec formats
| Format | Example | Behavior |
|---|---|---|
| Class-string | SendEmail::class |
Lazy-resolved from container via LazyListener |
Array with priority |
['listener' => X::class, 'priority' => 100] |
Resolved via PrioritizedListenerProvider |
| Callable | fn(Event $e) => ... |
Attached directly |
Every string entry is a container service id resolved lazily through the container; a string is never treated as a callable. An id the container does not have — as well as an array spec with no listener key — throws InvalidListenerConfigurationException (a ContainerExceptionInterface) while the listener provider is built, rather than being skipped silently.
Middleware
EventDispatcherMiddleware (PSR-15) injects the event dispatcher into the request as an attribute keyed by EventDispatcherInterface::class. Register it in your middleware pipeline to make the dispatcher available to all downstream handlers.
Architecture
ConfigProvider ──▶ container wiring (aliases, factories, listeners)
┌──────────────────┐
│ Container │
└──────┬───────────┘
│
┌─────────────────┼──────────────────┐
▼ ▼ ▼
ListenerProviderAggregate EventDispatcher EventDispatcherMiddleware
(resolves listeners) (phly) (PSR-15, injects into request)
│ │
└────────┬────────┘
│
dispatch(Event)
│
┌────────┴────────┐
▼ ▼
prioritized listeners standard listeners
Key classes
| Class | Namespace | Role |
|---|---|---|
Event |
Webware\Event |
Concrete event with name, target, params, and propagation control |
ConfigProvider |
Webware\Event |
Dependency wiring, default config, and the ConfigShape type alias |
ListenerProviderAggregateFactory |
Webware\Event\Container |
Builds the listener aggregate from config |
EventDispatcherAwareDelegator |
Webware\Event\Container |
Injects the dispatcher into aware services |
EventDispatcherMiddleware |
Webware\Event\Http\Middleware |
PSR-15 middleware for request-scoped dispatch |
EventAwareInterface / EventAwareTrait |
Webware\Event |
Pattern for event-carrying objects: an object can carry the event it is processing |
EventDispatcherAwareInterface / EventDispatcherAwareTrait |
Webware\Event |
Pattern for event-dispatching services |
EventInterface |
Webware\Event |
Contract an event satisfies; Event implements it |
ListenerInterface |
Webware\Event |
Contract a listener satisfies (__invoke(EventInterface $event): void) |
EventPropagationInterface / EventPropagationTrait |
Webware\Event |
Pattern for stoppable propagation |
Config arrays are typed by the @type ConfigShape alias declared on Webware\Event\ConfigProvider —
consumers @import-type ConfigShape from ConfigProvider and read the keys directly, rather than going
through an accessor class. The traits declare @require-implements, so mago reports a class that uses a
trait without also implementing its interface.
Development
composer test # unit suite composer test-integration # integration suite (in-process ServiceManager wiring) composer test-coverage # unit suite with clover + HTML coverage composer mutation-test # Infection, with Mago as staticAnalysisTool composer test-all # test + test-integration + mutation-test
Every command also runs in the tooling container, which needs no native PHP toolchain:
docker compose up -d docker compose exec tooling composer test docker compose exec tooling mago format --check
License
BSD-3-Clause