Статьи

Обработка ошибок в PHP

Ошибки — наиболее распространенное событие, с которым сталкивается разработчик при программировании. Ошибки могут быть классифицированы как синтаксические, во время выполнения или логические: отсутствие точки с запятой в конце оператора является примером синтаксической ошибки; попытка подключиться к базе данных, когда сервер не работает, является примером ошибки во время выполнения; предоставление неверных данных в переменную является примером логической ошибки. Чтобы уменьшить количество ошибок в вашем коде и смягчить их последствия, в вашем веб-приложении необходима правильная обработка ошибок.

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

Уровни отчетов об ошибках PHP

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

error constants

Уровни можно маскировать вместе с битовыми операторами, чтобы включить или вычесть их из конфигурации PHP. Например, E_ALL|E_STRICT включает все ошибки и предупреждения с добавлением E_STRICT (необходимо в версиях PHP до 5.4).

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

error directives

Директивы конфигурации могут быть установлены либо в php.ini , либо в файле конфигурации веб-сервера (файл httpd.conf или .htaccess ), либо во время выполнения в вашем скрипте с использованием функции ini_set() . Прочитайте документацию для получения дополнительной информации о директивах и как / где их устанавливать.

Создание пользовательских обработчиков ошибок

Также хорошей практикой является не отображать необработанные ошибки конечному пользователю. Отображаемые ошибки должны быть абстрагированы дружественными, настраиваемыми сообщениями об ошибках. PHP не только предоставляет встроенные функции для регистрации и отображения ошибок, но и для их устранения. Вы можете прагматически вызвать ошибку определенного уровня, используя trigger_error() . Например, этот код вызывает предупреждение E_USER_NOTICE если значение $test больше 1:

 <?php $test = 5; if ($test > 1) { trigger_error('Value of $test must be 1 or less', E_USER_NOTICE); } 

Активация ошибок с помощью trigger_error() полезна, когда у вас есть инфраструктура обработки ошибок, позволяющая унифицировать обработку как пользовательских ошибок, так и ошибок и предупреждений, возникающих в PHP.

Если вы хотите реализовать настраиваемые стратегии обработки ошибок, такие как отправка электронной почты или регистрация ошибок в базе данных в зависимости от их серьезности, вам необходимо определить собственные обработчики ошибок using set_error_handler() . Функция принимает два аргумента: функцию обратного вызова или статический метод, который будет вызываться при возникновении ошибки, и, необязательно, уровень ошибки, который обрабатывает функция / метод. Подпись обратного вызова:

  обработчик (int $ errno, строка $ errstr, строка $ errfile, int $ errline, массив $ errcontext) 

callback arguments

Давайте посмотрим на пользовательскую функцию обработчика ошибок. Приведенный ниже пример записывает ошибки в базу данных таблицы базы данных при каждом их обнаружении:

 <?php function errorHandler($errno, $errstr, $errfile, $errline) {    static $db;    if (empty($db)) {        $db = new PDO(DSN, DBUSER, DBPASS);   }    $query = "INSERT INTO errorlog (severity, message, filename, lineno, time) VALUES (?, ?, ?, ?, NOW())";   $stmt = $db->prepare($query);    switch ($errno) {        case E_NOTICE:        case E_USER_NOTICE:        case E_DEPRECATED:        case E_USER_DEPRECATED:        case E_STRICT:            $stmt->execute(array("NOTICE", $errstr, $errfile, $errline));            break;       case E_WARNING:        case E_USER_WARNING:            $stmt->execute(array("WARNING", $errstr, $errfile, $errline));            break;        case E_ERROR:        case E_USER_ERROR:            $stmt->execute(array("FATAL", $errstr, $errfile, $errline));            exit("FATAL error $errstr at $errfile:$errline");       default:            exit("Unknown error at $errfile:$errline");    } } set_error_handler("errorHandler"); $test = 5; if ($test > 1) {   trigger_error("Value of $test must be 1 or less", E_USER_NOTICE); } 

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

Однако существуют некоторые ограничения для пользовательских обработчиков ошибок, о которых вы должны знать. Обработчик ошибок обходит стандартное поведение обработки ошибок PHP, поэтому он не может обрабатывать ошибки, которые могут возникнуть внутри самого обработчика. Например, если сервер базы данных не работает, вышеуказанная функция не сможет записать журнал. Кроме того, обработчик ошибок не может перехватить некоторые внутренние ошибки, такие как ошибки E_COMPILE_ERROR и E_COMPILE_ERROR или E_STRICT в том же файле, в котором определен обработчик, поскольку эти ошибки возникают до того, как обработчик имеет шанс быть зарегистрированным.

Обработка ошибок с использованием исключений

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

Исключения представлены в PHP классом Excpetion (или любым из его подклассов). Они могут быть подняты с помощью throw и могут быть пойманы с помощью блока try/catch . Вы можете расширить Exception для создания пользовательских типов исключений для отслеживания определенных ошибок.

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

 <?php try {   $data = $this->getDataFromService(); } catch (Exception $e) {   echo "Caught exception: " . $e->getMessage() . "n"; } 

Если вымышленное getDataFromService() создает исключение, оно будет getDataFromService() в блоке catch и будет отображено сообщение. Если getDataFromService() выполняется успешно, тогда поток пройдет через блок catch и продолжит работу до конца скрипта. Любые исключения, которые выбрасываются и не перехватываются, генерируют E_FATAL error с сообщением «Uncaught Exception».

Класс Exception предлагает шесть различных методов для доступа к информации о том, что вызвало проблему, как показано в таблице ниже.

exception methods

Создание пользовательского обработчика исключений

PHP позволит вам выбросить любой объект, как если бы он был исключением, но, как правило, исключение должно расширять встроенный в PHP класс Exception . В зависимости от типа объекта вы можете обрабатывать исключения по-разному. Обработка пользовательских исключений может выполнять подходящие действия, такие как регистрация сообщений об ошибках в файле, предоставление точных сведений о строке, на которой произошла ошибка, путем изучения стека вызовов и т. Д. Посмотрите на этот пример:

 <?php class NameException extends Exception { } class EmailException extends Exception { } $name = ""; $email= ""; try {   if (empty($name)) {       throw new NameException();   }   elseif (empty($email)) {       throw new EmailException();   }   else {       echo "Name is " . $name . "<br>";       echo "Email is " . $email;   } } catch (NameException $n) {   echo "A name was not provided.";   error_log($n->getTraceAsString()); } catch (EmailException $e) {   echo "An email address was not provided.";   error_log($e->getTraceAsString()); } по <?php class NameException extends Exception { } class EmailException extends Exception { } $name = ""; $email= ""; try {   if (empty($name)) {       throw new NameException();   }   elseif (empty($email)) {       throw new EmailException();   }   else {       echo "Name is " . $name . "<br>";       echo "Email is " . $email;   } } catch (NameException $n) {   echo "A name was not provided.";   error_log($n->getTraceAsString()); } catch (EmailException $e) {   echo "An email address was not provided.";   error_log($e->getTraceAsString()); } 

Приведенный выше код определяет два новых пользовательских типа исключений, NameException и EmailException , которые могут использоваться для указания различных ошибок. Затем в блоке try код проверяет, были ли введены значения для переменных $name и $email . Если любой из них пуст, то соответствующее исключение выдается с помощью throw . Выполняется соответствующий блок catch который обрабатывает ошибку.

Перебрасывание исключений

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

 <?php class FileExistException extends Exception { } class FileReadException extends Exception { } $filename = 'D:Exception.txt'; try {    try {        $text = file_get_contents($filename);        if ($text === false) {            throw new Exception();        }    }   catch (Exception $e) {        if (!file_exists($filename)) {            throw new FileExistException($filename . " does not exist.");        }        elseif (!is_readable($filename)) {            throw new FileReadException($filename . " is not readable.");        }        else {            throw new Exception("Unknown error accessing file.");        }   } } catch (FileExistException $fe) {    echo $fe->getMessage();    error_log($fe->getTraceAsString()); } catch (FileReadException $fr) {    echo $fr->getMessage();   error_log($fr->getTraceAsString()); } 

Необработанный обработчик исключений

Подобно тому, как set_error_handler() позволяет вам указывать функцию для обработки ошибок во время выполнения, set_exception_handler() позволяет вам обрабатывать исключения, которые делают все это вверх по стеку вызовов, не будучи пойманными никакими блоками catch . Например, если исключение проходит весь ваш стек, было бы неплохо записать его в файл журнала. Вы можете создать функцию обратного вызова и зарегистрировать ее с помощью set_exception_handler() как показано в примере ниже.

 <?php set_exception_handler(function ($exception) {   $file = "var/log/exceptionLog.log";   file_put_contents($file, $exception->__toString(), FILE_APPEND); }); throw new Exception(); 

Резюме

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

Изображение через Илью Андриянова / Shutterstock