Many times while building Laravel applications, I ran into situations where a single process required handling multiple tasks. At first, I wrote everything inside service classes, but those classes quickly became too long and messy. Everything ended up wrapped inside one big transaction, and testing became painful because all the logic was tightly packed into the same place. That’s when I realized I needed a better approach. So I started breaking methods into separated classes. This approach was much better but when going through the Laravel Docs I found a better solution if used efficiently, our code becomes cleaner, more maintainable, and far easier to test.
Laravel Events
When you need to run a series of actions to complete a process, Laravel Events are the right tool. An event can trigger multiple listeners, and each listener is an independent class that doesn’t depend on the others. This keeps the workflow smooth, decoupled, and easy to extend. Instead of cramming everything into one oversized service class, you can break tasks into small, testable units that can be added, removed, or modified without touching the rest. By using events effectively, each part of your process remains clean, isolated, and much easier to manage as your application grows.
Example: E-Commerce – Store Initialization
Let’s say you’re building an e-commerce SaaS platform. A user comes in, registers, and creates their store.
When user presses Save button the following happens behind the scene.
- Save the store.
- Assign store to current authenticated user.
- Assign admin role to the user.
- Assign default permissions to that admin.
- Create default brands, categories, and products.
- Send an email to the admin telling: "Your store is ready."
- Send an email to the super admin telling"A new store has been registered".
Now think:
Would you put all this in the controller? In a service? In a job?
No. Just fire an event:
event(new StoreCreated($store));
This will trigger all the actions that are required to complete this process. Here's how we do it.
php artisan make:event StoreCreated
This will generate the following class in app/Events
folder.
<?php
namespace App\Events;
use App\Models\Store;
use Illuminate\Foundation\Events\Dispatchable;
class StoreCreated
{
use Dispatchable;
/**
* Create a new event instance.
*/
public function __construct(
public Store $store,
) {}
}
Next we need to create listeners for this event.
php artisan make:listener AssignStore --event=StoreCreated
php artisan make:listener AssignAdminRole --event=StoreCreated
php artisan make:listener AssignAdminAbilities --event=StoreCreated
php artisan make:listener AddDefaultBrands --event=StoreCreated
php artisan make:listener AddDefaultCategories --event=StoreCreated
php artisan make:listener AddDefaultProducts --event=StoreCreated
php artisan make:listener SendStoreReadtEmail --event=StoreCreated
php artisan make:listener SendNewStoreCreatedEmail --event=StoreCreated
Register Listeners: The New Way
If we create a listener, Laravel will automatically scan the
directory and register it for us. When we define a listener method like Listeners
and type-hint the event in its signature, Laravel knows which event it should respond to. For example, if you run:handle
php artisan make:listener AssignStore --event=StoreCreated
Laravel will generate the listener and link it to the
event automatically.StoreCreated
Note: You can also create directory inside listeners directory to group all the listeners that are required to be processed togather. For example to keep it simple use the same name as event to create a directory.
app/
└── Listeners/
└── StoreCreated/
└── AssignStore.php
└── AssignAdminRole.php
└── AssignAdminAbilities.php
└── AddDefaultBrands.php
└── AddDefaultCategories.php
└── AddDefaultProducts.php
└── SendStoreReadtEmail.php
└── SendNewStoreCreatedEmail.php
And our command will look like this.
php artisan make:listener StoreCreated/AssignStore --event=StoreCreated
My Customization
But if you want to prefer the old way just (as i do) you can do it like this.
php artisan make:provider EventServiceProvider
This will generate EventServiceProvider
class in app/Providers
folder.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}
Now we can create $listen
property and do the stuff.
protected $listen = [
StoreCreated::class => [
AssignStore::class
AssignAdminRole::class
AssignAdminAbilities::class
AddDefaultBrands::class
AddDefaultCategories::class
AddDefaultProducts::class
SendStoreReadtEmail::class
SendNewStoreCreatedEmail::class
],
];
If we do this we are free to place our classes anywhere in the application like services
, helpers
or AQC
. Just we have to define handle()
method and type-hint
the event class into it.
Finally we can call the event after we have saved the store data. I usually prefer to do it in Observer class.
<?php
namespace App\Observers;
use App\Models\Store;
use App\Events\StoreCreated;
class StoreObserver
{
public function created(Store $store)
{
event(new StoreCreated($store));
}
}
Final Thoughts
When building scalable Laravel applications, it’s easy to fall into the trap of overloading controllers or service classes with too much responsibility. Events and listeners provide a clean and elegant way to separate concerns while keeping your codebase flexible. By splitting processes into small, independent listeners, you gain testability, maintainability, and the ability to extend workflows without fear of breaking existing logic. Whether you stick to Laravel’s automatic event discovery or prefer the old-school
mapping, the key is consistency. Once you start embracing events, you’ll find that your code naturally evolves into a system that’s easier to reason about, easier to test, and far more adaptable to future requirements.EventServiceProvider
If you found this post helpful, consider supporting my work — it means a lot.
