Обработка ошибок, часть 2. Разбор примера. Исключения.

Пример из жизни.
Использование исключений.
Комментарии (12)

Пример из жизни.
Вот яркий образчик "обработки ошибок", который можно встретить практически в любом скрипте
if (is_writable($file) {
  $handle = fopen($file,'w') || die('error opening');        
  fwrite($handle, $text) || die('error writing');
  fclose($handle);
} else die("not writable");

Почему это неправильно, было рассказано в первой части.
Попробуем переписать этот код по-другому.

$handle = fopen($file,'w');        
$written=fwrite($handle, $text);
fclose($handle);
if ($written===FALSE) {
 $error="Извините, произошла ошибка. Попробуйте повторить позднее"
}


Функция is_writable имеет смысл только в том случае, если планируется как-то реагировать на невозможность записи. А если для пользователя, как это часто бывает, существует только два состояния - "операция прошла успешно" и "произошла ошибка", то и дополнительная проверка ничем не поможет. А вот запись в лог конкретной причины невозможности записи - очень поможет программисту.
поэтому мы убираем проверку is_writable, чтобы ошибки при открытии и записи файла пошли в лог, а для сообщения пользователю проверяем только самый конечный результат - запись в файл.

однако это решение подходит не для всех случаев. иногда от наличия ошибки зависят очень большие куски кода, которые гарантированно будут выдавать ошибки, которые ничего не добавят к самой первой. К примеру, если на месте fopen будет mysql_connect, за которым идет десяток запросов, то после нужной программисту ошибки соединения в логе будет еще куча ошибок запросов.
В таких случаях нужно обрабатывать эти "ключевые точки":
if ($handle = fopen($file,'w')) {
  $written=fwrite($handle, $text);
  fclose($handle);
}
if (!$handle OR $written===FALSE) {
  show_error_page();
}


Смысл здесь в чем?
Как описано в первой части, мы должны разделять саму ошибку, сообщение об ошибке, информирование о ней пользователя и программиста.
саму ошибку обрабатывает первый if - если файл не открылся, то записи не будет.
сообщение об ошибке, как положено, не пойдет на экран, а пойдет в лог.
программист будет проинформирован из лога же.
А пользователь, которому неважно, какие у нас там были ошибки, а интересует только одно: записалось-не записалось - получает свое сообщение. Для этого мы проверяем то, что интересует пользователя - произошла ли, собственно, запись в файл.

Использование исключений.
В PHP5 появился стандартный для большинства языков механизм исключений.
почитать про него можно в документации, а здесь мы просто посмотрим, как будет выглядеть код с применением этого механизма:
try {
  $handle=fopen($file,"a");
  if (!$handle) throw new Exception("open");
  fwrite($handle, $text);
  fclose($handle);

catch (Exception $e) {
  show_error_page();
}


Можно провести такую аналогию, что throw является аналогом die, но не для всего скрипта, а только для канарейки - кода, заключенного между try {}. Что позволяет, с одной стороны, не выполнять код, который все равно не сработает, а с другой - не обрывать работу всего скрипта, а завершить его корректно.

Вообще, механизм исключений предоставляет просто неограниченные возможности по управлению ошибками.
Взять, например, вот такой код:
function exceptions_error_handler($severity, $message, $filename, $lineno) { 
    throw new ErrorException($message, 0, $severity, $filename, $lineno); 
}
set_error_handler('exceptions_error_handler');


при его использовании наш тестовый пример становится совсем удивительным:
try {
  $handle = fopen($file,'w');
  fwrite($handle, $text);
  fclose($handle);

catch (Exception $e) {
  show_error_page();
}

Видишь обработку ошибок? И я нет. А она есть!
fwrite($handle, $text); не выполнится, если fopen отработала с ошибкой.

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