Обработка ошибок в php, это весьма занятная вещь. С одной стороны, в этом языке есть все, чтобы обработать и вывести ошибку. С другой, в нем есть такой тип ошибок, который ни коим образом не обрабатывается. Это Fatal Error, или ошибки синтаксиса. 

Ошибки таких типов не попадают в обработчики, они просто выводятся как есть. 

Дисклеймер

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

Итак, в прошлых уроках мы создали макет приложения, пару страниц, роутер и шаблонизатор. Мы даже объявили такой класс

class Except extends Exception{}

который ничего не делает. Зайду немного вперед, он и сейчас ничего не будет делать. Но мы поймем зачем он в принципе будет нужен в дальнейшем.

Когда в коде мы обрабатывали какую-то бизнес логику, мы выкидывали исключение, таким кодом

throw new Except('Error message');

это приводило к тому, что на экране выводилась подобная запись 

вывод ошибок в Ideal фреймворк до версии 1.0.2

Fatal error: Uncaught exception 'Except' with message 'File Z:\home\analize\ideal/application/views/page/pages/B2.php not found' in Z:\home\analize\ideal\ideal\classes\Controller.php:29 Stack trace: #0 Z:\home\analize\ideal\ideal\classes\Controller.php(43): Controller->_renderPartial('Z:\home\analize...', Array, true) #1 Z:\home\analize\ideal\application\views\page\read.php(2): Controller->renderPartial('pages/B2') #2 Z:\home\analize\ideal\ideal\classes\Controller.php(26): include('Z:\home\analize...') #3 Z:\home\analize\ideal\ideal\classes\Controller.php(43): Controller->_renderPartial('Z:\home\analize...', Array, true) #4 Z:\home\analize\ideal\ideal\classes\Controller.php(53): Controller->renderPartial('read', Array, true) #5 Z:\home\analize\ideal\application\controllers\PageController.php(4): Controller->render('read', Array) #6 [internal function]: PageController->actionRead('B2') #7 Z:\home\analize\ideal\ideal\classes\Controller.php(7): call_user_func_array(Array, Array) #8 Z:\home\analize\ideal\ideal\classes\App.php in Z:\home\analize\ideal\ideal\classes\Controller.php on line 29

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

Для того, чтобы сделать это более удобоваримым, нам потребуется установить обработчик исключений, через метод set_exception_handler. Мы обрабатываем не только исключения, но и ошибки php (все, кроме fatal), поэтому установим еще обработчик set_error_handler.

Но перед этим, добавим в класс App из ideal/classes/App.php несколько методов

public function handleError($code,$message,$file,$line){};
public function handleException($exception){};
public function displayError($code,$message,$file,$line){};
public function displayException($exception) {};
protected function initSystemHandlers() {};

В первую очередь напишем initSystemHandlers

protected function initSystemHandlers() {
		set_exception_handler(array($this,'handleException'));
		set_error_handler(array($this,'handleError'),error_reporting());
}

Все что он делает, это устанавливает два глобальных обработчика исключений, первый для исключений, второй для ошибок

Опишем два этих обработчика.

Обработка ошибок handleError

public function handleError($code,$message,$file,$line) {
  if($code ∓ error_reporting()) {
	restore_error_handler();
	restore_exception_handler();
  	try{
  		$this->displayError($code,$message,$file,$line);
  	} catch(Exception $e) {
  		$this->displayException($e);
	}
  }
}

error_reporting - это установленный где-то выше, уровень вывода ошибок. Точнее какие ошибки выводить. Имеет кучу вариаций, но если грубо, то E_ALL - выводить все ошибки, 0 - не выводить никакие ошибки. Значение сравнивается побитовым and со значением $code. Если выкинутому коду ошибки дозволено выводится на экран, то продолжаем.

restore_exception_handler и restore_error_handler - снимают все установленные обработчики. В конце должен остаться только один и этот один - это текущий метод.

Далее пробуем вывести ошибку на экран, если происходит что-то исключительное, то пытаемся вывести это исключение.

Обработка исключений handleException

public function handleException($exception){
	restore_error_handler();
	restore_exception_handler();
	$this->displayException($exception);
}

Идентично первому методу.

далее два метода для вывода ошибок и исключений

Вывод исключений на экран

public function displayException($exception) {
	echo '<h1>'.get_class($exception)."</h1>\n";
	echo '<p>'.$exception->getMessage().' ('.$exception->getFile().':'.$exception->getLine().')</p>';
	echo '<pre>'.$exception->getTraceAsString().'</pre>';
}

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

get_class - то, зачем был сделан отдельный класс Except. Вы можете создать сколько угодно таких классов, и назвать их все по разному (SQLExcept, CacheExcept и т.д.). Тогда посмотрев на ошибку, вы сразу же определите, какая часть логики вашего приложения сломалась. Кроме того, вы можете переопределить метод getMessage и вывести в нем к примеру SQL запрос, который вызвал сбой.

В следующем уроке, мы поговорим о моделях и работе с БД, вот тут нам и пригодятся эти исключения

Вывод ошибок на экран

С ошибками все немного сложнее. У нас нет класса ошибок, вы можете его создать, но пока вы просто выводим стек вызова при помощи функции debug_backtrace

public function displayError($code,$message,$file,$line){
	echo "<h1>PHP Error [$code]</h1>\n";
	echo "<p>$message ($file:$line)</p>\n";
	echo '<pre>';
  
	$trace=debug_backtrace();
  
	if(count($trace)>3) {
		$trace=array_slice($trace,3);
	}
	
	foreach($trace as $i=>$t){
		if(!isset($t['file']))
			$t['file']='unknown';
		if(!isset($t['line']))
			$t['line']=0;
		if(!isset($t['function']))
			$t['function']='unknown';
		echo "#$i {$t['file']}({$t['line']}): ";
		if(isset($t['object']) && is_object($t['object']))
			echo get_class($t['object']).'->';
		echo "{$t['function']}()\n";
	}
	echo '</pre>';
	exit();
}

Стек вызова может быть огромным, и поэтому ограничиваем его глубину 3-мя методами.

Теперь добавим в код нашего класса App, еще один метод

public function __construct(){
	$this->initSystemHandlers();
}

Т.е. устанавливаем наши обработчики, в момент создания экземпляра класса App

Вот и все, теперь исключения выводятся так, как мы этого хотим

Посмотреть, как работает исключение, можно на тестовом сайте. Если вы помните, то если у нас не было файла в папке вида, мы выкидывали сообщение

throw new Except('File '.$fullpath.' not found');

Поэтому просто перейдем на не существующий файл, в контроллере page http://ideal.xdan.ru/about1111.html

 

Исходные коды урока посмотреть и скачать

Исходные коды конечного фреймворка посмотреть и скачать

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

mes113
# mes113 13.03.2015 23:38
Огромное спасибо.
Очень хотелось бы увидеть ,как реализовать работу с моделью т.е было описано как вытащить данные из модели, а как вернуть и обработать нет .
Зарание спасибо
Leroy
# Leroy 14.03.2015 00:18
Материал уже набрал. заходите на сайт на следующей неделе. Будет статья про модели. Сам использую в одном проекте, очень удобно.
Leroy
# Leroy 23.03.2015 17:09
вышла статья про модели, приятного прочтения xdan.ru/kak-napisat-svoj-frejmvork-na-php-model.html
Михаил
# Михаил 27.05.2015 16:17
У Вас: "Тогда посмотрев на ошибку, вы сразу же определите, какая часть логики вашего приложения сломалась. Кроме того, вы можете переопределить метод getMessage и вывести в нем к примеру SQL запрос, который вызвал сбой."
А вот ни фига getMessage - final method и переопределение запрещено.

Вот так и не понял назначение : class Except extends Exception - как им пользоваться то?
Михаил
# Михаил 27.05.2015 16:21
К тому же класс Except - абстрактный,
а в файле Controller.php есть такая странная строчка:
throw new Except('File '.$fullpath.' not found');

В общем может я чего то не понял?
Leroy
# Leroy 28.05.2015 14:40
Except - в каком файле написано что он абстрактный?
Leroy
# Leroy 28.05.2015 14:40
Делаете к примеру
class SQLExcept extends Excep{}
а в классе работы с БД вызываете не
throw new Except('File '.$fullpath.' not found');
а
throw new SQLExcept ($sql.' - doesnt work!!!');
В итоге, когда вылезет это исключение:1) вы сразу поймете что ошибка в классе работы с БД
2)вы увидите сам запрос.
Это и есть роль исключений в пхп, это не только непреднамеренные ошибки, это еще и ошибки логики. Часто программисты забывают про то что SQL ошибки php не покажет. И отладка их бывает очень сложным делом.
С чего вы взяли что getMessage это final ?
Михаил
# Михаил 28.05.2015 15:07
Спасибо за разъяснение.
С абстрактным классом - хммм не пойму, вроде у вас исходники качал....
хммм может я сам виноват, случайно....

"С чего вы взяли что getMessage это final ?"-
Пробовал переопределить - вылезло сообщение, что final.
Leroy
# Leroy 28.05.2015 15:52
Хм, да точно, финал http://php.net/manual/ru/class.exception.php
Век живи век учись, и вам спасибо!)
Михаил
# Михаил 28.05.2015 16:02
Попробовал
throw new SQLExcept ($sql.' - doesnt work!!!');
работает :lol:

Неплохо бы придумать механизм замены вывода исключения.
Например в случае любого исключения писать: "Ошибка, обратитесь к администратору!"
В данном случае придётся править App.php метод displayError .

Вот был бы механизм делать это в приложении, чтоб не лезть в ядро....
Михаил
# Михаил 28.05.2015 16:13
Типа завести такой класс?
class MyApp extends App
{
public function displayException($exception)
{
echo "Обратитесь куда нибудь...\n";
}
}
Михаил
# Михаил 28.05.2015 16:16
Хотя неее
Виктор_З
# Виктор_З 11.04.2016 19:40
Здравствуйте.
Скажите, пожалуйста, как лучше сделать переадресацию на 404?

Думаю нужно сделать так?
При Except('File '.$fullpath.' not found') делать
header("HTTP/1.0 404 Not Found");

А как тогда сделать, чтобы URL был http://site/404/ и обрабатывался как контроллер, и выдавал текст в $content ?
Виктор_З
# Виктор_З 12.04.2016 17:27
Здравствуйте.
Скажите, пожалуйста, как лучше сделать переадресацию на 404?

Думаю нужно сделать так?
При Except('File '.$fullpath.' not found') делать
header("HTTP/1.0 404 Not Found");

А как тогда сделать, чтобы URL был http://site/404/ и обрабатывался как контроллер, и выдавал текст в $content ?
Виктор_З
# Виктор_З 14.04.2016 02:11
Здравствуйте.
Скажите, пожалуйста, как лучше сделать переадресацию на 404?

Думаю нужно сделать так?
При Except('File '.$fullpath.' not found') делать
header("HTTP/1.0 404 Not Found");

А как тогда сделать, чтобы URL был http://site/404/ и обрабатывался как контроллер, и выдавал текст в $content ?