PHP

Исключения PHP

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

Что такое исключения?

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

Как работают исключения?

Исключения могут быть брошены (thrown) и пойманы (caught). Когда исключение брошено, то значит, произошло что-то выходящее за рамки нормального течения процесса работы программы, и требуется выполнение какой-то другой функции. Подхват исключения осуществляется в специальной функции, которая сообщает остальной программе о том, что она готова обработать исключение.

Для объяснения действия исключений очень хорошо подходит аналогия с бейсболом.

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

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

Ловля мяча полевым игроком похожа на выполнение функции try-catch для обработки исключения. Функция try-catch может самостоятельно выполнить нужные действия, или перебросить исключение другой функции.


Как использовать исключения

Сформировать обработку исключений в PHP очень просто. Так как исключения являются частью стандартной библиотеки PHP(SPL), они входят в ядро PHP, начиная с версии 5. То есть исключения уже ждут, когда их начнут использовать.

Исключения реализуются также как и любой другой объект:
 	$exception = new Exception();
 	throw $exception;

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

Вот список методов исключений:

getMessage() – получает сообщение исключения;
getCode() – возвращает числовой код, который представляет исключение;
getFile() – возвращает файл, в котором произошло исключение;
getLine() – возвращает номер строки в файле, где произошло исключение;
getTrace() – возвращает массив backtrace() до возникновения исключения;
getPrevious() – возвращает исключение, произошедшее перед текущим, если оно было;
getTraceAsString() – возвращает массив backtrace() исключения в виде строки;
__toString() –возвращает все исключение в виде строки. Данную функцию можно переписать.

Для лучшей иллюстрации методов, создадим класс User, который осуществляет базовое управление записями пользователей:
 	<?php
 	class User
 	{
 	   protected $_user_id;
 	   protected $_user_email;
 	   protected $_user_password;
 	  
 	   public function __construct($user_id)
 	   {
 	      $user_record = self::_getUserRecord($user_id);
 	      $this->_user_id = $user_record['id'];
 	      $this->_user_email = $user_record['email'];
 	      $this->_user_password = $user_record['password'];
 	   }
 	  
 	   //Данные функции сформируем позже
 	   public function __get($value) {}
 	   public function __set($name, $value) {}
 	  
 	   private static function _getUserRecord($user_id)
 	   {
 	      //Для урока будем использовать модель записи для представления передачи данных в базу
 	      $user_record = array();
 	      switch($user_id) {
 	         case 1:
 	            $user_record['id'] = 1;
 	            $user_record['email'] = 'nikko@example.com';
 	            $user_record['password'] = 'i like croissants';
 	            break;
 	  
 	         case 2:
 	            $user_record['id'] = 2;
 	            $user_record['email'] = 'john@example.com';
 	            $user_record['password'] = 'me too!';
 	            break;
 	  
 	         case 'error':
 	            //имитируем неизвестное исключение от какой-нибудь используемой библиотеки SQL:
 	            throw new Exception('Ошибка библиотеки SQL!');
 	            break;
 	      }
 	  
 	      return $user_record;
 	   }
 	}
 	?>

В данном примере имеется несколько мест, где может проявиться исключение. Для начала, $user_id в нашей функции _getUserRecord() должен иметь тип integer. Если события сложатся по другому, то нужно генерировать исключение:
 	...
 	...
 	...
 	private static function _getUserRecord($user_id)
 	{
 	   $user_id = self::_validateUserId($user_id);
 	   ...
 	   ...
 	   ...
 	}
 	  
 	private static function _validateUserId($user_id)
 	{
 	   if( !is_numeric($user_id) && $user_id != 'error' ) {
 	      throw new Exception('Ой! Здесь что-то не так с идентификатором пользователя');
 	   }
 	   return $user_id;
	}


Теперь попробуем создать пользователя с неправильным идентификатором:
//Сначала создаем пользователя с номером один
$user = new User(1);
 
//Затем намеренно делаем ошибку
$user2 = new User('not numeric');

В итоге мы получим системное сообщение об ошибке.

А теперь позволим полевому игроку поймать исключение и обработать его:
	try {
 	   //Сначала создаем пользователя с номером один
 	   $user = new User(1);
 	  
 	   //Затем намеренно делаем ошибку
 	   $user2 = new User('not numeric');
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение: {$e->getMessage()}";
 	}

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

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

Создаем новый объект User в строке $user2 = new User('not numeric');
Выполняем функцию __construct() класса User
Переходим к функции _getUserRecord() класса User
Проверяем $user_id с помощью функции _validateUserId
Если $user_id не является числовым значением, выбрасываем объект исключения.

Мы также можем выполнить функцию getTraceAsString() для отслеживания исключения:
 	...
 	...
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение: {$e->getMessage()}";
 	   echo '<pre>';
 	   echo $e->getTraceAsString();
 	   echo '</pre>';
 	}

То есть, не смотря на то, что исключение произошло гораздо глубже вызова $user2 = new User('not numeric'), оно вскарабкалось наверх, пока блок try-catch не обработал его.

Теперь сформируем наше исключение для использования кода. Соответствие числового кода исключению является хорошим способом скрыть ошибку от клиента. По умолчанию, код исключения 0. Но мы можем изменить его при создании исключения:
 	...
 	...
 	...
 	if( !is_numeric($user_id) ) {
 	   throw new Exception('Ой! Здесь что-то не так с идентификатором пользователя', UserErrors::INVALIDID);
 	}
 	...
 	...
 	...

Обязательно нужно создать класс UserErrors:
 	class UserErrors
 	{
 	   const INVALIDID      = 10001;
 	   const INVALIDEMAIL      = 10002;
 	   const INVALIDPW         = 10003;
 	   const DOESNOTEXIST      = 10004;
 	   const NOTASETTING    = 10005;
 	   const UNEXPECTEDERROR   = 10006;
 	  
	   public static function getErrorMessage($code)
 	   {
 	      switch($code) {
 	         case self::INVALIDID:
 	            return 'Ой! Здесь что-то не так с идентификатором пользователя';
 	            break;
 	  
 	         case self::INVALIDEMAIL:
 	            return 'Адрес email неправильный!';
 	            break;
 	  
 	         case self::INVALIDPW:
 	            return 'Пароль меньше 4 символов!';
 	            break;
 	  
 	         case self::DOESNOTEXIST:
 	            return 'Пользователя не существует!';
	            break;
 	  
 	         case self::NOTASETTING:
 	            return 'Таких установок нет!';
	            break;
 	  
 	         case self::UNEXPECTEDERROR:
 	         default:
 	            return 'Какая-то ошибка!';
 	            break;
 	      }
 	   }
 	}

Теперь мы можем модифицировать обработчик исключений для вывода кода исключения вместо показа сообщения.
 	...
 	...
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение: #{$e->getCode()}";
 	}

Теперь будем использовать другие методы исключения для его отслеживания. Сначала создадим класс Logger:
 	class Logger
 	{
 	   public static function newMessage(
 	      Exception $exception,
 	      $clear = false,
 	      $error_file = 'exceptions_log.html'
 	   ) {
 	      $message = $exception->getMessage();
 	      $code = $exception->getCode();
 	      $file = $exception->getFile();
 	      $line = $exception->getLine();
 	      $trace = $exception->getTraceAsString();
 	      $date = date('M d, Y h:iA');
 	  
 	      $log_message = "<h3>Информация об исключении:</h3>
 	         <p>
 	            <strong>Дата:</strong> {$date}
 	         </p>
 	  
 	         <p>
 	            <strong>Сообщение:</strong> {$message}
 	         </p>
 	  
 	         <p>
 	            <strong>Код:</strong> {$code}
 	         </p>
 	  
 	         <p>
 	            <strong>Файл:</strong> {$file}
 	         </p>
 	  
 	         <p>
 	            <strong>Строка:</strong> {$line}
 	         </p>
 	  
 	         <h3>Stack trace:</h3>
 	         <pre>{$trace}
 	         </pre>
 	         <br />
 	         <hr /><br /><br />";
 	  
 	      if( is_file($error_file) === false ) {
 	         file_put_contents($error_file, '');
	      }
 	  
 	      if( $clear ) {
 	         $content = '';
 	      } else {
 	         $content = file_get_contents($error_file);
 	      }
 	  
 	      file_put_contents($error_file, $log_message . $content);
 	   }
 	}

Модифицируем обработку исключения, чтобы использовать класс Logger:
 	...
	} catch( Exception $e ) {
	   echo "Полевой игрок поймал исключение: #{$e->getCode()}";
	   Logger::newMessage($e);
 	}

Теперь при выполнении программы будет выводиться код исключения, а в файле exceptions_log.html будет представлена информация о месте появления исключения.

Заключение

В данном уроке представлены базовые сведения об исключениях PHP. В следующем уроке серии мы рассмотрим расширенное исключение и множественные блоки catch.

Исключения PHP

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

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

Чтобы создать расширенное исключение необходимо построить класс исключений, расширяющий класс Exception. Создадим пользовательское исключение UserException для нашего класса, построенного в предыдущем уроке серии:
 	class UserException extends Exception
 	{
 	   public function __construct($error_code)
 	   {
 	      parent::__construct(UserErrors::getErrorMessage($error_code), $error_code);
 	      Logger::newMessage($this);
 	   }
 	}

Мы переписали функцию __construct для реализации нашего собственного конструктора. Так как наш класс расширяет класс Exception, то мы можем использовать его конструктор с помощью директивы parent. Сделав так, мы можем отказаться от генерации сообщения об ошибке каждый раз при выбрасывании исключения, и использовать только код исключения. Также можно опустить запись в журнал, так как пользовательский конструктор автоматически будет заносить все исключения в журнал.

Обновим код для использования нашего нового класса UserException:
	private static function _validateUserId($user_id)
 	{
 	   if( !is_numeric($user_id) && $user_id != 'error' ) {
 	      throw new UserException(UserErrors::INVALIDID);
 	   }
 	   return $user_id;
 	}

Так как исключение автоматически заносит себя в журнал, можно Полевой игрок поймал исключение: опустить данную операцию в блоке try-catch:
...
...
} catch( Exception $e ) {
   echo "Полевой игрок поймал исключение: #{$e->getCode()}";
   //Logger::newMessage($e);
}

Теперь изменим код в других местах класса User, где ожидается появление исключений.

Класс User:
...
...
...
public function __get($value) {
   $value = "_{$value}";
   if( !isset($this->$value) ) {
      throw new UserException(UserErrors::NOTASETTING);
   }
   return $this->$value;
}
 
public function __set($name, $value) {
   switch($name) {
      case 'user_id':
         $user_id = self::_validateUserId($value);
         $this->_user_id = $user_id;
         break;
 
      case 'user_email':
         $user_email = self::_validateUserEmail($value);
         $this->_user_email = $user_email;
         break;
 
      case 'user_password':
         $user_password = self::_validateUserPassword($value);
         $this->_user_password = $user_password;
         break;
 
      default:
         throw new UserException(UserErrors::NOTASETTING);
         break;
   }
   return true;
}
 
private static function _getUserRecord($user_id)
{
   ...
   ...
   ...
   switch($user_id) {
      ...
      ...
      ...
      default:
         throw new UserException(UserErrors::DOESNOTEXIST);
         break;
   }
}

И попробуем генерировать три исключения:
 	$user = new User(1);
 	try {
 	   $user->user_email = 'invalid email';
 	} catch( Exception $e ) {
	   echo "Полевой игрок поймал исключение: #{$e->getCode()}<br />";
 	}
 	  
 	try {
 	   echo $user->setting_that_doesnt_exist;
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение: #{$e->getCode()}<br />";
 	}
 	  
 	try {
 	   //id пользователя не существует
 	   $user = new User(3);
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение: #{$e->getCode()}<br />";
 	}

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

Переписать функцию __construct и реализовать собственный код;
Организовать исключение в зависимости от типа объекта;
использовать блок множественных перехватов для обработки исключений в зависимости от типа!

Блок множественных перехватов

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

Наш блок try-catch предназначен для обработки обычного типа исключений Exception — так обеспечивается обработка ошибок для исключений незапланированных типов.

Изменим наш код так, чтобы исключение UserException обрабатывалось особенным образом. Будем использовать еще один блок перехвата, чтобы неожиданные исключения продолжали обрабатываться.
 	try {
 	   $user = new User(1);
 	   $user->user_password = '123';
 	} catch( UserException $e ) {
 	   echo "Полевой игрок поймал исключение UserException: #{$e->getCode()}<br />";
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение Exception:: #{$e->getCode()}<br />";
 	}
 	  
 	try {
 	   //Использование аргумента 'error' в классе User имитирует
 	   //ошибку в библиотеке SQL
 	   $user = new User('error');
 	} catch( UserException $e ) {
 	   echo "Полевой игрок поймал исключение UserException: #{$e->getCode()}<br />";
 	} catch( Exception $e ) {
 	   echo "Полевой игрок поймал исключение Exception:: #{$e->getCode()}<br />";
 	}

Исключения UserException и Exception будут обрабатываться по-разному. Может показаться, что пользы в раздельной обработке нет. Но самое очевидное применение - показ пользователю или запись в журнал только определенных исключений.

Заключение

Исключения PHP позволяют организовать простую и централизованную обработку различных ошибок во время выполнения кода.
Добавлено: 16 Августа 2013 11:44:55 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...