Skip to content

Responding to Application Events

When you need to run code in response to application events—sending welcome emails after user registration, invalidating caches when data changes, logging payment activity—register event listeners that fire when those events occur.

Register a listener with on() and trigger it with emit():

app()->on('user.created', fn($payload) => sendWelcomeEmail($payload['user']));
// Later, when a user is created
app()->emit('user.created', ['user' => $user]);

The listener runs whenever you emit that event.

Listeners can be classes that resolve through the container:

class SendWelcomeEmail
{
public function __construct(private EmailService $emails) {}
public function __invoke(array $payload): void
{
$this->emails->send($payload['user']['email'], 'Welcome!');
}
}
app()->on('user.created', SendWelcomeEmail::class);

Dependencies are automatically injected when the listener runs.

You can attach multiple listeners to the same event:

app()
->on('user.created', SendWelcomeEmail::class)
->on('user.created', UpdateAnalytics::class)
->on('user.created', LogUserCreation::class);

Listeners execute in registration order.

Use wildcards to match multiple events:

// Match user.created, user.updated, user.deleted, etc.
app()->on('user.*', fn($payload) => logUserEvent($payload));
// Match every event
app()->on('*', fn($payload) => logAllEvents($payload));

Events are useful for cache invalidation without coupling your business logic to caching:

app()->on('product.updated', function($payload) use ($cache) {
$cache->forget('product:' . $payload['id']);
});
// Elsewhere, after updating a product
app()->emit('product.updated', ['id' => $product->id]);

Run multiple side effects when something important happens:

app()->on('order.completed', function($payload) {
$order = $payload['order'];
sendCustomerEmail($order);
notifyWarehouse($order);
updateInventory($order);
});

Track important events across a domain:

app()->on('payment.*', function($payload) use ($logger) {
$logger->info('Payment event', $payload);
});

This catches payment.succeeded, payment.failed, payment.refunded, and any other payment events you emit.

Skip emitting events if nothing is listening:

if (app()->hasListeners('user.created')) {
app()->emit('user.created', ['user' => $user]);
}

Use dot notation to namespace related events:

  • user.created, user.updated, user.deleted
  • order.completed, order.refunded
  • payment.succeeded, payment.failed

This makes wildcard matching intuitive and keeps your events organized.