PHP: винятки проти помилок?


116

Можливо, я його десь пропускаю в керівництві PHP, але в чому саме різниця між помилкою та винятком? Єдина відмінність, яку я бачу, полягає в тому, що помилки та винятки обробляються по-різному. Але що викликає виняток і що викликає помилку?

Відповіді:


87

Винятки кидаються - вони призначені для спіймання. Помилки, як правило, не можна виправити. Скажімо, наприклад, у вас є код коду, який вставить рядок у базу даних. Цілком можливо, що цей виклик не вдасться (дублікат ідентифікатора) - вам потрібно буде "Помилка", яка в даному випадку є "Винятком". Коли ви вставляєте ці рядки, ви можете зробити щось подібне

try {
  $row->insert();
  $inserted = true;
} catch (Exception $e) {
  echo "There was an error inserting the row - ".$e->getMessage();
  $inserted = false;
}

echo "Some more stuff";

Виконання програми буде продовжено, тому що ви "застали" виняток. Виняток буде трактуватися як помилка, якщо його не знайдено. Це дозволить продовжувати виконання програми і після її відмови.


29
Errors are generally unrecoverable<- насправді це насправді не так. E_ERRORі E_PARSEє двома найбільш поширеними невиправні помилки (є кілька інших) , але переважна більшість помилок , які ви побачите в розробника видобувні ( E_NOTICE, і E_WARNINGін). На жаль, поводження з помилками PHP - це повний безлад - всілякі речі викликають помилки без потреби (наприклад, переважна більшість функцій файлової системи). Загалом винятки становлять "шлях OOP", але, на жаль, деякі з рідних API API PHP використовують помилки замість винятків :-(
DaveRandom

1
@DaveRandom E_NOTICE, E_WARNING не є "помилками" за визначенням, чи не так? Я завжди вважав їх "повідомленнями" PHP-дисплеїв, щоб повідомити програмісту, що щось не так з написаним кодом.
slhsen

2
@slhsen проблема насправді є хитрою термінологією, всі форми цих повідомлень проходять через "систему обробки помилок" в PHP, семантично всі ці події є "помилками", хоча семантично зауваження / попередження, безумовно, не те саме, що " помилка "в цьому контексті. На щастя, майбутній PHP7 принаймні проклав шлях до впорядкування цього безладу за допомогою перетворення більшості цих речей у виняткові винятки (за допомогою нового Throwableінтерфейсу), даючи набагато більш виразний та абсолютний спосіб відрізнити та правильно подати обидва реальних проблеми та дорадчі повідомлення
DaveRandom

"Виконання програми буде продовжено", я думаю, змінився? Оскільки PHP каже: "Коли буде викинуто виняток, код після заяви не буде виконаний" ( php.net/manual/en/language.exceptions.php )
Роберт Сінклер

1
Я думаю, що ОП означало більше про різницю між нащадками ErrorВ. С. нащадками Exception.
XedinНевідомо

55

Зазвичай я виконую set_error_handlerфункцію, яка помиляється і викидає виняток, так що що б не трапилося, я маю просто винятки, з якими потрібно боротися. Не більше @file_get_contentsпросто приємного та акуратного спробу / лову.

У ситуаціях налагодження у мене також є обробник винятків, який видає сторінку, схожу на asp.net. Я публікую це в дорозі, але якщо буде запит, я опублікую приклад джерела пізніше.

редагувати:

Крім того, як було обіцяно, я вирізав і вставив частину свого коду разом, щоб зробити зразок. Я зберег нижче, щоб подати файли на свою робочу станцію, ви НІКОЛИ НЕ БУДУТТЕ бачити результати тут (бо посилання порушено).

<?php

define( 'DEBUG', true );

class ErrorOrWarningException extends Exception
{
    protected $_Context = null;
    public function getContext()
    {
        return $this->_Context;
    }
    public function setContext( $value )
    {
        $this->_Context = $value;
    }

    public function __construct( $code, $message, $file, $line, $context )
    {
        parent::__construct( $message, $code );

        $this->file = $file;
        $this->line = $line;
        $this->setContext( $context );
    }
}

/**
 * Inspire to write perfect code. everything is an exception, even minor warnings.
 **/
function error_to_exception( $code, $message, $file, $line, $context )
{
    throw new ErrorOrWarningException( $code, $message, $file, $line, $context );
}
set_error_handler( 'error_to_exception' );

function global_exception_handler( $ex )
{
    ob_start();
    dump_exception( $ex );
    $dump = ob_get_clean();
    // send email of dump to administrator?...

    // if we are in debug mode we are allowed to dump exceptions to the browser.
    if ( defined( 'DEBUG' ) && DEBUG == true )
    {
        echo $dump;
    }
    else // if we are in production we give our visitor a nice message without all the details.
    {
        echo file_get_contents( 'static/errors/fatalexception.html' );
    }
    exit;
}

function dump_exception( Exception $ex )
{
    $file = $ex->getFile();
    $line = $ex->getLine();

    if ( file_exists( $file ) )
    {
        $lines = file( $file );
    }

?><html>
    <head>
        <title><?= $ex->getMessage(); ?></title>
        <style type="text/css">
            body {
                width : 800px;
                margin : auto;
            }

            ul.code {
                border : inset 1px;
            }
            ul.code li {
                white-space: pre ;
                list-style-type : none;
                font-family : monospace;
            }
            ul.code li.line {
                color : red;
            }

            table.trace {
                width : 100%;
                border-collapse : collapse;
                border : solid 1px black;
            }
            table.thead tr {
                background : rgb(240,240,240);
            }
            table.trace tr.odd {
                background : white;
            }
            table.trace tr.even {
                background : rgb(250,250,250);
            }
            table.trace td {
                padding : 2px 4px 2px 4px;
            }
        </style>
    </head>
    <body>
        <h1>Uncaught <?= get_class( $ex ); ?></h1>
        <h2><?= $ex->getMessage(); ?></h2>
        <p>
            An uncaught <?= get_class( $ex ); ?> was thrown on line <?= $line; ?> of file <?= basename( $file ); ?> that prevented further execution of this request.
        </p>
        <h2>Where it happened:</h2>
        <? if ( isset($lines) ) : ?>
        <code><?= $file; ?></code>
        <ul class="code">
            <? for( $i = $line - 3; $i < $line + 3; $i ++ ) : ?>
                <? if ( $i > 0 && $i < count( $lines ) ) : ?>
                    <? if ( $i == $line-1 ) : ?>
                        <li class="line"><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? else : ?>
                        <li><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? endif; ?>
                <? endif; ?>
            <? endfor; ?>
        </ul>
        <? endif; ?>

        <? if ( is_array( $ex->getTrace() ) ) : ?>
        <h2>Stack trace:</h2>
            <table class="trace">
                <thead>
                    <tr>
                        <td>File</td>
                        <td>Line</td>
                        <td>Class</td>
                        <td>Function</td>
                        <td>Arguments</td>
                    </tr>
                </thead>
                <tbody>
                <? foreach ( $ex->getTrace() as $i => $trace ) : ?>
                    <tr class="<?= $i % 2 == 0 ? 'even' : 'odd'; ?>">
                        <td><?= isset($trace[ 'file' ]) ? basename($trace[ 'file' ]) : ''; ?></td>
                        <td><?= isset($trace[ 'line' ]) ? $trace[ 'line' ] : ''; ?></td>
                        <td><?= isset($trace[ 'class' ]) ? $trace[ 'class' ] : ''; ?></td>
                        <td><?= isset($trace[ 'function' ]) ? $trace[ 'function' ] : ''; ?></td>
                        <td>
                            <? if( isset($trace[ 'args' ]) ) : ?>
                                <? foreach ( $trace[ 'args' ] as $i => $arg ) : ?>
                                    <span title="<?= var_export( $arg, true ); ?>"><?= gettype( $arg ); ?></span>
                                    <?= $i < count( $trace['args'] ) -1 ? ',' : ''; ?> 
                                <? endforeach; ?>
                            <? else : ?>
                            NULL
                            <? endif; ?>
                        </td>
                    </tr>
                <? endforeach;?>
                </tbody>
            </table>
        <? else : ?>
            <pre><?= $ex->getTraceAsString(); ?></pre>
        <? endif; ?>
    </body>
</html><? // back in php
}
set_exception_handler( 'global_exception_handler' );

class X
{
    function __construct()
    {
        trigger_error( 'Whoops!', E_USER_NOTICE );      
    }
}

$x = new X();

throw new Exception( 'Execution will never get here' );

?>

Це було б корисно. Все, що полегшить час, коли я змушений мати справу з PHP, допоможе. :-)
Джейсон Бейкер

Гарний код, дякую. Я не розумію, звідки походить клас X, і яке його призначення?
Алек

все нижче "set_exception_handler ('global_exception_handler');" це просто демонстрація, вона вам не знадобиться, це просто показати, що відбудеться в звичайній не виключенні помилки.
Кріс

Стандартний PHP визначає помилку ErrorException, яку потрібно викинути з загального обробника помилок. Ви дозволите мені редагувати та оновлювати вашу публікацію?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan: впевнений, але робочий приклад не синхронізується. Крім того , в даний час я , ймовірно , вказують людям github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php з private-void.com замість.
Кріс

21

Відповідь заслуговує на розмову про слона в кімнаті

Помилки - це старий спосіб поводження з помилками під час виконання. Зазвичай код здійснює дзвінок на щось подібне set_error_handlerдо виконання якогось коду. Слідуючи традиції складання мови переривається. Ось як виглядатиме якийсь базовий код.

on error :divide_error

print 1/0
print "this won't print"

:divide_error

if errcode = X
   print "divide by zero error"

Важко було переконатися, що set_error_handlerвін буде називатися правильним значенням. А ще гірше, можна зателефонувати до окремої процедури, яка змінила б обробку помилок. Плюс багато разів виклики перемежовувалися з set_error_handlerдзвінками та обробниками. Коду було легко швидко вийти з-під контролю. Обробка винятків прийшла на допомогу шляхом формалізації синтаксису та семантики того, що добре робить код.

try {
   print 1/0;
   print "this won't print";
} catch (DivideByZeroException $e) {
   print "divide by zero error";
}

Немає окремої функції або ризику викликати неправильний обробник помилок. Код тепер гарантовано знаходиться там же. Крім того, ми отримуємо кращі повідомлення про помилки.

PHP використовував лише обробку помилок, коли багато інших мов уже перетворилися на переважну модель обробки винятків. Врешті-решт, виробники PHP впровадили обробку виключень. Але, ймовірно, підтримують старий код, вони продовжували обробляти помилки і надавали спосіб зробити обробку помилок схожим на обробку винятків. Крім того, немає гарантії, що деякий код не може скинути оброблювач помилок, що саме було призначено для обробки винятків.

Остаточна відповідь

Помилки, кодовані до того, як було здійснено обробку винятків, ймовірно, все ж є помилками. Нові помилки - ймовірні винятки. Але немає жодної конструкції чи логіки, в якій є помилки та які є винятки. Він просто базується на тому, що було доступно на той час, коли воно було закодовано, та перевазі програміста, що кодує це.


3
Це справжня причина, чому винятки та помилки співіснують. Якщо розроблено з нуля, php повинен містити лише те чи інше.
Томаш Зубірі

1
На мою думку, це найкраща відповідь, оскільки вона є найбільш детальною та пояснювальною.
Роберт Кузнір

8

Тут слід додати одне про обробку винятків та помилок. З метою розробника програми, і помилки, і винятки - це "погані речі", які ви хочете записати, щоб дізнатися про проблеми, які має ваша програма - щоб ваші клієнти мали кращий досвід у довгостроковій перспективі.

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


Дякуємо, що надали посилання!
Майк Мур

@ Алекс Вайнштейн: посилання розірвано
Марко Демайо

7

Як зазначено в інших відповідях, встановлення обробника помилок на викид виключень - найкращий спосіб обробляти помилки в PHP. Я використовую трохи простішу настройку:

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
        if (error_reporting()) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
});

Будь ласка, зверніть увагу на error_reporting()чек, який потрібно зберегти@ оператор не працював. Крім того, не потрібно визначати спеціальні винятки, у PHP є один хороший клас для цього.

Велика перевага викидів винятків полягає в тому, що виняток має сліди стека, пов'язані з ними, тому легко знайти, де проблема.


5

Re: "але яка саме різниця між помилкою та винятком?"

Тут є багато хороших відповідей щодо відмінностей. Я просто додам щось, про що ще не говорили - виставу. Зокрема, це полягає в різниці між викидами / обробкою винятків та обробкою коду повернення (успіх чи помилка). Зазвичай у php це означає повернення falseабо null, але вони можуть бути більш детальними, наприклад, із завантаженням файлів: http://php.net/manual/en/features.file-upload.errors.php Ви навіть можете повернути об’єкт винятку !

Я зробив кілька циклів роботи на різних мовах / системах. Взагалі кажучи, обробка винятків приблизно на 10000 разів повільніше, ніж перевірка коду повернення помилки.

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

Редагувати:

PHP дуже оптимізовано для обробки виключень. Тести реального світу показують, що кидання винятку лише на 2-10 разів повільніше, ніж повернення значення.


3
Звичайно, але кількість втрачених циклів при викиданні винятків більше, ніж компенсується додатковими описовими повноваженнями, які ви отримуєте за винятками. Ви можете кидати конкретні типи винятків, навіть додавати дані до винятку, щоб містити коди помилок. Я серйозно сумніваюся також у вашій заяві 10 000 *. Навіть якщо ви маєте рацію щодо різниці в часі, час, витрачений на повернення & якщо порівняно з новим Execption, кидком, ловом у будь-якому реальному сценарії, ймовірно, настільки незначний у порівнянні з виконаним кодом, що це, безумовно, передчасна оптимізація. Викидайте винятки, їм приємніше займатися 90% часу.
gnarf

1
1. 10 000x точний - з деякою дисперсією на основі мови та параметрів компілятора. 2. Вам не потрібно повертати null / false. Ви можете повернути номер - до MAX_ULONG кодів повернення прямо туди. Ви також можете повернути строку відмови та просто перевірити наявність рядка успіху чи int чи null. 3. У реальних сценаріях враховується кожен тактовий цикл. У Facebook є 552 мільйони щоденних активних користувачів. Припустимо, що винятки - лише 2 рази, а перевірка користувача / проходу займає 0,001, що означає економію 153 годин обробки часу щодня. У 10000 разів це економить 175 років. Просто для перевірки спроб входу - кожен день.
еван

@evan: FYI, тут вони протестували код з винятками , і це , здається, не повільніше: stackoverflow.com/a/445094/260080
Marco DeMaio

@MarcoDemaio Це запитання охоплює лише блок "try / catch" без викидів. Кращим тестом було б повернути значення в noexcept () і викинути виняток у, крім (). Крім того, воно повинно перекидатися через кілька функцій. stackoverflow.com/a/104375/505172 стверджує, що різниця в PHP насправді 54x. Я провів власний тест, дивлячись у режимі реального часу, і, здається, це на 2-10 разів повільніше. Це все набагато краще, ніж очікувалося.
еван

@evan: Я б тоді не хвилювався, я використовую винятки лише для відстеження несподіваних / непоправних помилок, тому навіть якщо це буде в 100 разів повільніше, я б не хвилювався. Мої турботи полягали в тому, щоб зробити код повільнішим, просто додавши блоки "try / catch".
Марко Демайо

4

Я думаю, що коханець, якого ти шукає, це;

Помилки - це стандартний матеріал, до якого ви звикли, наприклад, повторення змінної $, яка не існує.
Винятки складаються лише з PHP 5 і надалі при роботі з об'єктами.

Щоб зробити це просто:

Виняток становлять помилки, які ви отримуєте під час роботи з об'єктами. Оператор try / catch дозволяє зробити щось з ними, хоча він використовується так само, як і if / else. Спробуйте зробити це, якщо проблема не має значення, зробіть це.

Якщо ви не «ловите» виняток, це перетворюється на стандартну помилку.

Помилки - це фундаментальні помилки php, які зазвичай зупиняють ваш сценарій.

Try / catch часто використовується для встановлення підключень до бази даних, таких як PDO, що добре, якщо ви хочете перенаправити сценарій або зробити щось інше, якщо з'єднання не працює. Але якщо ви просто хочете відобразити повідомлення про помилку і зупинити скрипт, тоді воно вам не потрібно, виняток, що виникла, перетворюється на фатальну помилку. Або ви також можете скористатися налаштуваннями обробки помилок на рівні сайту.

Сподіваюся, що це допомагає


3
Винятки так само добре використовуються з процедурним кодом у PHP.
Tiberiu-Ionuț Stan

2

У PHP 7.1 і через засувка блок може вказати кілька винятків , використовуючи труби (|) характер. Це корисно, коли різні винятки з різних ієрархій класів обробляються однаково.

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}

1

Винятки кидаються навмисно кодом за допомогою кидка, помилок ... не так вже й багато.

Помилки виникають внаслідок чогось, що зазвичай не обробляється. (Помилки вводу-виводу, помилки TCP / IP, помилки з нульовою посиланням)


1
Це не обов'язково правда. У багатьох випадках перевіряються помилки, а коди повернення навмисно надсилаються назад, якщо це доречно. Насправді це стосується кожної не об’єктно орієнтованої мови. Винятки - це лише винятки з правила. В обох випадках щось піде не так, помічено, і з цим слід поводитися. Завантаження файлів PHP - один із прикладів навмисної обробки помилок через зворотні коди - php.net/manual/en/features.file-upload.errors.php
evan

1

Я маю намір дати вам найбільш незвичайне обговорення контролю помилок.

Я створив дуже хороший обробник помилок на мові років тому, і хоча деякі з назв змінилися, принципи обробки помилок є однаковими і сьогодні. У мене була створена спеціальна багатозадачна ОС, і я мав змогу відновлюватися після помилок даних на всіх рівнях без витоків пам'яті, зростання стека або збоїв. Отже, наступне - це моє розуміння того, як повинні діяти помилки та винятки та як вони відрізняються. Я просто скажу, що я не розумію, як працює внутрішня спроба лову, тому я певною мірою здогадуюсь.

Перше, що трапляється під обкладинками для обробки помилок - це перехід з одного програмного стану в інший. Як це робиться? Я доберусь до цього.

Історично помилки старіші та простіші, а винятки - новіші та трохи складніші та спроможні. Помилки справно працюють, доки вам не доведеться їх замішувати, що є еквівалентом передачі важкої проблеми своєму керівнику.

Помилками можуть бути числа, як номери помилок, а іноді з одним або кількома пов'язаними рядками. Наприклад, якщо виникла помилка читання файлів, ви можете повідомити, що це таке, і, можливо, витончено вийти з ладу. (Сена, це крок від простої аварії, як колись.)

Що не часто говорять про винятки, це те, що винятки - це об'єкти, які розташовуються на спеціальній степі винятків. Це як стек повернення для потоку програми, але він утримує стан повернення лише для спроб помилок та перехоп. (Раніше я називав їх ePush та ePop, і? Abort був умовним кидком, який би ePop і відновився до цього рівня, тоді як Abort був повноцінним штампом або виходом.)

У нижній частині стеку розміщена інформація про початковий абонент, об'єкт, який знає про стан, коли була запущена зовнішня спроба, що часто відбувається при запуску вашої програми. На додаток, що або наступний шар у стеці, доки діти, а внизу - батьки, є об’єктом винятку наступного внутрішнього блоку спробу / лову.

Якщо ви поставите спробу всередині спроби, ви кладете внутрішню спробу поверх верхньої. Коли у внутрішній спробі виникає помилка і або внутрішній лов не може її впоратися, або помилка перекидається на зовнішню спробу, тоді керування передається зовнішньому блоку захоплення (об'єкту), щоб побачити, чи може він впоратися з помилкою, тобто ваш керівник.

Отже, що справді робить цей стек помилок - це можливість маркувати та відновлювати потік програми та стан системи, інакше кажучи, це дозволяє програмі не руйнувати зворотний стек і псувати речі для інших (даних), коли справи йдуть не так. Таким чином, це також економить стан будь-яких інших ресурсів, таких як пул розподілу пам’яті, і таким чином він може очистити їх, коли виконано вилов. Взагалі це може бути дуже складною справою, і саме тому обробка винятків часто повільна. Загалом у ці блоки виключень потрібно зайняти небагато держав.

Таким чином, блок "try / catch" встановлює стан, до якого можна повернутися, якщо все інше зіпсується. Це як батько. Коли наше життя зіпсується, ми можемо впасти назад у колі наших батьків, і вони знову зроблять це все добре.

Сподіваюся, я вас не розчарував.


1

Ви можете додати цей коментар

function doSomething()
{
   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();
}

0

Після того, як set_error_handler () визначений, обробник помилок схожий на виняток. Дивіться код нижче:

 <?php
 function handleErrors( $e_code ) {
   echo "error code: " . $e_code . "<br>";
 }

 set_error_handler( "handleErrors" ); 

 trigger_error( "trigger a fatal error", E_USER_ERROR);
 echo "after error."; //it would run if set_error_handler is defined, otherwise, it wouldn't show
?>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.