Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
Ngày xuất bản
Ngày xuất bản

PHP Throwable, Exception và Error là gì? So sánh chi tiết từ cơ bản đến nâng cao

Trong quá trình phát triển ứng dụng PHP, xử lý lỗi (error handling) là một trong những kỹ năng quan trọng nhất để xây dựng hệ thống ổn định, dễ bảo trì và production-ready.

Từ PHP 7 trở đi, cơ chế xử lý lỗi đã thay đổi đáng kể với sự xuất hiện của Throwable. Rất nhiều developer vẫn nhầm lẫn giữa: Throwable, ExceptionError.

Vậy chúng khác nhau như thế nào? Khi nào nên dùng Exception, khi nào nên catch Throwable? Error có nên xử lý hay không?
Bài viết này sẽ phân tích chi tiết từ cơ bản đến nâng cao, kèm ví dụ thực tế và best practices trong môi trường production.

1. Tổng quan về cơ chế xử lý lỗi trong PHP

Trước PHP 7

Ở PHP 5:

  • Chỉ có Exception là có thể try...catch
  • Fatal error không catch được
  • Script sẽ dừng ngay lập tức

Ví dụ:

try {
    undefinedFunction();
} catch (Exception $e) {
    echo "Caught";
}

Trong PHP 5 → script sẽ crash. Không catch được.


Sau PHP 7

PHP 7 giới thiệu:

  • Throwable (interface)
  • Error (class)
  • Nhiều lỗi nghiêm trọng trở thành catchable

Từ đó, hệ thống được chuẩn hóa lại.


2. Throwable là gì?

Throwable là một interface gốc, đại diện cho tất cả những gì có thể được throw.

Cấu trúc kế thừa:

Throwable (interface)
│
├── Exception
│   └── RuntimeException
│   └── LogicException
│
└── Error
    └── TypeError
    └── ParseError
    └── ArithmeticError

👉 Cả ExceptionError đều implement Throwable.

Điều đó có nghĩa:

catch (Throwable $e)

sẽ bắt được cả ExceptionError.


3. Exception là gì?

Exception(ngoại lệ) là một đối tượng đại diện cho lỗi hoặc điều kiện bất thường xảy ra trong quá trình thực thi chương trình, nhưng có thể được xử lý (catch) để tránh làm gián đoạn toàn bộ ứng dụng. Exception thường được sử dụng để báo hiệu các lỗi thuộc về business logic hoặc các tình huống mà ứng dụng có thể dự đoán và xử lý hợp lý.

Nói cách khác, Exception là cơ chế giúp lập trình viên kiểm soát luồng xử lý khi gặp lỗi, thay vì để chương trình bị dừng đột ngột. Exception có thể được tạo ra (throw) và bắt (catch) bằng các khối try-catch, giúp ứng dụng hoạt động ổn định và dễ debug.

Exception thường thuộc về:

  • Lỗi thuộc business logic
  • Lỗi có thể dự đoán được
  • Lỗi có thể xử lý hợp lý

Ví dụ:

function divide(int $a, int $b): float {
    if ($b === 0) {
        throw new Exception("Cannot divide by zero");
    }
    return $a / $b;
}

try {
    divide(10, 0);
} catch (Exception $e) {
    echo $e->getMessage();
}

Output:

Cannot divide by zero

Khi nào dùng Exception?

  • Validate input
  • Kiểm tra quyền
  • Kiểm tra rule nghiệp vụ
  • Kiểm tra tài nguyên tồn tại

Exception thường thuộc tầng Application / Domain.


4. Error là gì?

Error là một đối tượng đại diện cho các lỗi nghiêm trọng hoặc sai sót trong quá trình thực thi chương trình, thường xuất phát từ lỗi lập trình hoặc lỗi hệ thống mà ứng dụng không thể tự xử lý một cách an toàn. Error thường liên quan đến các vấn đề ở tầng engine, runtime hoặc môi trường thực thi.

Khác với Exception, Error thường không thể dự đoán trước và không nên xử lý ở tầng business logic. Khi gặp Error, ứng dụng có thể bị dừng đột ngột hoặc cần có cơ chế xử lý ở entry point/global handler.

Các loại Error phổ biến trong PHP bao gồm:

  • Parse Error (Syntax Error): Lỗi cú pháp khi code không đúng chuẩn PHP.
  • Fatal Error: Lỗi nghiêm trọng khiến chương trình dừng ngay lập tức (ví dụ gọi hàm không tồn tại, lỗi memory).
  • Warning: Cảnh báo về lỗi không nghiêm trọng, chương trình vẫn tiếp tục chạy nhưng có thể gây ra hành vi không mong muốn.
  • Notice: Thông báo về lỗi nhỏ, thường là lỗi không ảnh hưởng lớn đến chương trình.
  • TypeError: Lỗi kiểu dữ liệu.
  • DivisionByZeroError: Lỗi chia cho 0.
  • ArithmeticError: Lỗi toán học.
  • AssertionError: Lỗi khi assert thất bại.

Ví dụ TypeError:

function sum(int $a, int $b): int {
    return $a + $b;
}

try {
    sum("abc", 5);
} catch (Error $e) {
    echo $e->getMessage();
}

Output:

Argument 1 must be of type int, string given

Đây là TypeError, một subclass của Error.


5. Các phương thức (method) chung của Throwable, Exception, Error

Tất cả các đối tượng Throwable (bao gồm ExceptionError) đều có các method sau:

Method Ý nghĩa
getMessage() Lấy thông báo lỗi (message)
getCode() Lấy mã lỗi (nếu có)
getFile() Lấy tên file nơi lỗi phát sinh
getLine() Lấy số dòng nơi lỗi phát sinh
getTrace() Lấy mảng trace (stack trace dạng array)
getTraceAsString() Lấy stack trace dạng chuỗi
getPrevious() Lấy exception trước đó (nếu có, dùng khi re-throw)
__toString() Xuất thông tin lỗi dạng chuỗi đầy đủ

Ví dụ sử dụng:

try {
    throw new Exception("Lỗi demo", 123);
} catch (Throwable $e) {
    echo $e->getMessage(); // Lỗi demo
    echo $e->getCode();    // 123
    echo $e->getFile();    // Tên file
    echo $e->getLine();    // Số dòng
    print_r($e->getTrace()); // Mảng trace
    echo $e->getTraceAsString(); // Chuỗi trace
}

5. Bảng so sánh Throwable vs Exception vs Error

Tiêu chí Throwable Exception Error
Kiểu Interface Class Class
Giới thiệu từ PHP 7 PHP 5 PHP 7
Có thể throw
Có thể catch
Thuộc business logic Không Không
Thuộc lỗi engine Không
Có nên custom? Không Rất hiếm

7. Catch Exception vs Catch Throwable

Chỉ catch Exception

try {
    riskyFunction();
} catch (Exception $e) {
    echo "Handled business error";
}

Không bắt được Error.


Catch Throwable

try {
    riskyFunction();
} catch (Throwable $e) {
    echo "Caught everything";
}

Bắt được:

  • Exception
  • Error
  • TypeError
  • ParseError

8. Best Practices

1️⃣ Tầng Business → Catch Exception cụ thể

try {
    $service->process();
} catch (ValidationException $e) {
    return response($e->getMessage(), 422);
}

Không nên catch Throwable ở tầng business.


2️⃣ Tầng Entry Point → Catch Throwable

Trong index.php:

set_exception_handler(function (Throwable $e) {
    error_log($e);
    http_response_code(500);
    echo "Internal Server Error";
});

Đây là best practice production.


3️⃣ Không bao giờ swallow error

Sai:

catch (Throwable $e) {
    // ignore
}

Phải log.


9. Một số Error phổ biến

TypeError

function test(int $a) {}
test("abc");

ParseError

try {
    eval("echo 'Hello");
} catch (ParseError $e) {
    echo "Parse error";
}

DivisionByZeroError

intdiv(10, 0);

10. Custom Exception (Best Practice)

Tạo Exception riêng cho domain:

class InsufficientBalanceException extends Exception {}

function withdraw($balance, $amount) {
    if ($amount > $balance) {
        throw new InsufficientBalanceException("Not enough money");
    }
}

Giúp:

  • Code rõ ràng
  • Dễ maintain
  • Dễ phân loại lỗi

11. Thiết kế Exception Hierarchy trong hệ thống lớn

Ví dụ:

AppException
│
├── ValidationException
├── AuthenticationException
└── BusinessRuleException

Lợi ích:

  • Bắt lỗi theo tầng
  • Phân loại rõ ràng
  • Kiến trúc sạch (Clean Architecture)

12. Khi nào nên catch Throwable?

✔ Global error handler
✔ CLI tool
✔ Queue worker
✔ Framework core

❌ Không nên dùng khắp nơi trong service layer.

14. Ví dụ Production REST API

try {
    $order = $service->create($data);
    return json_encode($order);
} catch (ValidationException $e) {
    return response($e->getMessage(), 422);
} catch (Throwable $e) {
    error_log($e);
    return response("Server Error", 500);
}

Tách biệt rõ:

  • 4xx → Business
  • 5xx → System

15. Re-throw Exception (Advanced)

try {
    risky();
} catch (Exception $e) {
    throw new RuntimeException("Wrapped", 0, $e);
}

Giữ được stack trace gốc.


16. Log trace lỗi (Logging Stack Trace)

Khi xử lý lỗi, việc log đầy đủ stack trace giúp dễ dàng debug và truy vết nguồn gốc lỗi. Bạn nên sử dụng getTraceAsString() hoặc log mảng trace với context.

Ví dụ log trace đơn giản:

catch (Throwable $e) {
    error_log($e->getMessage());
    error_log($e->getTraceAsString());
}

Ví dụ log trace với context (dùng Monolog hoặc framework):

catch (Throwable $e) {
    $logger->critical($e->getMessage(), [
        'trace' => $e->getTraceAsString(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'code' => $e->getCode(),
    ]);
}

16. Logging Strategy trong hệ thống lớn

Trong production:

  • Catch cụ thể ở service layer
  • Catch Throwable ở entry layer
  • Log centralized (ELK, Sentry, CloudWatch…)

Ví dụ:

catch (Throwable $e) {
    $logger->critical($e->getMessage(), [
        'trace' => $e->getTraceAsString()
    ]);
}

17. Tổng kết quan trọng

Throwable là interface gốc
Exception dành cho business logic
Error dành cho lỗi lập trình
✔ Catch Exception ở application layer
✔ Catch Throwable ở system boundary
✔ Không bao giờ nuốt lỗi


18. Kết luận

Hiểu rõ Throwable, Exception, và Error giúp bạn:

  • Viết code an toàn hơn
  • Thiết kế kiến trúc tốt hơn
  • Debug dễ hơn
  • Tối ưu production system

Ở level senior, việc phân biệt rõ business error và system error là cực kỳ quan trọng.

Nếu bạn nắm vững phần này, bạn đã nâng trình từ intermediate PHP developer lên professional level.