---
description: https://viblo.asia/p/laravel-model-events-AyQMpJDpv0Ek
---

# Laravel Model Events

## Laravel Model Events

[PHP](https://viblo.asia/tags/php)[Laravel](https://viblo.asia/tags/laravel)

#### Tổng quan về Eloquent Model Events <a href="#tong-quan-ve-eloquent-model-events-0" id="tong-quan-ve-eloquent-model-events-0"></a>

Trong quá trình hoạt động của mình, mỗi Eloquent Model có thể tạo ra nhiều sự kiện khác nhau, cho phép chúng ta thao tác với những thời điểm khác nhau trong chu kỳ hoạt động của model đó. Các phương thức tương ứng với các sự kiện đó là: `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `restoring` và `restored`.

Tên của các phương thức trên cho chúng ta biết sự kiện nào sẽ được thực hiện và được thực hiện tại thời điểm nào. Các sự kiện `creating` và `created` được thực hiện khi một model được lưu lại trong cơ sở dữ liệu lần đầu tiên, nếu model đã tồn tại và phương thức `save` được gọi trên model đó hai sự kiện `updating` và `updated` sẽ được thực hiện. Khi một model được xóa bằng phương pháp [Soft Delete](https://laravel.com/docs/5.2/eloquent#soft-deleting) và được khôi phục lại qua phương thức `restore` hai event `restoring` và `restored` sẽ được thực hiện.

Sau đây chúng ta sẽ xem xét một ví dụ về việc sử dụng Model Events trong Laravel. Giả sử chúng ta có hai model là `User` và `Post` và một người dùng có thể có nhiều bài viết khác nhau.

```
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
```

```
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```

Bây giờ chúng ta muốn xóa toàn bộ những bài viết của một người dùng khi người dùng đó bị xóa khỏi hệ thống. Chúng ta sẽ thực hiện thao tác đó sử dụng sự kiện `deleting` của model `User`. Có rất nhiều cách để thực hiện thao tác trên này, cách đơn giản nhất là sử dụng phương thức `deleting` của model User trong một `ServiceProvider` chẳng hạn như `AppServiceProvider` có sẵn cùng với Laravel Framework.

```
<?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        User::deleting(function ($user) {
            $user->posts()->delete();
        });
    }
}
```

Cách thứ hai tương tự với cách đầu tiên nhưng thay vì sử dụng một Service Provider chúng ta sẽ ghi đè phương thức `boot` của class `Illuminate\Database\Eloquent\Model` bên trong User model. Và một cách khác là sử dụng Model Observer, trong đó các logic liên quan đến việc xử lý các sự kiện sẽ được lưu vào một class riêng biệt. Cụ thể như sau:

**Tạo Event Observer class cho User model**

```
<?php

namespace App\Models\Observers;

use App\Models\User;

class UserObserver
{
    /**
     * Hook into user deleting event.
     *
     * @param User $user
     * @return void
     */
    public function deleting(User $user)
    {
        $user->posts()->delete();
    }

    public function created(User $user)
    {
        //
    }

    public function restoring(User $user)
    {
        //
    }
}

```

**Đăng ký Observer class trong EventServiceProvider**

```
<?php

namespace App\Providers;

use App\Models\User;
use App\Models\Observers\UserObserver;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * Register any other events for your application.
     *
     * @param  \Illuminate\Contracts\Events\Dispatcher $events
     * @return void
     */
    public function boot(DispatcherContract $events)
    {
        User::observe(new UserObserver);
    }
}
```

#### Ví dụ về việc sử dụng Model Events để thu thập các hoạt động của người dùng <a href="#vi-du-ve-viec-su-dung-model-events-de-thu-thap-cac-hoat-dong-cua-nguoi-dung-1" id="vi-du-ve-viec-su-dung-model-events-de-thu-thap-cac-hoat-dong-cua-nguoi-dung-1"></a>

Bài toán đặt ra ở đây là chúng ta muốn ghi lại những hoạt động của một người dùng nào đó. Chẳng hạn khi người dùng đó tạo mới hoặc xóa một bài viết chúng ta muốn lưu lại thông tin đó để tạo Activity Feed sau này. Trong những mục tiếp theo chúng ta sẽ sử dụng Model Events để giải quyết bài toán này.

**Tạo Activity Model**

Bước đầu tiên chúng ta cần tạo một model liên quan đến các hoạt động của người dùng. Chúng ta sẽ gọi model đó là `Activity`. Cấu trúc của model trong ví dụ này khá đơn giản, chúng ta cần biết người dùng nào đã thực hiện hành động, hành động đó liên quan đến model nào khác - target model (giả sử khi người dùng tạo mới một bài viết thì model liên quan sẽ là `Post`) và tên của hành động đó là gì. Target model sẽ không cố định mà có thể là nhiều model khác nhau vì vậy chúng ta sẽ sử dụng [Polymorphic Relations](https://laravel.com/docs/5.2/eloquent-relationships#polymorphic-relations) để thuận tiện cho việc tìm target model sau này. Dưới đây là logic cho `Activity` model.

```
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Activity extends Model
{
    protected $fillable = ['user_id', 'targetable_id', 'targetable_type', 'action'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function targetable()
    {
        return $this->morphTo();
    }
}

```

**CapturesActivity Trait**

Trong bước này chúng ta sẽ sử dụng Model Events để ghi lại các hành động của một người dùng nào đó. Trước hết chúng ta sẽ xem xét một cách khác để đăng ký Model Events bằng cách sử dụng `trait`. Đi sâu một chút vào bên trong class `Illuminate\Database\Eloquent\Model` chúng ta sẽ thấy một phương thức tĩnh có tên là `bootTraits` cụ thể như sau:

```
protected static function bootTraits()
{
    foreach (class_uses_recursive(get_called_class()) as $trait) {
        if (method_exists(get_called_class(), $method = 'boot'.class_basename($trait))) {
            forward_static_call([get_called_class(), $method]);
        }
    }
}
```

Phương thức này sẽ được gọi mỗi khi model được khởi tạo. Nếu để ý kỹ trong điều kiện `if` của phương thức `bootTraits` chúng ta sẽ thấy một đoạn logic khá đặc biệt `$method = 'boot'.class_basename($trait)`. Với mỗi `trait` được sử dụng bện trong model, khi model được khởi tạo Laravel sẽ kiểm tra sự tồn tại của một phương thức đặc biệt trong `trait` đó - `boot + <trait_name>` và gọi phương thức đó nếu có. Nếu chúng ta đăng ký Model Events trong phương thức đặc biệt này thì chúng cũng sẽ hoạt động tương tự như ba cách làm phía trên.

Quay trở lại với bài toán đang xem xét, vì logic liên quan đến việc lưu hành động của người dùng sẽ là giống nhau khi target model thay đổi; chúng ta sẽ tách logic đó ra một `trait` riêng biệt và sử dụng nó trong các model cần thiết. Trong ví dụ này chúng ta sẽ gọi `trait` đó là `CapturesActivity`

```
<?php

namespace App\Models\Traits;

use App\Models\Activity;

trait CapturesActivity
{
    /**
     * Listen for events on model and capture the
     * appropriate activities.
     *
     * @return void
     */
    protected static function bootCapturesActivity()
    {
        // Register Models Event
        // Create appropriate activities
    }
}

```

**Những sự kiên cần ghi lại**

Tiếp theo chúng ta cần quyết định xem những sự kiện nào sẽ được ghi lại, tên hành động là gì, foreign key (nếu không tuân theo mặc định của framework) và các thông tin liên quan đến việc tạo mới hành động.

```
protected static function getModelEvents()
{
    if (isset(static::$capturedEvents)) {
        return static::$capturedEvents;
    }

    return ['created', 'updated', 'deleted'];
}
```

Nếu trong model có khai báo một biến static có tên `$capturedEvents` là một mảng các sự kiện thì đó sẽ là những sự kiện được ghi lại sau này. Trong trường hợp mặc định ba sự kiện là `created`, `updated` và `deleted` sẽ được ghi lại. Ngoài ra chúng ta có thể khai báo `$capturedEvents` là một mảng rỗng nếu chúng ta không muốn ghi lại các hành động liên quan đến Model Events.

**Tên hành động**

```
public function getActivityName($model, $action)
{
    $name = strtolower(class_basename($model));

    return "{$action}_{$name}";
}
```

**Thông tin liên quan đến việc tạo mới activity**

```
protected static function mapAttributes()
{
    return [
        'activityUserId' => 'user_id',
        'activityTargetId' => 'id',
        'activityTargetType' => static::class,
    ];
}
```

Phương thức này trả về một mảng các thuộc tính (foreign keys và tên của target model) cần thiết để tạo activity và các giá trị mặc định của nó. Trong hầu hết các trường hợp các giá trị mặc định sẽ được sử dụng, tuy nhiên trong một vài trường hợp chúng ta không thể làm như vậy. Giả sử chúng ta muốn ghi lại hành động một người dùng follow/unfollow một người dùng khác; trong trường hợp này các giá trị tương ứng cho `activityUserId`, `activityTargetId`, `activityTargetType` sẽ là `follower_id`, `followed_id` và `User::class`.

**Tạo hành động và đăng ký các Model Events**

```
public function captureActivity($event)
{
    $attributes = fetch_static_attributes(static::class, static::mapAttributes());
    list($userId, $targetId, $targetType) = $attributes;
    Activity::create([
        'user_id' => $this->$userId,
        'targetable_id' => $this->$targetId,
        'targetable_type' => $targetType,
        'action' => $this->getActivityName($this, $event),
    ]);
}
```

Trước hết chúng ta cần kiểm tra xem trong target model có khai báo các biến static `$activityUserId`, `$activityTargetId`, `$activityTargetType` hay không. Trong trường hợp các biến static đó được khai báo giá trị của chúng sẽ được sử dụng trong việc tạo activity, nếu không thì các giá tri mặc đinh trong phương thức `mapAttributes` sẽ được sử dụng. Sau khi đã có đầy đủ thông tin cần thiết công việc tiếp theo là tạo mới một activity.

Để hoàn tất `CapturesActivity` trait, công việc cuối cùng là đăng ký các model event cần thiết. Chúng ta sẽ thay thế phần comment trong phương thức `bootCapturesActivity` phía trên bằng logic sau đây.

```
protected static function bootCapturesActivity()
{
    foreach (static::getModelEvents() as $event) {
        static::$event(function ($model) use ($event) {
            $model->captureActivity($event);
        });
    }
}
```

**Sử dụng CapturesActivity**

Sau đây sẽ là một số ví dụ về việc sử dụng `CapturesActivity` trait.

```
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Models\Traits\CapturesActivity;

class Post extends Model
{
    use CapturesActivity;

    protected static $capturedEvents = ['created', 'updated'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```

```
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Models\Traits\CapturesActivity;

class Relationship extends Model
{
    use CapturesActivity;

    protected $table = 'follows';
    protected $fillable = ['follower_id', 'followed_id'];

    protected static $activityUserId = 'follower_id';
    protected static $activityTargetId = 'followed_id';
    protected static $activityTargetType = User::class;
    protected static $capturedEvents = ['created', 'deleted'];

    public function followed()
    {
        return $this->belongsTo(User::class, 'followed_id');
    }

    public function follower()
    {
        return $this->belongsTo(User::class, 'follower_id');
    }
}
```

**Tùy chỉnh tên hành động**

Trong nhiều trường hợp, ngoài những hành động liên quan đến các Model Events, chúng ta muốn ghi lại những hành động đặc biệt khác của người dùng. Để thực hiện việc này, chúng ta sẽ tạo một phương thức trợ giúp trong User model cho phép chúng ta tùy biến tên của hành động. Cụ thể như sau:

```
public function pushActivity($name, $relatedEntity)
{
    if (!method_exists($relatedEntity, 'captureActivity')) {
        throw new BadMethodCallException;
    }

    return $relatedEntity->captureActivity($name);
}
```

#### Kết luận <a href="#ket-luan-2" id="ket-luan-2"></a>

Trong bài viết này chúng ta đã tìm hiểu về Model Events trong Laravel và sử dụng chúng để thực hiện một ví dụ đơn giản về việc ghi lại hành động của người dùng. Events nói chúng và Model Events nói riêng trong Laravel là những công cụ rất hữu ích khi xây dựng các ứng dụng dạng event-driven.

#### Tham khảo <a href="#tham-khao-3" id="tham-khao-3"></a>

[https://laravel.com/docs/5.2/eloquent#events](https://laravel.com/docs/5.2/eloquent#events) [http://www.archybold.com/blog/post/booting-eloquent-model-traits](http://www.archybold.com/blog/post/booting-eloquent-model-traits) [http://laravel-tricks.com/tricks/using-model-events-to-delete-related-items](http://laravel-tricks.com/tricks/using-model-events-to-delete-related-items)

[Repo](https://github.com/vinhnguyen-fly/fels_78)
