Appearance
Events
Table of Contents
- Introduction
- Event Service Provider
- Creating Events and Listeners
- Registering Listeners
- Dispatching Events
- Async Events
- Listener Lifecycle
- Listener Priority and Limits
- Stopping Propagation
- Error Handling and Warnings
- Event Logging
- Event Faking
- Event CLI Commands
- Facade API Reference
- Testing Event Dispatch
Introduction
Phenix provides an event emitter that supports synchronous and asynchronous dispatch, listener priorities, one-time listeners, propagation control, and testing utilities (logging/faking/assertions).
The main facade is Phenix\Facades\Event.
Event Service Provider
The event system is registered by Phenix\Events\EventServiceProvider.
At boot time, it:
- Registers
Phenix\Events\EventEmitterin the container. - Binds
Phenix\Events\Contracts\EventEmittertoEventEmitter. - Registers CLI commands:
make:eventmake:listener
- Loads listeners from
listen/events.phpwhen that file exists.
Automatic Event File Loading
Use listen/events.php to register your listeners in one place:
php
<?php
declare(strict_types=1);
use Phenix\Events\Contracts\Event as EventContract;
use Phenix\Facades\Event;
Event::on('user.registered', function (EventContract $event): void {
// Handle event
});Creating Events and Listeners
Generate an Event Class
sh
php phenix make:event UserRegisteredWith force option:
sh
php phenix make:event UserRegistered --force
php phenix make:event UserRegistered -fNested namespace path:
sh
php phenix make:event Admin/UserRegisteredGenerated path: app/Events/....
Generate a Listener Class
sh
php phenix make:listener SendWelcomeEmailWith force option:
sh
php phenix make:listener SendWelcomeEmail --force
php phenix make:listener SendWelcomeEmail -fNested namespace path:
sh
php phenix make:listener Admin/SendWelcomeEmailGenerated path: app/Listeners/....
Manual Event Class
You can create event classes by extending AbstractEvent:
php
<?php
declare(strict_types=1);
namespace App\Events;
use Phenix\Events\AbstractEvent;
class UserRegistered extends AbstractEvent
{
public function __construct(public readonly int $userId)
{
// optional payload for generic handlers
$this->payload = ['user_id' => $userId];
}
}AbstractEvent already provides:
getName(): string(defaults tostatic::class)getPayload(): mixedstopPropagation(): voidisPropagationStopped(): bool
Manual Listener Class
You can create listener classes by extending AbstractListener:
php
<?php
declare(strict_types=1);
namespace App\Listeners;
use Phenix\Events\AbstractListener;
use Phenix\Events\Contracts\Event;
class SendWelcomeEmail extends AbstractListener
{
public function handle(Event $event): mixed
{
// ...
return null;
}
}Registering Listeners
Closure Listener
php
use Phenix\Events\Contracts\Event as EventContract;
use Phenix\Facades\Event;
Event::on('user.registered', function (EventContract $event): void {
$payload = $event->getPayload();
});Listener Class String
php
use App\Listeners\SendWelcomeEmail;
use Phenix\Facades\Event;
Event::on('user.registered', SendWelcomeEmail::class);When a class string is used, Phenix instantiates it and executes handle($event).
Listener Instance
php
use App\Listeners\SendWelcomeEmail;
use Phenix\Facades\Event;
$listener = (new SendWelcomeEmail())->setPriority(10);
Event::on('user.registered', $listener);One-Time Listener
php
use Phenix\Facades\Event;
Event::once('user.registered', function (): void {
// Runs only once
});Dispatching Events
Dispatch by Event Name
php
use Phenix\Facades\Event;
Event::emit('user.registered', [
'id' => 1,
'email' => 'john@example.com',
]);Dispatch Event Objects
php
use App\Events\UserRegistered;
use Phenix\Facades\Event;
Event::emit(new UserRegistered(userId: 1));Listener Return Values
emit() returns an array with every listener result in execution order.
php
$results = Event::emit('invoice.closed');Async Events
Use emitAsync() for asynchronous dispatch. It returns an Amp\Future.
php
use Phenix\Facades\Event;
$future = Event::emitAsync('user.registered', ['id' => 1]);
$results = $future->await();Listener Lifecycle
Remove a specific listener:
php
$listener = fn () => null;
Event::on('user.registered', $listener);
Event::off('user.registered', $listener);Remove all listeners of one event:
php
Event::off('user.registered');Remove every registered listener from every event:
php
Event::removeAllListeners();Listener Priority and Limits
Higher priority listeners run first:
php
Event::on('priority.test', fn () => 'low', 1);
Event::on('priority.test', fn () => 'high', 10);
Event::on('priority.test', fn () => 'medium', 5);Priority is normalized to range 0..100 for AbstractListener instances.
Maximum listeners per event (default 10):
php
Event::setMaxListeners(20);
$max = Event::getMaxListeners();When warnings are enabled and the max is exceeded, a warning is logged.
php
Event::setEmitWarnings(true);Useful introspection helpers:
Event::hasListeners(string $event): boolEvent::getListeners(string $event): arrayEvent::getListenerCount(string $event): intEvent::getEventNames(): array
Stopping Propagation
A listener can stop subsequent listeners for the same dispatch:
php
use Phenix\Events\Contracts\Event as EventContract;
Event::on('order.created', function (EventContract $event): void {
$event->stopPropagation();
});
Event::on('order.created', function (): void {
// Not executed
});Error Handling and Warnings
If a listener throws an exception:
- Errors are logged.
- With
Event::setEmitWarnings(true), synchronousemit()throwsPhenix\Events\Exceptions\EventException. - With
Event::setEmitWarnings(false), the emitter logs the error and continues.
For emitAsync(), listener failures are logged and the async result slot becomes null when failures are consumed during await.
Event Logging
Enable logging of dispatched events (for tests and diagnostics):
php
use Phenix\Facades\Event;
Event::log();
Event::emit('user.registered');
$log = Event::getEventLog();Log entries include:
nameeventtimestamp
Reset only the log:
php
Event::resetEventLog();Event Faking
Faking records dispatches in the log but prevents listener execution according to the fake strategy.
Fake All Events
php
Event::fake();Fake One Event
Fake one event indefinitely:
php
Event::fakeOnly('user.registered');Fake only the next dispatch of an event:
php
Event::fakeOnce('user.registered');Fake N Times
php
Event::fakeTimes('user.registered', 2);Conditional Faking
php
use Phenix\Data\Collection;
Event::fakeWhen('user.registered', function (Collection $log): bool {
return $log->count() < 3;
});Fake All Except
Fake every event except one:
php
Event::fakeExcept('user.registered');Reset Faking State
Reset log + fake configuration:
php
Event::resetFaking();Production Behavior
In production environment, faking/logging methods are intentionally ignored.
Event CLI Commands
Create event class:
sh
php phenix make:event UserRegistered
php phenix make:event Admin/UserRegistered
php phenix make:event UserRegistered --force
php phenix make:event UserRegistered -fCreate listener class:
sh
php phenix make:listener SendWelcomeEmail
php phenix make:listener Admin/SendWelcomeEmail
php phenix make:listener SendWelcomeEmail --force
php phenix make:listener SendWelcomeEmail -fFacade API Reference
Phenix\Facades\Event exposes:
- Registration and dispatch:
on(string $event, Closure|EventListener|string $listener, int $priority = 0)once(string $event, Closure|EventListener|string $listener, int $priority = 0)off(string $event, Closure|EventListener|string|null $listener = null)emit(string|EventContract $event, mixed $payload = null): arrayemitAsync(string|EventContract $event, mixed $payload = null): Future
- Listener inspection and controls:
getListeners(string $event): arrayhasListeners(string $event): boolremoveAllListeners(): voidsetMaxListeners(int $maxListeners): voidgetMaxListeners(): intsetEmitWarnings(bool $emitWarnings): voidgetListenerCount(string $event): intgetEventNames(): array
- Logging and fakes:
log(): voidfake(): voidfakeWhen(string $event, Closure $callback): voidfakeTimes(string $event, int $times): voidfakeOnce(string $event): voidfakeOnly(string $event): voidfakeExcept(string $event): voidgetEventLog(): CollectionresetEventLog(): voidresetFaking(): void
- Assertions helper:
expect(string $event): Phenix\Testing\TestEvent
Testing Event Dispatch
Use Event::expect('event.name') assertions:
php
use Phenix\Facades\Event;
Event::log();
Event::emit('user.registered', ['id' => 1]);
Event::expect('user.registered')->toBeDispatched();
Event::expect('user.registered')->toBeDispatchedTimes(1);
Event::expect('other.event')->toNotBeDispatched();Predicate assertions:
php
Event::expect('user.registered')->toBeDispatched(function ($event): bool {
return $event !== null && ($event->getPayload()['id'] ?? null) === 1;
});Assert that nothing was dispatched:
php
Event::expect('any.event')->toDispatchNothing();