Event mechanism in Laravel

Laravel Events provide a simple observer mode implementation that allows you to subscribe to and monitor events in your application.

Event classes are usually stored in the app/Events directory, and listeners are stored in the app/Listeners.

Events provide an effective solution for decoupling application modules because a single event can have multiple listeners and a single listener can also listen for multiple events, among which listeners are not interdependent.

1. Events are placed in the app/Events directory, such as

<?php

namespace App\Events\Order\Shipped;

use App\Models\Order as OrderModel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class Shipped
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * @var $order OrderModel
     */
    public $order;

    public $goodsId;

    public function __construct(OrderModel $order)
    {
        $this->order = $order;
        $this->goodsId = $order->goods_id;
    }
}

An event class is a simple data container that handles events.For example, here, suppose the OrderShipped event we generated received one Eloquent ORM Object, OrderModel, this event class does not contain any specific logic but is just a container for purchased Order objects. If the event object is serialized, the SerializesModels trait used by the event will serialize all Eloquent models using the PHP serialize function.The Eloquent model will be elegantly serialized and deserialized when the event is executed.If your event receives an Eloquent model in the constructor, only the primary key of the model is serialized to the queue, and when the task is actually executed, the event system automatically retrieves the entire model instance from the database.This is completely transparent to the application to avoid problems caused by serializing the entire Eloquent model instance.(

 

2, the listener is placed in the app/Listeners directory, for example

<?php

namespace App\Listeners\Order;

use App\Events\Order\Shipped as EventOrderShipped;
use App\Jobs\Notify\ShippedNotify;
use Illuminate\Contracts\Queue\ShouldQueue;

class Shipped implements ShouldQueue
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param object $event
     * @return void
     */
    public function handle(EventOrderShipped $event)
    {
        //Send template message
        dispatch(new ShippedNotify($event->order));
    }
}

3, binding listens in app/Providers/EventServiceProvider

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;

use App\Listeners\QueryListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

use App\Events\Order\{
    Shiped as OrderShipedEvent
};
use App\Listeners\{
    Order\Shipped as OrderShippedListener
};

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [

        Registered::class => [
            SendEmailVerificationNotification::class, //This pair of events and listeners is an example that comes with the framework
        ],
       
        OrderShippedEvent::class => [
            OrderShippedListener::class, //This is our own bound event and listener
            //other listeners
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();

        //
    }

Note: listeners execute in top-down order, followed by previous listeners.If one of the listeners return false, the current event will be interrupted and will no longer be listened on by subsequent listeners.

This is the way to compare "clumsy".A more efficient way is to add Events and Listeners to be created directly in the $Listener of the EventServiceProvider and specify the directory, such as adding

protected $listen = [
    OrderShippedEvent::class => [
        OrderShippedListener::class, //Existing listener
        \App\Listeners\Others\Other::class //The NEW
    ],
];

Then execute at the terminal

php artisan event::generate

You will then find that the class files for Other.php are automatically generated in the app/Listeners/Others/directory as follows

<?php

namespace App\Listeners\Others;

use App\Events\Order\Shipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class Other
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  Shipped  $event
     * @return void
     */
    public function handle(Shipped $event)
    {
        //Stop event processing
        return false;

        //Processing business logic
        logger()->channel('abc')->info('to process order:'.$event->order->id);
        var_dump($event->goodsId);

    }
}

Stop Events Continue Propagating Down - Sometimes you want to stop events propagating to other listeners by returning false from the listener's handle method.

Note: When using SerializesModels trait, you can reduce the size of data passed in the event system.However, since it only passes the primary key and then uses it to get data in the table, if the data changes before the event is listened for and processed, it will result in inconsistencies between the model data of the incoming event and the model data of the event when it is processed.Especially if the current event is in a transaction and it is an asynchronous event, such as when in a queue, the previous transaction changes the properties of the model and joins the asynchronous transaction before committing the transaction.If an event is processed later than an asynchronous transaction, the properties of the model remain unchanged when the event is processed, and the data remains consistent at this time, but it can cause a problem, such as a transaction that changes an order from a shipped state to a shipped state, but when the event is read, it is still not shipped.However, if the transaction is processed more quickly, the attributes are changed, and subsequent read attributes of the asynchronous transaction are also changed, which will cause inconsistencies in the data.

So we must be aware of the scenario in which events are used to decouple, either synchronously or in a queue or asynchronously.

 

Event listener queue

If the listener is going to perform time-consuming tasks such as sending mail or sending HTTP requests, queuing the listener is a good option.Before queuing a listener, make sure that the queue is configured and a queue listener is started on the server or in the local environment.To specify that a listener needs to be queued, simply let the listener class implement the ShouldQueue interface. The listener class generated by the Artisan command event:generate has already imported this interface into the current namespace, so you can use it directly:

<?php

namespace App\Listeners\Others;

use App\Events\Order\Shipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class Other implements ShouldQueue
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  Shipped  $event
     * @return void
     */
    public function handle(Shipped $event)
    {
        //
    }
}

It's that simple!When this listener is called, Laravel's Queue System Auto-push to queue via event distributor.If no exception is thrown while executing the listener through the queue, the queue task is automatically deleted when execution is complete.(

Custom Queue Connection & Queue Name If you want to customize the queue connection and queue name used by the event listener, you can define the $connection and $queue properties in the listener class:

<?php

namespace App\Listeners;

use App\Events\Order\Shipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    /**
     * The name of the connection to which the task will be pushed.
     *
     * @var string|null
     */
    public $connection = 'sqs';

    /**
     * The name of the connection to which the task will be pushed.
     *
     * @var string|null
     */
    public $queue = 'listeners';
}

Manual Queue Access

If you need to manually access the delete and release methods of the underlying queue task, the default imported Illuminate\Queue\InteractsWithQueue trait provides access to both methods in the generated listener:

<?php

namespace App\Listeners;

use App\Events\Order\Shipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(OrderShipped $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

The release method is to put the event back into the event queue (the parameter is the number of seconds of delay) and the deleted method is to delete the event from the event queue.

The source code is as follows

    /**
     * Release the job back into the queue.
     *
     * @param  int   $delay
     * @return void
     */
    public function release($delay = 0)
    {
        if ($this->job) {
            return $this->job->release($delay);
        }
    }

    /**
     * Delete the job from the queue.
     *
     * @return void
     */
    public function delete()
    {
        if ($this->job) {
            return $this->job->delete();
        }
    }

Handle failed tasks

Sometimes the event listener in the queue may fail to execute.If the listener task in the queue executes beyond the maximum number of attempts defined by the queue process, the failed method on the listener is invoked, the failed method receives event instances and exceptions that cause the failure:

<?php

namespace App\Listeners;

use App\Events\Order\Shipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(OrderShipped $event)
    {
        //
    }

    public function failed(OrderShipped $event, $exception)
    {
        //
    }
}

Distribution Events

To distribute an event, you can pass an instance of the event to the auxiliary function event, which distributes the event to all registered listeners.Since the auxiliary function event is globally valid, it can be called anywhere in the application:

<?php

namespace App\Http\Controllers;

use App\Order;
use App\Events\Order\Shipped as EventOrderShipped;
use App\Http\Controllers\Controller;

class OrderController extends Controller
{
    /**
     * Process a given order.
     *
     * @param  int  $orderId
     * @return Response
     */
    public function ship($orderId)
    {
        $order = Order::findOrFail($orderId);

        // Order Processing Logic...

        event(new EventOrderShipped($order));
    }
}

 

Thus, the approximate flow is to initiate a transaction at the business tier -> serialize the ORM model (u sleep()) into events -> the transaction is monitored by EventServiceProvider -> call listener list -> each listener executes from top to bottom -> the event arrives at the first listener -> the ORM model is deserialized from the event (u wakeup()) -> the listener executes the business ->If there is a queue, release/delete, etc. in handle can be used, and fail() can be used with the same level as handle - > if return false, the event ends and subsequent listeners will no longer be able to listen for it.

summary

1. The scenario in which Events are used is decoupling, decoupling from the main business logic some side logic that is not strongly related to the current business, which can be handled by events when some main business occurs.For example, when sending information, information can be sent when the order is paid, and information can also be sent when the order is shipped. These business operations of the order, and the information is not too many coupling relationship, then you can make the sending information into an event, which can be invoked every time there is an order payment, and the order is shipped.This is the Event side.On the listener side, such as sending messages, you can send short messages, micro messages, e-mails and so on, then this message-sending event can correspond to multiple listeners.

2, Event is a synchronous operation when ShouldQueue is not used.When the listener implements ShouldQueue, if the default value in the configuration file queue.php is sync, it is still a synchronization operation.It is an asynchronous operation only if you change the value of default or QUEUE_DRIVER, such as redis.

3, an Event can be listened on by multiple Listener s, which execute in sequence unless interrupted by return false.

4. Event focuses on what has already been sent and uses what has been done, similar to the English past.If the registration is complete, the update is complete, the publication is complete, and the sending is complete.Job focuses on what's going on.

Tags: PHP Laravel Database Redis

Posted on Mon, 16 Mar 2020 10:22:55 -0700 by dastaten