Leveraging Laravel Events: Forgotten Cart Example & User Activity Logging
Laravel’s event system is a powerful way to decouple different parts of your application. By using events and listeners, you can trigger side effects (like sending emails or logging activity) without cluttering your core business logic. In this post, we’ll explore how events work in Laravel, and walk through a practical example: handling a "forgotten cart" scenario in an e-commerce app, and logging user activity via an event listener.
Why Use Events?
Events allow you to:
- Decouple logic: Keep your controllers and services clean by moving side effects elsewhere.
- Easily extend behavior: Add more listeners without touching the core logic.
- Promote single responsibility: Each listener does one thing well.
Forgotten Cart Events in Practice
Overview
Imagine you want to remind users who have added items to their cart but haven’t checked out after a certain period. Here’s how you might model this with events in Laravel.
Step 1: Create the Event
php artisan make:event ForgottenCart
This generates an event class in app/Events/ForgottenCart.php
:
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class ForgottenCart
{
use Dispatchable, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Step 2: Create a Listener to Send Reminder Email
php artisan make:listener SendForgottenCartReminder --event=ForgottenCart
This will generate a listener in app/Listeners/SendForgottenCartReminder.php
:
namespace App\Listeners;
use App\Events\ForgottenCart;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailer;
class SendForgottenCartReminder implements ShouldQueue
{
public function handle(ForgottenCart $event)
{
// Send reminder email logic
\Mail::to($event->user->email)->send(new \App\Mail\ForgottenCartMail($event->user));
}
}
Step 3: Create a Listener to Log User Activity
php artisan make:listener LogForgottenCartActivity --event=ForgottenCart
In app/Listeners/LogForgottenCartActivity.php
:
namespace App\Listeners;
use App\Events\ForgottenCart;
use Illuminate\Support\Facades\Log;
class LogForgottenCartActivity
{
public function handle(ForgottenCart $event)
{
Log::info('User forgot cart', ['user_id' => $event->user->id]);
}
}
Event Registration in Laravel
Automatic Discovery of Event Listeners
By default, Laravel will automatically find and register your event listeners by scanning your application's Listeners
directory. When Laravel finds any listener class method that begins with handle
or __invoke
, it will register those methods as event listeners for the event that is type-hinted in the method's signature.
You usually don't have to manually register listeners if you follow Laravel's conventions.
Manually Registering Events
If you do want to manually register your events and listeners, you can do so using the Event
facade, typically within the boot
method of your application's AppServiceProvider
:
use App\Domain\Orders\Events\PodcastProcessed;
use App\Domain\Orders\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(
PodcastProcessed::class,
SendPodcastNotification::class,
);
}
Listing Registered Event Listeners
You can list all of the listeners registered within your application with the following Artisan command:
php artisan event:list
Dispatching and Scheduling Forgotten Cart Events
Dispatching the Event
Whenever your business logic detects a forgotten cart, dispatch the event, both of these approaches will fire the event and trigger any listeners:
ForgottenCart::dispatch($user);
// OR
event(new ForgottenCart($user));
ForgottenCart::dispatch($user) vs event(new ForgottenCart($user))
Which should you use?
ForgottenCart::dispatch($user);
is the preferred, modern, and more expressive syntax for custom events that use theDispatchable
trait. It makes your intent clear and is recommended by Laravel.event(new ForgottenCart($user));
is a generic way to fire any event, including framework or third-party events, and is useful if the event does not use theDispatchable
trait.
Best practice: For your own custom events, always use ForgottenCart::dispatch($user);
for clarity and maintainability.
When Should a Forgotten Cart Event Fire?
A "forgotten cart" event should typically fire when:
- The user has added items to their cart but has not checked out.
- A certain period of inactivity has passed (e.g., 6, 12, or 24 hours since the last cart update).
- The cart still contains items and the user is identifiable.
- No purchase has been made for that cart since the last update.
This is often handled by a scheduled task that runs periodically and checks for qualifying carts.
Example: Scheduled Command to Dispatch Forgotten Cart Events
You can use Laravel's scheduler to automate this process. First, create a custom Artisan command:
php artisan make:command DispatchForgottenCarts
In app/Console/Commands/DispatchForgottenCarts.php
:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Events\ForgottenCart;
use Carbon\Carbon;
class DispatchForgottenCarts extends Command
{
protected $signature = 'cart:dispatch-forgotten';
protected $description = 'Dispatch ForgottenCart events for users with inactive carts';
public function handle()
{
$cutoff = Carbon::now()->subHours(12); // e.g., 12 hours of inactivity
$users = User::whereHas('cart', function ($query) use ($cutoff) {
$query->where('updated_at', '<', $cutoff)
->whereHas('items');
})->get();
foreach ($users as $user) {
ForgottenCart::dispatch($user);
$this->info("Dispatched ForgottenCart event for user ID: {$user->id}");
}
}
}
Scheduling the Command
For Laravel 10 and below:
Schedule the command in app/Console/Kernel.php
:
protected function schedule(Schedule $schedule)
{
$schedule->command('cart:dispatch-forgotten')->hourly();
}
For Laravel 11 and above:
Scheduling is now handled in routes/console.php
:
// routes/console.php
schedule(function ($schedule) {
$schedule->command('cart:dispatch-forgotten')->hourly();
});
This setup ensures your app automatically checks for and handles forgotten carts, keeping your event-driven logic clean and automated.
Benefits of This Approach
- Separation of concerns: Emailing and logging are handled independently.
- Scalability: Add more listeners (e.g., send SMS, trigger push notification) without modifying your core logic.
- Testability: Listeners can be tested in isolation.
Conclusion
Laravel’s event system is a great way to keep your codebase clean and modular. By using events for things like forgotten carts and user activity logging, you can build applications that are both maintainable and easy to extend.