บทความ

Laravel Model Event ใช้ยังไง ดักตอนสร้าง แก้ไข หรือลบข้อมูลใน Eloquent

ดัก event ของ Eloquent model ได้สามแบบ คือ closure ใน booted(), คลาส Event คู่กับ Listener และ Observer พร้อมวิธีสั่งยกเลิกการบันทึกและปิด event ชั่วคราว

เผยแพร่
อัปเดต
— ปรับตัวอย่างให้ตรงกับ Laravel 13
เวลาอ่าน
5 นาที
แปลจาก
Paul Redmond · laravel-news.com
แท็ก
#Eloquent#Laravel

Model event ใน Laravel ให้เราแทรกโค้ดของเราเข้าไปตามจังหวะต่าง ๆ ของ model ได้ ตั้งแต่ตอนอ่านขึ้นมาจาก database ตอนบันทึก ไปจนถึงตอนลบ และยังสั่งไม่ให้บันทึกหรือลบได้ด้วย เอกสารของ Laravel บอกไว้แล้วว่าจะ hook เข้า event พวกนี้ยังไง ในบทความนี้เราจะลงรายละเอียดตอนตั้งค่า model event กับ listener กัน

Model มี event อะไรให้ดักบ้าง

Eloquent ส่ง event ออกมาหลายจังหวะ เราดักแล้วเพิ่มการทำงานให้ model ได้ตามตารางนี้

Event เกิดตอนไหน ยกเลิกได้
retrieved หลังอ่าน model ขึ้นมาจาก database
creating ก่อน insert ✓
created หลัง insert
updating ก่อน update ✓
updated หลัง update
saving ก่อนบันทึก ทั้ง insert และ update ✓
saved หลังบันทึก ทั้ง insert และ update
deleting ก่อนลบ ✓
deleted หลังลบ
trashed หลัง soft delete
forceDeleting ก่อนลบถาวรด้วย forceDelete() ✓
forceDeleted หลังลบถาวร
restoring ก่อนกู้คืนจาก soft delete ✓
restored หลังกู้คืนจาก soft delete
replicating ตอนคัดลอกเป็น model ใหม่ด้วย replicate()

trashed, forceDeleting, forceDeleted, restoring และ restored มีเฉพาะ model ที่ใช้ trait SoftDeletes อ่านเรื่อง Soft Delete กับ Replicating เพิ่มได้ในเอกสารของ Laravel

ดัก event ใน model ด้วย closure

วิธีที่ง่ายที่สุดคือเรียก static method ตามชื่อ event ใน method booted() ของ model

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // ...
        });

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

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

วิธีนี้เหมาะกับ logic สั้น ๆ ไม่ซับซ้อน แต่พอเราดักหลาย event หรือ logic เริ่มยาว คลาส model ก็จะอ่านยากขึ้นเรื่อย ๆ

ผูก model event เข้ากับคลาส Event

ใน model มี property $dispatchesEvents ไว้บอกว่า model event ไหนให้ส่งคลาส Event ตัวไหนออกไป ทีนี้ listener ของคลาส Event นั้นก็จะทำงานตามจังหวะของ model

event ที่ลงท้ายด้วย -ing บางตัว เช่น saving หรือ deleting จะเช็คว่า listener คืนค่า false มาหรือเปล่า ถ้าคืนมา Laravel จะหยุด ไม่บันทึกหรือลบต่อ เราจึงใช้วิธีนี้กันไม่ให้บันทึกหรือลบ user ได้

ลองใช้ model App\Models\User เป็นตัวอย่าง เราตั้งค่า model event ได้แบบนี้

use App\Events\UserSaving;

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

สร้างคลาส Event ด้วยคำสั่ง Artisan

php artisan make:event UserSaving

คลาสที่ได้มีส่วนของ broadcast มาด้วย ตัวอย่างนี้ไม่ได้ใช้ เราตัดออกแล้วรับ user เข้ามาทาง constructor

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserSaving
{
    use Dispatchable, SerializesModels;

    public function __construct(public User $user)
    {
    }
}

เราประกาศ $user เป็น public ไว้ listener จะได้หยิบ model ตัวนั้นไปใช้ได้

สร้าง Listener มารับ event

ต่อไปเราสร้างคลาส Listener มารับ event พอ model ส่ง event saving ออกมา Laravel จะเรียก listener ตัวนี้ระหว่างนั้น สร้างด้วยคำสั่ง Artisan

php artisan make:listener UserSavingListener --event=UserSaving

เราจะได้คลาส UserSavingListener ใน app/Listeners ที่รับ event UserSaving เข้ามาใน method handle()

<?php

namespace App\Listeners;

use App\Events\UserSaving;
use Illuminate\Support\Facades\Log;

class UserSavingListener
{
    public function handle(UserSaving $event): void
    {
        Log::info($event->user);
    }
}

ตัวอย่างนี้เขียน log ไว้ดูว่า model ที่ส่งเข้ามาใน listener หน้าตาเป็นยังไง

ต้นฉบับต้องไปลงทะเบียน listener ใน property $listen ของ EventServiceProvider แต่ตั้งแต่ Laravel 11 เราไม่ต้องทำแล้ว Laravel จะสแกนโฟลเดอร์ app/Listeners หา method handle() แล้วดูจาก type-hint ว่า listener ตัวไหนรับ event อะไร (event discovery) บน production ให้รัน php artisan event:cache (หรือ php artisan optimize) ตอน deploy เพื่อไม่ต้องสแกนทุก request

ถ้า listener อยู่นอก app/Listeners หรืออยากผูกเอง ก็สั่ง Event::listen ใน AppServiceProvider ได้

use App\Events\UserSaving;
use App\Listeners\UserSavingListener;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(UserSaving::class, UserSavingListener::class);
}

listener ที่อยู่ใน app/Listeners อยู่แล้วไม่ต้องสั่ง Event::listen ซ้ำ ไม่อย่างนั้น Laravel จะผูกสองครั้ง และ listener จะทำงานสองรอบ

ลองดูว่า listener ทำงานจริงไหม

ลองใน php artisan tinker ได้เลย

php artisan tinker
> App\Models\User::factory()->create();
= App\Models\User {#6189
    name: "Aiden Cremin",
    email: "[email protected]",
    email_verified_at: "2026-10-10 03:57:18",
    #password: "$2y$12$...",
    #remember_token: "QKxP0mVrZs",
    updated_at: "2026-10-10 03:57:18",
    created_at: "2026-10-10 03:57:18",
    id: 2,
  }

ถ้าเราสร้าง event และผูกกับ listener ถูก ในไฟล์ storage/logs/laravel.log จะมี JSON ของ model ตัวที่เพิ่งสร้าง

[2026-10-10 03:57:18] local.INFO: {"name":"Aiden Cremin","email":"[email protected]","email_verified_at":"2026-10-10T03:57:18.000000Z"}

สังเกตว่าตอนนี้ model ยังไม่ได้บันทึกลง database (saving เกิดก่อนบันทึกจริง) เราจึงไม่เห็น created_at กับ updated_at ใน log แต่ถ้าเรียก save() อีกครั้งกับ model เดิม ใน log จะมี timestamp ด้วย เพราะ saving เกิดทั้งตอนสร้างใหม่และตอนแก้ไข record เดิม

> $u = App\Models\User::factory()->create();
= App\Models\User {#6201
    name: "Eloisa Hirthe",
    email: "[email protected]",
    ...
    id: 3,
  }

> $u->save();
= true

สั่งยกเลิกการบันทึก

model event บางตัวให้เรายกเลิกการทำงานได้ สมมติว่าเราไม่อยากให้บันทึก user ที่มีคำว่า Paul อยู่ในชื่อ

public function handle(UserSaving $event): ?bool
{
    if (str_contains(strtolower($event->user->name), 'paul')) {
        return false;
    }

    return null;
}

ใน Model::save() ของ Laravel จะเช็คว่ามี listener ของ saving คืนค่า false มาหรือเปล่า ถ้ามีก็จะหยุดตรงนั้น ไม่บันทึกต่อ และ save() คืนค่า false

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

    $query = $this->newModelQuery();

    // 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;
    }

    // ...
}

รวมหลาย event ไว้ใน Observer

ตัวอย่างที่ผ่านมาเราดักทีละ event ต่อ listener ถ้าอยากดักหลาย event ของ model เดียวไว้ในที่เดียว ใช้คลาส Observer ได้ สร้างด้วยคำสั่ง

php artisan make:observer UserObserver --model=User

ตั้งชื่อ method ตามชื่อ event ที่อยากดัก

<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    public function created(User $user): void
    {
        // ...
    }

    public function deleting(User $user): void
    {
        // ...
    }
}

แล้วผูก Observer เข้ากับ model ด้วย attribute #[ObservedBy] บนคลาส model

use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Foundation\Auth\User as Authenticatable;

#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
    // ...
}

ต้นฉบับผูกด้วย User::observe(UserObserver::class) ใน method boot() ของ AppServiceProvider วิธีนี้ยังใช้ได้อยู่ แต่ใส่ attribute ไว้ที่ model จะเห็นทันทีว่า model นี้มี Observer อยู่

ให้ทำงานหลัง transaction commit

ถ้าเราบันทึก model ใน DB::transaction() event อย่าง created จะเกิดก่อน transaction commit ถ้า Observer ส่งอีเมลหรือ dispatch job ตรงนั้น แล้ว transaction rollback ทีหลัง เราจะส่งอีเมลเรื่องข้อมูลที่ไม่มีอยู่จริงออกไป

ใส่ interface ShouldHandleEventsAfterCommit ให้ Observer แล้ว Laravel จะรอจน transaction commit ก่อนค่อยเรียก method ของ Observer ถ้าไม่ได้อยู่ใน transaction ก็เรียกทันทีเหมือนเดิม

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    public function created(User $user): void
    {
        // ส่งอีเมลต้อนรับ ...
    }
}

interface นี้ใส่ให้ listener ได้ด้วย แต่ใช้กับ event ที่ต้องการยกเลิกอย่าง saving ไม่ได้ เพราะกว่า listener จะทำงาน การบันทึกก็เสร็จไปแล้ว เรื่อง job กับ transaction อ่านต่อได้ใน วิธี Run Job หลัง Commit Database Transaction

ปิด event ชั่วคราว

บางครั้งเราอยากบันทึกโดยไม่ให้ listener หรือ Observer ทำงาน เช่นตอน import ข้อมูลหรือตอนแก้ข้อมูลย้อนหลัง ใช้ method ที่ลงท้ายด้วย Quietly ได้

$user->name = 'Aiden';
$user->saveQuietly();

$user->updateQuietly(['name' => 'Aiden']);
$user->deleteQuietly();

ฝั่งสร้างใหม่ก็มี User::createQuietly([...]) และ model ที่ใช้ SoftDeletes มี restoreQuietly() กับ forceDeleteQuietly() ให้ด้วย

ถ้ามีหลายคำสั่ง ห่อด้วย withoutEvents() ได้ ทุก model ที่บันทึกใน closure นี้จะไม่ส่ง event และ withoutEvents() คืนค่าที่ closure คืนมาให้เรา

use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();

    $user = User::findOrFail(2);
    $user->update(['name' => 'Aiden']);

    return $user;
});

อ่านต่อ

เรื่อง event กับ listener ทั้งหมดอยู่ใน Laravel Events ส่วน Eloquent Events อธิบาย model event และ Observer ไว้ครบ ถ้าอยากเห็นว่าข้างในทำงานยังไง ลองเปิด method fireModelEvent() ใน trait HasEvents ดู trait นี้รวม model event ทั้งหมดไว้ด้วยกัน

อ่านต่อ

ล่าสุด

ดูทั้งหมด →