Previously, I wrote about Laravel Observers and mentioned it as a feature of Laravel framework that I love, since I came across it.
Read about it here
Today, I will be shedding light on another part of the Laravel framework that interests me too, events and listeners.
Events are just ways to alert your application that an action has happened, and the events can be dispatched at any point of your application, the controller, the model, the middleware, anywhere — even in the blade files(you shouldn’t do this, but you get my point).
Listeners, like the name implies, listens for events that occur in your application, but they do not just listen to any event, each listener must be mapped to an event before it listens for that event.
For a listener to respond to a dispatched event, the listener class must be mapped to a particular event class. These mappings happen in the EventServiceProvider class which can be found in the app\Providers folder.
An event can have multiple listeners mapped to it and when it is dispatched, all listening classes will be triggered in succession following the order in which it is mapped.
//Default EventServiceProvider Class
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends _ServiceProvider_
{
_/\*\*
\* The event listener mappings for the application.
\*
\*_ **_@var_** _array
\*/_ protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
_/\*\*
\* Register any events for your application.
\*
\*_ **_@return_** _void
\*/_ public function boot()
{
parent::_boot_();
//
}
}
Creating an event and listener class
To create an event class, make use of the make:event artisan command:
php artisan make:event
This command will create a new class into the app\Events folder of your application and that is all you need to create an event class.
To create a listener class, make use of the make:listener artisan command:
php artisan make:listener
This command, like in the Event creation, will create a new class into the app\Listeners folder of your application and that is all you need to create a listener class.
Another way of creating events and listeners, this way might even be termed easier than the ones earlier mentioned, is to register events and listeners in the EventServiceProvider class, then run:
php artisan event:generate
This command will scan through the EventServiceProvider class and generate the missing events and listeners based on registration.
You might already be wondering how to register an event and map a listener to it, yea? To do that, follow the pattern below:
_//Event Service Provider Class_
_/\*\*
\* The event listener mappings for the application.
\*
\*_ **_@var_** _array
\*/_ protected $listen = [
'Event Class' => [
'Listener Class',
'Another Listener Class',
'Yet Another Listener Class',
],
];
Like I earlier said, you can map more than one listener to a particular event and it will get processed in succession, following the order in which they are mapped.
Dispatching an event
To dispatch your event and trigger the listeners, there are two methods known to me as at the time of this writing:
- event(new EventClass());
- EventClass::dispatch();
If your event uses the Illuminate\Foundation\Events\Dispatchable trait, you may call the static dispatch method on the event. Any arguments passed to the dispatch method will be passed to the event's constructor.
You should note that public properties declared in the event class, can be accessed in the listener class which is mapped to it.
//Registered Event using in register controller.
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Registered
{
use SerializesModels;
_/\*\*
\* The authenticated user.
\*
\*_ **_@var_** _\Illuminate\Contracts\Auth\Authenticatable
\*/_ public $user;
_/\*\*
\* Create a new event instance.
\*
\*_ **_@param_** _\Illuminate\Contracts\Auth\Authenticatable $user
\*_ **_@return_** _void
\*/_ public function \_\_construct($user)
{
$this->user = $user;
}
}
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class RegisteredListener
{
_/\*\*
\* Create the event listener.
\*
\*_ **_@return_** _void
\*/_ public function \_\_construct()
{
//
}
_/\*\*
\* Handle the event.
\*
\*_ **_@param_** _object $event
\*_ **_@return_** _void
\*/_ public function handle($event)
{
$event->user
//this will have the content of the user property from the event class
}
}
Use Case
A very straightforward use-case of events and listeners would be the default user creation flow on the Laravel Framework.
For a lot of applications, there might be numerous processes that happen right after a user is created, and having all these logic in the controller method, might not just cluster up the controller method but for every additional process I want to take after a user signs up on my app.
I will have to go back to that method and do edits which defies both the Open/Close and the Single Responsibility Principle of the S.O.L.I.D principles, events, and listeners come to the rescue.
What is S.O.L.I.D Principle?
S.O.L.I.D is an acronym that represents five principles of object-oriented programming and code design theorized by our beloved Uncle Bob (Robert C. Martin) by the year 2000. The author Michael Feathers was responsible for creating the acronym:
[S]ingle Responsibility Principle
[O]pen/Closed Principle
[L]iskov Substitution Principle
[I]nterface Segregation Principle
[D]ependency Inversion PrincipleSingle Responsibility Principle (SRP) — A class should have one, and only one, reason to change.
Open/Closed Principle (OCP) — You should be able to extend a classes behavior, without modifying it.
Excerpts taken from: https://medium.com/@mari_azevedo/s-o-l-i-d-principles-what-are-they-and-why-projects-should-use-them-50b85e4aa8b6
On your default Register controller, you will see that a trait called RegisterUsers is used and this houses most of the logic for user creation. Inside the trait, there is a register method and that will be our concern.
_/\*\*
\* Handle a registration request for the application.
\*
\*_ **_@param_** _\Illuminate\Http\Request $request
\*_ **_@return_** _\Illuminate\Http\Response
\*/_ public function register(Request $request)
{
$this->validator($request->all())->validate();
**event(new Registered($user = $this->create($request->all())));**
$this->guard()->login($user);
if ($response = $this->registered($request, $user)) {
return $response;
}
return $request->wantsJson()
? new Response('', 201)
: redirect($this->redirectPath());
}
In the above code snippet, take notice of the emboldened line of code, this dispatches an event called Registered, for every time a user is registered on your application which can be listened to, by creating a listener class and mapping it to the event in the EventServiceProvider.
Imagine I am building a student portal and when a student is registered I have to:
- Assign the student a class
- Assign the student a seat in the class
- Assign the student a lab partner
- Assign the student a library card, and the list goes on and on and on.
A typical way to do this might be to dump all the logic into the register controller method, like below:
_/\*\*
\* Handle a registration request for the application.
\*
\*_ **_@param_** _\Illuminate\Http\Request $request
\*_ **_@return_** _\Illuminate\Http\Response
\*/_ public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
//Assign the student a class
//Assign the student a seat in the class
//Assign the student a lab partner
//Assign the student a library card
//more
$this->guard()->login($user);
if ($response = $this->registered($request, $user)) {
return $response;
}
return $request->wantsJson()
? new Response('', 201)
: redirect($this->redirectPath());
}
While this may work, it breaks more than one principle as mentioned earlier. A way around this will be to create listeners and map it to the Registered event.
_//Event Service Provider Class_
_/\*\*
\* The event listener mappings for the application.
\*
\*_ **_@var_** _array
\*/_ protected $listen = [
Registered::class => [
AssignClassToStudent::class,
AssignSeatToStudent::class,
AssignLabPartnerToStudent::class,
AssignLibraryCardToStudent::class,
],
];
With our provider having the above content, the php artisan event:generate command will generate 4 listener class in the app\Listener folder, but for brevity, I’ll continue with one — the assignClassToStudent listener
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class assignClassToStudent
{
_/\*\*
\* Create the event listener.
\*
\*_ **_@return_** _void
\*/_ public function \_\_construct()
{
//
}
_/\*\*
\* Handle the event.
\*
\*_ **_@param_** _object $event
\*_ **_@return_** _void
\*/_ public function handle($event)
{
$event->user->assignClassToStudentLogic();
}
}
Following this pattern means, if there is more logic to do after a user is registered, you do not need to edit the register method, you just need to spin up a new listener and map it to the event. This way the register method only handles student registration and the other listeners handle the other responsibilities.
The End.
Top comments (0)