Kodokon kodokon.com

Exception: ลำดับชั้น finally และตัวจัดการระดับโกลบอล

สร้างกลยุทธ์จัดการข้อผิดพลาดที่สมบูรณ์: business exception ที่ถูกร้อยต่อกัน finally ที่ควบคุมได้ และตัวจัดการระดับโกลบอลในฐานะแนวป้องกันสุดท้าย

9 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

ตั้งแต่ PHP 7 เป็นต้นมา ทุกสิ่งที่ถูกโยนออกมาจะนำ Throwable ไปใช้ โดยมีสองสาขา ได้แก่ Error (ความล้มเหลวของเอนจินและการกำหนดชนิด เช่น TypeError) และ Exception (ความล้มเหลวของแอปพลิเคชัน) คุณจะจับ Error เฉพาะที่ขอบเขตของโปรแกรมเท่านั้น ในฝั่งแอปพลิเคชัน SPL จัดเตรียมลำดับชั้นสำเร็จรูปมาให้: LogicException ส่งสัญญาณว่าเป็นบั๊กของนักพัฒนา ที่ต้องแก้ไข ส่วน RuntimeException เป็นเหตุการณ์ไม่คาดคิดขณะรัน ที่ต้องจัดการ ปฏิกิริยาแบบมืออาชีพคือ จงสืบทอด business exception ของคุณจากฐานเหล่านี้ หนึ่งตัวต่อหนึ่งสถานการณ์ที่ระบุได้

PHP
<?php

declare(strict_types=1);

abstract class AppException extends RuntimeException
{
}

final class UserNotFound extends AppException
{
    public static function withEmail(
        string $email
    ): self {
        return new self("No user for $email");
    }
}

try {
    throw UserNotFound::withEmail('lea@example.com');
} catch (UserNotFound $e) {
    echo $e->getMessage();
}
ลำดับชั้นเชิงธุรกิจ: คุณจับ UserNotFound, AppException หรือ RuntimeException ขึ้นอยู่กับระดับ

try/catch จะคุ้มค่าก็ต่อเมื่อคุณรู้ว่าจะจัดการกับข้อผิดพลาดอย่างไร มิฉะนั้นก็ปล่อยให้มันลอยขึ้นไปข้างบน มีเครื่องมือสามอย่างที่ช่วยปรับการควบคุมให้ละเอียดขึ้น: multi-catch catch (JsonException | ValueError $e) จัดกลุ่มการจัดการที่เหมือนกันเข้าด้วยกัน พารามิเตอร์ previous ร้อยต่อ สาเหตุดั้งเดิมเข้าไว้ เมื่อคุณแปลง exception เชิงเทคนิคให้เป็นเชิงธุรกิจ ส่วน finally ทำงานในทุกกรณี ทั้งสำเร็จ เกิด exception หรือแม้แต่ return ก่อนเวลา เหมาะอย่างยิ่งสำหรับการปลดปล่อยทรัพยากร

PHP
<?php

declare(strict_types=1);

final class PayloadInvalid extends RuntimeException
{
}

function decode(string $json): mixed
{
    try {
        return json_decode(
            $json,
            true,
            flags: JSON_THROW_ON_ERROR,
        );
    } catch (JsonException $e) {
        throw new PayloadInvalid(
            'Invalid JSON payload',
            previous: $e,
        );
    } finally {
        echo "decode() finished\n";
    }
}

try {
    decode('{broken');
} catch (PayloadInvalid $e) {
    echo $e->getPrevious()?->getMessage();
}
การแปลง exception: สาเหตุเชิงเทคนิคยังคงเข้าถึงได้ผ่าน getPrevious()

ที่ปลายสุดของสายโซ่ ตัวจัดการระดับโกลบอลสองตัวช่วยปิดฉากการตั้งค่านี้ set_exception_handler จับทุกสิ่งที่ไม่ได้ถูกจัดการ: บันทึกล็อกอย่างมีโครงสร้าง ตอบกลับด้วย 500 แบบทั่วไป โดยไม่เปิดเผยข้อความหรือ trace ให้ผู้ใช้เห็นเลย ส่วน set_error_handler แปลงข้อผิดพลาดยุคเก่า (warning, notice) ให้เป็น ErrorException ทำให้สองโลกเป็นหนึ่งเดียว: ทุกอย่างกลายเป็น exception ทุกอย่างเดินตามเส้นทางการจัดการเดียวกัน

PHP
<?php

declare(strict_types=1);

set_error_handler(function (
    int $severity,
    string $message,
    string $file,
    int $line,
): bool {
    throw new ErrorException(
        $message,
        0,
        $severity,
        $file,
        $line,
    );
});

set_exception_handler(function (Throwable $e): void {
    error_log($e->getMessage());
    http_response_code(500);
    echo 'A technical error occurred.';
});

trigger_error('Legacy warning', E_USER_WARNING);
echo 'Never reached';
warning กลายเป็น exception ที่ไม่ถูกจับ: ตัวจัดการระดับโกลบอลตอบสนอง จากนั้นสคริปต์ก็หยุดทำงาน

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. Error และ Exception มีอะไรเหมือนกันใน PHP สมัยใหม่?
    • Error สืบทอดจาก Exception
    • ทั้งคู่นำอินเทอร์เฟซ Throwable ไปใช้
    • ทั้งคู่สงวนไว้สำหรับเอนจิน PHP
  2. บล็อก finally ทำงานเมื่อใด?
    • เฉพาะเมื่อไม่มี exception ถูกโยนออกมา
    • เฉพาะหลังจาก catch เท่านั้น
    • ในทุกกรณี: สำเร็จ, exception ที่ถูกจับหรือไม่ถูกจับ, หรือ return ก่อนเวลา
  3. พารามิเตอร์ previous ของ exception มีไว้เพื่ออะไร?
    • รักษา exception ดั้งเดิมไว้เมื่อคุณแปลงมันเป็น exception ระดับสูงขึ้น
    • ป้องกันไม่ให้ exception ลอยขึ้นไปตาม call stack
    • กำหนด exception ที่จะโยนในความล้มเหลวครั้งถัดไป