Back to Blog
Lesson 13 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20263 min read

Introduction to Laravel Events and Listeners for Clean Code

Learn how to use Laravel events and listeners to decouple secondary side effects from your primary business logic, resulting in cleaner, maintainable code.

LaravelEventsListenersArchitectureRefactoringphpbackend

Previously in this course, we explored Service-Oriented Task Management to keep our controllers lean. Today, we take that modularity a step further by learning how to handle secondary side effects using events and listeners.

When you build a feature—like creating a new task on our project board—you often need to perform actions that aren't core to that task's success. You might need to send a notification, clear a cache, or log an audit entry. If you pack all that into your TaskService, your code quickly becomes a "god object" that knows too much.

The Power of Decoupling

Laravel's event system allows you to separate the intent (the user created a task) from the reactions (what happens next). By using events, your TaskService simply shouts, "A task was created!" and doesn't care who—or what—is listening. This decoupling makes your system easier to test and extend.

Defining a Custom Event

An event is a simple class that acts as a data carrier. It holds the information necessary for your listeners to do their work.

Run this command to create an event: php artisan make:event TaskCreated

Inside app/Events/TaskCreated.php, you'll define the properties:

PHP
namespace App\Events;

use App\Models\Task;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TaskCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(public Task $task)
    {
        #6A9955">//
    }
}

Implementing Listeners

A listener is a class that executes code when a specific event is fired. Let's create a listener that logs the task creation.

php artisan make:listener SendTaskNotification --event=TaskCreated

Inside app/Listeners/SendTaskNotification.php, the handle method receives the event instance:

PHP
namespace App\Listeners;

use App\Events\TaskCreated;
use Illuminate\Support\Facades\Log;

class SendTaskNotification
{
    public function handle(TaskCreated $event): void
    {
        Log::info("New task created: {$event->task->title}");
    }
}

Registering in EventServiceProvider

Laravel needs to know which listeners belong to which events. You register these in app/Providers/EventServiceProvider.php:

PHP
protected $listen = [
    TaskCreated::class => [
        SendTaskNotification::class,
    ],
];

Now, update your TaskService to fire the event:

PHP
use App\Events\TaskCreated;

public function createTask(array $data)
{
    $task = Task::create($data);
    
    #6A9955">// Dispatch the event
    event(new TaskCreated($task));
    
    return $task;
}

Hands-on Exercise

  1. Create a second listener, ClearProjectCache, that logs "Clearing project cache" when TaskCreated fires.
  2. Register it in your EventServiceProvider alongside the existing notification listener.
  3. Trigger the creation of a task via your API and check your storage/logs/laravel.log to confirm both listeners executed.

Common Pitfalls

  • Over-engineering: Don't turn every single method call into an event. Use them for side effects—things that aren't strictly required to return a successful response to the user.
  • Dependency Bloat: If a listener needs to know about the entire world, rethink your architecture. A listener should focus on one responsibility.
  • Silent Failures: If a listener throws an exception, it can stop the entire request cycle. Ensure your listeners have their own try-catch blocks if they perform risky operations.

Recap

By mastering events and listeners, you've gained a powerful tool for decoupling your application logic. You've moved from tightly coupled, procedural code to a reactive architecture where components communicate through shared events. This keeps your services focused and your codebase clean.

Up next: We will discuss how to move these potentially slow side effects into the background using Asynchronous Processing with Queues.

Similar Posts