На главную Назад
Добро пожаловать, уважаемый посетитель!

 

ГЛАВА 13

Cookie иотслеживание сеанса

Отслеживаниепользователей и персональная настройкасайта относятся к числу самых популярных ивместе с тем неоднозначно воспринимаемыхвозможностей web-сайтов. Преимуществаочевидны - вы можете предлагатьпользователям именно ту информацию,которая их интересует. С другой стороны,возникает немало вопросов, связанных сконфиденциальностью, поскольку появляетсявозможность <следить> за тем, какпользователь перемещается от страницы кстранице и даже от сайта к сайту.

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

Концепция <наблюдения>за пользователем в процессе перемещения посайту обычно называется <отслеживаниемсеанса> (session tracking). Принимая во вниманиеогромный объем полезной информации,получаемой в результате отслеживаниясеанса на сайте, можно сказать, чтопреимущества отслеживания сеансов иперсональной настройки содержания сайтазначительно превышают любые недостатки.Вряд ли эту книгу можно было бы считатьполноценным учебником по РНР, если бы я непосвятил в ней целую главу средствамотслеживания сеанса в РНР. В этой главе мырассмотрим некоторые концепции, имеющиенепосредственное отношение к отслеживаниюсеансов, а именно - cookie и их применение, атакже уникальные идентификаторы сеансов.Глава завершается сводкой стандартныхфункций РНР, предназначенных дляотслеживания сеансов.

Что такоеcookie?

Cookieпредставляет собой небольшой пакетинформации, переданный web-сервером ихранящийся на клиентском компьютере. В cookieможно сохранить полезные данные,описывающие состояние пользовательскогосеанса, чтобы в будущем загрузить их ивосстановить параметры сеансовой связимежду сервером и клиентом. Cookie используютсяна многих сайтах Интернета для расширениявозможностей пользователя и повышенияэффективности сайта за счет отслеживаниядействий и личных предпочтенийпользователя. Возможность хранения этихсведений играет ключевую роль на сайтахэлектронной коммерции, поддерживающихперсональную настройку и целевую рекламу.

Вследствиетого, что cookie обычно связываются сконкретным пользователем, в них частосохраняется уникальный идентификаторпользователя (UIN). Этот идентификаторзаносится в базу данных на сервере ииспользуется в качестве ключа для выборкииз базы всей информации, связанной с этимидентификатором. Конечно, сохранение UIN вcookie не является обязательным требованием;вы можете сохранить любую информацию приусловии, что ее общий объем не превосходит 4Кбайт (4096 байт).

Компонентыcookie

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

  • Имя- имя cookie является обязательнымпараметром, по которому программассылается на cookie. Можно провести аналогиюмежду именем cookie и именем переменной.
  • Значение-фрагмент данных, связанный с именем cookie. Вэтих данных может храниться любаяинформация - идентификатор пользователя,цвет фона, текущая дата и т. д.
  • Срокдействия -дата, определяющая продолжительностьсуществования cookie. Как только текущая датаи время превосходят заданный срок действия,cookie становится недействительным иперестает использоваться. В соответствиисо спецификацией cookie устанавливать срокдействия для cookie необязательно. Тем неменее, средства РНР для работы с cookie требуют,чтобы срок действия устанавливался.Согласно спецификации, если срок действияне указан, cookie становится недействительнымв конце сеанса (то есть когда пользовательпокидает сайт).
  • Домен -домен, который создал cookie и может читать егозначение. Если домен состоит из несколькихсерверов и доступ к cookie должен бытьразрешен всем серверам, то имя домена можнозадать в форме .phprecipes.com. В этом случае всепотенциальные домены третьего уровня,принадлежащие сайту PHPrecipes (например,wap.phprecipes.com или news.phprecipes.com), смогут работать сcookie. По соображениям безопасности cookie могутустанавливаться только для домена сервера,пытающегося создать cookie. Данный компонентнеобязателен; если он не указан, поумолчанию используется имя домена, изкоторого было полу; чено значение cookie.
  • Путь -URL, с которого предоставляется доступ к cookie.Любые попытки получения доступа к cookie запределами этого пути пресекаются. Данныйкомпонент необязателен; если он не задан, поумолчанию используется путь к документу,создавшему cookie.
  • Безопасность-параметр, показывающий, допускается личтение cookie в небезопасной среде. Поумолчанию используется значение FALSE.

Хотя присоздании cookie используются одни и те жесинтаксические правила, формат храненияcookie зависит от браузера. Например, NetscapeCommunicator хранит cookie в формате следующего вида:

.phprecipes.com  FALSE /  FALSE  97728956  bgcolor  blue

В Internet Explorerто же самое cookie выглядело бы иначе:

bgcolor

blue

localhost/php4/php.exe/book/13/

0

2154887040

29374385

522625408

29374377

*

Чтобыпросмотреть cookie, сохраненные браузеромInternet Explorer, достаточно открыть их в любомтекстовом редакторе. Помните, что некоторыередакторы не обрабатывают завершающиесимволы новой строки и на месте этихсимволов в документе могут выводитьсяквадратики.

InternetExplorer сохраняет свои cookie в папке с именем ,a Netscape Communicator использует для этой цели одинфайл с именем cookies.

Cookie и РНР

Хватиттеории. Конечно, вам не терпится поскорееузнать, как задать значение cookie в РНР.Оказывается, очень просто - для этой целииспользуется стандартная функция setcookie( ).

Функцияsetcookie( ) сохраняет cookie на компьютерепользователя. Синтаксис функции setcookie( ):

int setcookie(string имя [string значение [, int дата [, string путь [, stringдомен [, int безопасность]]]]])

Если выпрочитали общие сведения о cookie, то смыслпараметров setcookie( ) вам уже известен. Если выпропустили этот раздел и не знакомы скомпонентами cookie, я рекомендую вернуться кначалу главы и перечитать его, посколькувсе параметры setcookie( ) были описаны выше.

Преждечем следовать дальше, я попрошу васперечитать следующую фразу не один и не два,а целых три раза. Значение cookie должноустанавливаться до передачи в браузерлюбой другой информации, относящейся кстранице. Напишите эту фразу 500 раз втетрадке, сделайте татуировку, научитесвоего попугая произносить эти слова -короче, проявите фантазию. Другими словами,значение cookie не может устанавливаться впроизвольном месте web-страницы. Оно должнобыть задано до отправки любых данных вбраузер; в противном случае cookie небудет работать.

Есть ещеодно важное ограничение, о котором такженеобходимо помнить, - вы не сможете создатьcookie и использовать его на той же странице.Либо пользователь должен вручную обновитьстраницу (хотя рассчитывать на это нельзя),либо вам придется подождать следующегозапроса этой страницы - и только послеэтого можно будет использовать cookie.

В следующемпримере функция setcookie( ) используется длясоздания cookie с идентификаторомпользователя:

$userid ="4139b31b7bab052";

$cookie_set = setcookie("uid", $value, time()+3600, "/", ".phprecipes.com",0);

Последствиясоздания cookie:

  • Послеперезагрузки или перехода на другуюстраницу становится доступной переменная$userid, содержащая идентификатор 4139b31b7bab052.
  • Срокдействия cookie истекает ровно через один час(3600 секунд) после отправки. После истеченияэтого срока cookie становитсянедействительным.
  • Доступ к cookieразрешен только из домена phprecipes.com.
  • Разрешендоступ к cookie через небезопасный протокол.

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

Листинг13.1. Сохранениецвета фона, выбранного пользователем

<?

// Еслипеременная $bgcolor существует

if (isset($bgcolor)) :

setcookie("bgcolor",$bgcolor, time()+3600);

?>

<html>

<body bgcolor="<?=$bgcolor:?>">

<?

// Значение$bgcolor не задано, отобразить форму

else :

<body bgcolor="white">

<form action="<?print $PHP_SELF; ?>

method=="post">

What's your favoritebackground color?

<select name="bgcolor">

<option value="red">red

<option value="blue">blue

<option value="green">green

<option value="b1ack">black

</select>

<input type="submit"value="Set background color">

</form>

<?

endif;

?>

</body>

</html>

При загрузкев браузер сценарий проверяет, было лизадано значение переменной $bgcolor. Еслипеременная существует, для страницывыбирается цвет фона, определяемыйпеременной $bgcolor. В противном случае вбраузере выводится форма HTML с предложениемвыбрать цвет фона. После выбора цветазначение $bgcolor будет распознаваться припоследующей перезагрузке той же страницыили при переходе к другой странице.

Кстатиговоря, имена cookie могут выглядеть какэлементы массива. Вы можете использоватьимена вида uid[1], uid[2], uid[3] и т. д., а затемработать с ними, как с элементами обычногомассива. Пример приведен в листинге 13.2.

Листинг 13.2.

<?

setcookie("phprecipes[uid]","4139b31b7bab052", time( )+3600); setcookie("phprecipes[color]","black", time( )+3600); setcookie("phprecipes[preference]","english", timeO+3600);

if (isset($phprecipes)):

while (list ($name,$value) = each ($phprecipes)) :

echo "$name = $value<br>\n";

endwhile;

endif:

?>

В результатевыполнения этого фрагмента будет выведенследующий результат (а на клиентскомкомпьютере будут созданы три cookie):

uid = 4139b31b7bab052

color = black

preference = english

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

Cookie чащевсего применяются для хранения числовыхидентификаторов (UIN), по которым вдальнейшем на сервере производится выборкаинформации,

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

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

mysql>create tableuser_info (

->user_id char (18),

->fname char(15),

->email char(35));

По сравнениюс полноценными сценариями регистрациипользователя, работа листинга 13.3начинается <на половине пути>:предполагается, что данные пользователя (идентификатор,имя и адрес электронной почты) уже хранятсяв базе данных. Чтобы пользователю неприходилось вводить всю информацию заново,идентификатор (в листинге 13.3 для простотыон равен 15) загружается из cookie на клиентскомкомпьютере.

Листинг13.3. Загрузкаинформации пользователя из базы данных

<?

if (!isset($userid)) :

$id = 15;

setcookie ("userid",$id, time( )+3600);

print "A cookiecontaining your userID has been set on your machine.

Please refresh the pageto retrieve your user information";

else:

@mysql_connect("localhost","web", "4tf9zzzf") or die("Could not connect to MySQLserver!");

@mysql_select_db("user") or die("Could not selectuser database!");

// Объявитьзапрос

$query = "SELECT *FROM users13 WHERE user_id = '$userid'";

// Выполнитьзапрос

$result = mysql_query($query)l;

// Еслисовпадение будет найдено, вывести данныепользователя.

if (mysql_num_rows($result) == 1) :

$row = mysql_fetch_array($result);

print "Hi ".$row["?name"].",<br>";

print "Your emailaddress is ".$row[ "email"];

else:

print "InvalidUser ID!";

endif;

mysql_close();

endif;

?>

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

В следующемразделе приведен сценарии полнойрегистрации пользователя и последующегосохранения UIN в базе данных.

ФункцииMySQL, встречающиеся в листинге 13.3, былиописаны в главе 11.

Уникальныеидентификаторы

Вероятно, увас уже возник вопрос: как сгенерировать UIN,который действительно был бы уникальным?Отложите в сторону учебники - замысловатыеалгоритмы вам не понадобятся. В РНРпредусмотрено простое средство длясоздания уникальных UIN - встроеннаяфункция uniqid( ).

Функцияuniqid( ) генерирует уникальный идентификатор.из13 символов, значение которого основано натекущем времени. Синтаксис функции uniqid( ): intuniqid(string префикс boolean дополнение])

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

$uniq_id = uniqid("");

//Генерируется строка из 13 символов -например. '39b3209ce8ef2'

В другомварианте сгенерированное значениеприсоединяется к строке, определяемойпараметром префикс:

$uniq_id = uniqid("php",FALSE):

//Генерируется строка из 16 символов -например. 'php39b3209ce8ef2'

Посколькуuniqid( ) генерирует UIN на основании текущеговремени, существует ничтожная вероятностьтого, что идентификатор удастся подобрать.Чтобы значение идентификатора былодействительно случайным, можнопредварительно сгенерировать префикс припомощи еще одной стандартной функции РНР,rand( ). Эта возможность продемонстрирована вследующем примере:

srand((double)microtime( ) * 1000000);

$uniq_id = uniqid(rand( ));

Функция srand( )инициализирует (<раскручивает>) генераторслучайных чисел. Если вы хотите, чтобыфункция rand( ) генерировала действительнослучайные числа, необходимо предварительновызвать srand( ). Передача rand( ) в качествепараметра uniqid( ) приводит к тому, что функцияuniqid( ) вызывается с заранее сгенерированнымслучайным префиксом, что усложняет подборсгенерированного UIN.

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

форму сименем и адресом электронной почты. Этаинформация вместе со сгенерированнымуникальным идентификатором сохраняется втаблице user_info, определение которойприведено перед листингом 13.3. Cookie с этимидентификатором сохраняется на компьютерепользователя. При всех последующихпосещениях сценарий ищет в базе данныхуникальный идентификатор, взятый из cookie, ивыводит в браузере найденную информацию опользователе.

Листинг13.4. Процессрегистрации пользователя

<?

// Построитьформу

$form = "

<form action=\"Listingl3-4.php\"method=\"post\">

<input type=\"hidden\"name=\"seenform\" value=\"y\"> \

Your first name?:<br>

<input type=\"text\"name=\"fname\" size=\"20\" maxlength=\"20\" value=\"\"><br>

Your email?:<br>

<input type=\"text\"name=\"email\" size=\"20\" maxlength=\"35\" value=\"\"><br>

<input type=\"submit\"value=\"Register!\">

</form>

// Если формаеще не отображалась

// и дляданного пользователя еще не существует cookie...

1f ((! isset (Sseenform))&& (! isset ($userid))) :

print $form;

// Если формаотображалась.

// но данныепользователя еще не были обработаны...

elself (isset ($seenform)&& (! isset ($sserid))) :

srand ((double)microtime( ) * 1000000);

$uniq_id = uniqid(rand());

//Подключиться к серверу MySQL и выбрать базуданных users

@mysql_pconnect("localhost","root", "") or die("Could not connect to MySQL server!");

@mysql_select_db("book")or die("Could not select user database!");

// Объявить ивыполнить запрос

$query = "INSERTINTO users13 VALUES('$uniq_id', '$fname', '$email')";

$result = mysql_query($query)or die("Could not insert user information!");

// Создатьcookie "userid" со сроком действия один месяц,setcookie ("userid", $uniq_id, tirne( )+2592000);

print "Congratulations$fname! You are now registered! Your user information will be displayed upononeach subsequent visit to this page.";

// ... иначе, если cookieсуществует - использовать идентификатор

//пользователя для выборки данных из базыданных users elseif (isset($userid)) :

//Подключиться к серверу MySQL и выбрать базуданных users

@mysql_pconnect("localhost","root", "") or die("Could not connect to MySQL server!");

@mysql_select_db("book")or die("Could not select user database!");

// Объявить ивыполнить запрос

$query = "SELECT *FROM users,13 WHERE user_id = '$userid' ";

$result = mysql_query($query)or die("Could not extract user information!"

$row =mysql_fetch_array($result); print "Hi ".$row["fname"].",<br>";

print "Your email address is ".$row["email"];

endif;

?>

Обилиекоманд i f позволяет организовать весьпроцесс регистрации и последующуюидентификацию пользователя в одномсценарии. Принципиально возможны триситуации:

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

Конечно,общий процесс, продемонстрированный влистинге 13.4, может применяться при работе слюбыми базами данных. Листинг всего лишь наочень простом уровне показывает, как накрупных сайтах организуется сохранениепользовательских данных, благодарякоторому сайт <подстраивается> под каждогопосетителя.

На этомзавершается наше знакомство с применениемcookie в РНР. Если вы захотите побольше узнатьо механизме cookie, обратитесь к ресурсамИнтернета, перечисленным в следующемразделе.

Ссылки потеме

Дополнительнуюинформацию о cookie и их использовании можнонайти в специализированных web-pecypcax:

Какговорилось выше, в cookie очень удобно хранитьпараметры, специфические для данногопользователя, автоматически загружаемыепри последующих посещениях сайта. Впрочем,на cookie нельзя полностью положиться, потомучто пользователи могут запретить ихиспользование на своем компьютере внастройках браузера. К счастью, в РНР длясохранения подобной информации существуети другой способ. Эта методика называется отслеживаниемсеанса (session tracking) ирассматривается в следующем разделе.

Отслеживаниесеанса

Сеансом (session)называется период времени, которыйначинается с момента прихода пользователяна сайт и завершается, когда пользовательпокидает сайт. В течение сеанса частовозникает необходимость в сохраненииразличных переменных, которые бы <сопровождали>пользователя при перемещениях на сайте,чтобы вам не приходилось вручнуюкодировать многочисленные скрытые поля илипеременные, присоединяемые к URL.

Рассмотримследующую ситуацию. При входе на сайтпользователю присваивается уникальныйидентификатор сеанса (SID), которыйсохраняется на компьютере пользователя вcookie с именем PHPSESSJD. Если использование cookieзапрещено или cookie вообще не поддерживаются,SID автоматически присоединяется ко всемлокальным URL на протяжении сеанса. В то жевремя на сервере сохраняется файл, имякоторого совпадает с SID. По мере того какпользователь перемещается по сайту,значения некоторых параметров должнысохраняться в виде сеансовых переменных.Эти переменные сохраняются в файлепользователя. При последующем обращении ксеансовой переменной сервер открываетсеансовый файл пользователя и ищет в немнужную переменную. В сущности, в этом изаключается суть отслеживания сеанса.Конечно, информация с таким же успехомможет храниться в базе данных или в другомфайле.

Интересно?Еще бы. После всего сказанного вы,несомненно, лучше поймете различныепроблемы конфигурации, рассматриваемыениже. Особенно важную роль играют три флага.Первый флаг, --enable-trans-id, включается в процессконфигурации в том случае, если высобираетесь использовать SID (см. ниже). Двадругих флага, track_vars и register_globals, включаются иотключаются по мере необходимости в файлеphp.ini. Последствия активизации этих флаговрассматриваются ниже.

--enable-trans-id

Если РНРкомпилируется с этим флагом, ко всемотносительным URL автоматическиприсоединяется идентификатор сеанса (SID).Дополнение записывается в формате имя_сеанса=идентификатор_сеанса,где имя_сеанса определяется в файле php.ini (см.ниже). Если вы не захотите включать этотфлаг, в качестве SID можно использоватьконстанту.

track_vars

Установкафлага track_vars позволяет использовать массивы$HTTP_*_VARS[], где * заменяется одним из значенийEGPCS (Environment, Get, Post, Cookie, Server). Данный флагнеобходим для того, чтобы значения SIDпередавались с одной страницы на другую. ВРНР 4.03 этот флаг всегда находится вустановленном состоянии.

register_globals

В результатеустановки этого флага все переменные EGPCSстановятся доступными глобально. Если вы нехотите, чтобы массив глобальных переменныхзаполнялся данными, которые вам, возможно, ине понадобятся, флаг следует сбросить.

Если флагregister_globals сброшен, а флаг track_vars установлен,ко всем переменным GPC можно обращатьсячерез массив $HTTP_*_VARS[]. Например, еслисбросить флаг register_globals, к стандартнойпеременной $PHP_SELF придется обращаться в виде$HTTP_SERVER_VARS["PHP_SELF"].

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

Таблица13.1. Сеансовыедирективы в файле php.ini

Директива Описание
session.save_handler =files

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

session.save_path =/tmp

Определяеткаталог для сеансовых файлов РНР. Наплатформе Linux обычноиспользуется значение по умолчанию ('/tmp'). Наплатформе Windows следуетуказать путь к какому-нибудь каталогу, впротивном случаепроизойдет ошибка

session_use_cookies =1

Приустановке этого флага для сохраненияидентификатора сеанса на компьютерепользователя используются cookie

session.name =PHPRESSID.

Если флагsession.use_cookies установлен, то значение session.nameиспользуетсяв качестве имени cookie. Имя может состоятьтолько из алфавитно-цифровыхсимволов

session.auto_start = 0

Приустановке флага session.auto_start сеансавтоматически инициируетсяпри первоначальном запросе со стороныклиента

 session.cookie_lifetime = 0

Если флагsession.use_cookies установлен, то значение session.cookie_lifetimeопределяет срок действия отправляемых cookie.Еслипараметр равен 0, то все cookie становятсянедействительными призавершении сеанса

session.cookie_path = /

Если флагsession.use_cookies установлен, то значение session.cookie_pathопределяет каталог, для которогоотправляемые cookieсчитаются действительными

 session.cookie_domain =

Если флагsession.use_cookies установлен, то значение session.cookie_domainопределяет домен, для которогоотправляемые cookieсчитаются действительными

session.serialize_handler= php

Имяобработчика, используемого в процессесериализации данных. В настоящеевремя определены два возможных значения: phpи WDDX

session.gc_probability=1Вероятностьактивизации сборщика мусора РНР (впроцентах)
session.gc_maxlifetime=1440

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

session.referer_check = 

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

session.enthropy_fiie =

Ссылка навнешний файл с дополнительной случайнойинформацией, используемойпри генерации идентификаторов сеансов. Всистемах UNIX для этойцели обычно используются два устройства, /dev/randomи /dev/urandom.Устройство /dev/random получает случайныеданные от ядра, а устройство /dev/urandomгенерирует случайную строку при помощи хэш-алгоритмаМ05. Короче говоря, /dev/random работает быстрее, a/dev/urandom генерирует <более случайные> строки

session.enthropy_length= 0Если флаг session.enthropy_file установлен, тоsession.enthropyjength определяет количество байт,читаемых из файла session.enthropy_file
session.cache limiter= nocache Способуправления кэшем для страниц сеанса. Внастоящее время определены три возможныхзначения: nocache, public и private
session.cache_expire=180Продолжительностьжизни кэшированных страниц сеанса (вминутах)

Послевнесения всех необходимых изменений внастройку сервера мы переходим кнепосредственной реализации отслеживаниясеанса на вашем сайте. Благодаря несколькимстандартным функциям РНР этот процесс нетак уж сложен. Первое, что необходимо знать,- сеанс инициируется функцией session_start( ).Конечно, при включении директивы session.auto_startв файл php.ini (см. выше) необходимость в вызовеэтой функции отпадает. Тем не менее, воставшейся части этого раздела я будуиспользовать эту функцию, чтобы примерывыглядели более последовательно. Функцияsession_start( ) имеет простой синтаксис,поскольку она не получает параметров ивозвращает логическую величину.

Директиваsession.save_handler настолько важна, что я счелнеобходимым посвятить ей отдельный раздел.Он находится в конце главы под заголовком <Назначениепользовательских функций для хранениясеансовых данных>.

session_start( )

Функцияsession_start( ) имеет двойное назначение. Сначалаона проверяет, начал ли пользователь новыйсеанс, и если нет - начинает его. Синтаксисфункции

session_start( ): booleansession_start()

Если функцияначинает новый сеанс, она выполняет триоперации: назначение пользователю SID,отправку cookie (если в файле php.ini установленфлаг session_cookies) и создание файла сеанса насервере. Второе назначение функциизаключается в том, что она информирует ядроРНР о возможности использования в сценарии,в котором она была вызвана, сеансовыхпеременных.

Сеансначинается простым вызовом session_start( )следующего вида:

session_start( ):

Если сеансможно создать, значит, его можно иуничтожить. Это делается функцией session_destroy().

Функцияsession_start( ) возвращает TRUE независимо отрезультата. Следовательно, проверять ее вусловиях if или в команде die( ) бессмысленно.

session_destroy()

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

boolean session_destroy()

Следуетпомнить, что эта функция неуничтожает cookie набраузере пользователя. Впрочем, если вы несобираетесь использовать cookie после концасеанса, просто присвойте параметруsession.cookie_lifetime в файле php.ini значение ( ) (используемоепо умолчанию). Пример использования функции:

<?

session_start( );

// Выполнитьнекоторые действия для текущего сеанса

session_destroy( ):

?>

Теперь выумеете уничтожать сеансы, и мы можемперейти к работе с сеансовыми переменными.Возможно, самой важной сеансовойпеременной является SID (идентификаторсеанса). Его легко можно получить при помощифункции session_id( ).

session_id( )

Функцияsession_id( ) возвращает SID для сеанса, созданногофункцией session_start( ). Синтаксис функции session_id():

string session_id ([string sfd])

Если внеобязательном параметре передаетсяидентификатор, то значение SID текущегосеанса изменяется. Однако следуетучитывать, что cookie при этом заново непересылаются. Пример:

<?

session_start()

print "Yoursession identification number is ".sessionjd( ):

session_destroy( ):

?>

Результат,выводимый в браузере, выглядит примерно так:

Your sessionidentification number is 067d992a949114ee9832flcllcafc640

Как жесоздать свою сеансовую переменную? Спомощью функции session_register( ).

session_register( )

Функцияsession_register( ) регистрирует имена одной илинескольких переменных для текущего сеанса.Синтаксис функции session_register( ):

boolean session_register(mixed имя_переменной1 [, mixed имя_переменной2... ])

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

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

session_is_registered()

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

boolean session_is_registered(string имя_переменной)

Применениефункций session_register( ) и session_is_registered( ) будетпродемонстрировано на классическомпримере использования сеансовыхпеременных - счетчике посещений (листинг13.5).

Листинг13.5. Счетчикпосещений сайта пользователем

<?

session_start( ):

if (! sessionjs_registered('hits')) :

session_register( 'hits') ;

endif ;

$hits++:

print "You've seen this page $hitstimes.

?>

Сеансовыепеременные можно не только создавать, но иуничтожать. Для этой цели применяетсяфункция session_unregister( ).

session_unregister( )

Сеансовыепеременные уничтожаются функциейsession_unregister( ). Синтаксис:

boolean session_unregister(string имя_переменной')

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

<?

session_start()

session_register('username');

//Использовать переменную $username.

// Когдапеременная становится ненужной -уничтожить ее.

session_unregister('username');

session_destroy();

?>

Как и вслучае с функцией session_register, помните, что впараметре указывается не сама переменная (тоесть имя с префиксом $). Вместо этогоуказывается имя переменной.

session_encode( )

Функцияsession_encode( ) обеспечивает чрезвычайно удобнуювозможность форматирования сеансовыхпеременных для хранения (например, в базеданных). Синтаксис функции session_encode( ):

boolean session_encode()

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

Примериспользования session_encode( ) приведен влистинге 13.6. Предположим, что на компьютере<зарегистрированного> пользователяимеется cookie, в котором хранится уникальныйидентификатор этого пользователя. Когдапользователь запрашивает страницу,содержащую листинг 13.6, UID читается из cookie иприсваивается идентификатору сеанса. Мысоздаем несколько сеансовых переменных иприсваиваем им значения, после чегоформатируем всю информацию функциейsession_encode( ) и заносим в базу данных MySQL.

Листинг13.6. Использованиефункции session_encode( ) для сохранения данных вбазе данных MySQL

<?

//Инициировать сеанс и создать сеансовыепеременные

session_register('bgcolor');

session_register('fontcolor');

//Предполагается, что переменная $usr_id (суникальным

// идентификатором пользователя)хранится в cookie

// на компьютере пользователя.

// При помощифункции session_id( ) присвоить идентификатору

//сеанса уникальный идентификаторпользователя (UID),

// хранящийся в cookie. $id =session_id($usr_id);

// Значенияследующих переменных могут задаватьсяпользователем

// на форме HTML $bgcolor= "white"; $fontcolor = "blue";

//Преобразовать все сеансовые данные в однустроку

$usr_data = session_encode( );

//Подключиться к серверу MySQL и выбрать базуданных users

@mysql_pconnect("localhost", "web","4tf9zzzf")

or die("Could notconnect to MySQL server!");

@mysql_select_db("users")

or die("Could notselect user database!");

// Обновитьпользовательские параметры страницы

$query = "UPDATEuser_info set page_data='$usr_data' WHERE user_id= '$id'";

$result - mysql_query($query)or die("Could not update user information!");

?>

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

session_decode( )

Всесеансовые данные, ранее преобразованные встроку функцией sessi on_encode( ),восстанавливаются функцией session_decode( ).Синтаксис:

string session_decode(string сеансовые_данные)

В параметресеансовые_данные передаетсяпреобразованная строка сеансовыхпеременных, возможно - прочитанная изфайла или загруженная из базы данных.Строка восстанавливается, и все сеансовыепеременные в строке преобразуются кисходному формату.

В листинге13.7 продемонстрировано восстановлениезакодированных сеансовых переменныхфункцией session_decode( ). Предположим, таблица MySQLс именем user_info состоит из двух полей: user_id иpage_data. Пользовательский UID, хранящийся в cookieна компьютере пользователя, применяетсядля загрузки сеансовых данных, хранящихся вполе page_data. В этом поле хранитсязакодированная строка переменных, одна изкоторых ($bgcolor) содержит цвет фона, выбранныйпользователем.

Листинг13.7. Восстановлениесеансовых данных, хранящихся в базе данныхMySQL

<?

//Предполагается, что переменная $usr_id (суникальным

// идентификатором пользователя)хранится в cookie

// на компьютере пользователя.

$id = session_id($usr_id);

//Подключиться к серверу MySQL и выбрать базуданных users

@mysq]_pconnect("localhost", "web","4tf9zzzf")

or die("Could notconnect to MySQL server!");

@mysql_select_db("users")

or die("Could notselect company database!");

// Выбратьданные из таблицы MySQL

$query = "SELECTpage_data FROM user_info WHERE user_id= '$id'",

Sresult = mysql_query($query);

$user_data =mysql_result($result, 0. "page_data");

//Восстановить данные session_decode($user_data):

// Вывестиодну из восстановленных сеансовыхпеременных

print "BGCOLOR: $bgcolor";

?>

Как видно издвух приведенных листингов, функции session_encode( ) и ses-sion_decode( ) обеспечивают оченьудобные и эффективные сохранение изагрузку сеансовых данных.

Назначениепользовательских функций для хранениясеансовых данных

Хранитьсеансовые данные в файлах удобно, но вполневозможно, вы захотите воспользоватьсядругими средствами - например, базамиданных. А может быть, вы хотите применитьодин и тот же сценарий на разных сайтах дляразных баз данных. Существует и другаяраспространенная проблема - стандартнаядля РНР процедура хранения сеансовыхданных в файлах затрудняет совместноеиспользование данных на разных серверах. Ксчастью, все эти проблемы отслеживаниясеансов в РНР решаются очень просто,поскольку РНР дает пользователювозможность установить собственнуюпроцедуру сохранения при помощистандартной функции session_set_save_handler( ).

Функцияsession_set_save_handler( ) определяет процедурысохранения и загрузки сеансовых данныхпользовательского уровня.

Синтаксисфункции session_set_save_handler():

void session_set_save_handler(string open,string close, string read, string write, string destroy, string go)

Шестьпараметров session_set_save_handler( ) соответствуютшести функциям, вызываемым сеансовымифункциями РНР. Хотя имена этих функциймогут быть произвольными, каждая функциядолжна получать жестко заданный наборпараметров. Перед тем как переходить крассмотрению примера, просмотрите таблицу13.2 - в ней описаны назначение всех шестифункций и их параметры.

Чтобыиспользовать функцию session_set_save_handler( ),необходимо присвоить па-раметру session.save_handlerв файле php.ini значение user.

Таблица13.2. Шестьпараметров функции session_set_save_handler( )

ПараметрОписание
sess_close()Вызываетсяпри завершении сценария, в которомреализуются сеансовые функции. Непутайте эту функцию сфункцией sess_destroy( ), предназначенной дляуничтожения сеансовых переменных. Функцияsess_close( ) вызывается без параметров
sess_destroy($идент_ceaнca)Удаляет всесеансовые данные. Параметр определяетудаляемый сеанс
sess_gc($срок_действия) Удаляет все сеансы сзавершенным сроком действия. Срокопределяется параметром $срок_действия,значение которого задается в секундах.Параметр читается из файла php.ini исоответствует значению session.gcjifetime
sess_open($путь, $имя) Вызывается при инициализации нового сеансафункцией session_start( ) или session_register( ). Двапараметра читаются из файла php.ini исоответствуют значениям session.save_path и session.name
sess_read($ключ)Используетсядля выборки значения сеансовой переменной,определяемой заданным ключом
sess_write($ключ, $значение)Используетсядля сохранения сеансовых данных. Любыеданные, сохраненные функцией sess_write( ),позднее могут быть прочитаны функцией sess_read().Параметр $ключ соответствует именисеансовой переменной, а параметр $значение- значению, связываемому с заданным ключом

Теперь,когда вы знаете все, что необходимо знать опараметрах session_set_save_handler( ), мы рассмотримпример реализации сеансовых функций набазе MySQL (листинг 13.8).

Листинг13.8. Реализациясеансовых функций на базе MySQL

<?

// Реализациясеансовых функций на базе MySQL

// Хост, имяпользвателя и пароль

$host = "localhost"; $user ="web"; $pswd = "4tf9zzzf";

// Именатаблицы и базы данных

$db = "users";

$session table = "usersession data";

// Прочитатьзначение sess.gc_lifetime из файла php.ini

$sess_life =get_cfg_var("sess.gc_lifetime");

// Функция :mysql_sess_open()

// Назначение:подключение к серверу MySQL

// и выборбазы данных.

function mysql_sess_open($save_path.$session_name) {

GLOBAL $host. $user, $pswd,$db;

@mysql_connect($host, $user,$pswd)

or die("Can'tconnect to MySQL server!");

@mysql_select_db($db)

or die("Can'tselect session database!");

}

// Функция:mysql_sess_close()

// Назначение:в реализации на базе MySQL эта функция неиспользуется.

// Тем неменее, она Обязательно* должна бытьопределена.

function diysql_sess_close() {

return true:

}

// Функция:mysql_sess_read()

// Назначение:загрузка информации из базы данных MySQL.

function mysql_sess_read($key) {

GLOBAL $session_table:

$query = "SELECTvalue FROM $session_table WHERE sess_key = '$key'";

$result = mysql_query($query);

if (list($value) =mysql_fetch_row($result)) :

return $value;

endlf;

return false;

}

// Функция:mysql_sess_write( )

// Назначение:запись информации в базу данных MySQL.

function mysql_sess_write($key,$val) {

GLOBAL $sess_life, $session_table;

$expiratlon =time() + $sess_life;

$query = "INSERTINTO Ssession_table VALUES('$key', '$expiration', '$value')";

$result = mysql_query($query);

// Еслизапрос на вставку данных завершилсянеудачей // из-за присутствия первичногоключа в поле sess_key, // выполнить обновление.

if (! $result) :

$query = "UPDATE$session_table

SET sess_expiration = '$expiration',sess_value='Svalue'

WHERE sess_key = '$key'";$result = mysql_query($result);

endif;

}

// Функция:mysql_sess_destroy()

// Назначение:удаление из таблицы всех записей с ключом,равным $sess_id

function mysql_sess_destroy(Ssess_id) {

GLOBAL $session_table:

$query = "DELETEFROM $session_table WHERE sess_key = '$sess_id'";

$result = mysql_result($query);

return $result;

}

// Функция:mysql_sess_gc()

// Назначение:удаление всех записей, у которых

// срок жизни< текущее время - session.gc_lifetime

function mysql_sess_gc($max_lifetime){

GLOBAL $session_table:

$query = "DELETEFROM $session_table WHERE sess_expiration < ".time();

$result = mysql_query($query);

return mysql_affected_rows();

session_set_save_handler("mysql_sess_open","mysql_sess_close","mysql_sess_read", "mysql_sess_write","mysql_sess_destroy", "mysql_sess_gc");

?>

После тогокак эти шесть функций будутзарегистрированы в программе, их можновызывать по абстрактным именам (sess_close( ),sess_destroy( ), sess_gc( ), sess_open( ), sess_read( ) или sess_write( )).Такой подход удобен тем, что вы можетесоздать сколько угодно реализаций ипереключаться между ними, вызываяses-sion_set_save_handler( ) по мере необходимости.

Проект:журнал посещений сайта

Статистическиесведения о посетителях сайта приносятнемалую пользу. Как вы уже знаете,сохранение информации о посетителях широкопрактикуется на сайтах рекламных web-агентстви порталов, а также на многих других сайтах,желающих получить дополнительные сведенияо своих посетителях. Хотя системы учетабывают невероятно сложными, дажеотносительно простая система ведения учетаоткрывает немало интересных возможностей.Я покажу, как реализовать простейший журналпосещений на базе РНР, MySQL и cookie.

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

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

Какреализовать подобный сценарий на РНР?Прежде всего необходимо создать таблицу MySQLдля хранения информации:

mysql>create tablevisitors (

->browser char(85)NOT NULL. ->ip char(30) NOT NULL.

->host char(85) NOT NULL.

->timeOfVisitdatetime NOT NULL

->);

В поле browserхранится информация, непосредственноотносящаяся к браузеру посетителя. Онаберется из переменной РНР с именем $HTTP_USER_AGENT.В поле ip хранится IP-адрес посетителя. В полеhost хранится информация о провайдере, откоторого поступил IP-адрес. Наконец, полеtimeOfVisit содержит дату и время посещениясайта.

Полноценноеприложение для ведения журнала посещенийимеется на сайте ресурсов РНР phpinfo.net (http://www.phpinfo.net).Более того, вы сможете непосредственно

на сайтеувидеть, как оно работает. К сожалению,перед посещением этого сайта вам

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

Затем мысоздаем инициализационный файл приложенияinit.inc (листинг 13.9), содержащий определенияглобальных переменных и основных функций.Обратите внимание: в функции viewStats( )используется сценарий sniffer.php из главы 8.Этот сценарий включается в файл init.inc помере необходимости. Рекомендую потратитьнемного времени на просмотр этого сценарияи комментариев к нему.

Листинг13.9. Инициализационныйфайл приложения (init.inc) <?

// Файл: init.inc

// Назначение:инициализационный файл журнала посещенийсайта

// Параметрысоединения с сервером MySQL $host = "localhost";

$user = "root";$pswd = "";

// Имя базыданных Sdatabase = "myTracker";

// Имятаблицы $visitors_table = "visitors":

@mysql_pconnect($host,$user, $pswd) or die("Couldn't connect to MySQL server!");

// Выбратьбазу данных

@mysql_select_db($database)or die("Couldn't select $database database!");

//Максимальное количество посещений,отображаемое в таблице $maxNumVisitors = "5";

// Имя cookie

$cookieName = "visitorlog";

// Значениеcookie $cookieValue="1";

// Срок,который должен пройти с момента последнегопосещения сайта,

// чтобы информация отекущем посещении была сохранена в базеданных.

// Если переменная $timeLimit равна 0.сохраняются все посещения

// независимо отих частоты.

// Остальныецелочисленные значения интерпретируютсякак интервал

// времени в секундах.

$timeLimit = 3600:

// Форматотображения данных в браузере

$header_color = "#cbda74";

$table_color = "#000080";

$row_color = "IcOcOcO";

$font_color = "#000000":

$font_face = "Arial.Times New Roman. Verdana";

$font_size ="-1";

function recordUser() {

GLOBAL $visitors_table,$HTTP_USER_AGENT, $REMOTE_AODR, $REMOTE_HOST; if ($REMOTE_HOST - ""):

$REMOTE_HOST - "localhost";endif;

$timestamp - date("Y-m-dH:i:S");

$query - "INSERTINTO $visitors_table VALUES('$HTTP_USER_AGENT', '$REMOTE_ADDR', '$REMOTE_HOST','$timestamp')";

Sresult = @mysql_query($query);}

// recordUser functionviewStats() {

GLOBAL $visitors_table,$maxNumVisitors, $table_color, $header_color;

GLOBAL $row color. $font color,$font face, $font size:

$query = "SELECTbrowser, ip. host. TimeofVisit FROM $visitors_table ORDER BY TimeofVisit descLIMIT 0, $maxNumVisitors";

$result = mysql_query($query);

print "<tablecellpadding=\"2\" cellspacing=\"1\" width = \"700\"border = \"0\" bgcolor=\ " $table_color\ ">";

print "<trbgcolor= \"$header_color\"><th>Browser</th><th>IP</th><th>Host</th><th>TimeofVisit</th></tr>";

while($row =mysql_fetch_array($result));

list ($browse_type,$browse_version) = browser_info ($row["browser"]); $op_sys =opsys_info ($row["browser"]);

print "<trbgcolor=\"$row_color\">";

print "<td><fontcolor=\"$font_color\" face=\"$font_face\" size=\"$font_size\">$browse_type$browse_version = $op_sys</font></td>";

print "<td><fontcolor=\"$font_color\" face=\"$font_face\" si ze=\"$font_size\">".$row["ip"]."</font></td>";

print "<td><fontcolor=\"$font_color\" face=\"$font_face\" size=\"$font_size\">".$row["host"]."</font></td>";

print "<td><fontcolor=\"$font_color\" face=\"$font_face\" size=\"$font_size\">";

print $row["TimeofVisit"]."</font></td>";

print "</tr>";

endwhile;

print"</table>"; }

// viewStats

?>

Фрагменткода, приведенный в листинге 13.10, проверяетсуществование cookie и при необходимостивызывает функцию recordUser( ). Я привожу этотфрагмент в составе очень простогоиндексного файла index.php.

Листинг13.10. Проверкасуществования cookie (index.php)

<?

include("Listing13-9.php");if (! isset($$cookieName)) :

// Создатьcookie

setcookie($cookieName,$cookieValue, time()+$timeLimit);

// Сохранитьинформацию о посетителе recordUser();

endif:

?>

<html>

<head>

<title>Wecome toMy Site!</title>

</head>

<body bgcolor="#c0c0c0"text="#000000" link="#808040" " vlink="#808040"alink="#808040">

Welcome to my site.<a href = "visitors.php">Check out who else has recently visited</a>.

</body>

</html>

Какорганизовать просмотр информации,хранящейся в базе данных MySQL, в браузере?Задача решается простым вызовом функцииviewStats( ) в отдельном файле visitors.php:

<html>

<?

include("sniffer.inc"):

include("init.inc");

?>

<head>

<title>Mostrecent <?=$maxNumVisitors:?> visitors</title>

</head>

<body bgcolor="#ffffff"text="#000000" link="#808040" vlink="#808040"alink="#808040">

viewStats( );

?>

</body>

</html>

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

На рис. 13.1показан пример выходных данных viewStats( ) дляатрибутов форматирования, заданных в файлеinit.inc.

Рис. 13.1. Примеррезультата, сгенерированного функциейviewStats( )

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

Итоги

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

  • общиесведения о cookie;
  • работа сcookie в РНР;
  • созданиеуникальных идентификаторов;
  • сценариирегистрации пользователей;
  • общиесведения о сеансах;
  • параметрысеансов в файле php.ini;
  • стандартныесеансовые функции в РНР;
  • функцияsession_set_save_handler( );
  • учетпосещений сайта.

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

На этомзавершается вторая часть книги. Третьячасть, <РНР для профессионалов>,открывается обзором интеграции РНР с XML. Нерасслабляйтесь, самое интересное впереди.