`trik_error` проти` кинути виняток` в контексті магічних методів PHP


13

Я маю дискусію з колегою щодо правильного використання (якщо воно є) trigger_errorу контексті магічних методів . По-перше, я вважаю, що цього trigger_errorслід уникати, окрім цього одного випадку.

Скажімо, у нас є клас з одним методом foo()

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

Тепер скажімо, що ми хочемо надати той самий інтерфейс, але використовувати магічний метод, щоб спіймати всі виклики методу

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

Обидва класи однакові за тим, як вони реагують, foo()але відрізняються при виклику недійсного методу.

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

Мій аргумент полягає в тому, що магічні методи повинні викликати, trigger_errorколи невідомий метод потрапляє

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;
        }
    }
}

Так що обидва класи поводяться (майже) однаково

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

Мій випадок використання - це реалізація ActiveRecord. Я використовую , __callщоб перехоплювати і обробляти методи , які в основному роблять те ж саме , але мають модифікатори , такі як Distinctабо Ignore, наприклад ,

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

або

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

Такі методи , як where(), from(), groupBy()і т.д. жорстко закодовані.

Мій аргумент підкреслюється, коли ви випадково телефонуєте insret(). Якщо моя активна реалізація запису зашифрувала всі методи, то це буде помилкою.

Як і будь-яка добра абстракція, користувач повинен не знати про деталі реалізації та покладатися виключно на інтерфейс. Чому реалізація, яка використовує магічні методи, повинна вести себе інакше? В обох має бути помилка.

Відповіді:


7

Візьміть дві реалізації одного і того ж інтерфейсу ActiveRecord ( select(), where()і т.д.)

class ActiveRecord1 {
    //Hardcodes all methods
}

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

Якщо ви викликаєте недійсний метод першого класу, наприклад ActiveRecord1::insret(), поведінка PHP за замовчуванням - це викликати помилку . Недійсний виклик функції / методу не є умовою, з якою розумне додаток хотів би захопити та обробити. Звичайно, ви можете зловити його такими мовами, як Ruby або Python, де помилка є винятком, але інші (JavaScript / будь-яка статична мова / більше?) Не зможуть.

Повернутися до PHP - якщо обидва класи реалізують один і той же інтерфейс, чому вони не повинні проявляти однакову поведінку?

Якщо __callабо __callStaticвиявити недійсний метод, він повинен викликати помилку, що імітує поведінку мови за замовчуванням

$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);

Я не сперечаюся, чи слід використовувати помилки над винятками (вони на 100% не повинні), проте я вважаю, що магічні методи PHP є винятком - призначений каламбур :) - з цього правила в контексті мови


1
Вибачте, але я його не купую. Чому ми повинні бути овечками, оскільки винятків у PHP не існувало більше десяти років тому, коли вперше були впроваджені класи на PHP 4.something?
Меттью Шарлі

@Matthew за послідовність. Я не обговорюю винятків проти помилок (немає дебатів) або чи PHP має дизайн не вдається (це робить), я стверджую, що в цій дуже унікальній обставині для послідовності найкраще імітувати поведінку мови
chriso

Послідовність - це не завжди гарна річ. Послідовний, але поганий дизайн все ще поганий дизайн, який болісно використовувати в кінці дня.
Меттью Шарлі

@Matthew правда, але IMO, що викликає помилку при недійсному виклику методу, не є поганим дизайном 1) У багатьох (більшості?) Мов він є вбудованим, і 2) я не можу придумати жодного випадку, коли б вам хотілося щоб зловити недійсний виклик методу та обробити його?
chriso

@chriso: Всесвіт достатньо великий, що є випадки використання, що ні ти, ні я ніколи не мріяли про те, щоб це сталося. Ви використовуєте __call()для того, щоб робити динамічну маршрутизацію, чи справді так нерозумно очікувати, що десь вниз по трасі хтось може захотіти обробити випадок, коли це не вдається? У будь-якому випадку це відбувається по колах, тому це буде мій останній коментар. Зробіть те, що ви хочете, в кінці дня це зводиться до судового заклику: Краща підтримка проти послідовність. Обидва способи призведуть до однакового впливу на додаток у разі відсутності спеціального поводження.
Меттью Шарлі

3

Я збираюся викинути свою висловлену думку там, але якщо ви користуєтесь trigger_errorде-небудь, то ви робите щось не так. Винятки - це шлях.

Переваги винятків:

  • Їх можна зловити. Це величезна перевага, і вона повинна бути єдиною, яка вам потрібна. Люди насправді можуть спробувати щось інше, якщо вони сподіваються, що шанс щось піде не так. Помилка не дає вам такої можливості. Навіть налаштування користувальницького обробника помилок не містить свічки, щоб просто зловити виняток. Що стосується ваших коментарів у вашому запитанні, що таке "розумне" додаток, повністю залежить від контексту заявки. Люди не можуть ловити винятки, якщо вони думають, що це ніколи не відбудеться в їхньому випадку. Не давати людям вибору - це погана річ ™.
  • Складіть сліди. Якщо щось піде не так, ви знаєте, де і в якому контексті сталася проблема. Ви коли-небудь намагалися відстежити, де виникає помилка в деяких основних методах? Якщо ви викликаєте функцію з занадто малою кількістю параметрів, ви отримуєте марну помилку, яка підкреслює початок методу, який ви викликаєте, і повністю виходить звідки він дзвонить.
  • Чіткість. Комбінуйте вищевказані два і ви отримаєте чіткіший код. Якщо ви намагаєтеся використовувати власний обробник помилок для обробки помилок (наприклад, для генерування слідів стека для помилок), усі ваші помилки обробляються в одній функції, а не там, де фактично створюються помилки.

Вирішення ваших проблем, виклик методу, який не існує, може бути дійсною можливістю . Це повністю залежить від контексту коду, який ви пишете, але є деякі випадки, коли це може статися. Звертаючись до вашого точного випадку використання, деякі сервери баз даних можуть дозволити деяку функціональність, ніж інші. Використання try/ catchта виключення в __call()порівнянні з функцією для перевірки можливостей - це зовсім інший аргумент.

Єдиний варіант використання, який я можу придумати для використання, trigger_error- це для E_USER_WARNINGабо менше. Запуск, E_USER_ERRORхоча, на мою думку, завжди є помилкою.


Дякую за відповідь :) - 1) Я погоджуюся, що винятки майже завжди повинні використовуватися над помилками - всі ваші аргументи є достовірними балами. Однак .. Я думаю, що в цьому контексті ваш аргумент провалюється ..
chriso

2
" виклик методу, який не існує, може бути допустимою ", - я повністю не погоджуюся. У будь-якій мові (яку я коли-небудь використовував) виклик функції / методу, який не існує, призведе до помилки. Це не умова, яку слід спіймати і поводити з нею. Статичні мови не дозволяють вам компілювати недійсний виклик методу, а динамічні мови вийдуть з ладу, як тільки вони дойдуть до виклику.
chriso

Якщо ви прочитаєте мою другу редакцію, ви побачите мій регістр використання. Скажімо, у вас є дві реалізації одного класу, одна з використанням __call та одна із методами жорсткого кодування. Ігноруючи деталі реалізації, чому обидва класи повинні поводитися по-різному, коли реалізують один і той же інтерфейс? PHP спровокує помилку, якщо ви будете викликати недійсний метод з класом, який має жорсткі методи. Використання trigger_errorв контексті __call або __callStatic імітує поведінку мови за замовчуванням
chriso

@chriso: Динамічні мови, які не є PHP, не вдасться, за винятком, який можна зловити . Наприклад, Ruby кидає те, NoMethodErrorщо ви можете зловити, якщо цього захочете. По моїй думці помилки - це гігантська помилка PHP. Тільки тому, що ядро ​​використовує зламаний метод повідомлення про помилки, не означає, що ваш власний код повинен.
Меттью Шарлі

@chriso Мені подобається вважати, що єдиною причиною того, що ядро ​​все ще використовує помилки, є зворотна сумісність. Сподіваємось, PHP6 буде ще одним стрибком вперед, як це було PHP5, і помилками падіння взагалі. Зрештою, і помилки, і винятки генерують однаковий результат: негайне припинення виконання коду. За винятком ви можете діагностувати, чому і де так набагато простіше.
Меттью Шарлі

3

Стандартні помилки PHP слід вважати застарілими. PHP забезпечує вбудований клас ErrorException для перетворення помилок, попереджень та повідомлень у винятки з повним належним слідом стека. Ви використовуєте його так:

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

Використовуючи це, це питання стає суперечливим. Вбудовані помилки тепер спричиняють винятки, тому повинен бути і ваш власний код.


1
Якщо ви користуєтесь цим, вам потрібно перевірити тип помилки, щоб не перетворити E_NOTICEs у винятки. Це було б погано.
Меттью Шарлі

1
Ні, перетворення E_NOTICES у Винятки добре! Усі повідомлення слід вважати помилками; це хороша практика, перетворюєте ви їх у винятки чи ні.
Уейн

2
Зазвичай я погоджуюся з вами, але коли ви починаєте використовувати будь-який сторонній код, це швидко схиляється до його обличчя.
Меттью Шарлі

Практично всі бібліотеки сторонніх організацій є E_STRICT | E_ALL безпечний. Якщо я використовую невідповідний код, я ввійду і виправлю його. Я роками працював так без проблем.
Уейн

ви, очевидно, раніше не використовували Dual. Ядро справді добре, як це, але багато, багато модулів сторонніх не є
Matthew Scharley

0

IMO, це цілком коректний випадок використання для 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);

Використовуючи цю стратегію, ви отримуєте $errcontextпараметр, якщо ви виконуєте $exception->getTrace()в рамках функції handleException. Це дуже корисно для певних цілей налагодження.

На жаль, це працює лише в тому випадку, якщо ви використовуєте trigger_errorбезпосередньо зі свого контексту, а це означає, що ви не можете використовувати функцію / метод обгортки для псевдоніму trigger_errorфункції (тому ви не можете зробити щось на зразок, function debug($code, $message) { return trigger_error($message, $code); }якщо ви хочете, щоб дані контексту були у вашому трасі).

Я шукав кращої альтернативи, але поки що не знайшов жодної.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.