47782 (597357), страница 3
Текст из файла (страница 3)
function myErrorHandler($errno, $msg, $file, $line) {
// Если используется @, ничего не делать.
if (error_reporting() == 0) return;
// Иначе - выводим сообщение.
echo '';
echo "Произошла ошибка с кодом $errno!
";
echo "Файл: $file, строка $line.
";
echo "Текст ошибки: $msg";
echo "
}
// Регистрируем ее для всех типов ошибок.
set_error_handler("myErrorHandler", E_ALL);
// Вызываем функцию для несуществующего файла, чтобы
// сгенерировать предупреждение, которое будет перехвачено.
@filemtime("spoon");
?>
Теперь все работает корректно: предупреждение не отображается в браузере, т. к. применен оператор @.
2.4 ГЕНЕРАЦИЯ ОШИБОК
Функция
void trigger_error(string $message [, int $type])
предназначена для искусственной генерации сообщения об ошибки с указанным типом. В РНР существует несколько констант, чьи имена начинаются с E_USER_, которые можно использовать наравне с E_ERROR, E_WARNING и т. д., но только для персональных нужд. Именно с функцией trigger_error() они чаще всего и применяются. Вот эти константы:
E_ERROR
E_WARNING
E_USER_NOTICE
Как их использовать — целиком зависит от программиста: никаких ограничений не налагается.
int error_log(string $msg [,int $type=O] [,string $dest] [, string $extra_headers])
Выше мы рассматривали директивы log_errors и error_log, которые заставляют РНР записывать диагностические сообщения в файл, а не только выводить их в браузер. Функция error_log по своему смыслу похожа на trigger_error(), однако, она заставляет интерпретатор записать некоторый текст ($msg) в файл журнала (при нулевом $type и по умолчанию), заданный в директиве error_log. Основные значения аргумента $type, которые может принимать функция, перечислены ниже:
● $type = = 0
Записывает сообщение в системный файл журнала или в файл, заданный в директиве error_log.
● $type = = 1
Отправляет сообщение по почте адресату, чей адрес указан в $dest. При этом $extra_headers используется в качестве дополнительных почтовых заголовков.
● $type == 3
Сообщение добавляется в конец файла с именем $dest.
2.5 СТЕК ВЫЗОВОВ ФУНКЦИЙ
В РНР версии 4.3.0 и старше существует возможность проследить всю цепочку вызовов функций, начиная от главной программы и заканчивая текущей выполняемой процедурой.
Функция
list debug_backtrace()
возвращает большой список, в котором содержится информация о "родительских" функциях и их аргументах. Результат работы листинга 2.4 говорит сам за себя.
Листинг2.4. Файл trace.php
function inner($a) {
// Внутренняя функция.
echo "
"; print_r(debug_backtrace()); echo "";
}
function outer($x) {
// Родительская функция.
inner($x*$x);
}
// Главная программа.
outer(3);
?>
После запуска этого скрипта будет напечатан примерно следующий результат (его чуть сжали):
Array (
[0] => Array (
[file] => z:\home\book\original\src\interpreter\trace.php
[line] => 6
[function] => inner
[args] => Array ([0] => 9)
)
[1] => Array (
[file] => z:\home\book\original\src\interpreter\trace.php
[line] => 8
[function] => outer
[args] => Array ([0] => 3)
)
)
Как видите, в массиве оказалась все информация о промежуточных вызовах функций.
Функцию удобно применять, например, в пользовательском обработчике ошибок. Тогда можно сразу же определить, в каком месте было сгенерировано сообщение и какие вызовы к этому привели, В крупных программах уровень вложенности функций может достигать нескольких десятков, поэтому, наверное, вместо print_r() стоит написать свой собственный код для вывода дерева вызовов в более компактной форме.
2.6 ПРИНУДИТЕЛЬНОЕ ЗАВЕРШЕНИЕ ПРОГРАММЫ
Функция
void exit ()
немедленно завершает работу сценария. Из нее никогда не происходит возврата. Перед окончанием программы вызываются функции-финализаторы.
void die(string $message)
Функция делает почти то же самое, что и exit (), только перед завершением работы выводит строку, заданную в параметре $message). Чаще всего ее применяют, если нужно напечатать сообщение об ошибке и аварийно завершить программу.
Полезным примером использования die () может служить такой код:
$filename = '/path/to/data-file';
$file = @fopen($filename, 'r')
or die("не могу открыть файл $ filename!");
Здесь мы ориентируемся на специфику оператора or — "выполнять" второй операнд только тогда, когда первый "ложен". Заметьте, что оператор || здесь применять нельзя — он имеет более высокий приоритет, чем =.
С использованием || последний пример нужно было бы переписать следующим образом:
$filename = '/path/to/data-file';
($file = fopen($filename, 'r'))
|| die("не могу открыть файл $filename!");
Согласитесь, громоздкость последнего примера практически полностью исключает возможность применения || в подобных конструкциях.
2.7 ФИНАЛИЗАТОРЫ
Разработчики РНР предусмотрели возможность указать в программе функцию-финализатор, которая будет автоматически вызвана, как только работа сценария завершится — неважно, из-за ошибки или легально. В такой функции мы можем, например, записать информацию в кэш или обновить какой-нибудь файл журнала работы программы. Что же нужно для этого сделать?
Во-первых, написать саму функцию и дать ей любое имя. Желательно также, чтобы она была небольшой и в ней не было ошибок, потому что сама функция, вполне возможно, будет вызываться перед завершением сценария из-за ошибки.
Во-вторых, зарегистрировать ее как финализатор, передав ее имя стандартной функции register_shutdown_function().
int register_shutdown_function(string $func)
Регистрирует функцию с указанным именем с той целью, чтобы она автоматически вызывалась перед возвратом из сценария. Функция будет вызвана как при окончании программы, так и при вызовах exit() или die(), а также при фатальных ошибках, приводящих к завершению сценария — например, при синтаксической ошибке.
Конечно, можно зарегистрировать несколько финальных функций, которые будут вызываться в том же порядке, в котором они регистрировались.
Правда, есть одно "но". Финальная функция вызывается уже после закрытия соединения с браузером клиента. Поэтому все данные, выведенные в ней через echo, теряются (во всяком случае, так происходит в Unix-версии РНР, а под Windows CGI-версия РНР и echo работают прекрасно). Так что лучше не выводить никаких данных в такой функции, а ограничиться работой с файлами и другими вызовами, которые ничего не направляют в браузер.
Последнее обстоятельство, к сожалению, ограничивает функциональность финализаторов: им нельзя поручить, например, вывод окончания страницы, если сценарий по каким-то причинам прервался из-за ошибки. Вообще говоря, надо заметить, что в РНР никак нельзя в случке ошибки в некотором запущенном коде проделать какие-либо разумные действия (кроме, разумеется, мгновенного выхода).
3. ПЕРЕХВАТ ОШИБОК. МЕТОД ИСКЛЮЧЕНИЙ
Механизм обработки исключений или просто "исключения" (exceptions) — это технология, позволяющая писать код восстановления после серьезной ошибки в удобном для программиста виде. С применением исключений перехват и обработка ошибок, наиболее слабая часть в большинстве программных систем, значительно упрощается.
Концепция исключений базируется на общей идее объектно-ориентированного программирования: данные должны обрабатываться в том участке программы, который имеет максимум сведений о том, как это делать. Если в некотором месте еще не до конца известно, какое именно преобразование должно быть выполнено, лучше отложить работу "на потом". С использованием исключений код обработки ошибки явно отделяется от кода, в котором ошибка может возникнуть.
Исключения также позволяют удобно передавать информацию о возникшей ошибке вниз по дереву (стеку) вызовов функций. Таким образом, код восстановления может находиться даже не в текущей процедуре, а в той, что ее вызывает.
3.1 БАЗОВЫЙ СИНТАКСИС
Исключение — это некоторое сообщение об ошибке вида "серьезная". При своей генерации оно автоматически передается в участок программы, который лучше всего "осведомлен", что же следует предпринять в данной конкретной ситуации. Этот участок называется обработчиком исключения.
Любое исключение в программе представляет собой объект некоторого класса, создаваемый, как обычно, оператором new. Этот объект может содержать различную информацию, например, текст диагностического сообщения, а также номер строки и имя файла, в которых произошла генерация исключения. Допустимо добавлять и любые другие параметры.
Прежде чем идти дальше, давайте рассмотрим простейший пример вызова обработчика (листинг 3.1). Заодно получим представление о синтаксисе исключений.
Листинг.3.1. Файл simple.php
echo "Начало программы.
";
try {
// Код, в котором перехватываются исключения.
echo "Все, что имеет начало...
";
// Генерируем ("выбрасываем") исключение.
throw new Exception("Hello!");
echo "...имеет и конец.
";
} catch (Exception $e) {
// Код обработчика.
echo " Исключение: {$e->getMessage()}
";
}
echo "Конец программы.
";
?>
В листинге 3.1 приведен пример базового синтаксиса конструкции try...catch, применяемой для работы с исключениями.
Рассмотрим эту инструкцию подробнее:
● Код обработчика исключения помещается в блок инструкции catch (в переводе с английского — "ловить").
● Блок try (в переводе с английского — "попытаться") используется для того, чтобы указать в программе область перехвата. Любые исключения, сгенерированные внутри нее (и только они), будут переданы соответствующему обработчику.
● Инструкция throw используется для генерации исключения. Генерацию также называют возбуждением или даже выбрасыванием (или "вбрасыванием") исключения (от англ. throw — бросать). Как было замечено ранее, любое исключение представляет собой обычный объект РНР, который мы и создаем в операторе new.
● Обратите внимание на аргумент блока catch. В нем указано, в какую переменную должен быть записан "пойманный" объект-исключение перед запуском кода обработчика. Также обязательно задается тип исключения — имя класса. Обработчик будет вызван только для тех объектов-исключений, которые совместимы с указанным типом (например, для объектов данного типа).
Работа инструкции try...catch заключается в том, что одна часть программы "бросает" (throw) исключение, а другая — его "ловит" (catch).
3.2 ИНСТРУКЦИЯ throw
Инструкция throw не просто генерирует объект-исключение и передает его обработчику блока catch. Она также немедленно завершает работу текущего try-блока. Именно поэтому результат работы сценария из листинга 3.1 выглядит так:
Начало программы.
Все, что имеет начало...
Исключение: Hello!
Конец программы.
Как видите, за счет особенности инструкции throw наша программа подвергает серьезному скепсису тезис "Все, что имеет начало, имеет и конец" — она просто не выводит окончание фразы.
В этом отношении инструкция throw очень похожа на инструкции return, break и continue: они тоже приводят к немедленному завершению работы текущей функции или итерации цикла.
3.3 РАСКРУТКА СТЕКА
Самой важной и полезной особенностью инструкции throw является то, что ее можно использовать не только непосредственно в try-блоке, но и внутри любой функции, которая оттуда вызывается. При этом производится выход не только из функции, содержащей throw, но также и из всех промежуточных процедур. Пример — в листинге 3.2.
Листинг3.2.Файл stack.php
echo "Начало программы.
";
try {
echo "Начало try-блока.
";
outer();
echo "Конец try-блока.
";
} catch (Exception $e) {
echo " Исключение: {$e->getMessage()}
";
}
echo "Конец программы.
";
function outer() {
echo "Вошли в функцию ".__METHOD__."
";
inner();
echo "Вышли из функции ".__METHOD__."
";
}
function inner() {
echo "Вошли в функцию ".__METHOD__."
";
throw new Exception("Hello!");
echo "Вышли из функции ".__METHOD__."
";
}
?>
Результат работы данного кода выглядит так:
Начало программы.
Начало try-блока.
Вошли в функцию outer
Вошли в функцию inner
Исключение: Hello!
Конец программы.
Мы убеждаемся, что ни один из операторов echo, вызываемых после инструкции throw, не "сработал". По сути, программа даже не дошла до них: управление было мгновенно передано в catch-блок, а после этого — в следующую за try...catch строку программы.
Данное поведение инструкции throw называют раскруткой стека вызовов функций, потому что объект-исключение последовательно передается из одной функции в другую, каждый раз приводя к ее завершению — как бы "отматывает" стек.
Можно заметить, что инструкция throw очень похожа на команду return, однако она вызывает "вылет" потока исполнения не только из текущей функции, но также и из тех, которые ее вызвали (до ближайшего соответствующего catch-блока).
3.4 ИСКЛЮЧЕНИЯ И ДЕСТРУКТОРЫ
Деструктор любого объекта вызывается всякий раз, когда последняя ссылка на этот объект оказывается потерянной, например, программа выходит за границу области видимости переменной. Применительно к механизму обработки исключений это дает мощный инструмент — корректное уничтожение всех объектов, созданных до вызова throw. Листинг 3.3 иллюстрирует ситуацию.
Листинг 3.3. Файл destr.php














