Events/Listeners
Table of contents
- Overview
- How It Works
- File Locations
- Bootstrapping
- Creating Events & Listeners
- Replacing Direct Service Calls
- Writing Core vs Userland Events
- Advanced
- Queued Events
- Summary
A. Creating a Queued Event
B. Creating a Queued Listener
C. Registering the Listener
D. Dispatching the Event
E. Running the Queue Worker
F. Advantages of Using Queued Listeners
G. Key Notes
1. Overview Table of Contents
The Event/Listener system in Chappy.php allows you to decouple your application logic using the Observer Pattern. Instead of calling functions directly, you dispatch events, and listeners handle them automatically.
This makes your code cleaner, easier to maintain, and extensible — similar to Laravel’s event system.
2.🔹How It Works Table of Contents
- Events: Simple classes that describe something that happened (e.g.,
UserRegistered,OrderShipped). - Listeners: Classes that respond to specific events.
- EventDispatcher: Core service that registers listeners and dispatches events.
- EventServiceProvider: Registers all your event → listener mappings.
- EventManager: Boots all core and app providers and stores a shared dispatcher.
3. 📂 File Locations Table of Contents
| Type | Core Location | App (Userland) Location |
|---|---|---|
| Events | src/core/Lib/Events |
app/Events |
| Listeners | src/core/Lib/Listeners |
app/Listeners |
| Providers | src/core/Lib/Providers |
app/Providers |
| Config | config/providers.php |
Same file, add your app providers |
4. ⚙ Bootstrapping Table of Contents
Events and listeners are booted automatically via EventManager::boot() in bootstrap.php.
Config file: config/providers.php
Example:
<?php
return [
Core\Lib\Providers\EventServiceProvider::class, // Core events
App\Providers\EventServiceProvider::class, // Userland events
];
Add your app’s event provider here to register custom events/listeners.
5. 🚀 Creating Events & Listeners Table of Contents
1️⃣ Make a new Event
php console make:event UserPromoted
This creates:
app/Events/UserPromoted.php
Example:
namespace App\Events;
use App\Models\Users;
class UserPromoted
{
public $user;
public function __construct(Users $user)
{
$this->user = $user;
}
}
2️⃣ Make a new Listener
php console make:listener UserPromoted NotifyAdminOfPromotion
This creates:
app/Listeners/NotifyAdminOfPromotion.php
Example:
namespace App\Listeners;
use App\Events\UserPromoted;
class NotifyAdminOfPromotion
{
public function handle(UserPromoted $event): void
{
$user = $event->user;
// Perform your action (e.g., send email)
}
}
3️⃣ Make an EventServiceProvider (userland)
php console make:provider EventServiceProvider
This creates:
app/Providers/EventServiceProvider.php
Example:
namespace App\Providers;
use Core\Lib\Events\EventDispatcher;
use Core\Lib\Providers\ServiceProvider;
use App\Events\UserPromoted;
use App\Listeners\NotifyAdminOfPromotion;
class EventServiceProvider extends ServiceProvider
{
protected array $listen = [
UserPromoted::class => [
NotifyAdminOfPromotion::class,
],
];
public function boot(EventDispatcher $dispatcher): void
{
parent::boot($dispatcher);
}
}
🔄 Dispatching Events
You can dispatch an event anywhere after boot:
use Core\Lib\Events\EventManager;
use App\Events\UserPromoted;
// Get a user instance
$user = Users::find(1);
// Dispatch the event
EventManager::dispatcher()->dispatch(new UserPromoted($user));
This will automatically call handle() on every listener registered for UserPromoted.
6. 🧪 Example — Replacing Direct Service Calls Table of Contents
Instead of:
NotificationService::sendUserRegistrationNotification($user);
You can:
EventManager::dispatcher()->dispatch(
new \Core\Lib\Events\UserRegistered($user, true)
);
Your SendRegistrationEmail listener will handle sending notifications and welcome emails.
7. 🔧 Writing Core vs Userland Events Table of Contents
- Core events/listeners live in src/core/Lib/Events and src/core/Lib/Listeners.
- Userland events/listeners live in app/Events and app/Listeners.
Core is maintained by the framework and ships with default functionality. Userland is for your app-specific needs.
Built End Listeners:
- SendAccountDeactivatedEmail - Sends E-mail when account is deactivated
- SendPasswordResetEmail - Sends E-mail requesting user update their password
- SendRegistrationEmail - Sends E-mail when user registers for an account
- SendPasswordUpdatedEmail - Sends E-mail when user updates their password
8. 🛠 Advanced Table of Contents
Multiple Listeners
You can register multiple listeners for the same event in $listen:
protected array $listen = [
UserPromoted::class => [
NotifyAdminOfPromotion::class,
LogPromotionActivity::class,
],
];
Conditional Logic Listeners can use event data to decide what to do:
public function handle(UserRegistered $event): void
{
if ($event->shouldSendEmail) {
WelcomeMailer::sendTo($event->user);
}
}
9. 📦 Queued Event Listeners Table of Contents Table of Contents
Queued event listeners allow you to defer the execution of an event listener until it is processed by a queue worker, rather than executing it immediately when the event is fired. This is ideal for time-consuming tasks such as sending emails, processing images, or performing external API calls, which should not delay the user’s request.
A. Creating a Queued Event
When generating an event with the --queue flag, the event class will automatically include payload serialization (toPayload()) and rehydration (fromPayload()) methods.
These methods ensure that the event data can be stored in the queue and restored later when processed.
Example:
php console make:event TestEvent --queue
Generated Event Class:
<?php
namespace Core\Lib\Events;
use App\Models\Users;
/**
* Document class here.
*/
class TestEvent {
public $user;
/**
* Constructor
*
* @param User $user User associated with event.
*/
public function __construct(Users $user) {
$this->user = $user;
}
/**
* Adds instance variables to payload.
*
* @return array An associative array containing values of instance
* variables.
*/
public function toPayload(): array {
return [];
}
/**
* Retrieves information from payload array and returns new instance of
* this class.
*
* @param array $data The payload array.
* @return self New instance of this class.
*/
public static function fromPayload(array $data): self {
$user = Users::findById((int)$data['user_id']);
return new self($user);
}
}
Working example built into framework:
<?php
declare(strict_types=1);
namespace Core\Lib\Events;
use App\Models\Users;
class UserRegistered {
public $user;
public function __construct(Users $user) {
$this->user = $user;
}
public function toPayload(): array {
return [
'user_id' => (int)$this->user->id,
];
}
public static function fromPayload(array $data): self {
$user = Users::findById((int)$data['user_id']);
return new self($user);
}
}
B. Creating a Queued Listener
When generating a listener with the –queue flag, the listener will automatically implement:
- ShouldQueue – Marks this listener for queueing.
- QueuePreferences – Allows you to configure the queue name, delay, backoff, and max attempts.
Example:
php console make:listener TestListener --queue
Generated Listener Class:
<?php
namespace Core\Lib\Listeners;
use App\Events\TestEvent;
use Core\Lib\Events\Contracts\ShouldQueue;
use Core\Lib\Events\Contracts\QueuePreferences;
/**
* Add description for class here
*/
class TestListener implements ShouldQueue, QueuePreferences {
/**
* Handle the event.
*
* @param TestEvent $event The event.
* @return void
*/
public function handle(TestEvent $event) : void {
$user = $event->user;
}
/**
* Set name of queue to be used.
*
* @return string|null
*/
public function viaQueue(): ?string {
return 'default';
}
/**
* Set the delay in seconds.
*
* @return int The delay in seconds.
*/
public function delay(): int {
return 60;
}
/**
* Get backoff for job. Can be an array of integers or a single in
* seconds.
*
* @return int|array The backoff times.
*/
public function backoff(): int|array {
return [10, 30, 60];
}
/**
* Gets number of maximum allowed attempts.
*
* @return int The maximum allowed number of attempts.
*/
public function maxAttempts(): int {
return 5;
}
}
Working example built into framework:
<?php
namespace Core\Lib\Listeners;
use Core\Lib\Events\UserRegistered;
use Core\Lib\Events\Contracts\ShouldQueue;
use Core\Lib\Events\Contracts\QueuePreferences;
use Core\Services\UserService;
use Core\Services\NotificationService;
class SendWelcomeEmailListener implements ShouldQueue, QueuePreferences {
public function handle(UserRegistered $event) : void {
NotificationService::sendUserRegistrationNotification($event->user);
UserService::queueWelcomeMailer((int)$event->user->id, $this->viaQueue());
}
public function viaQueue(): ?string { return 'mail'; }
public function delay(): int { return 60; }
public function backoff(): int|array { return [10, 30, 60]; }
public function maxAttempts(): int { return 5; }
}
Implementation of queueWelcomeMailer:
public static function queueWelcomeMailer(int $user_id, string $queueName = 'default') {
$queue = new QueueManager();
$job = new SendWelcomeEmail(['user_id' => $user_id], 0); // delay=0
$payload = $job->toPayload();
// Fix DATETIME format
$payload['available_at'] = DateTime::nowPlusSeconds($job->delay());
$queue->push($payload, $queueName);
}
When pushing job into the queue make sure you use mail via the $queueName parameter. Otherwise, the job will not run due to conflicts.
C. Registering the Listener
All listeners (queued or synchronous) must be registered in your EventServiceProvider:
protected array $listen = [
UserRegistered::class => [
SendWelcomeEmailListener::class,
],
];
D. Dispatching the Event
You can dispatch the event normally.
If the listener implements ShouldQueue, it will be serialized into the queue rather than executed immediately.
EventManager::dispatcher()->dispatch(new UserRegistered($user,));
E. Running the Queue Worker
Queued listeners won’t run until a queue worker processes them:
php console queue:work
You can run multiple workers, or run them in the background using a process manager such as supervisord or systemd.
F. Advantages of Using Queued Listeners
- Improves response time for user requests.
- Decouples time-consuming tasks from your application’s main request lifecycle.
- Allows retry handling with
maxAttempts()andbackoff().
G. Key Notes
- Always implement
toPayload()and fromPayload()for queued events to ensure safe serialization. - Be careful not to include sensitive data (e.g., raw passwords) in the payload.
- You can change the queue name with
viaQueue(), or leave it as'default'. - Listeners without
ShouldQueuewill run immediately.
10. 📌 Summary Table of Contents
- Define your events in app/Events.
- Create listeners in app/Listeners.
- Register them in an EventServiceProvider.
- Add your provider to config/providers.php.
- Dispatch events anywhere in your code.
- Listeners automatically execute when their event is fired.