initphp / events
Events (Hook) library — bundles the EventEmitter primitive previously distributed as initphp/event-emitter.
Requires
- php: >=5.6
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^9.6
Replaces
README
A small, dependency-free event / hook library for PHP. Ships both a
high-level dispatcher with WordPress-style do_action-like semantics
(stop the chain when a listener returns false, simulate mode, debug
log) and a plain low-level EventEmitter you can instantiate and pass
around like any other object.
Heads-up — v2.0 fixes a long-standing priority-ordering bug. In 1.x, listeners ran in the order they were registered, not in the order their priorities asked for. v2.0 makes priority honour its name. If your 1.x code happened to register listeners in ascending priority order, the visible behaviour does not change; if it didn't, please re-read Priorities and ordering and the v2.0 changelog.
At a glance
Events— a static facade backed by a lazily-built singleton. Convenient for application-wide hooks where threading a dispatcher through every layer is overkill.Event— the high-level dispatcher itself. Adds priority-ordered dispatch with a "returnfalsestops the chain" contract, plus optional simulate and debug modes. Use this directly when you want a dispatcher you own.EventEmitter— the low-level primitive. Plainon/once/emit/removeListener. No simulate, no debug, no short-circuit. Use this when you need a dispatcher with the smallest possible surface — or when you are migrating from the deprecatedinitphp/event-emitterpackage (the legacy\InitPHP\EventEmitter\*class names still work via a BC alias).
Everything is in a single PSR-4 namespace (InitPHP\Events\) and has
zero runtime dependencies.
Requirements
| Requirement | Version |
|---|---|
| PHP | >= 5.6 (runtime); PHP >= 7.3 to run the test suite (PHPUnit 9.6) |
| Extensions | none |
| Composer dependencies | none |
Installation
composer require initphp/events
If you are coming from initphp/event-emitter, replace your dependency
— initphp/events declares a Composer replace for it and ships a
class alias so the old fully-qualified names still resolve. See
Migration for details.
Quick start
require __DIR__ . '/vendor/autoload.php'; use InitPHP\Events\Events; Events::on('helloTrigger', function (): void { echo 'Hello World' . PHP_EOL; }, 100); Events::on('helloTrigger', function (): void { echo 'Hi, World' . PHP_EOL; }, 99); Events::trigger('helloTrigger');
Output:
Hi, World
Hello World
The listener registered with priority 99 runs before the one with
priority 100 — lower numeric value means runs first. The three
named constants Events::PRIORITY_HIGH (10), Events::PRIORITY_NORMAL
(100), and Events::PRIORITY_LOW (200) exist for readability.
Passing arguments to listeners
Events::on('greet', function (string $name, string $me): void { echo "Hi {$name}. I am {$me}." . PHP_EOL; }, 99); Events::trigger('greet', 'World', 'John'); // Hi World. I am John.
Stopping the chain
A listener that returns boolean false halts the chain — subsequent
listeners are not invoked and trigger() itself returns false. This
is the same convention as WordPress's apply_filters short-circuit:
Events::on('save', function ($payload) { if (!isValid($payload)) { return false; // stops the chain } }, 10); Events::on('save', function ($payload): void { persist($payload); // never runs if validation returned false }, 20); if (!Events::trigger('save', $payload)) { // at least one listener vetoed the operation }
Working with Event directly
If you prefer a dispatcher you instantiate and pass around (easier to
test, easier to scope, no global state), use Event:
use InitPHP\Events\Event; $dispatcher = new Event(); $dispatcher ->on('user.registered', function ($user): void { sendWelcomeEmail($user); }) ->on('user.registered', function ($user): void { trackSignup($user); }, Event::PRIORITY_LOW); // run last $dispatcher->trigger('user.registered', $user);
Event and Events expose the same surface — Events::on(...) simply
forwards to a shared Event instance.
Working with the low-level EventEmitter
For libraries that want the smallest possible surface, the underlying emitter is also public:
use InitPHP\Events\EventEmitter; $emitter = new EventEmitter(); $emitter->on('hello', function (string $name): void { echo "Hello {$name}!" . PHP_EOL; }, 99); $emitter->once('hello', function (string $name): void { echo "Hi {$name}, this only fires once." . PHP_EOL; }, 10); $emitter->emit('hello', ['World']); $emitter->emit('hello', ['World']);
Differences from Event:
emit()returnsvoid(no short-circuit onfalse).- Listener arguments come in as a single array, not as varargs.
- No simulate or debug mode.
- No singleton.
EventEmitter is what Event is built on. The same instance is
reachable via $dispatcher->getEmitter() if you ever need both
high-level dispatch and low-level emit on the same listener registry.
Priorities and ordering
PRIORITY_HIGH = 10 ← runs first
PRIORITY_NORMAL = 100
PRIORITY_LOW = 200 ← runs last
Rules:
- Lower numeric priority runs first. The names are about importance (high-priority work runs early); the numbers are about position.
- Within the same priority, listeners run in registration order (FIFO).
- Priority is global per-event — once-listeners and regular listeners are merged into a single priority-sorted queue when an event fires.
- Event names are matched case-insensitively:
on('User.Created')andemit('user.created')see the same listener.
See docs/04-priorities-and-ordering.md
for the full ordering contract, including how once() interacts with
the priority queue.
One-shot listeners, removal, and cleanup
$dispatcher->once('boot', $listener); // fires at most once $dispatcher->off('boot', $listener); // removes a specific listener $dispatcher->removeAllListeners('boot'); // wipes one event $dispatcher->removeAllListeners(); // wipes every event
The once() contract is honoured even when the chain is stopped by a
false return — a one-shot listener that runs (or that would have
run, but for a short-circuit earlier in the queue) is dropped after
the trigger completes. See
docs/05-once-and-removal.md.
Simulate mode and debug log
Event (and therefore Events) carry two opt-in instrumentation
modes:
$dispatcher = new InitPHP\Events\Event(); $dispatcher->setSimulate(true); // listeners are not invoked; trigger() still returns true $dispatcher->setDebugMode(true); // each trigger() appends {start, end, event} to the log $dispatcher->trigger('checkout.complete', $order); $dispatcher->getDebug(); // [['start' => ..., 'end' => ..., 'event' => 'checkout.complete']] $dispatcher->clearDebug(); // empty the log
See docs/06-debug-and-simulate.md.
Public API
| Class | Purpose |
|---|---|
InitPHP\Events\Events |
Static facade; thin shim over a shared Event instance. |
InitPHP\Events\Event |
High-level dispatcher (priority, short-circuit, simulate, debug). |
InitPHP\Events\EventEmitter |
Low-level primitive (on / once / emit / removeListener / clearOnceListeners). |
InitPHP\Events\EventEmitterInterface |
Contract that EventEmitter implements. |
| Method | Class | Purpose |
|---|---|---|
trigger(string $name, ...$arguments): bool |
Event · Events |
Dispatch with priority + short-circuit semantics. |
on(string, callable, int $priority = PRIORITY_NORMAL): self |
Event · Events · EventEmitter |
Register a listener. |
once(string, callable, int $priority = PRIORITY_NORMAL): self |
Event · Events · EventEmitter |
Register a one-shot listener. |
off(string, callable): self |
Event · Events |
Remove a specific listener (alias for removeListener). |
removeListener(string, callable): void |
EventEmitter |
Remove a specific listener. |
removeAllListeners(?string = null): self/void |
Event · Events · EventEmitter |
Wipe one event, or every event. |
emit(string, array $args = []): void |
EventEmitter |
Low-level dispatch (no short-circuit). |
clearOnceListeners(?string = null): void |
EventEmitter |
Drop one-shot listeners without invoking them. |
listeners(?string = null): array |
EventEmitter |
Inspect the current listener registry. |
setSimulate(bool): self / getSimulate(): bool |
Event · Events |
Toggle / inspect simulate mode. |
setDebugMode(bool): self / getDebugMode(): bool |
Event · Events |
Toggle / inspect debug mode. |
getDebug(): array / clearDebug(): self |
Event · Events |
Read / clear the debug log. |
getEmitter(): EventEmitter |
Event · Events |
Access the backing emitter. |
getInstance(): Event |
Events |
Return the shared dispatcher. |
setInstance(Event): void |
Events |
Inject a pre-configured dispatcher. |
reset(): void |
Events |
Drop the shared instance (tests, lifecycle resets). |
Full signatures and exception behaviour: docs/09-api-reference.md.
Migrating from initphp/event-emitter
The standalone initphp/event-emitter package has been merged into
this one and is now deprecated. If your code currently uses
\InitPHP\EventEmitter\EventEmitter, no source changes are
required — this package ships a backwards-compatibility alias:
- "initphp/event-emitter": "^1.0", + "initphp/events": "^2.0"
Composer will not install both packages side-by-side because
initphp/events:^2.0 declares a replace for initphp/event-emitter.
When you next touch the code, prefer the new canonical namespace:
// Before use InitPHP\EventEmitter\EventEmitter; // After use InitPHP\Events\EventEmitter;
The alias is intended as a transition aid and may be removed in a
future major release. One important behaviour change that comes
with v2.0: in 1.x, EventEmitter::emit() had a bug where the entire
listeners array was passed to call_user_func_array instead of each
individual listener, so emitted events silently fired no listeners at
all. That is fixed in 2.0 — if you had quietly broken emit() calls
in 1.x, they will now actually run their listeners.
See docs/07-migration-from-event-emitter.md for the full migration checklist.
Documentation
The full guide lives under docs/:
- Getting started
- The
Eventsfacade - Using
EventEmitterdirectly - Priorities and ordering
- Once-listeners, removal, and cleanup
- Debug and simulate modes
- Migrating from
initphp/event-emitter - Recipes (plugin systems, request lifecycle, WordPress-style hooks)
- API reference
Development
composer install composer test # PHPUnit
CI runs the test suite across PHP 7.3 – 8.4 and also lints the source
on PHP 5.6 / 7.0 / 7.1 / 7.2 so the php >= 5.6 library contract stays
honest.
Contributing & Security
Credits
License
Released under the MIT License.