การจัดการ Error ใน PHP ยุคใหม่ (ตั้งแต่ PHP 7 เป็นต้นมา และสมบูรณ์แบบมากใน PHP 8) ได้เปลี่ยนผ่านจากระบบเดิมที่เป็น Error Reporting (พวก Notice, Warning, Fatal Error) มาเป็นระบบ Object-Oriented Exceptions เกือบทั้งหมดแล้ว
บทความนี้จะพาคุณไปเจาะลึกว่าเกิดอะไรขึ้นเมื่อ Internal Errors กลายเป็น Exceptions และเราจะเขียนโค้ดดักจับมันอย่างมืออาชีพได้อย่างไรครับ
🚀 จาก Error ดั้งเดิม สู่ Throwable Engine
ใน PHP 5 หรือเวอร์ชันที่เก่ากว่านั้น เมื่อโค้ดเกิดความผิดพลาดรุนแรง เช่น เรียกใช้ฟังก์ชันที่ไม่มีอยู่จริง (Fatal Error) หรือหน่วยความจำเต็ม ระบบจะหยุดทำงานทันที (Die) โดยที่เราไม่สามารถใช้บล็อก try-catch ไปดักจับมันได้
แต่ใน PHP 7+ มีการปรับปรุงโครงสร้างภายในใหม่ โดยให้ Internal Errors ส่วนใหญ่ถูกโยนออกมาเป็น Exceptions ผ่าน Interface ที่ชื่อว่า Throwable
โครงสร้างลำดับชั้น (Hierarchy) ของ Throwable
interface Throwable
├── Exception (สำหรับ User-level exceptions ทั่วไป)
└── Error (สำหรับ Internal PHP errors)
├── ArithmeticError
├── DivisionByZeroError
├── AssertionError
├── CompileError
├── TypeError
└── ArgumentCountError
💡 ข้อควรจำ: Error ไม่ได้สืบทอดมาจากคลาส Exception แต่ทั้งคู่สืบทอดมาจาก Throwable เหมือนกัน ดังนั้นถ้าคุณใช้ catch (Exception $e) คุณจะไม่สามารถดักจับ Internal Errors ได้
🛠️ วิธีดักจับ Internal Errors ที่ถูกต้อง
เมื่อ Internal Errors กลายเป็น Exceptions แล้ว วิธีการรับมือที่ถูกต้องและปลอดภัยที่สุดคือการดักจับด้วย Throwable หรือระบุคลาส Error ให้ตรงจุด
❌ แบบที่ผิด (จับไม่โดน)
โค้ดด้านล่างนี้จะยังคงทำให้เกิด Fatal Error และโปรแกรมหยุดทำงาน เพราะ TypeError ไม่ได้เป็นลูกของ Exception
try {
// ส่ง Argument ผิดประเภท (Internal TypeError)
strlen(["this is an array"]);
} catch (Exception $e) {
echo "จับคู่ข้อผิดพลาด: " . $e->getMessage();
}
แบบที่ถูกต้อง (จับได้อยู่หมัด)
เปลี่ยนไปใช้ Throwable เพื่อให้ครอบคลุมทั้งระบบ หรือใช้คลาส Error โดยตรง
try {
strlen(["this is an array"]);
} catch (Throwable $e) {
echo "⚠️ จับ Internal Error ได้แล้ว: " . $e->getMessage();
// ผลลัพธ์: ⚠️ จับ Internal Error ได้แล้ว: strlen(): Argument #1 ($string) must be of type string, array given
}
🔍 เจาะลึก Internal Errors ที่พบบ่อย
นี่คือตัวอย่างของ Internal Errors ที่อัปเกรดมาเป็น Exceptions และเราสามารถจัดการมันได้แล้วในปัจจุบัน
TypeError
เกิดขึ้นเมื่อส่ง Argument ผิดประเภท หรือฟังก์ชัน Return ค่าไม่ตรงกับที่กำหนดไว้
try {
function add(int $a, int $b) { return $a + $b; }
add("ห้า", 5);
} catch (TypeError $e) {
echo "Type mismatch: " . $e->getMessage();
}
DivisionByZeroError
เกิดขึ้นเมื่อมีความพยายามหารตัวเลขด้วยศูนย์ (เฉพาะการใช้โอเปอเรเตอร์ % หรือฟังก์ชันบางตัว ส่วนการหารด้วย / ใน PHP 8 จะได้ผลลัพธ์เป็น INF แต่จะโยน Error นี้ในบางกรณี)
try {
$result = 10 % 0;
} catch (DivisionByZeroError $e) {
echo "ล้มเหลว: ห้ามหารด้วยศูนย์!";
}
ArgumentCountError
เกิดขึ้นเมื่อส่ง Argument ให้ฟังก์ชันน้อยกว่าที่มันต้องการ
try {
function myProfile($name, $age) { }
myProfile("Tam"); // ส่งไม่ครบ
} catch (ArgumentCountError $e) {
echo "ข้อมูลไม่ครบ: " . $e->getMessage();
}
🎯 ประโยชน์ของการเปลี่ยนแปลงนี้
- Application ไม่ตายกลางคัน (Graceful Degradation): หากเกิด Error ในระบบหลังบ้าน เราสามารถดักจับแล้วแสดงหน้า Error Page สวย ๆ ให้ผู้ใช้ดู แทนที่จะปล่อยหน้าจอขาว (White Screen of Death)
- ทำ Logging ได้ง่ายขึ้น: สามารถเขียนโค้ดส่ง Error บันทึกเข้าคลังข้อมูล (เช่น Log files, Sentry) ได้ทันทีผ่านบล็อก
catch - เขียน Clean Code ได้ง่ายขึ้น: โครงสร้าง
try-catch-finallyทำให้แยกส่วนของ Business Logic ออกจาก Error Handling ได้อย่างชัดเจน
📢 ข้อควรระวัง: Warnings และ Notices ยังคงอยู่!
แม้ว่า Fatal Errors จะกลายเป็น Exceptions แล้ว แต่ความผิดพลาดระดับ Warning และ Notice (เช่น การใช้ตัวแปรที่ไม่ได้ประกาศ หรือ Array undefined index) ยังคงไม่พ่นออกมาเป็น Exception นอกเสียจากว่าคุณจะใช้ PHP 8 ซึ่งบางตัวถูกยกฐานะขึ้นมาแล้ว
หากต้องการเปลี่ยน Warning/Notice ทั้งหมดให้กลายเป็น Exceptions คุณต้องใช้ฟังก์ชัน set_error_handler ควบคู่ไปด้วย
// เปลี่ยนทุก Error ดั้งเดิมให้กลายเป็น ErrorException
set_error_handler(function($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
});
// ตอนนี้แม้แต่เรื่องเล็กๆ ก็ใช้ try-catch ครอบได้แล้ว
try {
echo $undefinedVariable; // Notice ดั้งเดิม จะถูกโยนเป็น Exception ทันที
} catch (ErrorException $e) {
echo "จับ Notice ได้: " . $e->getMessage();
}
อ่านเพิ่มเติม