Eloquent ใช้ง่ายอยู่แล้ว แต่ในคลาส Model ยังมีเมธอดซ่อนอยู่อีกเยอะ บทความนี้รวมเทคนิคที่ช่วยให้คลาสของเราอ่านง่ายขึ้น บางข้อย่อโค้ดหลายบรรทัดเหลือบรรทัดเดียว โดยทำงานเหมือนเดิมทุกอย่าง
1. เพิ่มหรือลดค่าด้วย increment() และ decrement()
แทนที่จะเขียน
$article = Article::find($article_id);
$article->read_count++;
$article->save();
เราเขียนแบบนี้ได้
$article = Article::find($article_id);
$article->increment('read_count');
หรือแบบนี้ก็ได้เหมือนกัน
Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($product_id)->decrement('stock'); // -1
2. ใช้เมธอดแบบ XorY แทน if
Model มีหลายเมธอดที่รวมสองอย่างไว้ในตัวเดียว คือ "ลองทำ X ก่อน ถ้าไม่ได้ค่อยทำ Y"
ตัวอย่างแรกคือ findOrFail() แทนที่จะเขียน
$user = User::find($id);
if (! $user) {
abort(404);
}
เขียนบรรทัดเดียวได้เลย
$user = User::findOrFail($id);
ตัวอย่างที่สองคือ firstOrCreate() แทนที่จะเขียน
$user = User::where('email', $email)->first();
if (! $user) {
$user = User::create([
'email' => $email,
]);
}
ก็เขียนแค่นี้
$user = User::firstOrCreate(['email' => $email]);
3. ดักจังหวะต่าง ๆ ของ Model ใน boot()
ในเมธอด boot() ของ Model เราใส่โค้ดดักไว้ได้หลายจังหวะ เช่น ตอนจะสร้างหรือตอนจะอัปเดต
class User extends Model
{
public static function boot()
{
parent::boot();
static::updating(function ($model) {
// เขียน log
// กำหนด property เพิ่ม เช่น
// $model->something = transform($something);
});
}
}
ตัวอย่างที่เจอบ่อยที่สุดคือตั้งค่าบาง field ตอนสร้าง Model ครั้งแรก เช่น สร้าง UUID ให้เอง
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$model->uuid = (string) Str::uuid();
});
}
ถ้าอยากรู้ว่า Model มี event อะไรอีกบ้าง และเขียน Event Listener ยังไง ลองอ่าน Model Event ต่อได้
4. ใส่ where และ orderBy ไว้ใน relation เลย
ปกติเราเขียน relation ใน Model แบบนี้
public function users()
{
return $this->hasMany(User::class);
}
แต่ถ้ารู้อยู่แล้วว่าจะเรียก relation นี้ด้วยเงื่อนไขเดิมทุกครั้ง เราต่อ where() หรือ orderBy() ไว้ท้าย relation ได้เลย
public function approvedUsers()
{
return $this->hasMany(User::class)
->where('approved', true)
->orderBy('email');
}
อีกวิธีที่ผมชอบใช้คือสร้าง relation ใหม่ต่อจาก relation เดิมที่มีอยู่แล้ว
public function users()
{
return $this->hasMany(User::class);
}
public function approvedUsers()
{
return $this->users()
->where('approved', true)
->orderBy('email');
}
แบบนี้จะเรียก users() เฉย ๆ หรือ approvedUsers() ก็ได้ แล้วแต่ว่าตอนนั้นต้องการแบบไหน
5. ตั้งค่า Model ผ่าน property อย่าง timestamps และ appends
Model มี "พารามิเตอร์" หลายตัวที่เราตั้งเป็น property ของคลาสได้ ตัวที่เห็นกันบ่อย ๆ ก็มี
class User extends Model
{
protected $table = 'users'; // ชื่อตาราง
protected $guarded = []; // ไม่ต้องป้องกันการ fill ค่าแบบ array
protected $casts = ['deleted_at' => 'datetime']; // แปลง field เป็นวันที่ (Carbon)
protected $appends = ['field1', 'field2']; // field ที่ต่อท้ายเพิ่มเวลาแปลงเป็น JSON
}
แต่จริง ๆ ยังมีอีก
protected $primaryKey = 'uuid'; // primary key ไม่จำเป็นต้องชื่อ "id"
public $incrementing = false; // primary key ไม่จำเป็นต้อง auto-increment
protected $perPage = 25; // จำนวนรายการต่อหน้าของ pagination (ค่าเริ่มต้น 15)
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at'; // เปลี่ยนชื่อ field created_at, updated_at ได้
public $timestamps = false; // ไม่ใช้ field created_at, updated_at เลยก็ได้
protected $with = ['profile']; // relation ที่ดึงมาด้วยทุกครั้งโดยอัตโนมัติ
ยังมีอีกหลายตัว เปิดดูได้ใน คลาส Model และ trait ที่ Model ใช้
6. find() หลาย record ในครั้งเดียว
find() ทุกคนคงรู้จักกันดีอยู่แล้ว
$user = User::find(1);
แต่คงมีไม่กี่คนที่รู้ว่าเราส่ง array ให้ find() ได้
$users = User::find([1, 2, 3]);
7. เขียน where ให้สั้นลงด้วย whereX()
where() ยังเขียนได้อีกแบบ จากแบบนี้
$users = User::where('approved', 1)->get();
เขียนเป็นแบบนี้ได้
$users = User::whereApproved(1)->get();
ต่อชื่อ field ไว้ท้าย where ได้เลย ที่ทำได้เพราะ Laravel ใช้ magic method __call() ของ PHP (เทคนิคที่เรียกว่า overloading)
ส่วนเงื่อนไขวันที่และเวลา ก็มีเมธอด where เตรียมไว้ให้หลายตัว
User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));
8. เรียงข้อมูลตามค่าใน relation
ข้อนี้ยากขึ้นอีกนิด สมมติเรามี Topic และอยากเรียง Topic ตาม Post ล่าสุดของแต่ละอัน ซึ่งเจอบ่อยเวลาทำระบบจัดการ content หรือ CMS
เริ่มจากเพิ่ม relation ของ Post ล่าสุดใน Topic
public function latestPost()
{
return $this->hasOne(Post::class)->latest();
}
แล้วใน controller เขียนแบบนี้
$topics = Topic::with('latestPost')
->get()
->sortByDesc('latestPost.created_at');
สังเกตว่าเราเรียก get() เอา collection ออกมาก่อน แล้วค่อยใช้ sortByDesc() ของ collection เรียงจาก post ล่าสุด
9. ใช้ when() แทน if
บางทีเราต้องใส่เงื่อนไข query ตามค่าที่ผู้ใช้ส่งมาใน request() แบบนี้
if (request('filter_by') == 'likes') {
$query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
$query->orderBy('created_at', request('ordering_rule', 'desc'));
}
ย่อให้สั้นลงได้ด้วยเมธอด when()
Author::query()
->when(request('filter_by') == 'likes', function ($q) {
return $q->where('likes', '>', request('likes_amount', 0));
})
->when(request('filter_by') == 'date', function ($q) {
return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});
when() ยังส่งค่าของพารามิเตอร์ตัวแรกเข้า closure ให้เราใช้ใน query ได้ด้วย
$authors = User::query()
->when(request('role', false), function ($q, $role) {
return $q->where('role_id', $role);
})
->get();
10. กัน error ตอน belongsTo ไม่มีข้อมูลด้วย withDefault()
สมมติ Post belongsTo Author และใน Blade เราเขียนแบบนี้
{{ $post->author->name }}
ถ้า author โดนลบไปแล้ว หรือ post นั้นไม่มี author ตั้งแต่แรก เราจะเจอ error ประมาณ Attempt to read property "name" on null
จะแก้ด้วย null coalescing operator (??) ที่มีมาตั้งแต่ PHP 7 ก็ได้
{{ $post->author->name ?? '' }}
แต่จริง ๆ เรากันไว้ตั้งแต่ใน relation ได้เลย
public function author()
{
return $this->belongsTo(Author::class)->withDefault();
}
ถ้า Post ไม่มี author ตัว author() จะคืน Model Author เปล่า ๆ มาให้แทน
และยังกำหนดค่าเริ่มต้นให้ Model เปล่าตัวนั้นได้ด้วย
public function author()
{
return $this->belongsTo(Author::class)->withDefault([
'name' => 'Guest Author',
]);
}
11. เรียงข้อมูลตาม accessor ด้วย sortBy() ของ collection
สมมติเรามี accessor แบบนี้
public function getFullNameAttribute()
{
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
ถ้าอยากเรียงตาม full_name จะเขียนแบบนี้ไม่ได้
$clients = Client::orderBy('full_name')->get(); // ใช้ไม่ได้
ต้องเรียก get() ก่อน แล้วค่อยเรียงด้วย sortBy() ของ collection
$clients = Client::get()->sortBy('full_name'); // ใช้ได้
สังเกตว่าตรงนี้เราใช้ sortBy ของ Collection ไม่ได้ใช้ orderBy ของ Query Builder
12. ให้ query เรียงตาม name ทุกครั้งด้วย global scope
ถ้าอยากให้ User::all() เรียงตาม field name ทุกครั้ง ใส่ global scope ไว้ในเมธอด boot() ของ Model ได้ แบบเดียวกับข้อ 3
protected static function boot()
{
parent::boot();
// เรียงตาม field name จากน้อยไปมาก
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('name', 'asc');
});
}
ดูรายละเอียดเรื่อง Query Scopes ได้ในเอกสารของ Laravel
13. เขียน SQL ตรง ๆ ด้วยเมธอดกลุ่ม raw
พอเงื่อนไขเริ่มซับซ้อน บางทีเขียน SQL ตรง ๆ ง่ายกว่า ใช้เมธอดกลุ่ม raw ได้เลย เช่น
// whereRaw
$orders = DB::table('orders')
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
// havingRaw
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();
// orderByRaw
User::where('created_at', '>', '2016-01-01')
->orderByRaw('(updated_at - created_at) desc')
->get();
14. คัดลอก Model เป็นแถวใหม่ด้วย replicate()
ถ้าอยากคัดลอกข้อมูลจาก Model หนึ่งไปเป็นอีกแถว ไม่ต้องไล่เขียนทุก field เป็น array แล้วส่งให้ Model ใหม่ ใช้ replicate() คัดลอกทุก field ได้เลย
$task = Task::find(1);
$newTask = $task->replicate();
$newTask->save();
15. ใช้ chunk() เมื่อข้อมูลเยอะ
ถ้าต้องวนข้อมูลเยอะ ๆ จากฐานข้อมูล แบ่งดึงทีละก้อน (chunk) จะกิน memory น้อยกว่า
แทนที่จะเขียน
$users = User::all();
foreach ($users as $user) {
// ...
}
ใช้ chunk() แทน
User::chunk(100, function ($users) {
foreach ($users as $user) {
// ...
}
});
16. สร้าง migration และ controller พร้อม Model ในคำสั่งเดียว
คำสั่ง Artisan ที่ใช้สร้าง Model เรารู้จักกันอยู่แล้ว
php artisan make:model Company
แต่ใส่ flag เพิ่มเพื่อสร้างไฟล์ที่เกี่ยวข้องไปพร้อมกันได้ด้วย
php artisan make:model Company -mcr
-mสร้างไฟล์ migration-cสร้างไฟล์ controller-rให้ controller ที่สร้างเป็นแบบ resource controller
17. save() โดยไม่ให้ updated_at เปลี่ยน
บางทีเราอยาก save Model โดยไม่ให้ updated_at เปลี่ยนเป็นเวลาปัจจุบัน เช่น ตอนแก้ข้อมูลย้อนหลัง
$product = Product::find($id);
$product->stock = 0;
Product::withoutTimestamps(fn () => $product->save());
18. update() คืนค่าอะไร
เคยสงสัยไหมว่าเมธอด update() คืนค่าอะไรกลับมา
$result = Product::whereNull('category_id')->update(['category_id' => 2]);
query นี้อัปเดตฐานข้อมูลแน่ ๆ แต่ใน $result มีอะไร
update() คืนจำนวนแถวที่อัปเดตไป (affected rows) ถ้าอยากรู้ว่าอัปเดตไปกี่ record ก็ใช้ค่านี้ได้เลย ไม่ต้องทำอะไรเพิ่ม
19. จัดกลุ่ม AND กับ OR ด้วย closure ให้ได้วงเล็บที่ถูก
บางทีเราต้องใช้ AND กับ OR ปนกันใน SQL เช่น
... WHERE (gender = 'Male' AND age >= 18) OR (gender = 'Female' AND age >= 65)
ถ้าเขียนแบบนี้ query ที่ได้จะไม่ตรงกับที่เราต้องการ
$q->where('gender', 'Male');
$q->orWhere('age', '>=', 18);
$q->where('gender', 'Female');
$q->orWhere('age', '>=', 65);
ต้องจัดกลุ่มด้วย closure แบบนี้ ยาวขึ้นนิดหน่อยแต่ได้เงื่อนไขที่ถูก
$q->where(function ($query) {
$query->where('gender', 'Male')
->where('age', '>=', 18);
})->orWhere(function ($query) {
$query->where('gender', 'Female')
->where('age', '>=', 65);
});
20. ส่งหลายเงื่อนไขให้ orWhere() ในครั้งเดียว
ข้อสุดท้าย orWhere() รับ array ได้ จากที่ปกติเขียนแบบนี้
$q->where('a', 1);
$q->orWhere('b', 2);
$q->orWhere('c', 3);
ย่อเหลือแบบนี้ได้
$q->where('a', 1);
$q->orWhere(['b' => 2, 'c' => 3]);
อยากได้เทคนิค Eloquent เพิ่มอีก
ลองอ่าน 6 เทคนิค Eloquent ที่ช่วยให้โค้ดอ่านง่ายขึ้น ต่อได้ครับ





