Simple Event Emitter / Observer in Pure PHP
PHP Code Editor
Execution Result
Ready to execute
Click the "Run Script" button to see the output here
Description
An event emitter decouples the code that triggers events from the code that reacts to them. Instead of calling handlers directly, you fire a named event and any number of registered listeners respond. This pattern avoids tight coupling between components and makes it easy to add or remove behavior without touching the emitting code.
Internally, listeners are stored as {fn, once} entries in an array keyed by event name. on() appends a persistent listener. once() appends a listener flagged for single-use removal. When emit() fires an event, it iterates all registered listeners in order, calls each one, and removes any flagged as once immediately after firing. off() without a second argument removes all listeners for that event; with a callable it removes that specific one.
<?php
/**
* Lightweight event emitter.
* Supports multiple listeners per event, one-time listeners, and wildcard removal.
*/
class EventEmitter
{
private array $listeners = [];
public function on(string $event, callable $listener): static
{
$this->listeners[$event][] = ['fn' => $listener, 'once' => false];
return $this;
}
public function once(string $event, callable $listener): static
{
$this->listeners[$event][] = ['fn' => $listener, 'once' => true];
return $this;
}
public function off(string $event, ?callable $listener = null): static
{
if ($listener === null) {
unset($this->listeners[$event]);
return $this;
}
$this->listeners[$event] = array_filter(
$this->listeners[$event] ?? [],
fn($l) => $l['fn'] !== $listener
);
return $this;
}
public function emit(string $event, mixed ...$args): void
{
foreach ($this->listeners[$event] ?? [] as $key => $listener) {
($listener['fn'])(...$args);
if ($listener['once']) {
unset($this->listeners[$event][$key]);
}
}
}
public function listenerCount(string $event): int
{
return count($this->listeners[$event] ?? []);
}
}
// Demo: order processing pipeline
$emitter = new EventEmitter();
$emitter->on('order.created', function (array $order): void {
echo "Log: order #{$order['id']} created for {$order['customer']}\n";
});
$emitter->on('order.created', function (array $order): void {
echo "Email: sending confirmation to {$order['email']}\n";
});
// once() fires only on the first event
$emitter->once('order.created', function (array $order): void {
echo "Promo: first order bonus applied!\n";
});
$emitter->on('order.shipped', function (array $order): void {
echo "SMS: order #{$order['id']} shipped\n";
});
echo "--- Order 1 ---\n";
$emitter->emit('order.created', ['id' => 1001, 'customer' => 'Alice', 'email' => 'alice@example.com']);
echo "\n--- Order 2 (once listener should not fire) ---\n";
$emitter->emit('order.created', ['id' => 1002, 'customer' => 'Bob', 'email' => 'bob@example.com']);
echo "\n--- Shipping ---\n";
$emitter->emit('order.shipped', ['id' => 1001]);
echo "\nListeners on order.created: " . $emitter->listenerCount('order.created') . "\n";
Comments
No comments yet
Be the first to share your thoughts!