Error và Exception trong lập trình PHP

    Tổng quan về các lỗi trong PHP

    Trong quá trình ứng dụng PHP (Website PHP) hoạt động, có thể có lỗi xảy ra, có những lỗi cảnh báo nhưng Script PHP vẫn chạy, có những lỗi làm cho chương trình kết thúc ngay lập tức, có những lỗi do bản thân hệ thống, có những lỗi do logic chương trình có vấn đề. Trong PHP các lỗi được phân ra thành các nhóm (loại): Mỗi nhóm biểu diễn bởi một giá trị và được định nghĩa bởi một hằng số trong PHP

    Bảng danh sách phân loại các lỗi, cảnh báo trong PHP
    Giá trị Tên hằng số lỗi Mô tả
    1 E_ERROR Lỗi nghiêm trọng (Fatal Error) script bị kết thúc, lỗi xảy ra như tràn bộ nhớ … Ví dụ, bạn gọi một hàm không tồn tại.

    hamnaykhongtontai();
    2 E_WARNING Cảnh báo khi thực thi. Lỗi này không dẫn đến script bị dừng. Ví dụ, nạp một mã nguồn php bằng include, trong khi file đó không tồn tại:

    include ("file-ma-nguon-nay-khong-co.php");
    4 E_PARSE Lỗi phát sinh do trình parser (phân tích cú pháp) – nó cũng kết thúc script, ví dụ bạn viết echo xuất ra một chuỗi, mà thiếu dấu ; ở cuối.

    echo 'XuanthuLab'
    8 E_NOTICE Cánh báo cho biết có thể có lỗi, vì PHP không chắc chắn đó là lỗi hay không.
    16 E_CORE_ERROR Giống E_ERROR do nhân PHP phát sinh
    32 E_CORE_WARNING Giống E_WARNING do nhân PHP phát sinh.
    64 E_COMPILE_ERROR Giống E_ERROR phát sinh bởi Zend Scripting Engine.
    128 E_COMPILE_WARNING Giống E_WARNING, phát sinh bởi Zend Scripting Engine.
    256 E_USER_ERROR Giống E_ERROR, do code của bạn tự phát sinh bằng cách gọi hàm trigger_error()
    512 E_USER_WARNING Giống E_WARNING, do code của bạn tự phát sinh bằng cách gọi hàm trigger_error()
    1024 E_USER_NOTICE Giống E_NOTICE, do code của bạn tự phát sinh bằng cách gọi hàm trigger_error()
    2048 E_STRICT Gợi ý của PHP để code của bạn tương thích được cho các bản tiếp theo.
    4096 E_RECOVERABLE_ERROR Có thể là lỗi nghiêm trọng, nếu không bắt lại bởi error_hander thì script dừng với lỗi E_ERROR.
    8192 E_DEPRECATED Thông báo – code của bạn có thể không hoạt động cho phiên bản tiếp theo của PHP
    16384 E_USER_DEPRECATED Giống E_DEPRECATED
    32767 E_ALL Tất cả lỗi

    Thiết lập các thông báo lỗi error_reporting() trong PHP

    Khi có một lỗi xảy ra, ứng dụng PHP của bạn có thể nhận được thông báo lỗi – hoặc không nhận được tùy theo cấu hình PHP. Nếu bạn thiết lập nhận thông báo lỗi, nếu lỗi xảy ra – chương trình bị dừng bạn sẽ đọc được thông tin về lỗi, như tên lỗi, file / dòng code xảy ra lỗi … . Nếu bạn không thiết lập nhận thông báo lỗi, thì chương trình bị dừng mà bạn không đọc được.

    Cơ bản, trong môi trường phát triển, bạn nên thiết lập nhận tất cả các thông báo lỗi, kể các các lỗi không nghiêm trọng, các cảnh báo E_NOTICE. Còn trong môi trường Product (website đang chạy phục vụ khách hàng) thì bạn nên tắt đi việc nhận các lỗi này để đảm bảo an toàn, dấu vết về lỗi không hiện thị cho khách truy cập mà sẽ lưu ở log.

    Để thiết lập các lỗi nhận được, có 2 cách: Thiết lập trong file cấu hình của PHP là php.ini hoặc trực tiếp trong code của bạn với hàm error_reporting().

    Thiết lập nhận thông báo lỗi trong php.ini

    ; Bật hiện thị các thông báo lỗi
    display_errors = On
    ; Thiết lập các lỗi hiện thị - tất cả các lỗi
    ; Môi trường phát triển nên thiết lập
    error_reporting = E_ALL
    
    ; Thiết lập chỉ hiện thị lỗi E_DEPRECATED và E_STRICT
    ; Môi trường Product nên thiết lập
    error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
    

    Chú ý: cần khởi động lại Web Server, PHP FPM sau khi sửa đổi php.ini

    Thiết lập nhận thông báo lỗi với hàm error_reporting()

    Bạn có thể dùng hàm error_reporting() để thiết lập các loại lỗi có thể hiện thị

    // Hiện thị tất cả các lỗi
    error_reporting(E_ALL);
    // Hoặc E_DEPRECATED và E_STRICT
    error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT)
    

    Cũng có thể dùng hàm ini_set để thiết lập chỉ thị error_reporting

    // Hiện thị E_DEPRECATED và E_STRICT
    ini_set('error_reporting', E_ALL & ~E_DEPRECATED & ~E_STRICT);
    

    Hàm trigger_error trong PHP, phát sinh thông báo lỗi

    Trong code của bạn, tùy logic của ứng dụng, có những thời điểm có thể bạn cần phát đi các lỗi do bạn tự định nghĩa. Lúc đó bạn sử dụng tới phương thức trigger_error

    trigger_error($mgs, $error_level);
    • $mgs dòng thông báo
    • $error_level cấp độ lỗi, là một trong các giá trị E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE. Không thiết lập mặc định là E_USER_NOTICE

    Ví dụ:

    <?php
        $a = rand(0, 2);
        //...
        if ($a == 0)
        {
            // Phát sinh lỗi
            trigger_error('Không thể chia cho 0', E_USER_WARNING);
        }
    ?>

    Hàm set_error_handler trong PHP, đăng ký error_handler

    Bạn có thể đăng ký một hàm callback trở thành error_handler của PHP, tức hàm đó được thực thi khi có lỗi xảy ra. Tuy nhiên các lỗi sau nó không bắt được E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING. Vậy sẽ bắt được các lỗi như: E_USER_*, E_WARNING, E_NOTICE 

    Đầu tiên bạn cần xây dựng một hàm callback có các tham số:

    // $errno : mã lỗi nhận được
    // $errstr: chuỗi thông báo lỗi
    // $errfile: file xảy ra lỗi
    // $errline: dòng có lỗi
    // $errcontext: mảng các biến tại thời điểm lỗi
    handler($errno, $errstr, $errfile = null, $errline = null, $errcontext = null) : bool
    

    Ví dụ xây dựng một hàm callback như sau:

    function my_error_handler($errno, $errstr, $errfile = null, $errline = null, $errcontext = null)
    {
        echo "Có lỗi xảy ra, mã lỗi: $errno ";
        echo "Thông báo: $$errstr ";
        // Trả về true handler mặc định không được thi hành
        return true;
    }
    

    Để thiết lập nó là error handler, gọi hàm sau để đăng ký:

    set_error_handler('my_error_handler');

    Bạn cũng có thể dùng hàm vô danh làm handler, ví dụ:

    set_error_handler(function ($errno, $errstr) {
        echo "Có lỗi xảy ra, mã lỗi: $errno ";
        echo "Thông báo: $$errstr ";
        return true;
    });
    

    Sau khi đăng ký hàm error-handler mới, nếu muốn quay về trạng thái trước thì dùng hàm restore_error_handler(), hoặc set_error_handler(null) để hủy không sử dụng error handler.

    Ngoại lệ Exception trong PHP

    throw và lớp Exception trong PHP

    Ngoại lệ là đối tượng chứa thông tin lỗi được phát sinh, bạn có thể tạo ra các ngoại lệ bằng cách tạo đối tượng mới từ bất kỳ lớp nào triển khai từ giao diện Throwable của PHP. Như các lớp Exception, Error, AssertionError, TypeError, ErrorException 

    Khi có một ngoại lệ, giả sử là đối tượng $exception, thì trong code bạn có thể phát – lan truyền ngoại lệ đó bằng từ khóa throw.

    throw $exception;

    Khi code thi hành gặp lệnh throw thì khối code đó sẽ dừng tại throw

    Ví dụ:

    // Hàm chi lấy dữ của hai số nguyên
    // Nếu tham số $a, $b không phải số nguyên thì phát sinh ngoại lệ
    function chia_lay_du($a, $b)
    {
        if (!is_int($a) || !is_int($b))
        {
            $exception = new Exception("Không phải là số nguyên");
            // gặp throw khối code sẽ kết thúc - hàm chi_lay_du kết thúc tại đây
            throw $exception;
        }
    
        return ($a % $b);
    }
    

    Giờ bạn sử dụng hàm, trường hợp này sẽ phát sinh ngoại lệ:

    $a = 20;
    $b = 3.5;
    
    // Điểm có thể phát sinh ngoại lệ
    $kq = chia_lay_du($a, $b);
    
    echo "$a chia $b dữ $kq";
    

    Khi gọi chia_lay_du() mà nó có quăng (phát ra) ngoại lệ, nếu tại đó bạn không bắt ngoại lệ lại để xử lý thì mặc định sẽ phát sinh lỗi Fatal error, và toàn bộ chương trình kết thúc tại điểm phát sinh ngoại lệ.

    Sử dụng try … catch … để bắt ngoại lệ

    Hiển nhiên, bạn muốn bắt lại ngoại lệ để điều hướng chương trình theo phương án tốt nhất. Lúc đó bạn sẽ sử dụng cú pháp try … catch …

    Cú pháp cơ bản như sau:

    try
    {
        //Khối lệnh mà có thể phát sinh Exception
    } 
    catch (Throwable|Exception $e)
    {    
        // Khối lệnh catch thực thi khi có ngoại lệ phát ra ở try
        // $e là đối tượng ngoại lệ bắt được
    }
    

    Ví dụ:

    $a = 20;
    $b = 3.5;
    
    try {
        // Điểm có thể phát sinh ngoại lệ
        $kq = chia_lay_du($a, $b);
        echo "$a chia $b dữ $kq";
    }
    catch (Throwable $e)
    {
        echo "Dữ liệu bạn nhập không chính xác, hãy nhập lại";
        // ...
    }
    // In ra:
    // Dữ liệu bạn nhập không chính xác, hãy nhập lại
    

    Như vậy, khi có ngoại lệ, bạn đã điều hướng được chương trình bằng cách xử lý ngoại lệ đó, chứ không bị kết thúc đột ngột tại nơi phát sinh ngoại lệ.

    Nhiều khối catch

    Bạn có thể sử dụng nhiều khối catch liên tiếp nhau để bắt các ngoại lệ phù hợp:

    try {
        ...
    }
    catch (Exception $e) {
        // thực thi khi ngoại lệ là đối tượng lớp Exception
        ....
    } 
    catch (ErrorException $e) {
        // thực thi khi ngoại lệ là đối tượng lớp ErrorException
    }
    

    Thêm vào finally

    Trong cú pháp try ... catch ... bạn cũng có thể tạo ra một khối ở cuối cùng có tên là finally, các code trong khối này luôn được thi hành dù có phát sinh ngoại lệ hay không.

    try {
        //...
    } catch (Exception $e) {
        //...
    } finally {
        //...Code luôn thi hành
    }
    

    Tạo ngoại lệ riêng – tùy biến trong PHP

    Nếu lớp Exception mặc định như Exception, ErrorException chưa đủ dùng cho bạn, bạn có thể tạo ra các Exeption riêng bằng cách kế thừa lớp Exception

    class MyExption extends Exception {
        //Định nghĩa các hàm riêng của bạn, ví dụ:
        public function errorMessage() {
            $errorMsg = 'Lỗi tại dòng '.
            $this->getLine().' trong file '.$this->getFile();
            return $errorMsg;
        }
    }
    
    //Lúc này MyExption có thể được tạo bằng
    //$e = new MyExption("Thông báo lỗi");
    
    
    // Try có thể sử dụng
    try {
        //...
    }
    catch (MyExption $e) {
        //...
    }
    catch (Exception $e) {
        //...
    }
    

    Thiết lập exception_handler bắt ngoại lệ bị bỏ qua bởi try … catch…

    Khi ứng dụng phát sinh ngoại lệ bằng lệnh throw, nếu ngoại lệ không bị bắt lại bằng khối lệnh try ... catch ... thì ngoại lệ đó sẽ chuyển cho hàm xử lý ngoại lệ mặc định của PHP. Giờ nếu bạn muốn tạo ra hàm mặc định này thì làm như sau: Tạo hàm riêng xử lý ngoại lệ có dạng function my_exception_handler(Throwable $exception), sau đó dùng hàm set_exception_handler(‘my_exception_handler’) để đăng ký với PHP. Ví dụ:

    function exception_handler(Throwable $exception) {
      echo "Bắt được lỗi: " , $exception->getMessage(), "\n"; 
    } 
     
    set_exception_handler('exception_handler'); 
    
    
    
    // Thử phát sinh ngọa lệ
    throw new Exception('Một ngoại lệ phát sinh');