PHP: исключения против ошибок? - PullRequest
103 голосов
/ 08 мая 2009

Может быть, я пропускаю это где-то в руководстве по PHP, но в чем именно разница между ошибкой и исключением? Единственное отличие, которое я вижу, состоит в том, что ошибки и исключения обрабатываются по-разному. Но что вызывает исключение и что вызывает ошибку?

Ответы [ 11 ]

81 голосов
/ 08 мая 2009

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

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

echo "Some more stuff";

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

52 голосов
/ 09 мая 2009

Я обычно set_error_handler для функции, которая принимает ошибку и выдает исключение, так что, что бы ни случилось, у меня просто будут исключения для работы. Не более @file_get_contents просто красиво и аккуратно пробовать / ловить.

В ситуациях отладки у меня также есть обработчик исключений, который выводит страницу, похожую на asp.net. Я публикую это в дороге, но если потребуется, я опубликую пример источника позже.

редактирование:

Кроме того, как и было обещано, я вырезал и вставил часть своего кода вместе, чтобы сделать пример. Я сохранил ниже в файл на моей рабочей станции, вы можете НЕТ Дольше посмотреть результаты здесь (потому что ссылка не работает).

<code><?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(); ?>
<? ENDIF; ?> <? // назад в php } set_exception_handler ('global_exception_handler'); класс X { функция __construct () { trigger_error («К сожалению!», E_USER_NOTICE); } } $ x = новый X (); бросить новое исключение («Выполнение никогда не попадет сюда»); ?>
14 голосов
/ 27 февраля 2014

Ответ заслуживает разговора о слоне в комнате

Ошибки - это старый способ обработки состояния ошибки во время выполнения. Обычно код выполняет вызов чего-то вроде 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 реализовали обработку исключений. Но, вероятно, поддерживая старый код, они сохраняли обработку ошибок и предоставляли способ сделать обработку ошибок похожей на обработку исключений. Кроме того, нет никакой гарантии, что какой-то код не сможет сбросить обработчик ошибок, который был именно тем, что предназначалось для обработки исключений.

Окончательный ответ

Ошибки, которые были закодированы до реализации обработки исключений, скорее всего, все еще являются ошибками. Новые ошибки, вероятно, являются исключениями. Но нет дизайна или логики, в которых бывают ошибки, а какие - исключения. Это просто основано на том, что было доступно в то время, когда оно было закодировано, и предпочтении программиста, кодирующего это.

8 голосов
/ 09 мая 2009

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

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

6 голосов
/ 28 августа 2014

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

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

Обратите внимание на проверку error_reporting(), чтобы оператор @ работал. Кроме того, нет необходимости определять пользовательское исключение, для этого в PHP есть один класс.

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

4 голосов
/ 15 августа 2013

Я думаю, что ищущий - вот что;

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

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

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

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

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

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

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

4 голосов
/ 17 ноября 2011

Re: «но в чем именно разница между ошибкой и исключением?»

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

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

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

Edit:

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

1 голос
/ 17 февраля 2014

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

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

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

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

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

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

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

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

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

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

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

1 голос
/ 08 мая 2009

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

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

0 голосов
/ 30 мая 2019

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

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...