Исключения PHP. Часть 1 из 2

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

Для лучшей иллюстрации методов, создадим класс User, который осуществляет базовое управление записями пользователей:
Code
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. Если события сложатся по другому, то нужно генерировать исключение:
Code
...
...
...
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');

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

А теперь позволим полевому игроку поймать исключение и обработать его:
Code
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() для отслеживания исключения:

Code
...
...
} catch( Exception $e ) {
  echo "Полевой игрок поймал исключение: {$e->getMessage()}";
  echo '<pre>';
  echo $e->getTraceAsString();
  echo '</pre>';
}


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

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


Обязательно нужно создать класс UserErrors:
Code
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;
  }
  }
}


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


Теперь будем использовать другие методы исключения для его отслеживания. Сначала создадим класс Logger:
Code
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>
  

  <hr />

";
   
  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:
Code
...
} catch( Exception $e ) {
  echo "Полевой игрок поймал исключение: #{$e->getCode()}";
  Logger::newMessage($e);
}


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


Заключение

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

  • FalleN

  • 3975

  • 1

  • 0

Ссылки на статью:

Похожие статьи: