Let’s create Events and Listeners in Laravel and use Queue and Task Scheduling features to process them in the background without affecting the speed of our application.
Understanding Events and Listeners In Laravel.
Laravel provides an observer pattern implementation that can watch an event in our web application and process actions according to that event. Let’s say, if a user registers, we send a welcome email. so that the registration is the Event and the Listener is the action of sending the welcome email. In other words, Laravel Event classes observe and Listeners classes do the actions.
One Event can have many Listeners for example when the user registers, we can send a welcome email, or send verification email. Another Event is when the user purchases an order we create a listener for sending a confirmation email with the invoice and another listener to decrease the stock in our database and we can create one more Listener to check if an order’s item becomes best-selling or not.
Why should we use Laravel Events and Listeners?
It will keep our code clean following solid principles (wink wink) and easy to reuse and maintain since it’s already existed in Laravel documentation and easy to work with queues. I believe that it’s one good observer pattern.
Let’s create Events and Listeners
We will create an Event class to observe the user login and a Listener for sending a Loggedin email notification. So that when the user login we send an email.
First thing, first let’s create our Event. By default, Laravel will create our class file in app\Events
php artisan make:event LoggedIn
Let’s create our Listener and assign it to LoggedIn
Event, as the following.
php artisan make:listener SendLoginNotification --event=LoggedIn
By default, Laravel will create our class file in app\Listeners
, we need to register our Event and Listener in app\Providers\EventServiceProvider.php
so that Laravel can know about our classes
<?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;
use App\Events\LoggedIn;
use App\Listeners\SendLoginNotification;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
LoggedIn::class => [
SendLoginNotification::class,
],
];
public function boot()
{
//
}
public function shouldDiscoverEvents()
{
return false;
}
}
Here In EventServiceProvider
we have $listen
array the index is the event class name and the value is an array with all the subscribed listener classes of that event.
I made the below example with some possible events and listeners. Once the event is triggered all its listeners will be processed.
protected $listen = [
Registered::class => [
SendWelcomeMail::class,
SendEmailVerificationNotification::class,
],
LoggedIn::class => [
SendLoginNotification::class,
],
NewOrder::class => [
ConfirmOrderNotification::class,
DecreaseStock::class,
SendingOrderDataToTheStaff::class,
],
];
Let’s open app\Events\LoggedIn.php
so that we can add a user model instance that we can pass to our listeners. The class will have only __construct
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class LoggedIn
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Let’s code our app\Listeners\SendLoginNotification.php
class to send the notification mail.
<?php
namespace App\Listeners;
use App\Events\LoggedIn;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Notifications\LoginNotification;
class SendLoginNotification implements ShouldQueue
{
use InteractsWithQueue;
public $tries = 5;
public function __construct()
{
//
}
public function handle(LoggedIn $event)
{
try{
$event->user->notify(new LoginNotification());
//more codes for other things
}catch(\Exception $e){
//
}
}
}
- If you don’t want to add the Listener to the queue remove
implements ShouldQueue
use InteractsWithQueue;
public $tries = 5;
so that It will be executed with the request. - We can call the user model in
handle
function from$event
parameter as the following$event->user
. LoginNotification()
that is a normal notification class, I made a youtube video about it here Laravel 9: How To Send Email Notifications in Laravel.
To use the Event, we can dispatch it at the login function in the authentication controller as the following.
public function login (LoginRequest $request){
//
if(auth()->attempt($credentials )){
$user = Auth::user();
//
LoggedIn::dispatch($user);
//
return response()->json( $success,200);
}else{
//
}
}
Configuring Laravel Queue
When we used ShouldQueue
in app\Listeners\SendLoginNotification.php
, we added the listener to the queue. But we need to make some more steps for configuration.
Database Queue
We need to create a Jobs table in the database to save the queue. Laravel has it out of the box.
php artisan queue:table
php artisan migrate
In .env
file, we make the queue connection database.
QUEUE_CONNECTION=database
If you tried to log in you should see the Job record added to the Jobs table in the database.
Redis queue
You read that, I see, you like Redis, here is more Redis: Laravel Cache Redis And File Store Tutorial With Example. we will use the same configuration as that tutorial.
Let’s install predis/predis
package, the flexible and feature-complete Redis client for PHP and Laravel.
composer require predis/predis
And make sure the configuration for the Redis client is 'predis'
in config\database.php
. I’m using Windows 10 with WSL2 and Ubuntu 22, that’s my configuration.
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
And make sure .env
we use Redis as our queue connection driver.
QUEUE_CONNECTION=redis
If you tried to log in the queue job should be saved in Redis.
Starting the queue worker
We added our listener to the queue in the database or Redis. to start the queue, we use the below artisan command.
php artisan queue:work
To make the queue work automatically in the background we can add it to the Laravel task scheduling as in app\Console\Kernel.php
//
protected function schedule(Schedule $schedule)
{
$schedule->command('queue:work')->everyMinute();
}
//
Finally, you need to set up a cron job in your server for Laravel but in development, you can use this artisan command to run the schedule.
php artisan schedule:run
I hope this tutorial was useful for you, thank you.