Events
Events let one part of your code react when something happens elsewhere, such as sending a welcome email after signup. The framework does not fire events itself; you define your own names and listeners.
Register listeners once at boot (for example in routes.php or a service provider), then trigger where the action happens.
Basic usage
use Roolith\Event\Event;
// register once at boot:
Event::listen('user.created', function (array $user) {
return 'welcome:' . $user['email'];
});
// where the action happens:
$results = Event::trigger('user.created', [['email' => 'a@b.com']]); // ['welcome:a@b.com']Passing data
No argument calls listeners with zero args, a single value is passed as one arg, and an array is spread into listener params.
use Roolith\Event\Event;
Event::listen('logout', function ($who) {
echo 'Goodbye ' . $who;
});
Event::trigger('logout', 'a@b.com');
Event::listen('updated', function ($field, $by) {
echo $field . ' changed by ' . $by;
});
Event::trigger('updated', ['email', 'admin']);Listener params must match what you trigger with, otherwise PHP throws an ArgumentCountError.
Trigger results
trigger() returns listener results in order, or [] when nothing matched. It does not throw for missing listeners.
$results = Event::trigger('login'); // e.g. ['ok', null]
if ($results === []) {
// no listener matched
}
if (Event::has('login')) {
Event::trigger('login');
}Returning false from a listener stops the rest from running:
Event::listen('login', fn () => false); // the next listener never runs
Event::listen('login', fn () => 'never');Group listeners
Register one listener for several events, or catch a group with a wildcard. Wildcards match one level: event.* matches event.login but not event.login.extra.
use Roolith\Event\Event;
Event::listeners(['login', 'user.login'], function () {
// runs for either event
});
Event::listen('event.*', function ($which) {
echo 'Saw ' . $which;
});
Event::trigger('event.login', 'login');Remove listeners
use Roolith\Event\Event;
Event::unregister('updated'); // remove all listeners for one event
Event::unregister(['a', 'b']); // remove several at once
$callback = function () {};
Event::listen('updated', $callback);
Event::unregister('updated', $callback); // remove just that oneEvent names
Names are letters, digits, and underscores joined by dots, with an optional trailing .* (for example login, user.login, event.*). Anything else throws an InvalidArgumentException:
use Roolith\Event\Event;
use Roolith\Event\Exceptions\InvalidArgumentException;
try {
Event::trigger('invalid..name');
} catch (InvalidArgumentException $e) {
// handle validation error
}Isolated dispatcher
Use a Dispatcher instance when you want dependency injection or isolated state in tests. Instances do not share listeners with the Event facade.
use Roolith\Event\Dispatcher;
use Roolith\Event\Event;
$events = new Dispatcher();
$events->listen('login', fn () => 'ok');
$events->trigger('login'); // ['ok']
// reset shared facade state in tests:
Event::reset();
Event::setSharedDispatcher(new Dispatcher());Worked example
Copy-paste register-once-at-boot shape. Use events only for decoupled side effects - welcome emails, order-placed hooks - not for the main request flow.
<?php
use Roolith\Event\Event;
// Call once at boot, for example in routes.php or a service provider.
function registerUserCreatedListeners(): void
{
Event::listen('user.created', static function (array $user): string {
return 'welcome:' . (string) ($user['email'] ?? '');
});
}
// Where the action happens.
function userCreated(array $user): array
{
return Event::trigger('user.created', [$user]);
}
registerUserCreatedListeners();
$results = userCreated(['email' => 'a@b.com']); // ['welcome:a@b.com']trigger() returns ordered listener results, [] when nothing matched; returning false from a listener stops propagation. Keep registration separate from triggering so tests can register doubles without touching global state.