Event System
The WebFramework provides a robust event system that allows for decoupled communication between different parts of your application. This system follows the observer pattern and supports both synchronous and asynchronous event handling.
Core Components
EventService
The EventService
is the central manager for all events in the application. It provides methods to:
- Register events and their listeners
- Dispatch events
- Manage event listeners
- Handle queued event listeners
Event Interface
Events are simple data containers that implement the Event
interface. They should contain only the data needed for the event, not the logic:
EventListener Interface
Event listeners implement the EventListener
interface and contain the actual logic for handling events:
QueuedEventListener
The QueuedEventListener
abstract class provides a base for event listeners that should be processed asynchronously:
abstract class QueuedEventListener implements EventListener
{
protected string $queueName = 'default';
public function getQueueName(): string
{
return $this->queueName;
}
}
Usage
Creating an Event
An Event should only contain data and no logic. All logic related to an Event should be in a separate EventListener.
class UserRegisteredEvent implements Event
{
public function __construct(
private int $userId,
private string $email
) {}
public function getUserId(): int
{
return $this->userId;
}
public function getEmail(): string
{
return $this->email;
}
}
Creating a Synchronous Event Listener
/**
* @implements EventListener<UserRegisteredEvent>
*/
class SendWelcomeEmailListener implements EventListener
{
public function handle(Event $event): bool
{
if (!$event instanceof UserRegisteredEvent) {
return false;
}
// Send welcome email logic here
return true;
}
}
Creating a Queued Event Listener
/**
* @extends QueuedEventListener<UserRegisteredEvent>
*/
class ProcessUserDataListener extends QueuedEventListener
{
protected string $queueName = 'user-processing';
public function handle(Event $event): bool
{
if (!$event instanceof UserRegisteredEvent) {
return false;
}
// Heavy processing logic here
return true;
}
}
Registering Events and Listeners
// Register an event with its listeners
$eventService->registerEvent(
UserRegisteredEvent::class,
[
SendWelcomeEmailListener::class,
ProcessUserDataListener::class,
]
);
// Add a new listener to an existing event
$eventService->addListener(
UserRegisteredEvent::class,
NewFeatureListener::class
);
Dispatching Events
Application Integration
Here are real examples from the WebFramework showing how events are used in practice:
User Login Events
When a user successfully logs in, the framework dispatches a UserLoggedIn
event:
$this->authenticationService->authenticate($user);
$this->eventService->dispatch(new UserLoggedIn($user));
Password Change Events
When a user changes their password, a UserPasswordChanged
event is dispatched:
$user->setSolidPassword($newHash);
$this->userRepository->save($user);
$this->eventService->dispatch(new UserPasswordChanged($user));
Email Change Events
When a user's email address is changed, a UserEmailChanged
event is dispatched:
$user->setEmail($email);
$this->userRepository->save($user);
$this->eventService->dispatch(new UserEmailChanged($user));
User Verification Events
When a user completes email verification, a UserVerified
event is dispatched:
$user->setVerified();
$this->userRepository->save($user);
$this->eventService->dispatch(new UserVerified($user));
Queue Setup for Asynchronous Events
To use queued event listeners, you must:
- Register the EventJobHandler in your application configuration:
- Run queue workers to process the jobs:
- Configure queue names in your listeners:
class HeavyProcessingListener extends QueuedEventListener
{
protected string $queueName = 'heavy-processing';
}
Queued events will only be handled if you have a QueueWorker active and registered the EventJobHandler to handle EventJobs.
Framework Events
WebFramework itself will send out the following events:
- UserLoggedIn
- UserPasswordChanged
- UserVerified
- UserEmailChanged
Error Handling & Debugging
Unregistered Events
If you dispatch an event that hasn't been registered, the EventService will log a debug message and silently continue:
// This will log: "Cannot dispatch unregistered event"
$eventService->dispatch(new UnregisteredEvent());
Listener Errors
If a listener throws an exception, it will bubble up and stop processing of subsequent listeners. Wrap listener logic in try-catch blocks for graceful error handling:
public function handle(Event $event): bool
{
try {
// Your event handling logic
return true;
} catch (\Exception $e) {
$this->logger->error('Event handling failed', ['exception' => $e]);
return false;
}
}
Debugging
Enable debug logging to see event dispatch activity: - Synchronous events: "Dispatching non-queued event" - Asynchronous events: "Dispatching queued event"
Performance Considerations
Synchronous vs Asynchronous Processing
- Use synchronous listeners for lightweight operations that must complete immediately
- Use
QueuedEventListener
for heavy operations like email sending, file processing, or external API calls - Synchronous listeners block the request until completion
- Queued listeners are processed asynchronously by queue workers
Queue Configuration
For queued events, ensure you have: 1. Queue workers running to process jobs 2. EventJobHandler registered with QueueService 3. Appropriate queue names for different types of processing
Testing Event-Driven Code
Testing Event Dispatch
// Mock the EventService to verify events are dispatched
$eventService = $this->createMock(EventService::class);
$eventService->expects($this->once())
->method('dispatch')
->with($this->isInstanceOf(UserLoggedIn::class));
Testing Event Listeners
public function testUserLoggedInListener()
{
$user = new User();
$event = new UserLoggedIn($user);
$listener = new MyEventListener();
$result = $listener->handle($event);
$this->assertTrue($result);
}
Best Practices
- Keep events focused on a single state change
- Use descriptive event names that reflect past tense (e.g.,
UserRegisteredEvent
) - Include only necessary data in events
- Use queued listeners for time-consuming operations
- Implement proper error handling in listeners
- Consider using different queues for different types of listeners
- Monitor event processing and queue sizes
- Implement proper logging in event listeners