Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
LaravelPHPJune 20, 20264 min read

Laravel Events and Listeners: A Practical Guide to Decoupling

Master laravel events and listeners to clean up your controllers. This tutorial shows you how to decouple logic for better, more maintainable PHP code.

LaravelPHPClean CodeEventsArchitectureBackend DevelopmentTutorial
A detailed overhead shot of a brown basketball court with white lines.

Last month, I was debugging a controller method that had ballooned to 150 lines. It was handling user registration, sending a welcome email, triggering a Slack notification, and updating a legacy CRM via an API call. Every time the marketing team wanted a change to the notification logic, I had to touch the registration code, risking a production outage.

That’s when I realized I needed to stop treating my controllers like junk drawers. By moving secondary tasks into laravel events and laravel listeners, I finally cleaned up that mess.

Why You Should Decouple Your Logic

In most applications, your controller should only do one thing: handle the incoming request and return a response. When you start piling on "side effects"—like sending emails or pinging third-party APIs—you violate the Single Responsibility Principle.

If you've ever felt like your code was getting too brittle, you're likely dealing with tight coupling. Learning php event-driven programming basics is the best way to fix this. It allows your core business logic to fire an event and "forget" about what happens next.

Implementing Your First Event

Let’s look at how to move that registration logic into a cleaner structure. First, you need to define an event. In a Laravel 10 or 11 project, you can generate this quickly:

Bash
php artisan make:event UserRegistered

This creates a simple class in app/Events. I usually just pass the User model into the constructor so my listeners have access to the data they need:

PHP
namespace App\Events;

use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;

class UserRegistered
{
    use Dispatchable;

    public function __construct(public User $user) {}
}

Creating the Listener

Next, you need the laravel listeners that actually do the work. You can create one for the welcome email:

Bash
php artisan make:listener SendWelcomeEmail --event=UserRegistered

Inside the handle method, you place the logic that used to clutter your controller:

PHP
namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user->email)->send(new \App\Mail\WelcomeEmail($event->user));
    }
}

Wiring it Together

You don't need to manually register these in older Laravel versions, but keep an eye on your EventServiceProvider. Since Laravel 11, the framework uses a more streamlined approach, but the core concept remains the same: map the event to the listener.

Once wired, your controller becomes remarkably thin:

PHP
public function store(Request $request)
{
    $user = User::create($request->validated());

    #6A9955">// The event triggers all attached listeners
    UserRegistered::dispatch($user);

    return response()->json(['message' => 'User created!']);
}

Common Pitfalls and Trade-offs

Early in my career, I tried to turn everything into an event. That was a mistake. I ended up with a codebase where I couldn't trace the execution flow because everything was hidden behind an event bus.

If your listener is just a simple database update, keep it in the controller. Save events for side effects—things that happen as a result of your main action but aren't strictly required to return a response to the user.

Also, remember that listeners run synchronously by default. If your listener hits a slow third-party API, the user will wait for that API to finish. To fix this, implement the ShouldQueue interface on your listener. This pushes the task to a background job, which is essential if you're dealing with Laravel Horizon graceful shutdowns or high-traffic apps.

When to Use This Pattern

You should reach for this pattern when:

  1. You have multiple actions triggered by one event.
  2. The side effects aren't critical to the immediate success of the request.
  3. You want to make your code easier to test by mocking the event dispatcher.

If you are new to dependency injection, you might want to review the Laravel Service Container: A Beginner’s Guide to Dependency Injection first, as understanding the container makes working with listeners much more intuitive. For more complex distributed systems, you might eventually graduate to the Laravel Event-Driven Architecture: The Transactional Outbox Pattern, but for 90% of apps, the built-in event system is more than enough.

FAQ

Q: Do events make debugging harder? A: Sometimes. Because the execution flow isn't linear, you can't just step through the code. I recommend using a tool like Laravel Telescope to track which events fired and which listeners handled them.

Q: Can I stop an event from firing? A: Yes, you can return false from a listener to stop the propagation to subsequent listeners, though I rarely find a use case for this. Keep your listeners independent of one another.

Q: Is this "clean code"? A: It's a step toward it. Clean code is about intent. By using events, your controller expresses the intent ("a user registered") rather than the implementation details ("send mail, notify slack, update crm").

I still struggle sometimes with naming my events correctly. Is it UserWasRegistered or UserRegistered? I've settled on the latter for simplicity, but consistency is more important than the specific naming convention you choose. Don't overthink it—just start moving those side effects out of your controllers and watch your codebase become readable again.

Back to Blog

Similar Posts

Detailed view of colorful programming code on a computer screen.
LaravelPHPJune 20, 20264 min read

Mastering Laravel API Resources: A Guide to Clean JSON Responses

Mastering Laravel API resources is the key to decoupling your database from your JSON output. Learn how to transform Eloquent models into clean, stable APIs.

Read more
A healthcare worker in scrubs prepares a syringe with precision.
LaravelPHPJune 20, 20265 min read

Laravel Service Container: A Beginner’s Guide to Dependency Injection

Master the Laravel service container and dependency injection to write cleaner, testable PHP code. Learn how to decouple your app logic like a senior dev.

Read more
Close-up of a vintage typewriter featuring a privacy policy document in focus, highlighting classic technology.
LaravelPHPJune 20, 20264 min read

Mastering Laravel Policies: A Practical Guide to Authorization Logic

Master Laravel Policies to secure your PHP applications. Learn how to move authorization logic out of controllers into clean, reusable, and testable classes.

Read more