Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6114
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

394

Часть IV. Стандартные функции PHP

Листинг 23.12. Файл trace.php

<?php ## Вывод дерева вызовов функции. function inner($a) {

// Внутренняя функция.

echo "<pre>"; print_r(debug_backtrace()); echo "</pre>";

}

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() стоит написать свой собственный код для вывода дерева вызовов в более компактной форме.

Принудительное завершение программы

void exit()

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

Глава 23. Управление интерпретатором

395

void die(string $message)

Функция делает почти то же самое, что и exit(), только перед завершением работы выводит строку, заданную в параметре $message. Чаще всего ее применяют, если нужно напечатать сообщение об ошибке и аварийно завершить программу.

Полезным примером использования die() может служить такой код:

$filename = '/path/to/data-file';

$file = @fopen($filename, 'r')

or die("не могу открыть файл $filename!");

Здесь мы ориентируемся на специфику оператора or — "выполнять" второй операнд только тогда, когда первый "ложен". Мы уже встречались с этим приемом в гл. 18, посвященной работе с файлами. Заметьте, что оператор || здесь применять нельзя — он имеет более высокий приоритет, чем =. С использованием || последний пример нужно было бы переписать следующим образом:

$filename = '/path/to/data-file';

($file = fopen($filename, 'r'))

|| die("не могу открыть файл $filename!");

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

Финализаторы

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

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

int register_shutdown_function(string $func)

Регистрирует функцию с указанным именем с той целью, чтобы она автоматически вызывалась перед возвратом из сценария. Функция будет вызвана как при окончании программы, так и при вызовах exit() или die(), а также при фатальных ошибках, приводящих к завершению сценария — например, при синтаксической ошибке.

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

Правда, есть одно "но". Финальная функция вызывается уже после закрытия соединения с браузером клиента. Поэтому все данные, выведенные в ней через echo, теряются (во всяком случае, так происходит в Unix-версии PHP, а под Windows CGIверсия PHP и echo работают прекрасно). Так что лучше не выводить никаких дан-

396

Часть IV. Стандартные функции PHP

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

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

Генерация кода во время выполнения

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

Выполнение кода

int eval(string $code)

Эта функция делает довольно интересную вещь: она берет параметр $code и, рассматривая его как код программы на PHP, запускает. Если этот код возвратил какое-то значение оператором return (как, например, это обычно делают функции), eval() также вернет эту величину.

Параметр $code представляет собой обычную строку, содержащую участок PHP-про- граммы. То есть в ней может быть все, что допустимо в сценариях:

ввод/вывод, в том числе закрытие и открытие тегов <? и ?>;

управляющие инструкции: циклы, условные операторы и т. д.;

объявления и вызовы функций;

вложенные вызовы функции eval().

Тем не менее нужно помнить несколько важных правил.

Код в $code будет использовать те же самые глобальные переменные, что и вызвавшая программа. Таким образом, переменные не локализуются внутри eval().

Любая критическая ошибка (например, вызов неопределенной функции) в коде строки $code приведет к завершению работы всего сценария (разумеется, сообщение об ошибке также напечатается в браузере). Это значит, что мы не можем перехватить все ошибки в коде $code, вставив его в eval().

Последний факт наводит на довольно удручающие мысли. К сожалению, разработчики PHP не задумались о том, как было бы удобно, если бы функция eval() при ошибке в вызванном ею коде просто возвращала значение false, помещая сообщение об ошибке в какую-нибудь переменную (как это сделано, например, в Perl).

Глава 23. Управление интерпретатором

397

Тем не менее синтаксические ошибки и предупреждения, возникающие при трансляции кода в $code, не приводят к завершению работы сценария, а всего лишь вызывают возврат из eval()значения false. Что ж, хоть кое-что.

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

eval("$clever = $dumb;"); // Неверно!

//Вы, видимо, хотели написать следующее: eval("\$clever = \$dumb");

//но короче будет так:

eval('$clever = $dumb');

Возможно, вы спросите: зачем нам использовать функцию eval(), если она занимается лишь выполнением кода, который мы и так можем написать прямо в нужном месте программы? Например, следующий фрагмент

eval('for ($i=0; $i<10; $i++) echo $i; ');

эквивалентен такому коду:

for ($i=0; $i<10; $i++) echo $i;

Почему бы всегда не пользоваться последним фрагментом? Да, конечно, в нашем примере лучше было бы так и поступить. Однако сила eval() заключается прежде всего в том, что параметр $code может являться (и чаще всего является) не статической строковой константой, а сгенерированной переменной. Вот, например, как мы можем создать 1000 функций с именами printSquare1(), ..., printSquare1000(), которые будут печатать квадраты первых 1000 чисел (листинг 23.13).

Листинг 23.13. Файл eval.php

<?php ## Генерация семейства функций.

for ($i=1; $i<=1000; $i++)

eval("function printSquare$i() { echo $i*$i; }");

printSquare303();

?>

Попробуйте-ка сделать это, не прибегая к услугам eval()!

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

Parse error: parse error in eval.php(4) : eval()'d code on line 1

Как видим, в круглых скобках после имени файла PHP печатает номер строки, в которой была вызвана сама функция eval(), а после "on line" — номер строки в

398

Часть IV. Стандартные функции PHP

параметре $code функции eval(). Впрочем, мы никак не сможем перехватить эту ошибку, поэтому последнее нам не особенно интересно.

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

$code = file_get_contents($fname, true);

eval("?>$code<?");

Всего две строчки, но какие... Рассмотрим их подробнее.

Что делает первая строка — совершенно ясно: она считывает все содержимое файла $fname и образует одну большую строку. Второй аргумент функции, равный true, заставляет ее искать считываемый файл не только в текущем каталоге, но и в путях поиска include_path.

Вторая строка, собственно, запускает тот код, который мы только что считали. Но зачем она предваряется символами ?> и заканчивается <? — тегами закрытия и открытия кода PHP? Суть в том, что функция eval() воспринимает свой параметр именно как код, а не как документ со вставками PHP-кода. В то же время, считанный нами файл представляет собой обычный PHP-сценарий, т. е. документ со "вставками" PHP. Иными словами, настоящая инструкция include воспринимает файл в контексте документа, а функция eval() — в контексте кода. Поэтому-то мы и используем ?> — переводим текущий контекст в режим восприятия документа, чтобы функция eval() "осознала" статический текст верно. Мы еще неоднократно столкнемся с этим приемом в будущем.

Генерация функций

В последнем примере мы рассмотрели, как можно создать 1000 функций с разными именами, написав программу, длиной в две строчки. Это, конечно, впечатляет, но мы должны жестко задавать имена функций. Почему бы не поручить эту работу PHP, если нас не особо интересуют получающиеся имена (листинг 23.14)?

Листинг 23.14. Файл mkfuncs.php

<?php ## Генерация "анонимных" функций. $squarers = array();

for ($i=0; $i<=1000; $i++) {

//Создаем строку, содержимое которой каждый раз будет разным. $id = uniqid("F");

//Создаем функцию.

eval("function $id() { echo $i*$i; }"); $squarers[] = $id;

}

// Так можно вызвать функцию, чье имя берется из массива. $squarers[303]();

?>

Глава 23. Управление интерпретатором

399

Теперь мы имеем список $squarers, который содержит имена наших сгенерированных функций. Как видите, вызвать какую-либо из них довольно просто. Даже в случае, если номер функции хранится в переменной $n, мы можем использовать следующий код:

echo $squarers[$n](); // выводит результат работы $n-й функции

Оказывается, в PHP существует функция, которая поможет нам упростить генерацию "анонимных" функций, подобных полученным в примере из листинга 23.14. Называется она create_function().

string create_function(string $args, string $code)

Создает функцию с уникальным именем, выполняющую действия, заданные в коде $code (это строка, содержащая программу на PHP). Созданная функция будет принимать параметры, перечисленные в $args. Перечисляются они в соответствии со стандартным синтаксисом передачи параметров любой функции. Возвращаемое значение представляет собой уникальное имя функции, которая была сгенерирована. Вот несколько примеров (листинг 23.15).

Листинг 23.15. Файл create_function.php

<?php ## Создание анонимных функций.

$mul = create_function('$a,$b', 'return $a*$b;'); $neg = create_function('$a', 'return -$a;'); echo $mul(10, 20) . "<br>"; // выводит 200

echo $neg(2) . "<br>"; // выводит -2 ?>

Не пропустите последнюю точку с запятой в конце строки, переданной вторым параметром create_function()!

И последний пример использования анонимных функций — в программах сортировки с вызовом пользовательских функций (листинг 23.16).

Листинг 23.16. Файл sort.php

<?php ## Анонимные функции и сортировка.

$fruits = array("orange", "apple", "apricot", "lemon"); usort($fruits, create_function('$a,$b', 'return strcmp($b, $a);')); foreach ($fruits as $key=>$value) echo "$key: $value<br>\n";

?>

Конечно, пример с strcmp() тривиален, и мы можем использовать и более сложный код. Учтите только, что на трансляцию функции во внутреннее представление требуется время, так что создание новой анонимной функции на каждом витке какогонибудь цикла — не очень хорошая идея.

400

Часть IV. Стандартные функции PHP

Другие функции

void usleep(int $micro_seconds)

Вызов этой функции позволяет сценарию "замереть" на указанное время (в микросекундах). При этом затрачивается очень немного ресурсов процессора, поэтому функцию вполне можно вызывать, чтобы дождаться выполнения какой-нибудь операции другого процесса — например, закрытия им файла.

Существует также функция sleep(), которая принимает в параметрах не микросекунды, а секунды, на которые нужно задержать выполнение программы.

int uniqid(string $prefix)

Функцию uniqid() мы уже применяли выше. Она возвращает строку, при каждом вызове отличающуюся от результата предыдущего вызова. Параметр $prefix задает префикс (до 114 символов) этого идентификатора.

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

Чтобы добиться большей уникальности, можно использовать uniqid() "в связке" с функциями md5() и mt_rand(), описанными в гл. 15 и 17 соответственно.

Резюме

В данной главе мы научились управлять настройками PHP в различных операционных системах. Узнали о тонких местах PHP и получили много новых сведений о директивах, до сих пор нами не рассмотренных. Отдельное внимание уделено отладке скриптов, а точнее — обработке сообщений об ошибках и предупреждений, которые могут возникнуть во время работы программы, а также выводу стека вызовов процедур (подобного тому, что существует в языках Java и Perl). Мы научились с осторожностью использовать оператор отключения предупреждений, а также возможности PHP по генерации кода программ (в частности, новых функций) во время выполнения.

ГЛАВА 24

Основы регулярных выражений в формате PCRE

Листинги данной главы можно найти в подкаталоге preg.

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

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

Тем не менее, регулярные выражения — это один из самых употребляемых инструментов в Web-программировании, и незнание их основ может сильно усложнить дальнейшее творчество в Web.

Начнем с примеров

Что же такое регулярное выражение? И чем именно оно "регулярно"? Проще всего разбираться с этим на примерах. Так мы и поступим, если вы не против. Ведь вы не против?..

Пример первый

Пусть программа обрабатывает какой-то входной файл с именем и расширением, и необходимо сгенерировать выходной файл, имеющий то же имя, но другое расширение. Например, файл file.in ваша программа должна обработать и записать результат в file.out. Проблема заключается в том, чтобы отрезать у имени входного файла все после точки и "приклеить" на это место out.

Проблема довольно тривиальна, и даже на PHP ее можно решить всего несколькими командами. Например, так:

402

Часть IV. Стандартные функции PHP

$p = strrpos($inFile, '.');

if ($p) $outFile = substr($inFile,0,$p); else $outFile = $inFile;

$outFile .= ".out";

На самом деле, выглядит довольно неуклюже (особенно из-за того, что приходится обрабатывать случаи, когда входной файл не имеет расширения, а значит, в нем нет точки). И эта "навороченность" имеет место, несмотря на то, что само действие можно описать всего одним предложением. А именно: "Замени в строке $inFile все, что после последней точки (и ее саму), или, в крайнем случае, "конец строки" на строку

.out, и присвой результат переменной $outFile".

Пример второй

Давайте теперь рассмотрим другой пример. Нам нужно разбить полное имя файла на две составляющие: имя каталога, в котором расположен файл, и само имя файла. Как мы знаем, для этого в PHP встроены функции basename() и dirname(), рассмотренные выше. Но предположим для тренировки, что их нет. Вот как мы реализуем требуемые действия:

$slash1 = strrpos($fullPath, '/'); $slash2 = strrpos($fullPath, '\\'); $slash = max($slash1, $slash2); $dirName = substr($fullPath, 0, $slash);

$filename = substr($fullPath, $slash+1, 10000);

Здесь мы воспользовались тем фактом, что функция strrpos() возвращает false, которое интерпретируется как 0, если искомый символ не найден. Обратите внимание на то, что пришлось два раза вызывать strrpos(), потому что мы не знаем, какой слэш будет получен от пользователя — прямой или обратный. Видите — код все увеличивается. И уменьшить его почти невозможно.

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

Опять же, сформулируем словами то, что нам нужно: "Часть слова после последнего прямого или обратного слэша или, в крайнем случае, после начала строки, присвой переменной $fileName, а "начало строки" — переменной $dirName". Формулировку "часть слова после последнего слэша" можно заменить на несколько другую: "Часть слова, перед которой стоит слэш, но в нем самом слэша нет".

Пример третий

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

Глава 24. Основы регулярных выражений в формате PCRE

403

тексте заменяются на их HTML-эквиваленты: например, < заменяется на <, > — на > и т. д. В PHP для этого существует специальная функция — htmlspecialchars(), пример использования которой представлен в листинге 24.1.

Листинг 24.1. Файл hsc.php

<?php ## Модель скрипта, принимающего текст от пользователя. if (@$_REQUEST['text'])

echo htmlspecialchars($_REQUEST['text'])."<hr>"; ?>

<form action="<?=$_SERVER['SCRIPT_NAME']?>" method="post"> <textarea name="text" cols="60" rows="10"> <?=@htmlspecialchars($_REQUEST['text'])?>

</textarea><br> <input type="submit"> </form>

Теперь, если злоумышленник наберет в текстовом поле, например, <iframe>, он уже не может испортить внешний вид страницы: ведь текст превратится в <iframe> и будет выведен в том же виде, что был введен.

Попробуйте ради эксперимента убрать в листинге 24.1 вызов htmlspecialchars(), затем введите в текстовое поле <iframe> и посмотрите, что получится.

Пусть мы хотим разрешить пользователю оформлять некоторые участки кода жирным шрифтом с помощью "тегов" [b]...[/b]. (Такой прием применяется, например, в известном форуме phpBB.) Команда на русском языке могла бы выглядеть так: "Об-

рами текст, расположенный между [b] и [/b], тегами <b> и </b>".

Обратите внимание на то, что нельзя просто поочередно [b] заменить на <b>, а [/b] — на </b>. Иначе злоумышленник сможет написать в конце текста один-единственный [b] и сделать, таким образом, весь текст страницы, идущий дальше, жирным.

Пример четвертый

При написании этой книги каждая глава помещалась в отдельный DOC-файл Microsoft Word. Но т. к. в процессе работы главы могут добавляться и удаляться, чтобы не путаться в нумерации, имена файлов имели следующую структуру:

or-НомерЧасти-НомерГлавы__Идентификатор.doc

Например, файл главы, которую вы читаете, имел имя or-04-24__preg.doc. Для того чтобы сослаться из одной главы на другую, авторы применяли не номера глав, а их идентификаторы, которые затем заменялись автоматически на соответствующие числа при помощи макросов Word. Это помогало менять главы местами и дописывать новые, не заботясь о соблюдении нумерации.

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.