Event trong Laravel


Các Events (sự kiện) của Laravel cung cấp simple observer pattern implementation, cho phép bạn đăng ký và lắng nghe các sự kiện xảy ra trong ứng dụng của bạn.

Giới thiệu

Event classes mặc định được lưu trữ trong thư mục “app/Events“, trong khi Listener được lưu trong thư mục “app/Listeners“. Đừng lo lắng nếu bạn không tìm ra 2 thư mục này, vì chúng sẽ được tạo ra cho bạn khi tạo Events và Listeners bằng lệnh Artisan console.

Laravel giới thiệu Event: decouple various aspects. Mình đọc qua cũng khó hiểu nữa.
Thì theo như mình tìm hiểu: Event là một hành động hay một tác vụ nào đó xảy ra ở thời điểm xác định. Trong đời thường cũng như trong quá trình hoạt động của một ứng dụng có rất nhiều event xảy ra. Ví dụ: Bạn muốn gửi một thông báo đến người quản lý khi người dùng đặt hàng hoặc giao hàng, … Thay vì viết chức năng thông báo thì bạn chỉ cần thêm một Event OrderShipped mà Listener có thể nhận và handle việc gửi thông báo.

Registering Events & Listeners

App\Providers\EventServiceProvider“ có trong ứng dụng của bạn cung cấp vị trí thuận tiện để đăng ký tất cả các event listeners. Thuộc tính “$listen“ chứa mảng tất cả các event(keys) và các listeners (values) của chúng. Tất nhiên, bạn có thể thêm nhiều event cho mảng này như yêu cầu của ứng dụng của bạn. Ví dụ: hãy thêm sự kiện “OrderShipped“:

use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;

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

php artisan event:list“ dùng để hiển thị danh sách tất cả các events và listeners được đăng ký trong ứng dụng

Generating Events & Listeners

Tất nhiên, để tạo thủ công cho các file Event và Listener rất rườm rà và tốn thời gian. Thay vào đó, bạn chỉ cần khai báo Event cũng như Listener vào “EventServiceProvider“ và chạy lện Artisan Console “php artisan event:generate“. Câu lệnh trên sẽ tự động generate bất kì events hoặc listeners nào được khai báo mà chưa tồn tại:

// folder - namespace after generate, default in folder Providers
use App\Events\DemoEvent;
use App\Listeners\DemoListen;

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    DemoEvent::class => [
        DemoListen::class,
    ]
];
php artisan event:generate 

Ngoài ra, bạn có thể sử dụng lệnh để tạo từng Event cũng như Listener:

php artisan make:event PodcastProcessed

php artisan make:listener SendPodcastNotification --event=PodcastProcessed

Option –event=PodcastProcessed để tiêm PodcastProcessed vào hàm “handle

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

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

Manually Registering Events

Bình thường, các event được khai báo trong array “$listen“ của “EventServiceProvider“. Tuy nhiên, bạn cũng có thể khai báo các class hoặc closure based event listeners theo cách thủ công bên trong method “boot“ của “EventServiceProvider“:

use App\Events\PodcastProcessed;
use App\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;

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

    Event::listen(
        PodcastProcessed::class,
        [SendPodcastNotification::class, 'handle']
    );

    Event::listen(function (PodcastProcessed $event) {
        // hanle something
    });

    Event::listen(PodcastProcessed::class, SendPodcastNotification::class);

}

Lưu ý, trong Listeners bạn có thể inject bất cứ thứ gì mà bạn cần để ứng dụng có thể hoạt động đúng theo yêu cầu

Queueable Anonymous Event Listeners
Khi khai báo thủ công, bạn có thể “bọc” Listeners trong hàm “Illuminate\Events\queueable“ để sử dụng hàng đợi cho việc lắng nghe sự kiện.

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

/**
 * Register any other events for your application.
 *
 * @return void
 */
public function boot()
{
    Event::listen(queueable(function (PodcastProcessed $event) {
        //
    }));
}

Lưu ý: Nếu bạn chỉ mới khởi tạo prject cần chạy lệnh để sinh ra bảng jobs cũng như cấu hình trong file .env:

php artisan queue:table

php artisan migrate
QUEUE_CONNECTION=database

hoặc bất cứ thứ gì mà giúp Queue hoạt động.

Giống như Queue jobs, bạn cũng có thể sử dụng các phương thức như: onConnection, onQueue, delay hay catch cho trình lắng nghe lỗi để tùy chỉnh:

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->catch(function (PodcastProcessed $event, Throwable $e) {
    // The queued listener failed...
}));

Wildcard Event Listeners
Bạn thậm chí có thể đang ký Listeners bằng cách sử dụng (*) như một giam số ký tự đại diện, cho phép bạn nắm bắt nhiều event trên cùng một Listener. Wildcard listener nhận tên event làm đối số đầu tiên và toàn bộ mảng dữ liệu event là đối số thứ hai của nó:

Event::listen('event.*', function ($eventName, array $data) {
    //
});

Event Discovery

Thay vì đăng ký thủ công như trên mình giới thiệu, bạn có thể bật chế độ tự động khám phá sự kiện (automatic event discovery). Khi kích hoạt, Laravel sẽ tự động tìm và đăng ký events và listeners.
Cụ thể là bạn mở file composer.json trong project Laravel lên xem, nó có object script về “post-autoload-dump”. Nên cứ “composer dump-autoload” là nó sẽ chạy và discover sau đó cache lại vào thư mục bootstrap (Giải thích cho newbie vì mình chưa hề nói vụ này trong blog).

Nó tìm và xử lý trong các hàm handle, từ đó Laravel sẽ đăng ký:

use App\Events\PodcastProcessed;

class SendPodcastNotification
{
    /**
     * Handle the given event.
     *
     * @param  \App\Events\PodcastProcessed  $event
     * @return void
     */
    public function handle(PodcastProcessed $event)
    {
        //
    }
}

Mặc định Laravel sẽ không tự discover events, nhưng bạn có thể bật lên bằng cách override “shouldDiscoverEvents

/**
 * Determine if events and listeners should be automatically discovered.
 *
 * @return bool
 */
public function shouldDiscoverEvents()
{
    return true;
}

Mặc định, tất cả Listeners trong folder “app/Listeners“ sẽ được quét. Bạn có thể thay đổi bằng cách override nó:

/**
 * Get the listener directories that should be used to discover events.
 *
 * @return array
 */
protected function discoverEventsWithin()
{
    return [
        $this->app->path('Listeners'),
    ];
}

Nên sử dụng event:cache để tăng tốc và event:clear để giải phóng bộ nhớ

Defining Events

Một class Event đơn giản là một bộ chứa dữ liệu liên quan đến event. Ví dụ: giả sử event OrderShipped được gọi, chúng ta nhận được một đối tượng Eloquent ORM:

<?php

namespace App\Events;

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

class OrderShipped
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * The order instance.
     *
     * @var \App\Models\Order
     */
    public $order;

    /**
     * Create a new event instance.
     *
     * @param  \App\Models\Order  $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }
}

Như các bạn thấy, class Event này không chứa logic. Nó đơn giản là một container cho tường hợp Order đã được mua. Thuộc tính “SerializesModels trait“ được sử dụng bởi event này sẽ khéo léo sắp xếp theo thứ tự bất kỳ Eloquent model nếu đối tượng event được nối tiếp bằng cách sử dụng function “serialize“ của PHP.

Defining Listeners

Tiếp theo, chúng ta hãy nhìn vào listener cho event ví dụ của chúng ta. Event listeners nhận instance event trong phương thức handle của nó. Đương nhiên bạn phải đăng ký nó để có thể hoạt động. Với hàm “handle“ bạn có thể thực hiện bất kì action nào mà ứng dụng cần:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;

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

    /**
     * Handle the event.
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        // Access the order using $event->order...
    }
}

Stopping The Propagation Of An Event
Đôi khi, bạn muốn ngừng việc lan truyền một Event cho những Listeners khác. Bạn trả về “false“ từ phương thức “handle“ của listener hiện tại.

Queued Event Listeners

Hàng đợi listener có thể có lợi nếu listener của bạn sẽ thực hiện một tác vụ chậm như gửi email hoặc yêu cầu HTTP. Trước khi bắt đầu với queued listener, hãy chắc chắn và start queue listener trên server hoặc môi trường phát triển local của bạn.

Trong class Listener cần handle trong hàng đợi, implements ShouldQueue interface vào class sau khi bạn tạo class nhá!

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;

class SendShipmentNotification implements ShouldQueue
{
    use Queueable;
    //
}

Bây giờ, khi listener này được gọi cho một event, nó sẽ tự động xếp hàng bởi event dispatcher bằng của Laravel. Nếu không có ngoại lệ nào được ném ra khi listener được thực hiện bởi hàng đợi, công việc được xếp hàng sẽ tự động bị xóa sau khi đã hoàn tất quá trình xử lý.

Customizing The Queue Connection & Queue Name
Đương nhiên, bạn có thể tùy chỉnh các option của thằng Queue này bằng cách override nó:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;

class SendShipmentNotification implements ShouldQueue
{
    use Queueable;
    /**
     * The name of the connection the job should be sent to.
     *
     * @var string|null
     */
    public $connection = 'sqs';

    /**
     * The name of the queue the job should be sent to.
     *
     * @var string|null
     */
    public $queue = 'listeners';

    /**
     * The time (seconds) before the job should be processed.
     *
     * @var int
     */
    public $delay = 60;
}

Manually Interacting With The Queue

Nếu bạn cần truy cập theo cách thủ công các phương thức delete và realse , bạn có thể thực hiện bằng cách sử dụng Illuminate\Queue\InteractsWithQueue trait. Trait này được import mặc định trên listener được tạo và cung cấp quyền truy cập vào các phương thức sau:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueue
{
    use Queueable;
    use InteractsWithQueue;

    /**
     * Handle the event.
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

Handling Failed Jobs

Đôi khi queued event listener của bạn có thể không thành công. Nếu queued listener vượt quá số lần cố gắng tối đa được xác định bởi queue worker của bạn, phương thức failed sẽ được gọi vào listener của bạn. Phương thức failed nhận được thể hiện của event và exception gây ra sự cố:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueue
{
    use Queueable;
    use InteractsWithQueue;

    /**
     * Handle the event.
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        //
    }

    /**
     * Handle a job failure.
     *
     * @param  \App\Events\OrderShipped  $event
     * @param  \Throwable  $exception
     * @return void
     */
    public function failed(OrderShipped $event, $exception)
    {
        //
    }
}

Dispatching Events

Để dispatch một event, bạn có thể gọi phương thức static dispatch trên event. Hoặc bạn có thể sử dụng “event helper“. Helper sẽ gửi Event đến tất cả Listeners đã đăng ký nó. Bạn có thể gọi event helper ở mọi lúc mọi nơi trong ứng dụng của bạn:

<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\Request;

class OrderShipmentController extends Controller
{
    /**
     * Ship the given order.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $order = Order::findOrFail($request->order_id);

        // Order shipment logic...

        OrderShipped::dispatch($order);

        // helper
        event(new OrderShipped($order));

    }
}

Event Subscribers

Writing Event Subscribers

Event subscriber là các class có thể đăng ký nhiều event từ bên trong class, cho phép bạn xác định một số event handler trong một single class. Subscriber nên định nghĩa phương thức “subscribe“, sẽ được thông qua một thể hiện của event dispatcher. Bạn có thể gọi phương thức listen trên dispatcher đã cho để đăng ký event listener:

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;

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

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

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     * @return void
     */
    public function subscribe($events)
    {
        $events->listen(
            Login::class,
            [UserEventSubscriber::class, 'handleUserLogin']
        );

        $events->listen(
            Logout::class,
            [UserEventSubscriber::class, 'handleUserLogout']
        );
    }
}

Nếu các phương thức của trình xử lý được xác thực trong chính subscriber của nó, bạn có thể thấy thuận tiện hơn khi trả về một mảng các sự kiện và tên phương thức “subscribe“. Laravel sẽ tự động xác định tên subscriber’s class khi đăng ký trình nghe sự kiện:

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;

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

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

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     * @return array
     */
    public function subscribe($events)
    {
        return [
            Login::class => 'handleUserLogin',
            Logout::class => 'handleUserLogout',
        ];
    }
}

Registering Event Subscribers

Sau khi viết subscriber, bạn đã sẵn sàng để đăng ký với event dispatcher. Bạn có thể đăng ký subscribers sử dụng thuộc tính “$subscribe“ trong “EventServiceProvider“. Ví dụ, hãy thêm “UserEventSubScber“ vào:

<?php

namespace App\Providers;

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

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

    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [
        UserEventSubscriber::class,
    ];
}

Kết luận

Trên đây mình giới thiệu hầu như là hết các chức năng hữu dụng cũng như code mẫu để bạn có thể hình dung về Event cũng như cách hoạt động của nó như thế nào. Nội dung phía trên hiện tại chỉ là kiến thức của mình, các bạn hãy thực hành nhiều để nó trở thành kiến thức của các bạn nhé! Hy vọng bài viết sẽ giúp ích cho các bạn trong quá trình học tập và làm việc.
Nếu có sai sót gì mong các bạn đóng góp ý kiến ở phần bình luận hoặc trao đổi trực tiếp qua Facebook hoặc Email mình có gắn phía cuối trang web.

Nguồn tham khảo:

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!


Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Nguyễn Quang Đạt !
Comments
  TOC