`trigger_error` vs` throw Exception` trong bối cảnh các phương thức ma thuật của PHP


13

Tôi đang có một cuộc tranh luận với một đồng nghiệp về cách sử dụng đúng (nếu có) trigger_errortrong bối cảnh của các phương pháp ma thuật . Đầu tiên, tôi nghĩ rằng trigger_errornên tránh ngoại trừ trường hợp này.

Nói rằng chúng tôi có một lớp với một phương thức foo()

class A {
    public function foo() {
        echo 'bar';
    }
}

Bây giờ nói rằng chúng tôi muốn cung cấp cùng một giao diện nhưng sử dụng một phương thức ma thuật để bắt tất cả các cuộc gọi phương thức

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        }
    }
}

$a = new A;
$b = new B;

$a->foo(); //bar
$b->foo(); //bar

Cả hai lớp đều giống nhau theo cách chúng phản hồi foo()nhưng khác nhau khi gọi một phương thức không hợp lệ.

$a->doesntexist(); //Error
$b->doesntexist(); //Does nothing

Lập luận của tôi là các phương thức ma thuật nên gọi trigger_errorkhi một phương thức không xác định bị bắt

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        default:
            $class = get_class($this);
            $trace = debug_backtrace();
            $file = $trace[0]['file'];
            $line = $trace[0]['line'];
            trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
            break;
        }
    }
}

Vì vậy, cả hai lớp cư xử (gần như) giống hệt nhau

$a->badMethod(); //Call to undefined method A::badMethod() in [..] on line 28
$b->badMethod(); //Call to undefined method B::badMethod() in [..] on line 32

Trường hợp sử dụng của tôi là một triển khai ActiveRecord. Tôi sử dụng __callcác phương pháp đánh bắt và xử lý mà chủ yếu làm điều tương tự nhưng có bổ như Distincthay Ignore, ví dụ như

selectDistinct()
selectDistinctColumn($column, ..)
selectAll()
selectOne()
select()

hoặc là

insert()
replace()
insertIgnore()
replaceIgnore()

Các phương pháp như where(), from(), groupBy()vv được mã hóa cứng.

Đối số của tôi được tô sáng khi bạn vô tình gọi insret(). Nếu việc thực hiện bản ghi hoạt động của tôi đã mã hóa tất cả các phương thức thì đó sẽ là một lỗi.

Như với bất kỳ sự trừu tượng hóa tốt nào, người dùng nên không biết về các chi tiết thực hiện và chỉ dựa vào giao diện. Tại sao việc thực hiện sử dụng các phương pháp ma thuật lại hành xử khác đi? Cả hai nên là một lỗi.

Câu trả lời:


7

Mất hai hiện thực của giao diện ActiveRecord cùng ( select(), where(), vv)

class ActiveRecord1 {
    //Hardcodes all methods
}

class ActiveRecord2 {
    //Uses __call to handle some methods, hardcodes the rest
}

Nếu bạn gọi một phương thức không hợp lệ trên lớp đầu tiên, ví dụ ActiveRecord1::insret(), hành vi mặc định của PHP là gây ra lỗi . Một cuộc gọi hàm / phương thức không hợp lệ không phải là điều kiện mà một ứng dụng hợp lý muốn bắt và xử lý. Chắc chắn, bạn có thể bắt gặp nó bằng các ngôn ngữ như Ruby hoặc Python trong đó lỗi là một ngoại lệ, nhưng các ngôn ngữ khác (JavaScript / bất kỳ ngôn ngữ tĩnh nào / nhiều hơn?) Sẽ thất bại.

Quay lại PHP - nếu cả hai lớp thực hiện cùng một giao diện, tại sao chúng không thể hiện cùng một hành vi?

Nếu __callhoặc __callStaticphát hiện một phương thức không hợp lệ, họ sẽ kích hoạt một lỗi để bắt chước hành vi mặc định của ngôn ngữ

$class = get_class($this);
$trace = debug_backtrace();
$file = $trace[0]['file'];
$line = $trace[0]['line'];
trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);

Tôi không tranh luận liệu có nên sử dụng lỗi trong các trường hợp ngoại lệ hay không (tuy nhiên chúng không nên 100%), tuy nhiên tôi tin rằng các phương thức ma thuật của PHP là một ngoại lệ - ý định chơi chữ :) - theo quy tắc này trong ngữ cảnh của ngôn ngữ


1
Xin lỗi, nhưng tôi không mua nó. Tại sao chúng ta phải là cừu vì các trường hợp ngoại lệ không tồn tại trong PHP hơn một thập kỷ trước khi các lớp được triển khai lần đầu tiên trong PHP 4.something?
Matthew Scharley

@Matthew cho sự nhất quán. Tôi không tranh luận về các trường hợp ngoại lệ và lỗi (không có tranh luận) hoặc liệu PHP có thiết kế thất bại hay không, tôi cho rằng trong trường hợp rất độc đáo này, để thống nhất , tốt nhất là bắt chước hành vi của ngôn ngữ
chriso

Sự nhất quán không phải lúc nào cũng là một điều tốt. Một thiết kế phù hợp nhưng kém vẫn là một thiết kế kém đó là một nỗi đau để sử dụng vào cuối ngày.
Matthew Scharley

@Matthew đúng, nhưng IMO gây ra lỗi trong một cuộc gọi phương thức không hợp lệ không phải là thiết kế kém 1) nhiều ngôn ngữ (hầu hết?) Được tích hợp sẵn và 2) Tôi không thể nghĩ về một trường hợp duy nhất mà bạn muốn để bắt một cuộc gọi phương thức không hợp lệ và xử lý nó?
chriso

@chriso: Vũ trụ đủ lớn đến mức có những trường hợp sử dụng mà cả bạn và tôi đều không mơ ước xảy ra. Bạn đang sử dụng __call()để thực hiện định tuyến động, có thực sự quá vô lý khi hy vọng rằng ở đâu đó theo dõi ai đó có thể muốn xử lý trường hợp không thành công? Ở bất cứ giá nào, điều này sẽ diễn ra trong vòng tròn, vì vậy đây sẽ là bình luận cuối cùng của tôi. Làm những gì bạn sẽ, vào cuối ngày, điều này dẫn đến một lời kêu gọi phán xét: Hỗ trợ tốt hơn so với tính nhất quán. Cả hai phương pháp sẽ dẫn đến cùng một hiệu ứng trên ứng dụng trong trường hợp không xử lý đặc biệt.
Matthew Scharley

3

Tôi sẽ ném ý kiến ​​của tôi ra khỏi đó, nhưng nếu bạn sử dụng trigger_errorbất cứ nơi nào, thì bạn đang làm gì đó sai. Ngoại lệ là con đường để đi.

Ưu điểm của ngoại lệ:

  • Họ có thể bị bắt. Đây là một lợi thế rất lớn, và nên là người duy nhất bạn cần. Mọi người thực sự có thể thử một cái gì đó khác nhau nếu họ mong muốn có một cái gì đó có thể sai. Lỗi không cho bạn cơ hội này. Ngay cả việc thiết lập một trình xử lý lỗi tùy chỉnh cũng không giữ một ngọn nến để chỉ bắt một ngoại lệ. Về ý kiến ​​của bạn trong câu hỏi của bạn, ứng dụng 'hợp lý' phụ thuộc hoàn toàn vào bối cảnh của ứng dụng. Mọi người không thể bắt ngoại lệ nếu họ nghĩ rằng điều đó sẽ không bao giờ xảy ra trong trường hợp của họ. Không cho mọi người lựa chọn là một điều xấu.
  • Dấu vết chồng chất. Nếu có sự cố xảy ra , bạn biết vấn đề xảy ra ở đâu và trong bối cảnh nào . Bạn đã bao giờ cố gắng theo dõi nơi một lỗi đến từ một số phương pháp cốt lõi chưa? Nếu bạn gọi một hàm có quá ít tham số, bạn sẽ gặp một lỗi vô dụng, điều này làm nổi bật sự bắt đầu của phương thức bạn đang gọi và hoàn toàn rời khỏi nơi nó đang gọi.
  • Trong trẻo. Kết hợp hai điều trên và bạn sẽ có được mã rõ ràng hơn. Nếu bạn cố gắng sử dụng trình xử lý lỗi tùy chỉnh để xử lý lỗi (ví dụ: để tạo ra dấu vết ngăn xếp cho lỗi), tất cả việc xử lý lỗi của bạn chỉ nằm trong một hàm, không phải là lỗi thực sự được tạo ra.

Giải quyết các mối quan tâm của bạn, gọi một phương pháp không tồn tại có thể là một khả năng hợp lệ . Điều này phụ thuộc hoàn toàn vào ngữ cảnh của mã bạn đang viết, nhưng có một số trường hợp điều này có thể xảy ra. Giải quyết trường hợp sử dụng chính xác của bạn, một số máy chủ cơ sở dữ liệu có thể cho phép một số chức năng mà những người khác không có. Sử dụng try/ catchvà ngoại lệ trong __call()so với một chức năng để kiểm tra khả năng là một đối số hoàn toàn khác nhau.

Trường hợp sử dụng duy nhất tôi có thể nghĩ đến để sử dụng trigger_errorlà cho E_USER_WARNINGhoặc thấp hơn. Kích hoạt một E_USER_ERRORmặc dù luôn luôn là một lỗi trong quan điểm của tôi.


Cảm ơn bạn đã phản hồi :) - 1) Tôi đồng ý rằng các trường hợp ngoại lệ hầu như luôn luôn được sử dụng cho các lỗi - tất cả các đối số của bạn là điểm hợp lệ. Tuy nhiên .. tôi nghĩ rằng trong bối cảnh này, cuộc tranh luận của bạn thất bại ..
chriso

2
" Gọi một phương thức không tồn tại có thể là một khả năng hợp lệ " - Tôi hoàn toàn không đồng ý. Trong mọi ngôn ngữ (mà tôi đã từng sử dụng), việc gọi một hàm / phương thức không tồn tại sẽ dẫn đến lỗi. Nó không phải là một điều kiện nên được bắt và xử lý. Các ngôn ngữ tĩnh sẽ không cho phép bạn biên dịch với một cuộc gọi phương thức không hợp lệ và các ngôn ngữ động sẽ thất bại khi chúng thực hiện cuộc gọi.
chriso

Nếu bạn đọc bản chỉnh sửa thứ hai của tôi, bạn sẽ thấy trường hợp sử dụng của tôi. Giả sử bạn có hai triển khai của cùng một lớp, một sử dụng __call và một với các phương thức được mã hóa cứng. Bỏ qua các chi tiết triển khai, tại sao cả hai lớp nên hành xử khác nhau khi chúng thực hiện cùng một giao diện? PHP sẽ gây ra lỗi nếu bạn gọi một phương thức không hợp lệ với lớp có các phương thức được mã hóa cứng. Sử dụng trigger_errortrong ngữ cảnh của __call hoặc __callStatic bắt chước hành vi mặc định của ngôn ngữ
chriso

@chriso: Các ngôn ngữ động không phải là PHP sẽ thất bại với một ngoại lệ có thể bị bắt . Ruby chẳng hạn ném một NoMethodErrorcái mà bạn có thể bắt nếu bạn mong muốn. Theo quan điểm cá nhân của tôi, lỗi là một lỗi rất lớn. Chỉ vì lõi sử dụng một phương pháp bị hỏng để báo cáo lỗi không có nghĩa là mã của riêng bạn nên.
Matthew Scharley

@chriso Tôi muốn tin rằng lý do duy nhất mà lõi vẫn sử dụng lỗi là để tương thích ngược. Hy vọng rằng PHP6 sẽ là một bước nhảy vọt khác giống như PHP5 và bỏ hoàn toàn lỗi. Vào cuối ngày, cả lỗi và ngoại lệ đều tạo ra cùng một kết quả: chấm dứt ngay lập tức mã thực thi. Với một ngoại lệ, bạn có thể chẩn đoán tại sao và ở đâu dễ dàng hơn nhiều.
Matthew Scharley

3

Lỗi PHP tiêu chuẩn nên được coi là lỗi thời. PHP cung cấp một ErrorException lớp tích hợp để chuyển đổi các lỗi, cảnh báo và thông báo thành các ngoại lệ với một dấu vết ngăn xếp thích hợp đầy đủ. Bạn sử dụng nó như thế này:

function errorToExceptionHandler($errNo, $errStr, $errFile, $errLine, $errContext)
{
if (error_reporting() == 0) return;
throw new ErrorException($errStr, 0, $errNo, $errFile, $errLine);
}
set_error_handler('errorToExceptionHandler');

Sử dụng điều đó, câu hỏi này trở thành tranh luận. Lỗi tích hợp bây giờ cũng đưa ra các ngoại lệ và do đó, mã của riêng bạn cũng vậy.


1
Nếu bạn sử dụng điều này, bạn cần kiểm tra loại lỗi, kẻo bạn biến E_NOTICEs thành ngoại lệ. Điều đó sẽ rất tệ.
Matthew Scharley

1
Không, biến E_NOTICE thành Ngoại lệ là tốt! Tất cả các thông báo nên được coi là lỗi; đó là cách thực hành tốt cho dù bạn có chuyển đổi chúng thành ngoại lệ hay không.
Wayne

2
Thông thường tôi đồng ý với bạn, nhưng lần thứ hai bạn bắt đầu sử dụng bất kỳ mã bên thứ ba nào, điều này nhanh chóng có xu hướng rơi vào mặt của nó.
Matthew Scharley

Hầu như tất cả các thư viện của bên thứ 3 là E_STRICT | E_ALL an toàn. Nếu tôi đang sử dụng mã không, tôi sẽ đi vào và sửa nó. Tôi đã làm việc theo cách này trong nhiều năm mà không có vấn đề.
Wayne

rõ ràng bạn chưa từng sử dụng Dual trước đây. Cốt lõi thực sự tốt như thế này nhưng nhiều mô-đun của bên thứ ba thì không
Matthew Scharley

0

IMO, đây là trường hợp sử dụng hoàn toàn hợp lệ cho trigger_error:

function handleError($errno, $errstring, $errfile, $errline, $errcontext) {
    if (error_reporting() & $errno) {
        // only process when included in error_reporting
        return handleException(new \Exception($errstring, $errno));
    }
    return true;
}

function handleException($exception){
    // Here, you do whatever you want with the generated
    // exceptions. You can store them in a file or database,
    // output them in a debug section of your page or do
    // pretty much anything else with it, as if it's a
    // normal variable

    switch ($code) {
        case E_ERROR:
        case E_CORE_ERROR:
        case E_USER_ERROR:
            // Make sure script exits here
            exit(1);
        default:
            // Let script continue
            return true;
    }
}

// Set error handler to your custom handler
set_error_handler('handleError');
// Set exception handler to your custom handler
set_exception_handler('handleException');


// ---------------------------------- //

// Generate warning
trigger_error('This went wrong, but we can continue', E_USER_WARNING);

// Generate fatal error :
trigger_error('This went horrible wrong', E_USER_ERROR);

Sử dụng chiến lược này, bạn sẽ có được $errcontexttham số nếu bạn thực hiện $exception->getTrace()trong hàm handleException. Điều này rất hữu ích cho các mục đích gỡ lỗi nhất định.

Thật không may, điều này chỉ hoạt động nếu bạn sử dụng trigger_errortrực tiếp từ ngữ cảnh của mình, điều đó có nghĩa là bạn không thể sử dụng chức năng / phương thức trình bao bọc để đặt bí danh cho trigger_errorhàm (vì vậy bạn không thể làm gì đó function debug($code, $message) { return trigger_error($message, $code); }nếu bạn muốn dữ liệu ngữ cảnh trong dấu vết của mình).

Tôi đã tìm kiếm một sự thay thế tốt hơn, nhưng cho đến nay tôi vẫn chưa tìm thấy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.