PHP: исключения vs ошибки?


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 представляет собой полный беспорядок - все виды вещей вызывают ошибки без необходимости (например, подавляющее большинство функций файловой системы). В общем случае исключения - это «способ ООП», но, к сожалению, некоторые из собственных 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
Я думаю, что OP имел в виду больше о различии между потомками ErrorVS потомками Exception.
XedinUnknown 01

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

Здесь можно добавить еще об обработке исключений и ошибок. Для разработчика приложения как ошибки, так и исключения - это «плохие вещи», которые вы хотите записывать, чтобы узнать о проблемах, с которыми сталкивается ваше приложение, чтобы ваши клиенты в конечном итоге получили лучший опыт.

Поэтому имеет смысл написать обработчик ошибок, который будет делать то же, что и вы для исключений.


Спасибо за ссылку!
Майк Мур,

@Alex Weinstein: ссылка не работает
Марко Демайо

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 Вы даже можете вернуть объект Exception !

Я сделал несколько пробежек на разных языках / системах. Вообще говоря, обработка исключений примерно в 10 000 раз медленнее, чем проверка кода возврата ошибки.

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

Редактировать:

PHP оптимизирован для обработки исключений. Тесты в реальных условиях показывают, что выброс исключения всего в 2–10 раз медленнее, чем возврат значения.


3
Конечно, но количество циклов, потерянных при выдаче исключений, более чем компенсируется дополнительными описательными возможностями, которые вы получаете с помощью исключений. Вы можете генерировать определенные типы исключений, даже добавлять в исключение данные, содержащие коды ошибок. Я тоже серьезно сомневаюсь в вашем заявлении о 10,000 *. Даже если вы правы относительно разницы во времени, время, потраченное на выполнение return и if по сравнению с новым Execption, throw, catch в любом реальном сценарии, вероятно, настолько незначительно по сравнению с исполняемым кодом, что это определенно преждевременная оптимизация. Выбросьте исключения, с ними лучше иметь дело в 90% случаев.
gnarf 02

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

@evan: К вашему сведению, здесь они протестировали код с исключениями, и он, похоже, не медленнее: stackoverflow.com/a/445094/260080
Марко Демайо,

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

@evan: Тогда я бы не стал беспокоиться, я использую исключения только для отслеживания неожиданных / неисправимых ошибок, поэтому даже если это будет в 100 раз медленнее, мне все равно. Я беспокоился о том, чтобы замедлить код, просто добавив блоки try / catch.
Marco Demaio

4

Я думаю, что вам нужен ответ;

Ошибки - это стандартные вещи, к которым вы привыкли, например, повторение несуществующей переменной $.
Исключения только начиная с PHP 5 и возникают при работе с объектами.

Для простоты:

Исключения - это ошибки, возникающие при работе с объектами. Однако оператор try / catch позволяет вам что-то с ними делать и используется во многом так же, как оператор if / else. Попробуйте сделать это, если проблема, неважно, сделайте это.

Если вы не «ловите» исключение, оно превращается в стандартную ошибку.

Ошибки - это основные ошибки php, которые обычно останавливают ваш скрипт.

Try / catch часто используется для установления соединений с базой данных, таких как PDO, что хорошо, если вы хотите перенаправить скрипт или сделать что-то еще, если соединение не работает. Но если вы просто хотите отобразить сообщение об ошибке и остановить скрипт, тогда он вам не нужен, неперехваченное исключение превращается в фатальную ошибку. Или вы также можете использовать настройку обработки ошибок для всего сайта.

надеюсь, это поможет


3
Исключения также можно использовать с процедурным кодом в PHP.
Tiberiu-Ionuț Stan

2

В PHP 7.1 и более поздних версиях блок catch может указывать несколько исключений с помощью символа вертикальной черты (|). Это полезно, когда разные исключения из разных иерархий классов обрабатываются одинаково.

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

1

Исключения создаются намеренно кодом с использованием throw, ошибок ... не так много.

Ошибки возникают в результате чего-то, что обычно не обрабатывается. (Ошибки ввода-вывода, ошибки TCP / IP, ошибки нулевой ссылки)


1
Это не обязательно правда. Во многих случаях ошибки проверяются, и коды возврата намеренно отправляются обратно в случае необходимости. Фактически, так обстоит дело со всеми не объектно-ориентированными языками. Исключения - это тоже исключения из правил. В обоих случаях что-то идет не так, это замечается, и с этим нужно бороться. Загрузка файла PHP - один из примеров преднамеренной обработки ошибок с помощью кодов возврата - php.net/manual/en/features.file-upload.errors.php
evan

1

Я собираюсь дать вам весьма необычное обсуждение контроля ошибок.

Много лет назад я встроил в язык очень хороший обработчик ошибок, и хотя некоторые названия изменились, принципы обработки ошибок остались прежними. У меня была специально созданная многозадачная ОС, и я должен был иметь возможность восстанавливать данные после ошибок на всех уровнях без утечек памяти, роста стека или сбоев. Далее следует мое понимание того, как должны работать ошибки и исключения и чем они отличаются. Я просто скажу, что не понимаю, как работает функция try catch, поэтому в некоторой степени предполагаю.

Первое, что происходит под прикрытием обработки ошибок, - это переход из одного состояния программы в другое. Как это сделать? Я к этому вернусь.

Исторически ошибки старше и проще, а исключения новее, немного сложнее и эффективнее. Ошибки работают нормально до тех пор, пока вам не придется их всплескивать, что равносильно передаче сложной проблемы вашему руководителю.

Ошибки могут быть числами, например номерами ошибок, а иногда и одной или несколькими связанными строками. Например, если возникает ошибка чтения файла, вы можете сообщить, что это такое, и, возможно, корректно завершиться ошибкой. (Эй, это шаг вперед от того, чтобы просто рушиться, как в старые времена.)

Об исключениях нечасто говорят, что исключения - это объекты, расположенные в специальном стеке исключений. Это как стек возврата для потока программы, но он содержит состояние возврата только для попыток и уловов ошибок. (Раньше я называл их ePush и ePop, а? Abort - это условный бросок, при котором ePop восстанавливается до этого уровня, а Abort - это полный кубик или выход.)

Внизу стека находится информация об исходном вызывающем объекте, объекте, который знает о состоянии, когда была запущена внешняя попытка, а это часто бывает при запуске вашей программы. На вершине этого или следующего слоя в стеке, где up являются дочерними элементами, а down являются родителями, является объектом исключения следующего внутреннего блока try / catch.

Если вы помещаете попытку внутри попытки, вы кладете внутреннюю попытку поверх внешней. Когда во внутренней попытке возникает ошибка, и либо внутренний catch не может ее обработать, либо ошибка передается внешней попытке, тогда управление передается внешнему блоку catch (объекту), чтобы узнать, может ли он обработать ошибку, т.е. ваш руководитель.

Итак, что на самом деле делает этот стек ошибок, так это возможность отмечать и восстанавливать поток программы и состояние системы, другими словами, он позволяет программе не разрушать стек возврата и не портить вещи для других (данных), когда что-то идет не так. Таким образом, он также сохраняет состояние любых других ресурсов, таких как пулы распределения памяти, и может очистить их, когда будет выполнен перехват. В общем, это может быть очень сложной задачей, и поэтому обработка исключений часто выполняется медленно. В общем, в эти блоки исключений нужно включить довольно много состояний.

Таким образом, блок try / catch устанавливает состояние, к которому можно вернуться, если все остальное испортилось. Это как родитель. Когда наша жизнь портится, мы можем снова упасть на колени к родителям, и они все исправят.

Надеюсь, я тебя не разочаровал.


1

Вы можете добавить этот комментарий

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

0

После определения set_error_handler () обработчик ошибок похож на Exception. Смотрите код ниже:

 <?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.