Самоучитель PHP 4 - Котеров Д. В.
.pdf338 |
Часть IV. Стандартные функции PHP |
$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 работают прекрасно). Так что лучше не выводить никаких данных в такой функции, а ограничиться работой с файлами и другими вызовами, которые ничего не направляют в браузер.
Последнее обстоятельство, к сожалению, ограничивает функциональность финализаторов: им нельзя поручить, например, вывод окончания страницы, если сценарий по каким-то причинам прервался из-за ошибки. Вообще говоря, надо заметить, что в PHP никак нельзя в случае ошибки в некотором запущенном коде проделать какиелибо разумные действия (кроме, разумеется, мгновенного выхода). Это несколько может ограничивать область применимости PHP для написания шаблонизатора (о шаблонах будет подробно рассказано в части V этой книги).
Глава 24. Управление интерпретатором |
339 |
Генерация кода во время выполнения
Так как PHP в действительности является транслирующим интерпретатором, в нем заложены возможности по созданию и выполнению кода программы прямо во время ее выполнения. То есть мы можем писать сценарии, которые в буквальном смысле создают сами себя, точнее, свой код! Это незаменимо при написании шаблонизаторов и функций, занимающихся динамическим формированием писем. Мы поговорим о таких функциях в части V книги.
Выполнение кода
int eval(string $code)
Эта функция делает довольно интересную вещь: она берет параметр $st и, рассматривая его как код программы на PHP, запускает. Если этот код возвратил какое-то значение оператором return (как, например, это обычно делают функции), eval() также вернет эту величину.
Параметр $st представляет собой обычную строку, содержащую участок PHPпрограммы. То есть в ней может быть все, что допустимо в сценариях:
rввод-вывод, в том числе закрытие и открытие тэгов <? и ?>;
rуправляющие инструкции: циклы, условные операторы и т. д.;
rобъявления и вызовы функций;
rвложенные вызовы функции eval().
Тем не менее, нужно помнить несколько важных правил.
rКод в $st будет использовать те же самые глобальные переменные, что и вызвавшая программа. Таким образом, переменные не локализуются внутри eval().
rЛюбая критическая ошибка (например, вызов неопределенной функции) в коде строки $st приведет к завершению работы всего сценария (разумеется, сообщение об ошибке также напечатается в браузер). Это значит, что мы не можем перехватить все ошибки в коде, вставив его в eval().
Последний факт вызывает довольно удручающие мысли. К сожалению, разра- ботчики PHP опять не задумались о том, как было бы удобно, если бы eval() при ошибке в вызванном ей коде просто возвращала значение false, поме- щая сообщение об ошибке в какую-нибудь переменную (как это сделано, на- пример, в Perl).
340 |
Часть IV. Стандартные функции PHP |
rТем не менее, синтаксические ошибки и предупреждения, возникающие при трансляции кода в $st, не приводят к завершению работы сценария, а всего лишь вызывают возврат из eval()значения ложь. Что ж, хоть кое-что.
Не забывайте, что переменные в строках, заключенных в двойные кавычки, в PHP интерполируются (то есть заменяются на соответствующие значения). Это значит, что, если мы хотим реже использовать обратные слэши для защиты символовкавычек, нужно стараться применять строки в апострофах для параметра, передаваемого eval(). Например:
eval("$a=$b;"); // Неверно!
//Вы, видимо, хотели написать следующее: eval("\$a=\$b");
//но короче будет так:
eval('$a=$b');
Возможно, вы спросите: зачем нам использовать eval(), если она занимается лишь выполнением кода, который мы и так можем написать прямо в нужном месте программы? Например, следующий фрагмент
eval('for($i=0; $i<10; $i++) echo $i; ');
эквивалентен такому коду:
for($i=0; $i<10; $i++) echo $i;
Почему бы всегда не пользоваться последним фрагментом? Да, конечно, в нашем примере лучше было бы так и поступить. Однако сила eval() заключается прежде всего в том, что параметр $st может являться (и чаще всего является) не статической строковой константой, а сгенерированной переменной. Вот, например, как мы можем создать 100 функций с именами Func1()...Func100(), которые будут печатать квадраты первых 100 чисел:
Листинг 24.1. Генерация семейства функций
for($i=1; $i<=100; $i++)
eval("function Func$i() { return $i*$i; }");
Попробуйте-ка сделать это, не прибегая к услугам eval()!
Я уже говорил, что в случае ошибки (например, синтаксической) в коде, обрабатываемом eval(), сценарий завершает свою работу и выводит сообщение об ошибке в браузер. Как обычно, сообщение сопровождается указанием того, в какой строке произошла ошибка, однако вместе с именем файла выдается уведомление, что программа оборвалась в функции eval(). Вот как, например, может выглядеть такое сообщение:
Parse error: parse error in eval.php(4) : eval()'d code on line 1
Глава 24. Управление интерпретатором |
341 |
Как видим, в круглых скобках после имени файла PHP печатает номер строки, в которой была вызвана сама функция eval(), а после "on line" — номер строки в параметре eval() $st. Впрочем, мы никак не можем перехватить эту ошибку, поэтому последнее нам не особенно-то интересно.
Давайте теперь в качестве тренировки напишем код, являющийся аналогом инструкции include. Пусть нам нужно включить файл, имя которого хранится в $fname. Вот как это будет выглядеть:
$code=join("",File($fname)); eval("?>$code<?");
Всего две строчки, но какие...… Рассмотрим их подробнее.
Что делает первая строка — совершенно ясно: она сначала считывает все содержимое файла $fname по строкам в список, а затем образует одну большую строку путем "склеивания" всех элементов этого списка. Заметьте, как получилось лаконично: нам не нужно ни открывать файл, ни использовать функцию fread() или fgets().
Вторая строка, собственно, запускает тот код, который мы только что считали. Но çŕ÷ĺě îíŕ ďđĺäâŕđ˙ĺňń˙ ńčěâîëŕěč ?> č çŕęŕí÷čâŕĺňń˙ <? — ňýăŕěč îňęđűňč˙ č çŕęđűňč˙ ęîäŕ
PHP? Наверное, вы уже догадались: суть в том, что функция eval() воспринимает свой параметр именно как код, а не как документ со вставками PHP-кода. В то же время, считанный нами файл представляет собой обычный PHP-сценарий, т. е. документ со "вставками" PHP. Иными словами, настоящая инструкция include воспри-
нимает файл в контексте документа, а функция eval() — в контексте кода. По-
этому-то мы и используем ?> — переводим текущий контекст в режим восприятия документа, чтобы eval() "осознала" статический текст верно. Мы еще неоднократно столкнемся с этим приемом в будущем.
Генерация функций
В последнем примере мы рассмотрели, как можно создать 100 функций с разными именами, написав программу длиной в 2 строчки. Это, конечно, впечатляет, но мы должны жестко задавать имена функций. Почему бы не поручить эту работу PHP, если нас не особо интересуют получающиеся имена?
Листинг 24.2. Генерация "анонимных" функций
$Funcs=array();
for($i=0; $i<=100; $i++) { $id=uniqid("F");
eval("function $id() { return $i*$i; }"); $Funcs[]=$id;
}
342 |
Часть IV. Стандартные функции PHP |
Теперь мы имеем список $Funcs, который содержит имена наших сгенерированных функций. Как нам вызвать какую-либо из них? Это очень просто:
echo $Funcs[12](); // выводит 144
Однако мы могли бы написать с тем же результатом и
echo Func12();
при том условии, если бы воспользовались кодом генерации функций из листинга 24.1. Кажется, что так короче? Тогда не торопитесь. Все хорошо, если мы точно знаем, что надо вызвать 12-ю функцию, но как же быть, если номер хранится в переменной — например, в $n? Вот решение:
echo $Funcs[$n](); // выводит результат работы $n-й функции
Не правда ли, просто? Выглядит явно лучше, чем такой код:
$F="Func$n"; $F();
Тут нам не удастся обойтись без временной переменной $F (вариант с допол- нительной eval() тоже не подойдет, т. к. у функции могут быть строковые па- раметры, и придется перед всеми кавычками ставить слэши, чтобы поместить их в параметр функции eval().
Оказывается, в PHP версии 4 существует функция, которая поможет нам упростить генерацию "анонимных" функций, подобных полученным в примере из листинга
24.2. Называется она create_function().
string create_function(string $args, string $code)
Создает функцию с уникальным именем, выполняющую действия, заданные в коде $code (это строка, содержащая программу на PHP). Созданная функция будет принимать параметры, перечисленные в $args. Перечисляются они в соответствии со стандартным синтаксисом передачи параметров любой функции. Возвращаемое значение представляет собой уникальное имя функции, которая была сгенерирована. Вот несколько примеров:
$Mul=create_function('$a,$b', 'return $a*$b;'); $Neg=create_function('$a', 'return -$a;');
echo |
$Mul(10,20); |
// |
выводит |
200 |
echo |
$Neg(2); |
// |
выводит |
-2 |
Не пропустите последнюю точку с запятой в конце строки, переданной вторым параметром create_function()!
Глава 24. Управление интерпретатором |
343 |
Давайте теперь перепишем наш пример из листинга 24.2 с учетом create_function(). Это довольно несложно. Обратите внимание, насколько сократился код.
$Funcs=array(); for($i=0; $i<=100; $i++)
$Funcs[]=create_function("","return $i*$i;"); echo $Funcs[12](); // выводит 144
И последний пример применения анонимных функций — в программах сортировки с использованием пользовательских функций:
$a=array("orange", "apple", "apricot", "lemon"); usort($a,create_function('$a,$b', 'return strcmp($a,$b);')); foreach($a as $key=>$value) echo "$key: $value<br>\n";
Проверка синтаксической корректности кода
С помощью create_function() можно проверить, является ли некоторая строка верным PHP-кодом, не запуская при этом сам код. В самом деле, если создание функции с телом — заданной строкой — прошло успешно, значит, код синтаксически корректен. Вот пример:
$fname="file.php"; $code=join("",File($fname));
if(create_function("","?>$code<?"))
echo "Файл $fname является программой на PHP"; else
echo "Файл $fname — не PHP-сценарий";
Мы используем оператор @, чтобы подавить сообщение о том, что функцию создать не удалось, если файл не является верным PHP-сценарием. И, конечно, нам нужно перевести наш код в контекст восприятия документа, для чего, собственно, и нужно обрамление строки тэгами ?> и <?.
Представленный фрагмент, конечно, будет воспринимать любой текстовый файл и HTML-документ как "программу на PHP". И он будет прав, т. к., дейст- вительно, статический текст, в котором нет PHP-вставок, является верным PHP-сценарием.
Другие функции
void usleep(int $micro_seconds)
344 |
Часть IV. Стандартные функции PHP |
Вызов |
этой функции позволяет сценарию "замереть" не указанное время |
(в микросекундах). При этом затрачивается очень немного ресурсов процессора, так что функцию вполне можно вызывать, чтобы дождаться выполнения какой-нибудь операции другого процесса — например, закрытия им файла.
Существует также функция sleep(), которая принимает в параметрах не мик- росекунды, а секунды, на которые нужно задержать выполнение программы.
int uniqid(string $prefix)
Функция uniqid() возвращает строку, при каждом вызове отличающуюся от результата предыдущего вызова. Параметр $prefix задает префикс (до 114 символов длиной) этого идентификатора.
Зачем нужен префикс? Представьте себе, что сразу несколько интерпретаторов на разных хостах одновременно вызвали функцию uniqid(). В этом случае существует вероятность того, что результат работы функций совпадет, чего нам бы не хотелось. Задание в качестве префикса имени хоста решит проблему.
Чтобы добиться большей уникальности, можно использовать uniqid() "в связке" с функциями mt_rand() и md5(), описанными в предыдущих главах.
Глава 25
Управление сессиями
Сессии, наконец-то появившиеся в PHP версии 4, представляют собой механизм, позволяющий хранить некоторые (и произвольные) данные, индивидуальные для каждого пользователя (например, его имя и номер счета), между запусками сценария.
Термин "сессия" является транслитерацией от английского слова session, что в буквальном переводе должно бы означать "сеанс". Однако последнее слово в программистском жаргоне не особенно-то прижилось (насколько я знаю), по- этому я буду употреблять термин "сессия". И да простят меня студенты, если у них это вызывает нехорошие ассоциации.
Фактически, сессия — это некоторое место долговременной памяти (обычно часть на жестком диске и часть — в Cookies браузера), которое сохраняет свое состояние между вызовами сценариев одним и тем же пользователем. Иными словами, поместив в сессию переменную (любой структуры), мы при следующем запуске сценария получим ее в целости и сохранности. Трудно переоценить удобства, которые это предоставляет нам, программистам.
Зачем нужны сессии?
ВWeb-программировании есть один класс задач, который может вызвать довольно много проблем, если писать сценарии "в лоб". Речь идет о слабой стороне CGI — невозможности запустить программу на длительное время, позволив ей при этом обмениваться данными с пользователями.
Вобщем и целом, сценарии должны запускаться, моментально выполняться и возвращать управление системе. Теперь представьте, что мы пишем форму, но в ней такое большое число полей, что было бы глупо поместить их на одну страницу. Нам нужно разбить процесс заполнения формы на несколько этапов, или стадий, и представить их в виде отдельных HTML-документов. Это похоже на работу мастеров Windows — диалоговых окон для ввода данных с кнопками Назад и Дальше, благодаря которым можно переместиться на шаг в любом направлении.
Например, в первом документе с диалогом у пользователя может запрашиваться его имя и фамилия, во втором (если первый был заполнен верно) — данные о его месте
346 |
|
|
|
Часть IV. Стандартные функции PHP |
||
жительства, |
и |
в |
третьем — |
номер |
кредитной |
карточки. |
В любой момент можно вернуться на шаг назад, чтобы исправить те или иные данные. Наконец, если все в порядке, накопленная информация обрабатывается — например, помещается в базу данных.
Реализация такой схемы оказывается для Web-приложений довольно нетривиальной проблемой. Действительно, нам придется хранить все ранее введенные данные в ка- ком-нибудь временном хранилище, которое должно аннулироваться, если пользователь вдруг передумает и "уйдет" с сайта. Для этого, как мы знаем, можно использовать функции сериализации и файлы. Однако ими мы решаем только половину проблемы: нам нужно также как-то привязывать конкретного пользователя к конкретному временному хранилищу. Действительно, предположим, что мы этого не сделали. Тогда, если в момент заполнения какой-нибудь формы одним пользователем на сайт "зайдет" другой и тоже попытается ввести свои данные, получится куча мала.
Все эти проблемы решаются с применением сессий PHP, о которых сейчас и пойдет речь.
Механизм работы сессий
Как же работают сессии? Для начала должен существовать механизм, который бы позволил PHP идентифицировать каждого пользователя, запустившего сценарий. То есть при следующем запуске PHP нужно однозначно определить, кто его запустил: тот же человек, или другой. Делается это путем присвоения клиенту так называемого уникального идентификатора сессии. Чтобы этот идентификатор был доступен при каждом запуске сценария, PHP помещает его в Cookies браузера.
Использовать Cookies не обязательно, существует и другой способ. Мы пого- ворим о нем чуть позже.
Теперь, зная идентификатор (дальше для краткости я буду называть его SID), PHP может определить, в каком же файле на диске хранятся данные пользователя.
Немного о том, как сохранять переменную (обязательно глобальную) в сессии. Для этого мы должны ее зарегистрировать с помощью специальной функции. После регистрации мы можем быть уверены, что при следующем запуске сценария тем же пользователем она получит то же самое значение, которое было у нее при предыдущем завершении программы. Это произойдет потому, что при завершении сценария PHP автоматически сохраняет все переменные, зарегистрированные в сессии, во временном хранилище. Конечно, можно в любой момент аннулировать переменную — "вычеркнуть" ее из сессии, или же уничтожить вообще все данные сессии.
Где же находится то промежуточное хранилище, которое использует PHP? Вообще говоря, вы вольны сами это задать, написав соответствующие функции и зарегистри-
Глава 25. Управление сессиями |
347 |
ровав их как обработчики сессии. Впрочем, делать это не обязательно: в PHP уже существуют обработчики по умолчанию, которые хранят данные в файлах (в системах Unix для этого обычно используется директория /tmp). Если вы не собираетесь создавать что-то особенное, вам они вполне подойдут.
Инициализация сессии
Но прежде, чем работать с сессией, ее необходимо инициализировать. Делается это путем вызова специальной функции session_start().
Если вы поставили в настройках PHP режим session.auto_start=1, то функция инициализации вызывается автоматически при запуске сценария. Од- нако, как мы вскоре увидим, это лишает нас множества полезных возможно- стей (например, не позволяет выбирать свою, особенную, группу сессий). Так что лучше не искушать судьбу и вызывать session_start() в первой строч- ке вашей программы. Следите также за тем, чтобы до нее не было никакого вывода в браузер — иначе PHP не сможет установить SID для пользователя!
void session_start()
Эта функция инициализирует механизм сессий для текущего пользователя, запустившего сценарий. По ходу инициализации она выполняет ряд действий.
rЕсли посетитель запускает программу впервые, у него устанавливается Cookies с уникальным идентификатором, и создается временное хранилище, ассоциированное с этим идентификатором.
rОпределяется, какое хранилище связано с текущим идентификатором пользователя.
rЕсли в хранилище имеются какие-то переменные, их значения восстанавливаются. Точнее, создаются глобальные переменные, которые были сохранены в сессии при предыдущем завершении сценария.
Вообще говоря, рассмотренный механизм, как всегда, не совсем точно соот- ветствует истинному положению вещей. А именно, все зависит от того, какое значение присвоено настроечному параметру register_globals. Если register_globals=0, то в сессии можно будет сохранять (а потом и восста- навливать) только величины, содержащиеся в глобальном ассоциативном мас- сиве $HTTP_SESSION_VARS. Если же этот параметр содержит значение "исти- на" (как обычно и происходит по умолчанию), то в сессии можно регистрировать глобальные переменные.
