-
Notifications
You must be signed in to change notification settings - Fork 2
Recipes
Practical, copy-pasteable patterns that combine the building blocks documented elsewhere in this wiki. Each recipe links back to the relevant reference page.
A drop-in for the do_action / add_action mental model: a global
event name, multiple subscribers, no return value coordination.
use InitPHP\Events\Events;
// Subscribers — declared anywhere in your app.
Events::on('post.published', function (array $post) {
PingSitemap::send($post['url']);
});
Events::on('post.published', function (array $post) {
Newsletter::queue($post['id']);
});
Events::on('post.published', function (array $post) {
Cache::invalidate('feed');
});
// Trigger — from wherever the action happens.
Events::trigger('post.published', $post);Reference: Events facade.
Validate a payload via a chain of listeners. Any listener can stop the
save by returning false. Use Events::trigger() and check its
return value.
use InitPHP\Events\Events;
Events::on('user.before_save', function (array &$user) {
if (!filter_var($user['email'], FILTER_VALIDATE_EMAIL)) {
return false; // veto
}
});
Events::on('user.before_save', function (array &$user) {
$user['email'] = strtolower($user['email']); // mutate
});
Events::on('user.before_save', function (array $user) {
if (User::emailTaken($user['email'])) {
return false; // veto
}
});
if (Events::trigger('user.before_save', $userArray) === false) {
throw new RuntimeException('Validation failed.');
}
User::save($userArray);References: Stopping Propagation, passing arguments by reference (PHP listener convention).
When you would rather depend-inject an event bus than reach for a global, instantiate one and pass it through your DI container.
use InitPHP\Events\EventEmitter;
use InitPHP\Events\EventEmitterInterface;
final class OrderService
{
/** @var EventEmitterInterface */
private $events;
public function __construct(EventEmitterInterface $events)
{
$this->events = $events;
}
public function place(array $order): void
{
// … persist …
$this->events->emit('order.placed', [$order]);
}
}
// composition root
$bus = new EventEmitter();
$bus->on('order.placed', new SendOrderConfirmation());
$bus->on('order.placed', new EnqueueShipping());
$service = new OrderService($bus);
$service->place($order);Reference: EventEmitter.
Run an initialiser exactly once, even if the trigger fires more than
once (e.g. lazy initialisation). Both APIs expose once():
use InitPHP\Events\Events;
Events::once('app.ready', function () {
Container::warmup();
});
Events::trigger('app.ready'); // warmup runs
Events::trigger('app.ready'); // silent no-opOr, with the low-level emitter:
use InitPHP\Events\EventEmitter;
$bus = new EventEmitter();
$bus->once('app.ready', fn () => Container::warmup());
$bus->emit('app.ready'); // warmup runs
$bus->emit('app.ready'); // silent no-opReference: Events facade › once
and EventEmitter › once.
Call Events::reset() in setUp() / tearDown() so listeners,
simulate / debug flags, and the debug log from one test never bleed
into the next:
use InitPHP\Events\Events;
final class MyTest extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void { Events::reset(); }
protected function tearDown(): void { Events::reset(); }
public function test_save_path_is_reached(): void
{
$captured = null;
Events::on('save', function ($payload) use (&$captured): void {
$captured = $payload;
});
runProductionCodePath();
$this->assertSame('expected', $captured);
}
}If you need a dry-run of a code path (walk the listener queue without
actually firing the side effects), combine reset() with
setSimulate(true) — or inject a pre-configured dispatcher:
$dryRun = (new Event())->setSimulate(true)->setDebugMode(true);
Events::setInstance($dryRun);
runProductionCodePath();
$dispatched = array_column(Events::getDebug(), 'event');
// $dispatched contains the events that *would* have fired.Reference: Simulate & Debug Mode,
Events facade › Singleton lifecycle.
Enable debug mode around a critical section and inspect per-listener timings:
use InitPHP\Events\Events;
Events::setDebugMode(true);
Events::trigger('checkout.flow', $cart);
$debug = Events::getDebug();
usort($debug, fn($a, $b) => ($b['end']-$b['start']) <=> ($a['end']-$a['start']));
foreach (array_slice($debug, 0, 5) as $row) {
$ms = ($row['end'] - $row['start']) * 1000;
printf("%6.2fms %s\n", $ms, $row['event']);
}
Events::setDebugMode(false);Reference: Simulate & Debug Mode › Debug mode.
EventEmitter::removeListener() needs the exact callable. If you can't
keep a reference (e.g. the original code is a third-party plugin),
nuke the entire event:
$bus->removeAllListeners('order.placed');If you only want to drop listeners temporarily, snapshot first:
$snapshot = $bus->listeners('order.placed');
$bus->removeAllListeners('order.placed');
try {
$bus->emit('order.placed', [$order]); // no listeners run
} finally {
foreach ($snapshot as $listener) {
$bus->on('order.placed', $listener);
}
}References: EventEmitter › removeListener,
removeAllListeners.
Group listener logic into a class — handy when the handler needs dependencies of its own.
final class SendOrderConfirmation
{
public function __construct(private Mailer $mailer) {}
public function __invoke(array $order): void
{
$this->mailer->send($order['email'], 'Order confirmation', /* … */);
}
}
$bus->on('order.placed', new SendOrderConfirmation($mailer));The same trick works on the Events facade:
Events::on('order.placed', new SendOrderConfirmation($mailer));You can wire one bus's emissions into another by registering a thin adapter listener:
$bus = new EventEmitter();
$bus->on('user.registered', function (...$args) {
Events::trigger('user.registered', ...$args);
});Useful while migrating from one API to the other, or when integrating two subsystems that each prefer a different style.
-
Eventsfacade andEventEmitter— the two APIs. - Listeners & Priorities — ordering and callables.
- Troubleshooting — when things don't fire.
initphp/events · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core APIs
Practical
Reference