Laravel Model Event ใช้อย่างไร

1 กุมภาพันธ์ 2564
Laravel Model Event ใช้อย่างไร

Laravel Model Event ช่วยให้เราแทรกการทำงานเข้าไปในจุดต่าง ๆ ตาม Model Lifecycle และสามารถป้องกันการบันทึกหรือการลบข้อมูลได้ ใน Laravel model event documentation ได้วางโครงให้เราเห็นว่าเราจะ hook เข้าไปใน event เหล่านี้ได้อย่างไรโดยการใช้คลาส Event แต่ในบทความนี้เราจะมาโฟกัสไปที่รายละเอียดเพิ่มเติมในการตั้งค่า Model Event และ Listener

ภาพรวม Event

Eloquent มีหลาย event ให้เรา hook เข้าไปแล้วเพิ่มการทำงานใน Model ของเราได้ โดยใน Model มี Event ให้เราดักจับเหตุการณ์ดังต่อไปนี้

Eventคำอธิบาย
retrievedModel ถูกอ่านจาก database
creatingก่อนการ Insert
createdหลังการ Insert
updatingก่อนการ Update
updatedหลังการ Update
savingก่อนการบันทึก (ทั้ง Insert และ Update)
savedหลังการบันทึก (ทั้ง Insert และ Update)
deletingก่อนการ Delete
deletedหลังการ Delete
restoringก่อนการเรียกคืนจาก Soft Delete
restoredหลังการเรียกคืนจาก Soft Delete
replicatingก่อนการถูกคัดลอก Replicate เป็น Model ใหม่

ใน Laravel Document มีอธิบายเรื่อง Soft Delete และ Replicating เพิ่มเติม

การดัก Event ในคลาส Model

วิธีแรกสุดในการดัก Event คือการเรียก static method ตามชื่อ event ใน method booted() ใน Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     *
     * @return void
     */
    protected static function booted()
    {
        static::created(function ($user) {
            //
        });

        static::saving(function ($user) {
            //
        });

        // updating, deleting, ... ตาม event ที่ต้องการดัก
    }
}

วิธีการนี้เป็นวิธีการที่ง่ายเหมาะสำหรับ logic ที่ไม่ซับซ้อนมากนัก แต่หากมีการดักหลาย event หรือ logic เริ่มมีความซับซ้อนก็จะทำให้คลาส Model ของเราอ่านยากมากขึ้นตาม

ใช้คลาส Event Listener

ในคลาส Model จะมี property ที่ชื่อว่า $dispatchesEvents ใช้เพื่อระบุ Model Event ที่เราต้องการดักจับและคลาส Event Listener ที่เราต้องการให้เรียกใช้งาน

บาง Event เช่น delete จะเช็คว่า Event return false มาหรือไม่แล้วจะยกเลิกการดำเนินการ จากตัวอย่างเราสามารถใช้วิธีนี้เพื่อป้องกันการบันทึกหรือการลบ User ได้

ใช้ Model the App\Models\User เป็นตัวอย่างเราสามารถตั้งค่า Model Event ได้แบบนี้

protected $dispatchesEvents = [
    'saving' => \App\Events\UserSaving::class,
];

เราสามารถใช้คำสั่ง Artisan command เพื่อสร้าง Event ให้เราได้

php artisan make:event UserSaving

โดยคลาสที่ได้จะได้หน้าตาประมาณนี้

<?php

namespace App\Events;

use App\User;
use Illuminate\Queue\SerializesModels;

class UserSaving
{
    use SerializesModels;

    public $user;

    /**
     * Create a new event instance.
     *
     * @param \App\User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

ใน Event คลาสเราจะกำหนด property $user เป็น public เพื่อเราสามารถเรียกไปยัง User Model instance ใน event ได้

สร้าง Event Listener

ถัดมาจะเป็นการสร้างคลาส Listener ขึ้นมาเพื่อทำการรับ Event เมื่อ Model ทำการ file event saving คลาส Listener จะถูกเรียกเป็นการทำงานระหว่าง Event นั้น เราสามารถสร้าง listener ได้โดยใช้ Artisan command

php artisan make:listener UserSavingListener -e UserSaving

เราจะได้คลาส UserSavingListner ใน App\Listeners โดยมีการรับ event UserSaving เข้ามาใน method handle()

<?php

namespace App\Listeners;

use App\Events\UserSaving;

class UserSavingListener
{
    /**
     * Handle the event.
     *
     * @param  UserSaving  $event
     * @return void
     */
    public function handle(UserSaving $event)
    {
        app('log')->info($event->user);
    }
}

ในตัวอย่างได้เพิ่มการเรียกใช้ logger ขึ้นมาเพื่อดู Model ที่ส่งเข้ามาใน Listener ทีนี้ถ้าจะให้ Listener ทำงานได้เราต้องทำการ register listener ใน property $listen ในคลาส EventServiceProvider

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        \App\Events\UserSaving::class => [
            \App\Listeners\UserSavingListener::class,
        ],
    ];

    // ...
}

ทีนี้เมื่อ Model ทำการ dispatch event และเราได้ทำการ register Listener เรียบร้อยแล้ว Listener จะถูกเรื่องในระหว่างที่มีการ saving เกิดขึ้น

ลองใช้งาน Event Handler

เราสามารถทดลองได้ โดยใช้ Artisan Tinker

php artisan tinker
>>> \App\Models\User::factory()->create();
=> App\User {#794
     name: "Aiden Cremin",
     email: "josie05@example.com",
     updated_at: "2018-03-15 03:57:18",
     created_at: "2018-03-15 03:57:18",
     id: 2,
   }

ถ้าเราสร้าง Event และผูกกับ Listener ได้ถูกต้องในไฟล์ laravel.log จะพบกับ JSON ของ Model ที่ถูกสร้างขึ้น

[2018-03-15 03:57:18] local.INFO: {"name":"Aiden Cremin","email":"josie05@example.com"}  

สังเกตุว่า ณ จุดนี้ Model ยังไม่ได้ถูกสร้างขึ้น (saving คือกำลังทำก่อนการ save ลง database จริง) เราจึงไม่เห็น property created_at และ updated_at แต่ถ้าเราเรียก save() อีกครั้งใน Model เดิม ในไฟล์ log เราจะเห็น timestamps เพราะ saving ถูกเรียกทั้งตอนที่สร้างขึ้นใหม่และแก้ไข record เดิม

>>> $u = \App\Models\User::factory()->create();
=> App\Models\User {#741
     name: "Eloisa Hirthe",
     email: "gottlieb.itzel@example.com",
     updated_at: "2018-03-15 03:59:37",
     created_at: "2018-03-15 03:59:37",
     id: 3,
   }
>>> $u->save();
=> true
>>>

หยุดการ Save

บาง Model Event อนุญาตให้เราหยุดการดำเนินการได้ จากตัวอย่างของเราสมมุติว่าเราไม่ต้องการให้ Model save ได้ถ้าชื่อมีคำว่า Paul อยู่ในนั้น

/**
 * Handle the event.
 *
 * @param  \App\Events\UserSaving $event
 * @return mixed
 */
public function handle(UserSaving $event)
{
    if (stripos($event->user->name, 'paul') !== false) {
        return false;
    }
}

โดยภายใน base Model::save() จะมีการเช็คว่าถ้ามี event saving() ที่ return false มาจะไม่ทำการบันทึกข้อมูลต่อไป

public function save(array $options = [])
{
    $query = $this->newQueryWithoutScopes();

    // If the "saving" event returns false we'll bail out of the save and return
    // false, indicating that the save failed. This provides a chance for any
    // listeners to cancel save operations if validations fail or whatever.
    if ($this->fireModelEvent('saving') === false) {
        return false;
    }

การใช้ Observers

จากตัวอย่างข้างต้นเป็นการดักทีละ Event ต่อ Listener ถ้าเราต้องการดักหลาย Event ที่เกิดขึ้นใน Model ไว้ในที่เดี่ยวเราสามารถใช้คลาสที่เรียกว่า Observer ในการดัก Event ทั้งหมดที่เราต้องการได้

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * Listen to the User created event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * Listen to the User deleting event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleting(User $user)
    {
        //
    }
}

เราทำการ register คลาส Observer ผ่านทาง method boot() ใน ServiceProvider เบื้องต้นเราสามารถระบุไว้ในคลาส AppServiceProvider ได้เลย

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    User::observe(UserObserver::class);
}

เรียนรู้เพิ่มเติม

หากต้องการเรียนรู้เพิ่มเติมลองดูใน Laravel events documentation เพื่อศึกษาเกี่ยวกับวิธีการทำงานของ Event และ Listener ใน framework ใน Eloquent events documentation ก็เป็นแหล่งอ้างอิงที่ดีสำหรับ Event และวิธีใช้งาน Observer สุดท้ายการลองดูใน code ที่เกี่ยวข้องใน method fireModelEvent() ในคลาส  Illuminate\Database\Eloquent\Model ก็จะช่วยให้เห็นภาพการทำงานของ Model Event ได้มากขึ้นและ trait HasEvents ที่ทำการผูก Event ทั้งหมดไว้ด้วยกัน.

Phattarachai Chaimongkol

เกี่ยวกับ phattarachai.dev

มองหาคนช่วยทำ Web App ใช้ภายในธุรกิจอยู่มั้ยครับ
มีความชำนาญในการพัฒนา Web Application ด้วย Laravel รับพัฒนาโปรเจคให้ผู้ประกอบการ ธุรกิจ SME ทั้งขนาดเล็กและขนาดใหญ่ พัฒนาระบบใช้ในองค์กรทั้งภาครัฐและเอกชน เป็นพาร์ทเนอร์กับบริษัททางด้าน Digital Agency เพื่อพัฒนาโปรเจคให้แก่ลูกค้า ทักเข้ามาพูดคุยกันก่อนได้เลยครับ

เรื่องล่าสุด

Backup ฐานข้อมูลด้วย Laravel อย่างง่าย ๆ ด้วย Spatie DB Snapshots
1 กุมภาพันธ์ 2564
Backup ฐานข้อมูลด้วย Laravel อย่างง่าย ๆ ด้วย Spatie DB Snapshots
วิธีการให้ git จดจำ password โดยไม่ต้องระบุใหม่ทุกครั้ง
1 กุมภาพันธ์ 2564
วิธีการให้ git จดจำ password โดยไม่ต้องระบุใหม่ทุกครั้ง
วิธีการเช็ค Detect Browser ผู้ใช้จาก Laravel
1 กุมภาพันธ์ 2564
วิธีการเช็ค Detect Browser ผู้ใช้จาก Laravel