How to use the Laravel's Events System to delegate tasks wisely?

How to use the Laravel's Events System to delegate tasks wisely?

Laravel comes with an Event System that implements an observable pattern. It allows your application logic to dispatch an event from one location and multiple listeners to take action on it.

What are Events?

The most visible event of any web application is receiving an HTTP request. When Laravel receives a request, it gets controllers and actions to take care of it. So, inevitably, the web application itself can be considered as an event-driven system. The Laravel Events represent a broad range of actions performed after receiving the HTTP request. Any change of the application status or an action performed by the users can consider as an event. Few examples are:

  • New order submitted
  • New user registered
  • Product deleted
  • Order status updated
  • Payment received
  • Comment added
  • Image uploaded

And, the list will go on…

You can see almost every action can trigger an event. The application performs various tasks based on actions. Most of the time, the same controller performs all these tasks since they are contextually related.

Why event system?

It makes task delegation more meaningful and encourages writing code that respects Single Responsibility Principle, which is indicated by the S in SOLID Principles. The idea is to let one class worry only about one thing.

Look at the following example code:

An online support system lets users open support tickets. The application should send emails to notify support agents of the action. Also, it should create a new customer account if that user’s email address is not already in the database.

public function store(Request $request)
{
  $ticket = Ticket::create($request->data());

  if ($request->filled('email')) {
    // send the new ticket notification to user
    Mail::to($request->email)->send(new NewTicketNotification($ticket));

    // create a new account if email is not registers
    if (Customer::where('email', $request->email)->doesntExist()) {
      $customer = Customer::create([
        'email' => $request->email;
      ]);
    }
  }
}

Sending emails or creating customer accounts is not the responsibility of the TicketsController. It should focus only on creating a new ticket, instead of sending emails and creating customer accounts. Simply, it can forget about everything other than creating a ticket and dispatch the Ticket Created event to let the application know that a new ticket has just been created.

public function store(Request $request)
{
  $ticket = Ticket::create($request->data());

  // dispatch the TicketCreated event
  \App\Events\TicketCreated::dispatch($ticket);
}

Event Listeners

The observing part of the event system is handled by the Event Listeners. Those are the classes that keep listening to the events until they get dispatched and take action on them. Look at the following Listener that sends out new ticket notification:

namespace App\Listeners;

use Mail;
use App\Events\TicketCreated;
use App\Notifications\NewTicketNotification;

class SendNewTicketNotification
{
  /**
   * Handle the event.
   *
   * @param  TicketCreated  $event
   * @return void
   */
  public function handle(TicketCreated $event)
  {
    if (isset($event->ticket->email)) {
      // send the new ticket notification to user
      Mail::to($event->ticket->email)->send(new NewTicketNotification($event->ticket));
    }
  }
}

And, the following Listener takes care of the creating new account:

namespace App\Listeners;

use App\Events\TicketCreated;
use App\Models\Customer;

class CreateCustomerAccount
{
  /**
   * Handle the event.
   *
   * @param  TicketCreated  $event
   * @return void
   */
  public function handle(TicketCreated $event)
  {
    if (isset($event->ticket->email)) {
      // create a new account if email is not registers
      if (Customer::where('email', $event->ticket->email)->doesntExist()) {
        $customer = Customer::create([
          'email' => $event->ticket->email;
        ]);
      }
    }
  }
}

The SendNewTicketNotification and CreateCustomerAccount classes can keep listening to the Ticket Created event and take care of their respective tasks. They also can listen to other events that might want to trigger the same tasks.

Register events and listeners

Listeners should be registered in the application to get them to listen to respective events. One way to do that is to add them to the $listen property of the App\Providers\EventServiceProvider class:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
  TicketCreated::class => [
    SendNewTicketNotification::class,
    CreateCustomerAccount::class,
  ],
];

Or, you can register them in the boot() method of EventServiceProvider.

/**
 * Register any events for your application.
 *
 * @return void
 */
public function boot()
{
  Event::listen(
    TicketCreated::class,
    [SendNewTicketNotification::class, 'handle']
  );
}

Laravel documentation provides more information on generating and registering events.

Now, each time a new ticket is created, the TicketCreated event gets dispatched. Whatever task that should take place after creating a ticket goes in an event listener. Everyone is happy that the responsibility is nicely delegated, and the Single Responsibility principle of SOLID is not broken.

Saranga
Saranga A web developer and highly passionate about research and development of web technologies around PHP, HTML, JavaScript and CSS.
comments powered by Disqus