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

1 กุมภาพันธ์ 2564 เวลาอ่าน 14 นาที
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 คำอธิบาย
retrieved Model ถูกอ่านจาก 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 Application Development ให้แก่องค์กร ธุรกิจ SME และหน่วยงานราชการ
Web Developer ผู้มีใจรักใน Laravel เป็นพาร์ทเนอร์บริษัท Digital Agency ชั้นนำทางด้าน UX/UI เพื่อพัฒนาโปรเจคให้แก่ลูกค้า ผมช่วยสร้างเครื่องมือทางด้าน Web ที่มีคุณภาพให้ผู้ประกอบการดำเนินธุรกิจได้ง่ายขึ้นใช้งานได้จริง เน้นประสบการณ์ ความชำนาญ ผลงานคุ้มค่าเทียบเท่าจ้างงานกับบริษัทใหญ่ ๆ

ยามว่าง ๆ ชอบเล่นเกมส์บน Steam ครับ

เรื่องที่เกี่ยวข้อง

PHP function แสดงขนาดไฟล์แบบคนอ่านรู้เรื่อง เป็น KB, MB, GB
1 กุมภาพันธ์ 2564
PHP function แสดงขนาดไฟล์แบบคนอ่านรู้เรื่อง เป็น KB, MB, GB
คำสั่ง Postgresql ที่เอาไว้ใช้จัดการเกี่ยวกับ Replication
1 กุมภาพันธ์ 2564
คำสั่ง Postgresql ที่เอาไว้ใช้จัดการเกี่ยวกับ Replication
สรุป Taylor Otwell Keynote ใน Laracon US 2024 - Inertia 2.0, VS Code Extension และ Laravel Cloud
1 กุมภาพันธ์ 2564
สรุป Taylor Otwell Keynote ใน Laracon US 2024 - Inertia 2.0, VS Code Extension และ Laravel Cloud