23

So according to the laravel event doc, when defining listeners, it can receive the event instance in their handle method and perform any logic necessary:

public function handle(FoodWasPurchased $event)

So if my FoodWasPurchased event is defined as below (assumed EventServiceProvider is set):

public function __construct(Food $food)
{
    $this->food = $food;
}

I could access the $food in event from listener by doing:

$event->food->doSomething();

But now my question is what if a listener listen to multiple events?

Event FoodWasPurchased -> Listener Bill
Event DrinksWasPurchased -> Listener Bill

What I did now is I did not specify the event instance in the listener handle method:

public function handle($event)

where I can later use an if condition to check what is received in the $event:

if (isset($event->food)) {

    // Do something...

} elseif (isset($event->drinks)) {

    // Do something else...

}

I'm sure there is a better way.

Or the best practice is ensure that one listener only listens to one single event ?

6 Answers 6

33

You can listen to multiple events by using Event Subscribers which are placed in the Listeners folder but are capable of listening to multiple events.

<?php

namespace App\Listeners;

class UserEventListener{
    /**
     * Handle user login events.
     */
    public function onUserLogin($event) {}

    /**
     * Handle user logout events.
     */
    public function onUserLogout($event) {}

    /**
     * Register the listeners for the subscriber.
     *
     * @param  Illuminate\Events\Dispatcher  $events
     * @return array
     */
    public function subscribe($events){
        $events->listen(
            'App\Events\UserLoggedIn',
            'App\Listeners\UserEventListener@onUserLogin'
        );

        $events->listen(
            'App\Events\UserLoggedOut',
            'App\Listeners\UserEventListener@onUserLogout'
        );
    }

}
Sign up to request clarification or add additional context in comments.

2 Comments

how can we call this class from controller?
EventListeners are triggered when the corresponding event is raised. So you have to trigger the event within the controller.
17

If your events conform to a contract or interface it seems you can pass as many as you like to a listener...

class MyEventOne extends Event implements MyEventInterface

Then in the EventServiceProvider you connect up your events and listeners as so...

protected $listen = [
    'App\Events\MyEventOne' => [
        'App\Listeners\MyListener',
    ],
    'App\Events\MyEventTwo' => [
        'App\Listeners\MyListener',
    ],
];

And finally in your listener you type hint your handler to accept the event object based on the contract/interface...

public function handle(MyEventInterface $event)

I've unit tested this, it may not be appropriate for every scenario, but it seems to work. I hope it helps.

1 Comment

this solution is very clean and enforces interfaces. ++
9

If you are using PHP 8, you can take adventage of Union Types.

class BillListener
    {
    /**
     * Handle the event.
     *
     * @param FoodWasPurchased|DrinksWasPurchased $event
     * @return void
     */
    public function handle(FoodWasPurchased|DrinksWasPurchased $event): void
    {
        // Listener code goes here...
    }
}

Comments

8

You may try something like this as well:

// Instead of Food or Drink use parent Type
use Illuminate\Database\Eloquent\Model;

class DrinWasPurchased extends Event
{
    // Instead of Food or Drink typehint Model        
    public function __construct(Model $model)
    {
        // Instead of $this->food or $this->drink use a generic name
        $this->item = $model;
    }
}

Then in the handle method of listener try something like this:

public function handle(\App\Events\Event $event)
{
    if($event->item instanceof \App\Food)
    {
        $item->eat(); // Just an example
    }

    if($event->item instanceof \App\Drink)
    {
        $item->drink(); // Just an example
    }
}

2 Comments

This do increases readability. But what if a listener listen to more than 2 events ? ( Or maybe the best way is to ensure all the events are passing the same type of instance ? )
If you use PHPStorm you can leave out type-hinting of $event and use @param doc.
3

1- Add to EventServiceProvider:

'App\Events\NewMessageSent' => [
    'App\Listeners\SendNewMessageNotificationToRecipients',
],
'App\Events\MessageReplied' => [
    'App\Listeners\SendNewMessageNotificationToRecipients',
],
'App\Events\MessageForwarded' => [
    'App\Listeners\SendNewMessageNotificationToRecipients',
],

2- Generate events and listener:

php artisan event:generate

3- On SendNewMessageNotificationToRecipients.php (listener):

use App\Events\Event;
use App\Events\NewMessageSent;
use App\Events\MessageReplied;
use App\Events\MessageForwarded;

public function handle(Event $event)
{
    if ($event instanceof NewMessageSent) {
        dd('message sent');
    } else if ($event instanceof MessageReplied) {
        dd('message replied');
    } else if ($event instanceof MessageForwarded) {
       dd('message forwarded');
    }
}

Comments

3

In the following of @The Alpha answer and simplifying it:

First, create an interface and implement the interface in each Event.

interface BillPublihed {}

And here are our events implementing it:

class FoodPurchased implements BillPublished {}
class FoodPurchased implements BillPublished {}

The Listener gets the interface in the handle method:

class BillListener {
   public function handle(BillPublished $event) {...}
}

This is the main difference. No need to listen/subscribe to other events. It should only listen to the interface. So in the EventServiceProvider we'll have:

protected $listen = [
    //...
    BillPublished::class => [BillListener::class],
    //...
];

And if you dispatch FoodPurchaced or DrinkPurchased or any event implementing the BillPublished interface, the BillListener will be triggered without extra effort.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.