การจัดการ Exception ที่ไม่ได้ถูก Catch
ในบทที่แล้ว เราได้เรียนรู้วิธีการเข้าถึงค่า Environment Variables ที่โหลดมาจากไฟล์ .env ในบทนี้ เราจะมาพูดถึงการจัดการ Exception ที่ไม่ได้ถูก catch หรือถูกจับ เพื่อให้แอปพลิเคชันของเราทำงานได้อย่างราบรื่นและสามารถให้ข้อมูลที่เป็นประโยชน์เมื่อเกิดข้อผิดพลาดที่ไม่คาดคิด
Global Exception Handler คืออะไร?
โดยปกติแล้ว เมื่อเกิด Exception ใน PHP ถ้าเราไม่ได้ใช้ try-catch เพื่อดักจับ Exception นั้น โปรแกรมจะหยุดทำงานทันที และแสดงข้อผิดพลาด (error) ออกมา ซึ่งอาจจะไม่เป็นมิตรกับผู้ใช้งาน และไม่ช่วยให้เราเข้าใจถึงปัญหาที่เกิดขึ้น ดังนั้น เราจึงจำเป็นต้องมีวิธีในการจัดการ Exception ที่ไม่ได้ถูก catch เพื่อให้:
- บันทึกข้อมูลของ Exception เพื่อนำไปวิเคราะห์และแก้ไข
- แสดงข้อความที่เป็นมิตรต่อผู้ใช้งาน แทนที่จะเป็นข้อความ error ที่ซับซ้อน
- ป้องกันไม่ให้โปรแกรมหยุดทำงานอย่างกะทันหัน
PHP มีฟังก์ชัน set_exception_handler() ที่ช่วยให้เราสามารถกำหนดฟังก์ชันที่จะถูกเรียกใช้งานเมื่อเกิด Exception ที่ไม่ได้ถูก catch ได้
การใช้งาน set_exception_handler()
set_exception_handler() รับพารามิเตอร์เป็นชื่อของฟังก์ชัน (หรือ closure) ที่จะถูกเรียกใช้งานเมื่อเกิด Exception ที่ไม่ได้ถูก catch
<?php
function handleUncaughtException(Throwable $exception) {
// บันทึกข้อมูลของ Exception
error_log("Uncaught Exception: " . $exception->getMessage() . " in " . $exception->getFile() . ":" . $exception->getLine());
// แสดงข้อความที่เป็นมิตรต่อผู้ใช้งาน
echo "<h1>ขออภัย เกิดข้อผิดพลาดขึ้น</h1>";
echo "<p>โปรดลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ</p>";
// หยุดการทำงานของสคริปต์ (ป้องกันการทำงานต่อที่อาจทำให้เกิดปัญหา)
exit;
}
set_exception_handler('handleUncaughtException');
// ตัวอย่างการใช้งาน
throw new Exception("Something went wrong!");
?>
ในตัวอย่างข้างต้น เราได้สร้างฟังก์ชัน handleUncaughtException() ที่รับพารามิเตอร์เป็น Throwable ซึ่งเป็น interface ที่ Exception ทั้งหมด implement ฟังก์ชันนี้จะ:
- บันทึกข้อมูลของ Exception โดยใช้
error_log()ซึ่งจะบันทึกข้อมูลไปยังไฟล์ error log ของ PHP (ตำแหน่งของไฟล์ error log จะขึ้นอยู่กับการตั้งค่าของ PHP) - แสดงข้อความที่เป็นมิตรต่อผู้ใช้งาน โดยใช้ HTML
- หยุดการทำงานของสคริปต์ โดยใช้
exitเพื่อป้องกันไม่ให้โปรแกรมทำงานต่อ ซึ่งอาจจะทำให้เกิดปัญหาเพิ่มเติม
จากนั้นเราได้เรียกใช้ set_exception_handler('handleUncaughtException') เพื่อกำหนดให้ฟังก์ชัน handleUncaughtException() เป็น Global Exception Handler
เมื่อเรา throw Exception ขึ้นมา (throw new Exception("Something went wrong!")) แต่ไม่มี try-catch ดักจับ Exception นั้น ฟังก์ชัน handleUncaughtException() จะถูกเรียกใช้งาน
การบันทึก Log
ในตัวอย่างข้างต้น เราใช้ error_log() เพื่อบันทึกข้อมูลของ Exception แต่ในบทก่อนๆ เราได้เรียนรู้การใช้งาน Monolog ซึ่งเป็น logging library ที่มีประสิทธิภาพมากกว่า ดังนั้น เราสามารถใช้ Monolog ใน Global Exception Handler ได้
<?php
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// สร้าง Logger
$log = new Logger('app');
$log->pushHandler(new StreamHandler(__DIR__ . '/logs/error.log', Logger::ERROR));
function handleUncaughtException(Throwable $exception) {
global $log; // เข้าถึงตัวแปร $log ที่อยู่ภายนอก scope
// บันทึกข้อมูลของ Exception โดยใช้ Monolog
$log->error("Uncaught Exception", [
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString(),
]);
// แสดงข้อความที่เป็นมิตรต่อผู้ใช้งาน
echo "<h1>ขออภัย เกิดข้อผิดพลาดขึ้น</h1>";
echo "<p>โปรดลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ</p>";
// หยุดการทำงานของสคริปต์ (ป้องกันการทำงานต่อที่อาจทำให้เกิดปัญหา)
exit;
}
set_exception_handler('handleUncaughtException');
// ตัวอย่างการใช้งาน
throw new Exception("Something went wrong!");
?>
ในตัวอย่างนี้ เราได้:
- Include
autoload.phpเพื่อให้สามารถใช้งาน Monolog ได้ - สร้าง Logger โดยใช้ Monolog และกำหนดให้บันทึกข้อมูลไปยังไฟล์
logs/error.log - แก้ไขฟังก์ชัน
handleUncaughtException()ให้ใช้ Monolog ในการบันทึกข้อมูลของ Exception โดยเพิ่มข้อมูลเพิ่มเติม เช่น stack trace - เพิ่ม
global $log;ในฟังก์ชันhandleUncaughtException()เพื่อให้สามารถเข้าถึงตัวแปร$logที่สร้างไว้ภายนอก scope ของฟังก์ชันได้
การใช้ Monolog ทำให้เราสามารถบันทึกข้อมูลของ Exception ได้อย่างละเอียด และสามารถกำหนดรูปแบบการบันทึก (formatter) และตำแหน่งที่จะบันทึก (handler) ได้อย่างยืดหยุ่น
การแสดงผลที่เป็นมิตร
การแสดงข้อความ error ที่ซับซ้อนให้ผู้ใช้งานเห็น อาจทำให้ผู้ใช้งานสับสนและไม่เข้าใจถึงปัญหาที่เกิดขึ้น ดังนั้น เราควรแสดงข้อความที่เป็นมิตรและเข้าใจง่าย เช่น "ขออภัย เกิดข้อผิดพลาดขึ้น โปรดลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ"
นอกจากนี้ เรายังสามารถแสดงข้อมูลเพิ่มเติมที่เป็นประโยชน์ต่อผู้ใช้งานได้ เช่น:
- หมายเลขอ้างอิง (reference number) ที่ผู้ใช้งานสามารถนำไปแจ้งกับผู้ดูแลระบบได้
- ลิงก์ไปยังหน้าช่วยเหลือ หรือหน้า FAQ
แต่ควรระมัดระวังในการแสดงข้อมูลที่ละเอียดเกินไป เช่น stack trace เพราะอาจจะเปิดเผยข้อมูลที่เป็นความลับของระบบ
ข้อผิดพลาดที่พบบ่อย
- ลืม include
autoload.php: ถ้าเราใช้ Composer และ logging library (เช่น Monolog) เราต้อง includeautoload.phpเพื่อให้ PHP รู้จัก class ที่เราใช้ - ไม่ได้กำหนด Handler: ถ้าเราไม่ได้กำหนด Handler ใน Monolog ข้อมูล log จะไม่ถูกบันทึกที่ไหนเลย
- Scope ของตัวแปร: ถ้าเราต้องการเข้าถึงตัวแปรที่สร้างไว้ภายนอก scope ของฟังก์ชัน เราต้องใช้ keyword
global - การแสดงข้อมูลที่ละเอียดเกินไป: ควรระมัดระวังในการแสดงข้อมูลที่ละเอียดเกินไป เพราะอาจจะเปิดเผยข้อมูลที่เป็นความลับของระบบ
สรุป
การจัดการ Exception ที่ไม่ได้ถูก catch เป็นสิ่งสำคัญในการพัฒนาแอปพลิเคชัน PHP เพื่อให้แอปพลิเคชันทำงานได้อย่างราบรื่นและสามารถให้ข้อมูลที่เป็นประโยชน์เมื่อเกิดข้อผิดพลาดที่ไม่คาดคิด เราสามารถใช้ set_exception_handler() เพื่อกำหนด Global Exception Handler และใช้ logging library (เช่น Monolog) เพื่อบันทึกข้อมูลของ Exception
ในบทถัดไป เราจะพูดถึงการจัดการ Error ที่ไม่ได้ถูก Handle ซึ่งเป็นอีกหนึ่งสิ่งที่สำคัญในการพัฒนาแอปพลิเคชัน PHP