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 ทั้งหมดไว้ด้วยกัน.