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

Самоучитель по PHP 4

.pdf
Скачиваний:
82
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 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. Если же этот параметр содержит значение "исти- на" (как обычно и происходит по умолчанию), то в сессии можно регистрировать глобальные переменные.

348

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

Регистрация переменных

bool session_register(mixed $name [, mixed $name1, ...])

PHP узнает о том, что ту или иную переменную нужно сохранить в сессии, если ее

предварительно зарегистрировать. Для этого и предназначена функция session_register(). Она принимает в параметрах одно или несколько имен переменных (имена задаются в строках, без знака $ слева), регистрирует их в текущей запущенной сессии и возвращает значение "истина", если все прошло корректно.

Почему же тогда я описал типы параметров как mixed, а не как string? Да потому, что на самом деле в функцию можно передавать не одну строку в ка- ждом параметре, а сразу список строк. Каждая такая строка будет регистриро- вать отдельную переменную с соответствующим именем. Более того эле- ментом списка может опять же быть список строк, и т. д.

Нет ничего страшного, если мы дважды зарегистрируем одну и ту же переменную в сессии. На самом деле, чаще всего как раз так и происходит — при повторном запуске сценария. Вот пример:

Листинг 25.1. Пример работы с сессиями

<? session_start();

session_register("count"); $count=@$count+1;

?>

<body> <h2>Счетчик</h2>

В текущей сессии работы с браузером Вы открыли эту страницу <?=$count?> раз(а). Закройте браузер, чтобы обнулить счетчик. </body>

Как видим, все предельно просто.

Идентификатор сессии и имя группы

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

Глава 25. Управление сессиями

349

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

Имя группы сессий

Что, не совсем понятно? Хорошо, тогда рассмотрим пример. Пусть разработчик A написал сценарий счетчика, приведенный в листинге 25.1. Он использует переменную $count, и не имеет никаких проблем. До тех пор, пока разработчик B, ничего не знающий о сценарии A, не создал систему статистики, которая тоже использует сессии. Самое ужасное, что он также регистрирует переменную $count, не зная о том, что она уже "занята". В результате, как всегда, страдает пользователь: запустив сначала сценарий разработчика B, а потом — A, он видит, что данные счетчиков перемешались. Непорядок!

Нам нужно как-то разграничить сессии, принадлежащие одному сценарию, от сессий, принадлежащих другому. К счастью, разработчики PHP предусмотрели такое положение вещей. Мы можем давать группам сессий непересекающиеся имена, и сценарий, знающий имя своей группы сессии, сможет получить к ней доступ. Вот теперь-то разработчики A и B могут оградить свои сценарии от проблем с пересечениями имен переменных. Достаточно в первой программе указать PHP, что мы хотим использовать группу с именем, скажем, sesA, а во второй — sesB.

string session_name([string $newname])

Эта функция устанавливает или возвращает имя группы сессии, которая будет использоваться PHP для хранения зарегистрированных переменных. Если $newname не задан, то возвращается текущее имя. Если же этот параметр указан, то имя группы будет изменено на $newname, при этом функция вернет предыдущее имя.

Session_name() лишь сменяет имя текущей группы и сессии, но не создает новую сессию и временное хранилище! Это значит, что мы должны в большин- стве случаев вызывать session_name(имя_группы) еще до ее инициализа- ции вызова session_start(), в противном случае мы получим совсем не то, что ожидали.

Если функция session_name() не была вызвана до инициализации, PHP будет использовать имя по умолчанию — PHPSESID.

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

350

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

менно активизировать две и более сессий с точки зрения PHP он будет вы- глядеть как два ли более различных пользователя. Однако не забывайте, что, случайно установив в сценарии Cookie, имя которого совпадает с одним из имен группы сессий, вы "затрете" Cookie.

Вот простой пример применения этой функции.

<? session_name("CounterScript" session_start(); session_register("count"); $count=@$count+1;

?>

В текущей сессии Вы открыли эту страницу <?=$count?> раз(а).

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

Идентификатор сессии

Мы уже говорили с вами, зачем нужен идентификатор сессии (SID). Фактически, он является именем временного хранилища, которое будет использовано для хранения данных сессии между запусками сценария. Итак, один SID — одно хранилище. Нет SID, нет и хранилища, и наоборот.

В этом месте очень легко запутаться. В самом деле, как же соотносится идентификатор сессии и имя группы? А вот как: имя — это всего лишь собирательное название для нескольких сессий (то есть, для многих SID), запущенных разными пользователями. Один и тот же клиент никогда не будет иметь два различных SID в пределах одного имени группы. Но его браузер вполне может работать (и часто работает) с несколькими SID, расположенными логически в разных "пространствах имен".

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

string session_id([string $sid])

Функция возвращает текущий идентификатор сессии SID. Если задан параметр $sid, то у активной сессии изменяется идентификатор на $sid. Делать это, вообще говоря, не рекомендуется.

Фактически, вызвав session_id() до session_start(), мы можем подключиться к любой (в том числе и к "чужой") сессии на сервере, если знаем ее идентификатор.