Обработка ошибок, часть 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($message0$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 отработала с ошибкой.

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