Laravel Multiple Model Events
Asked Answered
A

2

10

I'm trying to sync my database with an external service.

I'm using Algolia search in a couple of places across a web application.

It's indexed with a couple of models however I need it to re-index in the event that any changes are made to the database i.e. when multiple model events are fired.

My first approach was to action everything within the boot method of the AppServiceProvider

public function boot()
{
    $events = ['created', 'updated', 'deleted', 'restored'];

    // reindex handlers for models relevant to Algolia search
    foreach ($events as $evt) {
        Order::registerModelEvent($evt, function () {
            Order::reindex();
        });
        Product::registerModelEvent($evt, function () {
            Product::reindex();
            Product::setSettings();
        });
    }
}

This is my approach to avoid multiple conditionals using the standard model functions exampled in the docs.

However I'm assuming there's a better way using Laravel Event Listeners.

namespace App\Listeners;

class OrderEventListener
{
    // handlers

    public function subscribe($events)
    {
        $events->listen(
            // model events
        );
    }
}

Although I'm unsure how to tap into the model events in the listen method.

Acre answered 3/6, 2016 at 10:33 Comment(0)
P
8

I would strongly recommend adding your own event and handler for this situation.

In your Product and Order classes you can override the boot method of the model:

class Product extends Model
{
    protected static function boot()
    {
        parent::boot();

        self::created(function($product) {
            event(new ProductCreatedEvent($product));
        });
    }
}

You'll need to create your own ProductCreatedEvent object. Now in the EventServiceProvider you will want to add to the listeners array;

protected $listeners = [
    'App\Events\ProductCreatedEvent' => [
        'App\Listeners\UpdateAlgoliaProductIndex',
    ]
];

Once you've set this up you can actually run php artisan event:generate and this will create the event object and listener for you. I'll skip the event object as it's quite simple it purely takes the product that was created and sends it through to the UpdateAlgoliaProductIndex listener.

Now in your listener you will have something like the following:

class UpdateAlgoliaProductIndex
{
    public function handle($event)
    {
        Product::reindex();
        Product::setSettings();
    }
}

The reason I suggest this approach is you can then queue the listener using the ShouldQueue interface, meaning that you don't block the request whilst waiting for your app to reindex with Algolia leading to a better experience for your users.

You can read more about the event objects and listeners here.

An alternative option is to use a model observer.

Physiognomy answered 6/6, 2016 at 21:30 Comment(4)
I've done this sort of thing for notifications e.g. a product is updated a ProductCreatedEvent is fired. I'm ok with this because I need only trigger it with self::updated() in the boot method like you show. However something like re-indexing here should be executed whenever any model event is fired and I was looking for a solution that didn't require listing every model event with subsequent event listeners, handlers etc. Looking into it more would it not be better to create a job and dispatch it in boot? Then it can still implement ShouldQueue but it will always fire.Acre
If you want every model event you probably would have more luck if you watch for saved rather than created. The ShouldQueue removes the need for an additional Job object being defined. If you don't want to run it instantaneously I would suggest just adding a requires_search_indexing field to your product object. Then you could have a cron job doing a sweep periodically, in order to group up the updates.Physiognomy
That's a good point. I take it if you wanted some middle ground you're still advocating for using all the event listeners. Laravel 5.2 seems to have dropped model observers from the docs which makes me wonder if they're not recommending the approach any more.Acre
Yeah I do wonder if model observers are being dropped in favour of just raising a more domain specific event rather than hooking directly into Eloquent events. It does decouple you a bit more from Eloquent if that's a concern.Physiognomy
A
0

With some digging I've come across Laravel event subscribers which is more akin to what I was looking for so I've submitted it as its own answer.

I think this produces more concise code than having listeners for every event.

namespace App\Listeners;

use App\Events\OrderCreatedEvent;
use App\Events\OrderUpdatedEvent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderEventListener
{
    /**
     * Handle order created events.
     *
     * @param OrderCreatedEvent $event
     */
    public function onOrderCreation(OrderCreatedEvent $event)
    {
        // do something
    }

    /**
     * Handle order updated events.
     *
     * @param OrderUpdatedEvent $event
     */
    public function onOrderUpdate(OrderUpdatedEvent $event)
    {
        // do something
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'App\Events\OrderCreatedEvent',
            'App\Listeners\OrderEventListener@onOrderCreation'
        );

        $events->listen(
            'App\Events\OrderUpdatedEvent',
            'App\Listeners\OrderEventListener@onOrderUpdate'
        );
    }
}

I'll keep marcus.ramsden answer as marked but this is probably very relevant for people coming across this question.

Acre answered 9/6, 2016 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.