PHP 4 на практике

         

Динамическое конструирование форм


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

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

он экономит немало времени как при исходном программировании, так и при последующем сопровождении программы.

Пример 7: построение раскрывающегося списка

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

В листинге 10.9, как и в предыдущих примерах, реализован вариант с одним сценарием. Сначала мы проверяем, было ли присвоено значение переменной $site. Если проверка дает положительный результат, вызывается функция header( ) с параметром, в котором значение $site присоединяется к строке «Location:http://». При передаче этой команды функция header О перенаправляет браузер на указанный URL. Если значение переменной $site не задано, форма выводится в браузере. Раскрывающийся список строится в цикле, количество итераций зависит от размера массива Sfavsites. В листинге 10.9 я включил в этот массив пять своих любимых сайтов. Конечно, вы можете добавить в него сколько угодно своих сайтов.

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

Листинг 10.9.

Динамическое построение раскрывающегося списка

<?

if ($site != "") :

header("Location: http://Ssite");

exit;

else :

?>

<html>

<head>

<title>Listing 10-9</Fit1e>


</head>

<body bgcolor="#ffffff" text="#000000" Iink="#cbda74" vlink="#808040" alink="#808040"

$favsites = array ("www.k10k.com". "www.yahoo.com",



"www.drudgereport.com",

"www.phprecipes.com",

"www.frogdesign.com"):

// Создать форму

<?

<form action = "Listing10-9.php" method="post">

<select name="site">

<option value = "">Choose a site:

$х = 0:

while ( $х < sizeof ($favsites) ) :

print "<option value='$favsites[$x]'>$favsites[$x]";

$x++;

endwhile;

?>

</select>

<input type="submit" value="go!">

</form>

<?

endif;

?>

Динамическое конструирование форм особенно удобно при обработке больших объемов данных, которые в любой момент могут измениться, что приведет к устареванию всей жестко закодированной информации форм. Впрочем, я рекомендую жестко кодировать все статические данные (например, список штатов США), поскольку это ускорит работу программы.


Формы и РНР


Обработка данных в формах имеет много общего с обработкой переменных, передаваемых в URL, — эта тема подробно рассматривалась в предыдущей главе.



к числу сильнейших сторон РНР,


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

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

Элементы форм, ориентированные на ввод с клавиатуры


Наше знакомство с построением форм начнется с элементов, ориентированных на ввод с клавиатуры. Таких элементов всего два — текстовое поле (text box) и текстовая область (text area).

Текстовое поле

В текстовых полях обычно вводится короткая текстовая информация — скажем, адрес электронной почты, почтовый адрес или имя. Синтаксис определения текстового поля:

<input type="text" nате="имя_переменной" size="N" maxlength="N" value="">

Определение текстового поля включает пять атрибутов:

type — тип элемента (для текстовых полей — text);

name — имя переменной, в которой сохраняются введенные данные;

size — общий размер текстового поля в браузере;

maxlength — максимальное количество символов, вводимых в текстовом поле;

value — значение, отображаемое в текстовом поле по умолчанию.

Текстовое поле изображено на рис. 10.1.

Рис. 10.1.

Текстовое поле

Особой разновидностью текстовых полей является поле для ввода паролей. Оно работает точно так же, как обычное текстовое поле, однако вводимые символы заменяются звездочками. Чтобы создать в форме поле для ввода паролей, достаточно указать type="password" вместо type="text".

Текстовая область

Текстовая область (text area) используется для ввода несколько больших объемов текста, не ограничивающихся простым именем или адресом электронной почты. Синтаксис определения текстовой области:

<textarea name="имя_переменной" rows="N" cols="N" value=""></textarea>

Определение текстового поля включает три атрибута:

name — имя переменной, в которой сохраняются введенные данные;

rows — количество строк в текстовой области;

cols — количество столбцов в текстовой области.

Текстовая область изображена на рис. 10.2.

Рис. 10.2.

Текстовая область



Элементы форм, ориентированные на ввод с мыши


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

Флажок

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

<input type="checkbox" name="имя_переменной" valuе="начальное_значение">

Определение флажка включает три атрибута:

type — тип элемента (для флажков — checkbox);

name — имя переменной, в которой сохраняются введенные данные (в данном случае — состояние элемента);

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

Флажок изображен на рис. 10.3.

Рис. 10.3.

Флажок

Переключатель

Переключатель (radio button) представляет собой разновидность флажка; он работает практически так же за одним исключением — в любой момент времени в группе может быть установлен лишь один переключатель. Синтаксис определения переключателя:

<input type="radio" name="имя_переменной" value="начальное_значение">

Как видите, синтаксис почти не отличается от определения флажка. Определение переключателя поля включает три атрибута:

type — тип элемента (для переключателей — radio);

name — имя переменной, в которой сохраняются введенные данные (в данном случае — состояние элемента);

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

Переключатель изображен на рис. 10.4.

Рис. 10.4.

Переключатель

Раскрывающийся список

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


<select name="имя_переменной">

<option valuе="имя_переменной1 ">

<option value="имя_переменной2">

<option value="имя_переменнойЗ">

<option value="имя_переменнойN">

</select>

Определение переключателя поля включает три атрибута:



name — имя переменной, в которой сохраняются введенные данные (в данном случае — строка, выбранная в списке);

value — значение, отображаемое в списке по умолчанию.

Раскрывающийся список изображен на рис. 10.5.

 


Рис. 10.5.

Раскрывающийся список

Скрытые поля

Скрытые поля не отображаются в браузере и обычно используются для передачи данных между сценариями. Хотя передача в скрытых полях работает вполне нормально, в РНР существует другое, более удобное средство — сеансовые переменные (см. главу 13). Впрочем, скрытые поля также используются в некоторых ситуациях и потому заслуживают упоминания.

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

<input type="hidden" name="имя_переменной" value="начальное_значение">

Определение скрытого поля включает три атрибута:



type — тип элемента (для скрытых полей — hidden);

name — имя переменной, в которой сохраняются скрытые данные;

value — значение, по умолчанию сохраняемое в скрытом поле.

Вообще говоря, название этого элемента — скрытое поле — несколько неточно. Хотя скрытые поля не отображаются в браузерах, пользователь может просто выполнить команду View Source и увидеть, какие скрытые значения хранятся в форме.

Кнопка отправки данных

Кнопка отправки данных инициирует действие, заданное атрибутом action тега <form>. Синтаксис определения:

<input type="submit" value="текст_на_кнопке">

Определение кнопки включает два атрибута:





type — тип элемента (для кнопки отправки данных — submit);

value — текст, по умолчанию отображаемый на кнопке.



Рис. 10.6.

Кнопка отправки данных

Кнопка сброса

Кнопка сброса отменяет все изменения, внесенные в элементы формы. Обычно никто ею не пользуется, однако кнопки сброса так часто встречаются на формах в Web, что я решил привести ее описание. Синтаксис определения:

<input type="reset" value=" текст _на_кнопке">

Определение кнопки включает два атрибута:



type — тип элемента (для кнопки сброса — reset);

value — текст, по умолчанию отображаемый на кнопке.

Кнопка сброса выглядит точно так же, как и кнопка отправки данных, если не считать того, что на ней обычно выводится слово «Reset» (рис. 10.6).

Джейкоб Нильсен (Jakob Nielsen), известный авторитет в области Web, недавно напи-сал интересную статью о проблемах, связанных с использованием кнопки сброса. Статья опубликована по адресу http://www.useit.com/alertbox/20000416.html.


Общие сведения о формах


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

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

<form action = действие method = "метод" - элементы формы -</form>

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

Метод get передает все данные формы в конце URL. Из-за различных ограничений, связанных со спецификой языков и длиной данных, этот метод применяется редко.

Метод post передает все данные формы в теле запроса. Этот метод используется чаще, чем get.

В этой главе приведена лишь очень краткая вводная информация по основному син-таксису форм HTML. Более полную информацию можно найти в книге А. Хоумера и К. Улмена «Dynamic HTML. Справочник» (СПб.: Питер, 1999).



Проект: гостевая книга


С первых дней World Wide Web разработчики сайтов стремились к тому, чтобы посетители могли поделиться своими мыслями и комментариями по поводу сайта. На сайтах эта возможность обычно называется «гостевой книгой» (guestbook). Я покажу, как легко создать гостевую книгу при помощи форм HTML, средств обработки форм РНР и текстового файла.

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

Листинг 10.10.

Файл init.inc, используемый при создании гостевой книги

<?

// Файл: init.inc

// Назначение: глобальные переменные и функции для проекта гостевой книги

// Заголовок страницы по умолчанию

$title = "My Guestbook";

// Цвет фона

$bg_color = "white": /

// Гарнитура шрифта

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

// Цвет шрифта

$font_color = "black";

// Дата отправки $post_date - date("M d y");

// Файл данных гостевой книги

$guest_file = "comments.txt";

// Функция читает данные гостевой книги

//и отображает их в браузере

function view_guest($guest_file) {

GLOBAL $font_face, $font_color;

print "Return to <a href=\"index.php\">index</a>,<br><br>";

// Если в файле гостевой книги имеются данные...

if (filesize($guest_file) > 0) :

// Открыть файл данных гостевой книги

$fh = fopen($guest_file. "r") or die("Couldn't open $guest_file");

print "<table border=1 cellpadding=2 cellspacing=0 width=\"600\">";

// Повторять до конца файла

while (! feof($fh)) :

// Прочитать следующую строку

$line <= fgetsdfh, 4096);

// Разбить строку на компоненты

// и присвоить каждый компонент переменной

list($date. $name, $email, $comments) = explode("|", $line):

// Если указано имя посетителя, вывести его

if ($name != "") :

print "<tr>":

print "<td><font color=\"$font_co!or\"


face=\"$font_face\">Date:</font></td>";

print "<td><font color=\"$font_color\"

face=\"$font_face\">$date</font></td>";

print "</tr>";

print "<tr>";

print "<td><font color=\"$font_color\"

face=\"$font_face\">Name:</font></td>";

print "<td><font color=\"$font_color\"

 face=\"$font_face\">$name</font></td>";

print "</tr>";

print "<tr>";

print "<td><font color=\"$font_color\"

 face=\"$font_face\">Email:</font></td>";

print "<td><font color=\"$font_color\"

 face=\"$font_face\">$email</font></td>";

print "</tr>";

print "<tr>";

print "<td valign=\'top\"><font color=\"$font_color\"

face=\"$font_face\">Message:</font></td>";

print "<td><font color=\"$font_color\"

face=\"$font_face\">$comments</font></td>";

print "</tr>";

print "<tr><td colspan=\"2\">&nbsp:</td></tr>";

endif;

endwhile;

print "</table>";

// Закрыть файл

fclose($fh);

else :

print "<h3> Currently there are no entries in the guestbook!</h3>";

endif;

} // view_guest

// Функция сохраняет новую информацию в файле данных

function add_guest($name, $email, $comments) {

GLOBAL $post_date, $guest_file;

// Отформатировать данные для ввода ,

$contents = "$post_date|$name|$email |$comments\n";

// Открыть файл данных

$fh = fopen($guest_file. "a") or dieC'Could not open $guest_file!");

// Записать данные в файл

$wr = fwrite($fh, $contents) or die("Could not write to $guest_file!");



// Закрыть файл fclose($fh);

} // add_guest

?>

Затем создаются еще три файла: файл ссылок index.php, файл add_guest.php для вывода информации гостевой книги и файл view_guest.php для ввода новых данных. Файл index.php (листинг 10.11) просто отображает две ссылки для выполнения основных функций гостевой книги — просмотра и добавления новых данных. Эти ссылки легко включаются в сайт, имеющий более сложную структуру.

Листинг 10.11.

Файл index.php со ссылками для просмотра и добавления новых данных в гостевую книгу

<html>

<?

INCLUDE("init.inc");

?>

<head>

<title><?=$page_title;?></title>

</head>

<body bgcolor="<?=$bg_color;?>" text="#000000" link="#808040" vlink="#808040" alink="#808040">

<a href="view_guest.php">View the guestbook!</a><br>

<a href="add_guest.php">Sign the guestbook!</a><br>

</body>

</html>

Файл view_guest.php (листинг 10.12) выводит всю информацию гостевой книги, хранящуюся в файле данных.

Листинг 10.12.

Файл view_guest.php

<html>

<?

INCLUDE("init.inc");

?>

<head>

<t1tle><?=$page_title;?></t1tle>

</head>

<body bgcolor="<?=$bg_color:?>" text="#000000" link=" vlink="#808040" alink="#808040">

vi ew_guest ( $guest_file );

?>

Файл add_guest.php (листинг 10.13) запрашивает у пользователя новые данные для внесения в гостевую книгу. Введенная информация записывается в файл данных.

Листинг 10.13.

Файл add_guest.php

<html>

<?

INCLUDE("init.inc");

?>

<head>

<title><?=$page_title:?></title>

</head>

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

?<

// Если форма еще не отображалась - запросить данные у пользователя



if (! $seenform) :

?>

<form action="add_guest.php" method="post">

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

Name:<br>

<input type="text" name="name" size="15" maxlength="30" value=""><br>

Email:<br>

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

Comment: <br>

<textarea name="comment" rows="'3" cols="40"></textarea><br>

<input type="submit" value="submit">

</form>

// Форма уже отображалась - добавить данные в текстовый файл.

else :

add_guest($name, $email, $comment);

print "<h3> Your comments have been added to the guestbook.

<a href=\"index.php\">Click here</a> to return to the index. </h3>";

endif;

?>

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

На рис. 10.8 показано, как выглядит гостевая книга после сохранения пары записей.

 


Рис. 10.8. Просмотр гостевой книги (view_guest.php)

Информация, показанная на рис. 10.8, хранится в файле данных в следующем виде:

Oct 29 00|Michele|michelle@latorre.com|I love cheese!

Oct 29 00|Nino|nino@latorre.com|Great site!


Проверка ошибок


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

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

Пример 6: вывод информации о пустых или ошибочно заполненных полях формы

Ни один разработчик сайта не захочет раздражать пользователя невразумительными сообщениями об ошибках в данных — особенно если пользователь запрашивает дополнительную информацию о товаре или оформляет покупку! Чтобы пользователь понял, какие поля формы остались пустыми или были заполнены неверно, сообщения должны быть четкими и конкретными.

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

Листинг 10.8.

Проверка данных формы и вывод сообщений об ошибках

<html>


<head>

<title>Listing 10-8</title>

</head>

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

<?

// Создать форму

$form = "

<form action=\"Listing10-8.php\" method=\"post\">

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

<b>Give us some information!</b><br>

Your Name:<br>

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

Your Email:<br>

<input type=\"text\" name=\"email\" s1ze=\"20\" maxlength=\"40\" value=\"$email\"><br>

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

</form>":

// Заполнялась ли форма ранее?

if ($seenform != "у"):

print "$form";

// Пользователь заполнил форму. Проверить введенные данные, else :

$error_flag = "n";

// Убедиться в том. что поле имени содержит информацию

if ($name == "") :

print "<font color=\"red\">* You forgot to enter your name!

</font> <br>":

$error_flag = "y";

endif:

// Убедиться в том. что поле адреса содержит информацию

if ($email == "") :

else :

print "<font color=\"red\">* You forgot to enter your email !

</font> <br>"

$error_flag = "y";

// Преобразовать все алфавитные символы в адресе

// электронной почты к нижнему регистру

$email = strtolower(trim($email)):

// Убедиться в правильности синтаксиса

// адреса электронной почты

if (! @eregi('^[0-9a-z]+'.

'([0-9a-z-]+\.)+'.

'([0-9a-z]){2.4}$'. $email)) :

print "<font color=\"red\">* You entered an invalid email address!

</font> <br>" :

$error_flag = "y";



endif;

endif;

// Если флаг ошибки $error_flag установлен.

// заново отобразить форму

if ($error_flag == "у") : print "$form";

else :

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

print "You entered valid form information!";

endif;

endif;

?>

</body>

</html>

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

за необходимым товаром или услугой в другое место.


Все вместе: пример формы


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

Листинг 10.1.

Пример формы для сбора данных

<form action = "process.php" method = "post">

<b>Please take a moment to tell us what you think about our site:</b><p>

<b>Name:</b><br>

<input type="text" name="name" size="15" maxlength="25" value=""><br>

<b>Email:</b><br>

<input type="text" name="email" size="15" maxlength="45" value=""><br>

<b>How frequently do you visit our site?:</b><br>

<select name="frequency">

<option value="">Site frequency:

<option value="0">This is my first time

<option value="l">&lt; 1 time a month

<option value="2">Roughly once a month

<option value="3">Several times a week

<option value="4">Every day

<option va1ue-"5">I'm addicted

</select><br>

<b>I frequently purchase the following products from our site:</b><br>

<input type="checkbox" name="software" value="software">Software<br>

<input type="checkbox" name="cookware" value="cookware">Cookware<br>

<input type="checkbox" name="hats" value="hats">Chef's Hats<br>

<b>0ur site's greatest asset is:</b><br>

<input type="radio" name="asset" value="products">Product selection<br>

<input type="radio" name="asset" value="design">Cool design<br>


<input type="radio" name="asset" value="service">Customer Service<br>

<b>Comments:</b><br>

<textarea name="comments" rows="3" cols="40"></textarea><br>

<input type="submit" value="Submit!">

</form>

Внешний вид формы в браузере изображен на рис. 10.7.



Рис. 10.7. Пример формы для ввода данных

Вроде бы все понятно. Возникает вопрос — как получить данные, введенные пользователем, и сделать с ними что-нибудь полезное? Этой теме посвящен следующий раздел, «Формы и РНР».

Не забывайте: все сказанное ранее — не более чем вводный курс. Приведенная информация ни в коем случае не исчерпывает всех возможностей, предоставляемых различными компонентами форм. За дополнительной информацией обращайтесь к многочисленным учебникам по работе с формами, опубликованным в Web, а также книгам по HTML.

От предварительного знакомства с формами HTML мы переходим к самому интересному — применению РНР для обработки данных, введенных пользователем в форме.


Вводные примеры


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

Пример 1: передача данных формы из одного сценария в другой

В первом примере представлена характерная ситуация — когда пользовательские данные вводятся на одной странице и отображаются на другой. В листинге 10.2 приведен код формы для ввода имени пользователя и адреса электронной почты. Когда пользователь щелкает на кнопке отправки данных (кнопка Go!), форма обращается к странице, приведенной в листинге 10.3. В свою очередь, листинг 10.3 выводит переменные $name и $mail, переданные с запросом.

Листинг 10.2.

Простая форма

<html>

<head>

<title>Listing 10-2</title>

</head>

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

<form action="listingl0-3.php" method="post">

<b>Give us some information!</b><br>

Your Name:<br>

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

Your Email:<br>

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

<input type="submit" value="go!">

</form>

</body> </html>

Листинг 10.3.

Отображение данных, введенных в листинге 10.1

<html> <head>

<title>Listing 10-3</title>

</head>

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

<?

// Вывести имя и адрес электронной почты.

print "Hi. $name!. Your email address is $email";

?>

</body> </html>

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


Существует и другой способ обработки данных форм, при котором используется всего один сценарий. К недостаткам этого способа относятся увеличение сценария и как следствие — затруднения с редактированием и сопровождением. Впрочем, есть и достоинства — уменьшение количества файлов, с которыми вам приходится работать. Более того, в этом варианте сокращается избыточный код при проверке ошибок (эта тема рассматривается ниже в данной главе). Конечно, в некоторых ситуациях работать с одним сценарием неудобно, но, по крайней мере, вы должны знать об этой возможности. В примере 2 воспроизводится пример 1, но с использованием лишь одного сценария.

Пример 2: альтернативная обработка формы (с одним сценарием)

Обработка данных формы в одном сценарии реализуется относительно просто. Вы проверяете, были ли присвоены значения переменным формы. Если значения присвоены, сценарий обрабатывает их (в нашем примере — просто выводит), а если нет — отображает форму. Решение о том, было ли задано значение переменной или нет, принимается при помощи функции strcmp( ), описанной в главе 8. Пример реализации формы с одним сценарием приведен в листинге 10.4. Обратите внимание: атрибут action формы ссылается на ту же страницу, в которой определяется сама форма. Условная команда i f проверяет состояние переменной скрытого поля с именем $seenform. Если значение $seenform не задано, форма отображается в браузере, а если задано — значит, форма была заполнена пользователем и введенные данные обрабатываются сценарием (в данном примере — просто выводятся в браузере).

Листинг 10.4.

Ввод данных на форме в одном сценарии

<html>

<head>

<title>Listing 10-4</title>

</head>

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

<?

// Все кавычки внутри $form должны экранироваться,

// в противном случае произойдет ошибка.

$form = "

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



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

<b>Give us some information!</b><br>

Your Name:<br>

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

Your Email:<br>

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

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

</form>";

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

// Для проверки используется значение скрытой переменной $seenform.

if ($seenform != "у"):

print "$form";

else :

print "Hi. $name!. Your email address is $email";

endif;

?>

</body>

</html>

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

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

Пример 3: автоматическая отправка данных по электронной почте

Вывести пользовательские данные в браузере несложно, но вряд ли это можно назвать содержательной обработкой пользовательского ввода. Один из способов обработки информации заключается в ее отправке по электронной почте — например, администратору сайта. Хотя при помощи гиперссылки mailto: можно отправить сообщение прямо из браузера, следует учитывать, что внешние приложения электронной почты настроены не на каждом компьютере. Следовательно, отправка сообщений с web-формы более надежно гарантирует, что сообщение будет доставлено адресату.



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

mail ( )

Функция mail( ) отправляет сообщение заданному адресату по электронной почте. Синтаксис функции mail( ):

boolean mail (string получатель, string тема, string сообщение [, string доп_заголовки])

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

В системе UNIX функция mail( ) использует утилиту sendmail. В Windows эта функция работает лишь при наличии установленного почтового сервера или если функция mail( ) связана с работающим сервером SMTP. Эта задача решается модификацией переменной SMTP в файле php.ini.

Если вы сделали все необходимое и функция mail( ) работает в вашей системе, попробуйте выполнить следующий фрагмент (конечно, адрес youraddress@yourserver.com заменяется вашим настоящим адресом электронной почты):

$email = "youraddress@yourserver.com";

$subject = "This is the subject";

$message = "This is the message";

$headers = "From: somebody@somesite.com";

mail ($email, $subject, $message, $headers);

Хотя при обширной переписке, конечно, следует использовать специализированные почтовые программы вроде majordomo (http://www.greatcircle.com/majordomo), в простых случаях функции РНР mail( ) оказывается вполне достаточно.

Итак, после знакомства с функцией mail( ) можно применить ее на практике. В листинге 10.5 показано, как получить информацию от пользователя и отправить ее по адресу, заданному администратором сценария.

Листинг 10.5.

Пересылка пользовательских данных функцией mail( )



<html>

<head>

<title>Listing 10-5</title>

</head>

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

// Все кавычки внутри $form должны экранироваться.

// в противном случае произойдет ошибка.

$form = "

<form action=\"listing10-5.php\" method=\"post\">

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

<b>Send us your comments!</b><br>

Your Name:<br>

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

Your Email:<br>

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

Your Comments:<br>

<textarea name=\"comments\" rows=\"3\" cols=\"30\"></textarea><br>

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

</form>

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

//

Для проверки используется значение скрытой переменной $seenform.

if ($seenform != "у") :

print "$form"; else :

// Переменная $recipient определяет получателя данных формы

$recipient = "yourname@youremail.com";

// Тема сообщения

$subject = "User Comments ($name)";

// Дополнительные заголовки $headers = "From: $email";

// Отправить сообщение или выдать сообщение об ошибке

mail($recipient, $subject, $comments, $headers) or die("Could not send email!");

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

print "Thank you $name for taking a moment to send us your comments!";

endif;

?>

</body>

</html>

Неплохо, правда? Листинг 10.5 работает так же, как листинг 10.4; сначала мы проверяем, отображалась ли форма ранее. Если это происходило, программа вызывает функцию mail( ) и пользовательские данные отправляются по адресу, определяемому переменной $recipient. Затем в браузере выводится благодарственное сообщение для пользователя.



Простейшим расширением этого примера будет отправка благодарственного сообщения по электронной почте (вторым вызовом mail( )). Следующий пример развивает эту идею — пользователю предлагается на выбор несколько бюллетеней. Выбранные бюллетени отправляются по электронной почте.

Пример 4: отправка запрашиваемой информации по электронной почте

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

Информационные сообщения хранятся в отдельных файлах. В нашем примере используются три текстовых файла:



site.txt — информация о сайте;

team.txt — информация о талантливых разработчиках сайта;

events.txt — приглашение на очередное мероприятие.

Исходный текст примера приведен в листинге 10.6.

Листинг 10.6.

Отправка информации, запрашиваемой пользователем

<html>

<head>

<title>Listing10-5</title>

</head>

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

<?

$form = "

<form action=\"Listing10-6.php\" method=\"post\">

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

<b>Receive information about our site!</b><br>

Your Email:<br>

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

<input type=\"checkbox\" name=\"information[site]\" value=\"y\">Site Architecture<br>

<input type=\"checkbox\" name=\"information[team]\" value=\"y\">Development Team<br>



<input type=\"checkbox\" name=\"information[events]\" value=\"y\">Upcoming Events<br>

<input type=\"submit\" value=\"send it to me!\">

</form>":

if ($seenform != "y") :

print "$form"; else :

$headers = "From: devteam@yoursite.com";

// Перебрать все пары "ключ/значение"

while ( list($key, Sval) = each ($information) ) :

// Сравнить текущее значение с "у" if ($val == "у") :

// Построить имя файла, соответствующее текущему ключу

$filename = "$key.txt":

$subject = "Requested $key information";

// Открыть файл

$fd = fopen ($filename, "r");

// Прочитать содержимое всего файла в переменную $contents = fread ($fd. filesize ($filename));

// Отправить сообщение

mail($email, $subject, $contents, $headers) or die("Can't send email!");; fclose($fd);

endif;

endwhile;

// Известить пользователя об успешной отправке

print sizeof($information)." informational newsletters

have been sent to $email!";

endif;

?>

</body>

</html>

В листинге 10.6 мы перебираем пары «ключ/значение» в цикле while и отправляем только те бюллетени, у которых значение равно у. Следует помнить, что имена текстовых файлов должны соответствовать ключам массива

(site.txt, team.txt и events.txt). Имя файла строится динамически по ключу, после чего файл открывается по имени и его содержимое загружается в переменную ($contents). Затем переменная $contents передается функции mail( ) в качестве параметра.

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

Пример 5: сохранение пользовательских данных в текстовом файле

Пользовательские данные сохраняются в текстовом файле для последующего статистического анализа, поиска и т. д. — короче, любой обработки по вашему усмотрению. В листинге 10.7, как и в предыдущих примерах, данные формы обрабатываются в одном сценарии. Пользователю предлагается ввести четыре объекта данных: имя, адрес электронной почты, язык и профессию. Введенная информация сохраняется в текстовом файле user_information.txt. Элементы данных разделяются символами «вертикальная черта» (|).



Листинг 10.7.

Сохранение пользовательской информации в текстовом файле

<html>

<head>

<titlexisting 10-7</title>

</head>

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

<?

// Создать форму

$form = "

<form action=\"Listing10-7.php\" method=\"post\">

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

<b>Give us your personal info!</fb><br>

Your Name:<br>

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

Your Email:<br>

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

Your Preferred Language:<br>

<select name=\"language\">

<option value=\"\">Choose a language:

<option value=\"English\">English

<option value=\"Spanish\">Spanish

<option value=\"Italian\">Italian

<option value=\"French\">French

<option value=\"Japanese\">Japanese

<option value=\"newyork\">NewYork-ese

</select><br>

Your Occupation:'"ibr>

<select name=\"job\">

<option value=\"\">What do you do?:

<option value=\"student\">Student

<option value=\ "programmed ">Programmer

<option value=\"manager\">Project Manager

<option value=\"slacker\">Slacker

<option value=\"chef\">Gourmet Chef

</select><br>

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

</form>";

// Заполнялась ли форма ранее? if ($seenform != "у") :

print "$form"; else :

$fd = fopen("useMnformation.txt", "a");



// Убедиться, что во введенных данных не встречается

// вертикальная черта.

$name = str_replace("|", "", $name);

$email = str_replace("|", "", $email);

// Построить строку с пользовательскими данными

$user_row = $name." ".$email."|".$language." ".$job."\n";

fwrite($fd, $user_row) or die("Could not write to file!");

fclose($fd);

print "Thank you for taking a moment to fill out our brief questionnaire!":

endif;

?>

</body>

</html>

Обратите внимание на фрагмент, в котором мы проверяем, что пользователь не включил в имя или адрес электронной почты символы «вертикальная черта» (|). Функция str_replace( ) удаляет эти символы, заменяя их пустой строкой. Если бы это не было сделано, пользовательские символы | нарушили бы структуру файла данных и существенно затруднили (а то и сделали невозможным) его правильную обработку.

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

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


Что такое SQL?


SQL обычно описывается как стандартный язык, используемый для взаимодействия с реляционными базами данных (см. ниже). Однако SQL не является языком программирования, как С, C++ или РНР. Скорее, это интерфейсное средство для выполнения различных операций с базами данных, предоставляющее в распоряжение пользователя стандартный набор команд. Возможности SQL не ограничиваются выборкой данных из базы. В SQL поддерживаются разнообразные возможности для взаимодействия с базой данных, в том числе:

определение структуры данных

— определение конструкций, используемых при хранении данных;

выборка данных

— загрузка данных из базы и их представление в формате, удобном для вывода;

обработка данных

— вставка, обновление и удаление информации;

контроль доступа

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

контроль целостности данных

— сохранение структуры данных при возникновении таких проблем, как параллельные обновления или системные сбои.

Обратите внимание: в определении SQL было сказано, что этот язык предназначен для работы с реляционными базами данных. В реляционных СУБД данные организуются в виде набора взаимосвязанных таблиц. Связи между таблицами реализуются в виде ссылок на данные других таблиц. Таблицу можно представить себе как двухмерный массив, в котором расположение каждого элемента характеризуется определенными значениями строки и столбца. Пример реляционной базы данных изображен на рис. 11.1.

Рис. 11.1.

Пример реляционной базы данных

Как видно из рис. 11.1, каждая таблица состоит из строк (записей) и столбцов (полей). Каждому полю присваивается уникальное (в рамках данной таблицы) имя. Обратите внимание на связь между таблицами customer и orders, обозначенную стрелкой. В информацию о заказе включается короткий идентификатор клиента, что позволяет избежать избыточного хранения имени и прочих реквизитов клиента. В изображенной базе данных существует еще одна связь — между таблицами orders и products. Эта связь устанавливается по полю prod_id, в котором хранится идентификатор товара, заказанного данным клиентом (определяемого полем custjd). Наличие этих связей позволяет легко ссылаться на полные данные клиента и товара по простым идентификаторам. Правильно организованная база данных превращается в мощное средство организации и эффективного хранения данных с минимальной избыточностью. Запомните эту базу данных, я буду часто ссылаться на нее в дальнейших примерах.


Итак, как же выполняются операции с реляционными базами данных? Для этого в SQL существует специальный набор общих команд — таких, как SELECT, INSERT, UPDATE и DELETE. Например, если вам потребуется получить адрес электронной почты клиента с идентификатором 2001cu (см. рис. 11.1), достаточно выполнить следующую команду SQL:

SELECT cust_email FROM customers WHERE custjd = '2001cu'

Все вполне логично, не правда ли? В обобщенном виде команда выглядит так:

SELECT имя_поля FROM имя_таблицы [ WHERE условие ]

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

SELECT cust_email FROM customers

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

INSERT into products VALUES ('1009pr', 'Red Tomatoes', '1.43');

Если позднее эти данные потребуется удалить, воспользуйтесь следующей командой:

DELETE FROM products WHERE prod_id = 1009r';

Существует много разновидностей команд SQL, и полное их описание выходит за рамки этой книги. На эту тему вполне можно написать отдельную книгу! Я постарался сделать так, чтобы команды SQL, используемые в примерах, были относительно простыми, но достаточно реальными. В Web существует много учебной информации и ресурсов, посвященных SQL. Некоторые ссылки приведены в конце этого раздела.

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

Раз вы читаете эту книгу, вероятно, вас интересует вопрос, как же организуется работа с базами данных в среде Web? Как правило, сначала при помощи какого-

либо интерфейсного языка (РНР, Java или Perl) создается соединение с базой данных, после чего программа обращается к базе с запросами, используя стандартный набор средств. Интерфейсный язык можно рассматривать как своего рода «клей», связывающий базу данных с Web. Я перехожу к своему любимому интерфейсному языку — РНР.

Дополнительные ресурсы

Ниже перечислены некоторые ресурсы Интернета, посвященные SQL. Они пригодятся как новичкам, так и опытным программистам.



Учебники по SQL: http://perl.about.com/compute/perl/cs/beginningsql/index.htm.

SQLCourse.com (с примером базы данных): http://www.sqlcourse.com.

SQL для вундеркиндов Web: http://www.arsdigita.com/books/sql.

Введение в SQL (применительно к MySQL): http://www.devshed.com/Server_side/MySQL/Intro.


PHP 4 на практике


Базы данных

Средства эффективного хранения и выборки больших объемов информации внесли огромный вклад в успешное развитие Интернета. Обычно для хранения информации используются базы данных. Работа таких известных сайтов, как Yahoo, Amazon и Ebay, в значительной степени зависит от надежности баз данных, хранящих громадные объемы информации. Конечно, поддержка баз данных ориентирована не только на интересы гигантских корпораций — в распоряжении web-программистов имеется несколько мощных реализаций баз данных, распространяемых по относительно низкой цене (а то и бесплатно).

Правильная организация базы данных обеспечивает более быстрые и гибкие возможности выборки данных. Она существенно упрощает реализацию средств поиска и сортировки, а проблемы прав доступа к информации решаются при помощи средств контроля за привилегиями, присутствующими во многих системах управления базами данных (СУБД). Кроме того, упрощаются процессы репликации и архивации данных.

Глава начинается с подробного описания выборки и обновления данных в MySQL — вероятно, самой популярной СУБД, используемой в PHP (http://www.mysql.com). На примере MySQL будет показано, как в РНР происходят загрузка и обновление данных в базе; мы рассмотрим базовые средства поиска и сортировки, используемые во многих web-приложениях. Затем мы перейдем к реализованной в РНР поддержке ODBC (Open Data Base Connectivity) — обобщенного интерфейса, который может использоваться для одновременного соединения с разными СУБД. Поддержка ODBC в РНР будет продемонстрирована на примере соединения и выборки данных из базы данных Microsoft Access. Глава завершается проектом, в котором РНР и СУБД MySQL используются для создания иерархического каталога с информацией об избранных сайтах. При включении в каталог новых сайтов пользователь относит их к одной из стандартных категорий, определяемых администратором сайта.

Прежде чем переходить к обсуждению MySQL, я хочу сказать несколько слов об SQL — самом распространенном языке для работы с базами данных. Язык SQL заложен в основу практически всех существующих СУБД. Чтобы перейти к рассмотрению примеров работы с базами данных, необходимо хотя бы в общих чертах представлять, как работает SQL.



Эта глава получилась довольно большой,


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

общие сведения о SQL;
стандартную поддержку баз данных в РНР;
вводную информацию о сервере MySQL;
стандартные функции РНР для работы с MySQL;
реализацию простейшей поисковой системы;
сортировку таблиц;
общие сведения об ODBC;
стандартные функции РНР для работы с ODBC;
взаимодействие с Microsoft Access в РНР;
хранение пользовательских данных на web-сайте.
Читателю, занимающемуся разработкой больших, подлинно динамических web-сайтов на РНР, предстоит снова и снова возвращаться к теме баз данных. Я рекомендую тщательно изучить не только документацию РНР, но и другие ресурсы, посвященные технологиям баз данных. В этой области, как и во многих современных технологиях, даже эксперты не всегда успевают следить за всеми новшествами.
Следующая глава посвящена шаблонам — одной из нетривиальных тем web-программирования. Применение шаблонов при программировании на РНР обеспечивает заметную экономию времени и усилий при работе над крупномасштабными web-сайтами.

Microsoft Access и РНР


Популярность СУБД Microsoft Access (http://www.microsoft.com/office/access) отчасти объясняется ее удобным графическим интерфейсом. Помимо использования Access в качестве самостоятельной СУБД, вы можете использовать ее графический интерфейс для организации работы с другими базами данных — например, MySQL или Microsoft SQL Server.

Чтобы продемонстрировать поддержку ODBC в РНР, я опишу процесс подключения к базам данных Microsoft Access на РНР. Делается это на удивление просто, но благодаря популярности Microsoft Access это станет полезным дополнением в вашем творческом арсенале. Я опишу этот процесс шаг за шагом:

Создайте базу данных Access. Предполагается, что вы уже умеете это делать, а если не умеете, но все равно хотите проследить за выполнением этого примера, — воспользуйтесь услугами программы-мастера. Я создал стандартную базу данных для хранения контактных данных при помощи мастера. Обязательно создайте в таблице несколько записей и запомните имя таблицы, оно нам вскоре понадобится!

Сохраните базу данных на своем компьютере.

На следующем шаге мы организуем доступ к базе Access средствами ODBC. Выполните команду Пуск > Настройка > Панельуправления (Start > Settings > Control Panel). Найдите на панели управления значок Источники данных ODBC (32) (ODBC Data Sources (32 bit)). При помощи этого значка запускается Администратор ODBC, предназначенный для настройки различных драйверов и источников данных в вашей системе. Запустите программу, дважды щелкнув на этом значке. Окно Администратора по умолчанию открывается на вкладке Пользовательский DSN (User DSN). На этой вкладке перечисляются источники данных-, которые относятся к конкретному пользователю и могут использоваться только на этом компьютере. В данном примере будет использоваться именно такой источник данных.

Нажмите кнопку Добавить... (Add...) в правой части окна. Открывается новое окно для выбора драйвера, предназначенного для работы с новым источником. Выберите строку Microsoft Access Driver (*.mdb) и нажмите кнопку Finish (Готово).


На экране появляется новое окно Установка драйвера ODBC для Microsoft Access (ODBC Microsoft Access Setup). Найдите в форме текстовое поле Имя источника данных (Data Source Name) и введите в нем имя созданной вами базы данных Access. При желании можете ввести описание в текстовом поле, расположенном непосредственно под полем Имя источника данных.

Нажмите кнопку Выбрать... (Select...) — появляется окно в стиле Проводника Windows. В нем вам предлагается найти базу данных, доступ к которой будет осуществляться средствами ODBC.

Найдите в дереве каталогов свою базу данных и дважды щелкните на ней. Вы снова возвращаетесь к окну Установка драйвера ODBC для Microsoft Access. Путь к выбранной базе данных отображается над кнопкой Выбрать.... Нажмите кнопку ОК.

Готово! Теперь вы можете работать с базой данных Access средствами ODBC.

Все, что вам остается сделать — создать сценарий, в котором вы будете работать с базой данных через ODBC. В приведенном ниже сценарии общие функции ODBC (см. выше) будут использоваться для вывода всей информации из таблицы контактов, созданной при помощи мастера Access. Однако перед рассмотрением сценария желательно знать, как таблица Contacts выглядит в Access (рис. 11.3).



Рис. 11.3.

Таблица Contacts в MS Access

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

Листинг 11.7.

Применение функций ODBC для работы с MS Access

<?php

// Подключиться к источнику данных ODBC 'ContactDB' ;connect = odbc_connect("ContactDB", "","")

or die("Couldn't connect to datasource.");

// Создать текст запроса

$query = "SELECT First_Name, Last_Name, Cell_Phone, Email FROM Contacts";

// Подготовить запрос

$result = odbc_prepare($connect,$query);

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

odbc_execute($result);

odbc_result_all($result, "BGCOLOR='#c0c0c0' border=1");

// Обработка результатов закончена, освободить память odbc_free_result($result);

// Закрыть соединение odbc_close($connect);

?>

Не правда ли, все просто? А самое замечательное — то, что этот сценарий полностью совместим с любой другой СУБД с поддержкой ODBC. Для тренировки попробуйте повторить все описанные действия для другой СУБД, запустите сценарий — и вы получите те же результаты, которые изображены на рис. 11.4.



Рис. 11.4.

Содержимое таблицы Contacts в web-браузере


MySQL


MySQL (http://www.mysql.com) — надежная СУБД на базе SQL, разработанная и сопровождаемая фирмой Т.с.Х DataKonsultAB (Стокгольм, Швеция). Начиная с 1995 года, MySQL стала одной из самых распространенных СУБД в мире, что отчасти обусловлено ее скоростью, надежностью и гибкой лицензионной политикой (см. ниже).

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

MySQL распространяется на условиях общей лицензии GNU (GPL, GNU Public License). Полное описание текущей лицензионной политики MySQL приведено на сайте MySQL (http://www.mysql.com).



Настройка MySQL


После успешной установки сервер MySQL необходимо настроить. Процесс настройки в основном состоит из создания новых баз данных и редактирования таблиц

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

На первый взгляд, таблицы привилегий MySQL выглядят устрашающе, но если в них как следует разобраться, дальнейшее сопровождение становится очень простой задачей. Полное описание таблиц привилегий выходит за рамки этой книги. Впрочем, в Web существует немало ресурсов, предназначенных для помощи начинающим пользователям MySQL. За дополнительной информацией обращайтесь на сайт MySQL (http://www.mysql.com).

После успешной установки и настройки пакета MySQL можно начинать эксперименты с базами данных в среде Web! Именно этой теме и посвящен следующий раздел. Начнем с изучения поддержки MySQL в РНР.



ODBC


Специализированные функции хорошо подходят для работы с одним конкретным типом СУБД. Но что делать, если вам приходится подключаться к MySQL, Microsoft SQL Server и IBM DB2, притом в одном приложении? Аналогичная проблема возникает при разработке приложений, которые не должны зависеть от СУБД; такие приложения работают «над» существующей инфраструктурой клиентской базы данных. ODBC (сокращение от «Open Database Connectivity», то есть «открытая архитектура баз данных») представляет собой интерфейс прикладных программ (API), позволяющий использовать общий набор абстрактных функций для работы с разными типами баз данных. Преимущества подобного подхода очевидны — вам не придется многократно переписывать один и тот же фрагмент кода только для того, чтобы выполнять одинаковые операции с разнотипными базами данных.

Работа с сервером баз данных через ODBC возможна лишь в том случае, если этот сервер является ODBC-совместимым. Другими словами, для него должны существовать драйверы ODBC. За дополнительной информацией о драйверах ODBC обращайтесь к документации СУБД. Возможно, вам придется дополнительно загрузить их из Интернета и установить на своем компьютере. Хотя стандарт ODBC, разработанный компанией Microsoft, стал открытым стандартом, он в основном используется для работы с СУБД на платформе Windows; впрочем, драйверы ODBC также существуют и на платформе Linux. Ниже приведены ссылки на драйверы для некоторых популярных СУБД.

Драйверы баз данных для Windows 95/98/NT: http://www.microsoft.com/data/odbc

Automation Technologies: http://www.odbcsdk.com

Easysoft: http://www.easysoft.com/products/oob/main.phtml

Драйверы ODBC для MySQL (MyODBC): http://www.mysql.com

OpenLinkSoftware: http://www.openlinksw.com

Драйверы ODBC различаются по целям, платформе и назначению. За информацией о различных аспектах работы с этими драйверами обращайтесь к документации по конкретным пакетам. Впрочем, невзирая на все различия, использование этих драйверов в РНР обходится без проблем.

Когда вы определите, какой комплект драйверов ODBC лучше подходит для ваших целей, загрузите его и выполните все инструкции по установке и настройке. После этого можно переходить к следующему разделу — «Поддержка ODBC в РНР».



Поддержка баз данных в РНР


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

Adabas D

Informix

Dbase

Ingres

Direct MS-SQL

InterBase

Empress

mSQL

File-Pro (read-only)

MySQL

FrontBase

ODBC

IBM DB2

Oracle (OCI7 и OC18)

Как показывает этот список, поддержка баз данных в РНР простирается от совместимости с базами данных, известных всем (например, Oracle), до тех, о которых многие даже не слышали. Мораль — если вы собираетесь использовать серьезную СУБД для хранения информации, распространяемой через Web, скорее всего, эта база данных поддерживается в РНР. Поддержка базы данных в РНР представлена набором стандартных функций для соединения с базой, обработки запросов и разрыва связи.

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



Поддержка ODBC в РНР


Функции ODBC в РНР, обычно называемые общими функциями ODBC, не только обеспечивают типовую поддержку ODBC, но и позволяют работать с некоторыми СУБД, обладающими собственным API, через стандартный ODBC API. К числу последних относятся следующие СУБД:

Adabas D;

IODBC;

IBM DB2;

Solid;

Sybase SQL Anywhere.

Обратите внимание: при работе с перечисленными СУБД стандартный механизм ODBC на самом деле не используется. Просто поддерживаемые в РНР общие функции ODBC применяются для взаимодействия с базами данных. Это удобно, поскольку при переходе на другую ODBC-совместимую СУБД (или СУБД из приведенного выше списка) все ваши сценарии будут нормально работать.

Поддержка ODBC встроена в комплект поставки РНР, поэтому вам за редкими исключе-ниями практически не придется заниматься специальной настройкой.

В настоящее время существует около 40 общих функций ODBC. Впрочем, для выборки информации из ODBC-совместимой базы данных вполне достаточно нескольких функций, описанных ниже. Полный список общих функций ODBC в РНР приведен в документации (http://www.php.net/manual).

odbc_connect( )

Перед тем как обращаться к ODBC-совместимой базе данных с запросами, необходимо сначала установить с ней связь. Соединение создается функцией ocbc_connect( ). Синтаксис функции odbc_connect( ):

int odbc_connect (string источник_данных, string имя_пользователя, string пароль [, int тип_курсора])

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

SQL_CUR_USE_IF_NEEDED;

SQL_CURSOR_USE_ODBC;

SQL_CUR_USE_DRIVER;

SQL CUR DEFAULT.

Возможно, вам не придется использовать этот параметр, но все же о нем следует помнить на случай, если ваш пакет ODBC не справится с обработкой того или иного запроса.


Использовать odbc_connect( ) в программе несложно. Пример:

<?

obdc_connect("myAccessDB", "user", "secret")

or die( " Could not connect to ODBC database");

?>

Функция используется для открытия восстанавливаемых (persistent) соединений с базами данных. Она экономит системные ресурсы, поскольку odbc_pconnect( ) проверяет, не было ли данное соединение открыто ранее, и если было, использует предыдущее соединение.

odbc_close( )

После завершения работы с ODBC-совместимой базой данных соединение необходимо закрыть, чтобы освободить все ресурсы, используемые открытым соединением. Соединение закрывается функцией odbc_close( ):

void odbc_close ([int идентификатор_соединения])

В параметре идентификатор_соединения передается идентификатор открытого соединения. Рассмотрим небольшой пример:

<?

obdc_connect("myAccessDB", "user", "secret")

or die("Could not connect to ODBC database");

print "Currently connected to ODBC database!"; odbc_close($connect);

?>

odbc_prepare( )

Перед непосредственной обработкой запрос необходимо «подготовить». Задача решается функцией odbc_prepare( ):

int odbc_prepare (int идентификатор_соединения , string запрос)

В параметре идентификатор_соединения передается идентификатор соединения, возвращаемый функцией odbc_connect( ). В параметре запрос передается текст запроса, который должен быть выполнен сервером. Если запрос не может быть выполнен, функция возвращает FALSE; в противном случае возвращается идентификатор результата, в дальнейшем используемый при вызове функции odbc_execute( ) (см. ниже).

odbc_execute( )

Запрос, подготовленный функцией odbc_prepare( ), выполняется функцией odbc_execute( ). Синтаксис функции odbc_execute( ):

int odbc_execute (int идентификатор результата [, array параметры])

В параметре идентификатор_результата передается идентификатор результата, возвращаемый при успешном вызове odbc_prepare( ). Необязательный параметр передается лишь в том случае, если при вызове функции передаются дополнительные данные.



Рассмотрим следующий пример:

<?

$connect = @obdc_connect("myAccessDB", "user", "secret")

or die(" Could not connect to ODBC database");

$query = "UPDATE customers SET cust_id = \"Milano, Inc.\"

WHERE cust_id \"2000cu\"";

$result = odbc_prepare($connect, $query) or die("Couldn't prepare query!");

$result = odbc_execute($result) or die("Couldn't execute query!");

odbc_close($connect);

?>

В приведенном примере продемонстрирована транзакция ODBC, при которой данные запроса не отображаются в браузере (как это обычно делается при использовании команды SELECT). Транзакция ODBC с использованием запроса QUERY описана ниже, в разделе «odbc_result_all( )».

odbc_exec( )

Функция odbc_exec( ) объединяет odbc_prepare( ) и odbc_execute( ). Синтаксис функции odbc_exec( ):

int odbc_exec (int идентификатор_соединения , string запрос)

В параметре идентификатор_соединения передается идентификатор соединения, возвращаемый функцией odbc_connect( ). В параметре запрос передается текст запроса, который должен быть выполнен сервером. Если запрос не может быть выполнен, функция возвращает FALSE; в противном случае возвращается идентификатор результата, используемый при последующих вызовах функций:

<?

obdc_connect("myAccessDB", "user", "secret")

or die("Could not connect to ODBC database");

$query = "SELECT * FROM customers";

$result = odbc_exec($connect, $query) or die("Couldn't execute query!");

odbc_close($connect) ;

?>

В этом примере функция odbc_exec( ) пытается выполнить запрос, текст которого содержится в переменной $query. При успешном выполнении переменной $result присваивается идентификатор результата; в случае неудачи ей присваивается значение FALSE и выводится сообщение, передаваемое при вызове die( ).

odbc_result_all( )

Очень удобная функция odbc_result_all( ) форматирует и отображает все записи для идентификатора результата, полученного при вызове odbc_exec( ) или odbc_execute( ). Синтаксис функции odbc_result_all( ):



int odbc_result_all (int идентификатор_результата [, string формат_таблицы])

В параметре идентификатор_результата передается идентификатор результата, возвращаемый при успешном вызове odbc_exec( ) или odbc_execute( ). В необязательном параметре формат_таблицы передаются характеристики таблицы HTML. Рассмотрим следующий пример:

<?

obdc_connect("myAccessDB", "user", "secret")

or die("Could not connect to ODBC database");

$query = "SELECT * FROM customers";

$result = odbc_exec($connect, $query) or die("Couldn't execute query!");

odbc_result_all($result, "BGCOLOR='#c0c0c0' border='1' ");

odbc_close($connect);

?>

В результате выполнения этого примера содержимое таблицы customers будет выведено в таблице на светло-сером фоне с рамкой толщиной 1. Внешний вид таблицы для данных, приведенных на рис. 11.1, изображен на рис. 11.2.



Рис. 11.2.

Данные ODBC в браузере

odbc_free_result ( )

Хороший стиль программирования требует освобождения всех ресурсов, связанных с завершенными операциями. При работе с запросами ODBC эта задача решается функцией odbc_free_result( ). Синтаксис функции odbc_free_result( ):

int odbc_free_result (int идентификатор_результата)

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

<?

obdc_connect("myAccessDB", "user", "secret")

or die("Could not connect to ODBC database");

$query = "SELECT * FROM customers":

$result = odbc_exec($connect, $query) or die("Couldn't execute query!");

odbc_result_all($result, "BGCOLOR='#c0c0c0' border='1' "');

odbc_free_result($result);

odbc_close($connect);

?>

После того как функция odbc_result_all( ) завершила использование идентификатора результата, память возвращается в систему при помощи odbc_free_result( ).

На этом завершается наше знакомство с функциями ODBC в РНР, незаменимыми при создании простых интерфейсов на базе ODBC для доступа через Web. В следующем разделе многие из этих функций будут использованы для демонстрации того, как легко на базе РНР организуется взаимодействие с одной из самых популярных СУБД — Microsoft Access.


Проект: каталог ссылок


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

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

Прежде всего вы должны решить, какие сведения о сайтах будут храниться в базе данных SQL. Для простоты я ограничусь следующим набором атрибутов: название, URL, категория, дата включения и описание. Таким образом, определение таблицы в MySQL выглядит примерно так:

mysql>create table bookmarks ( category INT, site_name char(35), url char(50),

date_added date, description char(254) );

В определении таблицы bookmarks имеется пара моментов, заслуживающих внимания. Во-первых, информация о категории сайта почему-то хранится в виде целого числа — но разве не правильнее было бы сделать названия категорий более содержательными и понятными для пользователя? Не беспокойтесь, в ини-циализационном файле будет создан массив, связывающий целочисленные индексы с названиями категорий. В будущем администратор может изменять и даже удалять отдельные категории. Хранение информации о категориях заметно упрощает эту задачу. Кроме того, целочисленное поле обеспечивает экономию места на диске, поскольку название категории многократно сохраняется в таблице. Другое обстоятельство, относящееся к структуре таблицы, — ограничение длины описания 254 символами. В зависимости от этого объема описаний вместо типа char( ) можно воспользоваться типом medium или text. За дополнительной информацией о типах полей обращайтесь к документации MySQL.


Следующим шагом в работе над этим приложением будет создание инициализа-ционного файла. Помимо глобальных переменных, в инициализационном файле определяются две функции: add_bookmark( ) и view_bookmark( ). Функция add_bookmark( ) получает пользовательский ввод из формы и включает его в базу. Функция view_bookmark( ) получает индекс категории, читает из базы данных все записи, относящиеся к указанной категории, и отображает их в браузере. Инициализацион-ный файл приложения init.inc с комментариями приведен в листинге 11.8.

Листинг 11.8.

Инициализационный файл каталога ссылок (init.inc)

<?

// Файл: init.inc

// Назначение: глобальные переменные и функции.

// используемые в проекте

// Стандартный заголовок страницы $title = "My Bookmark Repository":

// Цвет фона $bg_color = "white";

// Дата

$post_date = date("Ymd");

// Категории $categories = array(

"computers",

"entertainment",

"dining",

"lifestyle",

"government",

"travel");

// Данные сервера MySQL $host = "localhost"; $user = "root"; $pswd = "";

// Имя базы данных $database = "book";

// Имя таблицы $bookmark_table = "bookmarks";

// Цвет ячеек таблицы $cell_color = "#c0c0c0";

// Установить соединение с сервером MySQL

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

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

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

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

// Назначение: включение новой ссылки в таблицу bookmark.

function add_bookmark (Scategory, Ssitejname. $url, $description) {

GLOBAL $bookmark_table, $post_date;

$query = "INSERT INTO $bookmark_table

VALUES(\"$category\", \"$site_name\", \"$url\", \"$post_date\", \"$description\")";

$result = @mysql_query($query) or die("Couldn't insert bookmark information!");



} // add_bookmark <

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

// Назначение: выборка из таблицы bookmark всех ссылок,

// относящихся к категории $category.

function view_bookmark ($category) {

GLOBAL $bookmark_table, $cell_color, $categories;

$query = "SELECT site_name, url, DATE_FORMAT(date_added,'%m-%d-%Y') AS date_added, description

FROM $bookmark table WHERE category = $category ORDER BY datejdded DESC";

$result = @mysql_query($query);

print "<div align=\"center\"><table cellpadding=\"2\" cellspacing=\"1\" border = \"0\" width = \"600\">";

print "<tr><td bgcolor-\"$cell_color\"><b>Category: $categories[$category]</b></td></tr>";

if (mysql_numrows($result) > 0) ;

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

Posted: ".$row["date added"]."<br>"

else

print "<tr><td>";

print "<b>".$row["site_name"]."</b>

print "</td></tr>";

print "<tr><td>";

print "<a href = \"http://".$row["url"]."\">http://".

$row["url"]."</a><br>";

print "</td></tr>";

print "<tr><td valign=\"top\">";

print $row["description"]."<br>";

print "</td></tr>";

print "<tr><td><hr></td></tr>";

endwhile;

print "<tr><td>There are currently no bookmarks falling under this category. Why don't you <a href=\"add_bookmark.php\">add one</a>?</td></tr>";

endif:

print "</table><a href=\"Listing11-11.php\">Return to index</a>

print "<a href=\"add_bookmark.php\">Add a bookmark</a></div>";



// view bookmark

?>

Следующая страница, add_bookmark.php (листинг 11.9), предназначена для ввода информации о новой ссылке, включаемой в базу данных. Для обработки пользовательских данных вызывается функция add_bookmark( ).

Листинг 11.9.

Программа add_bookmark.php

<html>

<?

INCLUDE("init.inc");

?>

<head>

<title><?=$title:?></title> </head>

<body bgcolor="#ffffff" text="#000000" link="#808040" vlink="#808040" alink="#808040"> if (! $seenform) :

<form action="add_bookmark.php" method="post"> <1nput type="hidden" name="seenform" value="y">

Category:<br>

<select name="category">

<option value="">Choose a category:

while (list($key, $value) = each($categories)) :

print "<option value=\"$key\">$value"; endwhile:

</select><br>

Site Name:<br>

<input type="text" name="site_name" size="15" maxlength="30" value=""><br>

URL: (do <i>not</1> include "http://"!)<br>

<input type="text" name="url" size="35" maxlength="50" value=""><br>

Description:<br>

<textarea name="description" rows="4" cols="30"></textarea><br>

<input type="submit" value="submit">

</form>

else :

add_bookmark($category, $site_name, $url, $description);

print "<h4>Your bookmark has been added to the repository.

<a href=\"Listing11-11.php\">Click here</a> to return to the index.</h4>";

endif;

?>

При исходной загрузке страницы в браузере отображается форма (рис. 11.5).

После сохранения ссылки в базе программа выдает соответствующее сообщение и создает ссылку для перехода к домашней странице приложения index.php (листинг 11.11).





Рис. 11.5.

Форма, отображаемая страницей add_bookmark.php

Следующая страница, view_bookmark.php, просто вызывает функцию view_bookmark( ). Код этой страницы приведен в листинге 11.10.

Листинг 11.10.

Программа view_bookmark.php

<html>

<?

INCLUDE("Listing11-8.php"); ?>

<head>

<title><?=$title:?></title>

</head>

<body bgcolor="<?=$bg_color;?>" text="#000000" link="#808040" vlink="#808040" alink="#808040">

view_bookmark($category) :

?>

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



Рис. 11.6.

Выполнение страницы view_bookmark.php для категории dining

Остается лишь создать страницу, на которой пользователь выбирает ссылки из списка. Я назвал этот файл index.php (листинг 11.11).

Листинг 11.11.

Программа index.php

<html>

<?

INCLUDE("init.inc");

?>

<head>

<titlex?-$title:?></title>

</head>

<body bgcolor="<?=$bg_color;?>" text="#000000" link="#808040" vlink="#808040"

alink="#808040">

<h4>Choose bookmark category to view:</h4>

<?

// Перебрать категории и создать соответствующие ссылки

while (list($key, Svalue) = each(Scategories)) :

print "<a href = \"view_bookmark.php?category=$key\">$value</a><br>";

endwhile;

?>

<p>

<b><a href="add_bookmark.php">Add a new bookmark</a></b>

</body>

</html>

Если оставить в массиве $categories значения, сохраненные в файле init.inc, в результате выполнения листинга 11.11 в браузер будет отправлен код HTML, приведенный в листинге 11.12.

Листинг 11.12.

Выходные данные, сгенерированные при выполнении index.php

<html>

<head>

<title></title>

</head>



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

<h4>Choose bookmark category to view:</h4>

<a href = "view_bookmark.php?category=0">computers</a><br>

<a href = "view_bookmark.php?category=1">entertainment</a><br>

<a href = "view_bookmark.php?category=2">dining</axbr>

<a href - "view_bookmark.php?category=3">lifestyle</a><br>

<a href = "view_bookmark.,php?category=4">government</a><br>

<a href = "view_bookmark.php?category=5">travel</a><br>

<p>

<b><a href=add_bookmark.php">Add a new bookmark</a></b>

</body>

</html>

Если щелкнуть на любой ссылке из приведенного выше,фрагмента HTML, в браузере загружается файл view_bookmark.php, который вызывает функцию view_bookmark( ) и передает ей значение переменной $category.


Простейшая поисковая система


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

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

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

Листинг 11.5.

Простейшая поисковая система (searchengine.php)

<?

$form =

"<form action=\"Listing11-5.php\" method=\"post\">

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

Keyword:<br>

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

Search Focus:<br>

<select name=\"category\">

<option value=\"\">Choose a category:

<option value-\"cust_id\">Customer ID

<option value=\"cust_name\">Customer Name

<option value=\"cust_eman\">Customer Email

</select><br>

<input type-\"submit\" value=\"search\"> ,

</form>

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


if (Sseenform != "у") :

print $form; else :

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

@mysql_connect("localhost", "web", "ffttss")

or die(" Could not connect to MySQL server!");

@mysql_select_db("company")

or die("Could not select company database!");

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

$query = "SELECT cust_id. cust_name, cust_email

FROM customers WHERE $category = '$keyword'";

$result = mysql_query($query);

// Если совпадения не найдены, вывести сообщение

// и заново отобразить форму

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

print "Sorry, but no matches were found. Please try your search again:";

print $form;

// Найдены совпадения. Отформатировать и вывести результаты, else :

// Отформатировать и вывести значения полей.

list($id, $name, $email) = mysql_fetch_row($result);

print "<h3>Customer Information:</h3>";

print "<b>Name:</b> $name <br>";

print "<b>Identification #:</b> $id <br>";

print "<b>Email:</b> <a href-\"mailto:$email\">$email</a> <br>";

print "<h3>Order History:</h3>";

// Построить и выполнить запрос к таблице 'orders'

$query = "SELECT order_id, prod_id, quantity

FROM orders WHERE cust_id = '$id'

ORDER BY quantity DESC";

$result = mysql_query($query):

print "<table border = 1>";

print "<tr><th>0rder ID</th><th>Product ID</th><th>Quantity</th></tr>";

// Отформатировать и вывести найденные записи.

while (list($order_id, $prod_id, $quantity) = mysql_fetch_row($result));

print "<tr>";

print "<td>$order_id</td><td>$prod_id</td><td>$quantity</td>";

print "</tr>";

endwhile;

print "</table>";

endif;

endif;

?>



Если ввести ключевое слово Mi 1 апо и выбрать в раскрывающемся списке категорию Customer Name (Имя клиента), программа выводит следующую информацию:

Customer information:

Name:Milano

Identification#:2000cu

Email:felix@milano.com

Order History:

PostgreSQL
Solid
Sybase
UNIX dbm
Velods
Конечно, мы рассмотрели простейшую реализацию поисковой системы. Существует немало дополнительных возможностей — поиск по нескольким ключевым словам, поиск по неполным ключевым словам или автоматическая выборка записей с похожими ключевыми словами. Попробуйте применить свою творческую фантазию и реализовать их самостоятельно.


Сортировка таблиц


При выводе данных из базы необходимо предусмотреть возможность их сортировки по различным критериям. В качестве примера рассмотрим результаты, выведенные нашей поисковой системой, — обратим особое внимание на следующие после заголовка Order History: (История заказов). Допустим, список получился очень длинным, и вы хотите отсортировать данные по идентификатору товара (или идентификатору заказа). Чтобы вы лучше поняли, о чем идет речь, рекомендую посетить один из моих любимых сайтов, http://download.cnet.com. Если в процессе просмотра программ конкретной категории щелкнуть на заголовке столбца (название, дата размещения, количество загрузок или размер файла), то список автоматически упорядочивается по содержимому указанного столбца. Далее показано, как реализовать подобную возможность.

В листинге 11.6 мы производим выборку данных из таблицы orders. По умолчанию данные сортируются по убыванию объема заказа (поле quantity). Однако щелчок на любом заголовке таблицы приводит к тому, что страница загружается заново с упорядочением таблицы по указанному столбцу.

Листинг 11.6.

Сортировка таблиц (tablesorter.php)

<?

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

@mysql_connect("localhost". "web", "ffttss")

or die("Could not connect to MySQL server!");

@mysql_select_db( "company")

or die("Could not select company database!");

// Если значение переменной $key не задано, по умолчанию

// используется значение 'quantity' if (! isset($key)) :

$key = "quantity"; endif;

// Создать и выполнить запрос.

// Выбранные данные сортируются по убыванию столбца $key

$query = "SELECT order_id, cust_id, prod_id, quantity FROM orders ORDER BY $key DESC" $result = mysql_query($query);

// Создать заголовок таблицы

print "<table border = 1>";

print "<tr>

<th><a href=\"Listing11-6.php?key=order_id\">Order ID</a></th>

<th><a href=\"Listing11-6.php?key=cust_id\">Customer ID</a></th>


<th><a href=\"Listing11-6.php?key=prod_id\">Product ID</a></th>

<th><a href=\"Listing11-6.php?key=quantity\">Quantity</a>

</th></tr>";

// Отформатировать и вывести каждую строку таблицы

while (list($order_id,$cust_id,$prod_id, $quantity)

= mysql_fetch_row($result)) :

print "<tr>";

print "<td>$order_id</td><td>$cust_id</td><td>$prod_id</td><td>

$quantity</td>";

print "</tr>";

endwhile;

// Завершить таблицу

print "</table>";

Для базы данных company, изображенной на рис. 11.1, стандартные выходные данные листинга 11.6 выглядят следующим образом:

Order Id Product Id Quantity
100003 1000pr 12
100005 1002pr 11
100003

100005

100004

100002

100001

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

Order ID Customer ID Product ID Quantity
2000cu 1000pr 12
2000cu 1002pr 11
2000cu 1000pr 9
2000cu 1001pr 5
2000cu 1002pr 3
100005

100004

100003

100002

100001

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

На этом наше знакомство с MySQL подходит к концу. Учтите, что материал этой главы отнюдь не исчерпывает всего, что необходимо знать о MySQL. Полный список команд MySQL в РНР приведен в документации (http://www.php.net/manuat).


Стандартные функции РНР для работы с MySQL


Итак, вы успешно создали и протестировали все необходимые разрешения; все готово для работы с сервером MySQL. В этом разделе я представлю стандартные функции РНР, при помощи которых вы сможете легко организовать взаимодействие сценариев РНР с сервером MySQL. Общая последовательность действий при взаимодействии с сервером MySQL выглядит так:

Установить соединение с сервером MySQL. Если попытка завершается неудачей, вывести соответствующее сообщение и завершить процесс.

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

Обработать запросы к выбранной базе (или базам).

После завершения обработки запросов закрыть соединение с сервером баз данных.

В примерах этого раздела используются таблицы products, customers и orders (см. рис. 11.1). Если вы захотите самостоятельно проверить все примеры, создайте эти таблицы или скопируйте страницу с описанием структуры, чтобы вам не приходилось постоянно листать книгу.

Итак, начнем с самого начала — то есть с подключения к серверу MySQL.

mysql_connect()

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

int mysql_connect ([string хост [:порт] [:/путь//к/сокету] [, string имя пользователя] [, string пароль])

В параметре хост передается имя хостового компьютера, указанное в таблицах привилегий сервера MySQL. Конечно, оно же используется для перенаправления запросов на web-сервер, на котором работает MySQL, поскольку к серверу MySQL можно подключаться в удаленном режиме. Наряду с именем хоста могут указываться необязательные параметры — номер порта, а также путь к сокету (для локального хоста). Параметры имя_пользователя и пароль должны соответствовать имени пользователя и паролю, заданным в таблицах привилегий MySQL. Обратите внимание: все параметры являются необязательными, поскольку таблицы привилегий можно настроить таким образом, чтобы они допускали соединение без проверки. Если параметр хост не задан, mysql_connect( ) пытается установить связь с локальным хостом.


Параметр имя_базы_данных определяет выбираемую базу данных, идентификатор

которой возвращается функцией mysql_select_db( ). Обратите внимание: параметр

идентификатор_соединения необязателен лишь при одном открытом соединении с

сервером MySQL. При наличии нескольких открытых соединений этот параметр

должен указываться. Пример выбора базы данных функцией mysql_select_db( ):

<?

@mysql_connect("localhost", "web". "4tf9zzzf")

or die("Could not connect to MySQL server!");

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

?>

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

mysql_close( )

После завершения работы с сервером MySQL соединение необходимо закрыть. Функция mysql_close( ) закрывает соединение, определяемое необязательным параметром. Если параметр не задан, функция mysql_close( ) закрывает последнее открытое соединение. Синтаксис функции mysql_close( ):

int mysql_close ([int идентификатор_соединения])

Пример использования mysql_close( ):

<?

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

or die("Could not connect to MySQL server!");

@mysql_select_db("company") or die("Could not select company database!"); print "You're connected to a MySQL database!";

?>

В этом примере указывать идентификатор соединения не нужно, поскольку на момент вызова mysql_close( ) существует лишь одно открытое соединение с сервером.

Соединения, открытые функцией mysql_pconnect( ), закрывать не обязательно.

mysql_query( )

Функция mysql_query( ) обеспечивает интерфейс для обращения с запросами к базам

данных. Синтаксис функции mysql_query( ):



int mysql_query (string запрос [, int идентификатор_соединения])

Параметр запрос содержит текст запроса на языке SQL. Запрос передается либо соединению, определяемому необязательным параметром идентификатор_соедине-ния, либо, при отсутствии параметра, последнему открытому соединению.

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

успешном выполнении команды SQL SELECT возвращается идентификатор результата, который впоследствии передается функции mysql_result( ) для последующего форматирования и отображения результатов запроса. Если обработка запроса завершилась неудачей, функция возвращает FALSE. Функция mysql_result( ) описана в одном из следующих разделов. Количество записей, участвующих в запросе, определяется при помощи функции mysql_num_rows( ). Эта функция также описана далее.

Учитывая сказанное, я приведу примеры использования mysql_query( ) лишь после описания функций mysql_result( ) и mysql_affected_rows( ).

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

mysqLaff ected_rows ( )

Во многих ситуациях требуется узнать количество записей, участвующих в запросе SQL с командами INSERT, UPDATE, REPLACE или DELETE. Задача решается функцией mysql_affected_rows( ). Синтаксис функции:

int mysql_affected_rows ([int идентификатор_соединения])

Обратите внимание: параметр идентификатор_соединения не является обязательным. Если он не указывается, mysql_affected_rqws( ) пытается использовать последнее открытое соединение. Пример:

<?

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

@mysql_connect("localhost", "web". "4tf9zzzf")



or die(" Could not connect to MySQL server!");

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

// Создать запрос

$query = "UPDATE products SET prod_name = \"cantaloupe\"

WHERE prod_id = \'10001pr\";

// Выполнить запрос $result = mysql_query($query);

// Определить количество обновленных записей

print "Total row updated; ".mysql_affected_rows( );

mysql_close( );

?>

При выполнении этого фрагмента будет выведен следующий результат:

Total row updated: 1

Функция mysql_affected_rows( ) не работает с запросами, основанными на команде SELECT. Для определения количества записей, возвращенных при вызове SELECT, используется функция mysql_num_rows( ), описанная в следующем разделе.

В одной специфической ситуации функция mysql_affected_rows( ) работает с ошибкой. При выполнении команды DELETE без секции WHEREmysql_affected_rows( ) всегда возвращает 0.

mysql_num_rows( )

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

int mysql_num_rows(int результат)

Пример использования mysql_num_rows( ):

<?

// Подключиться к серверу и выбрать базу данных @mysql_connect("localhost", "web", "4tf9zzzf")

or die("Could not connect to MySQL server!");

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

// Выбрать все товары, названия которых начинаются с 'р'

$query = "SELECT prod_name FROM products WHERE prod_name LIKE \"p*\"";

// Выполнить запрос $result = mysql_query($query);

print "Total rows selected: ".mysql_num_rows($result);

mysql_close( );

?>

Поскольку таблица содержит лишь один товар, название которого начинается с буквы р (pears), возвращается только одна запись. Результат:

Total rows selected: 1

mysql_result( )

Функция mysql_result() используется в сочетании с mysql_query( ) (при выполнении запроса с командой SELECT) для получения набора данных. Синтаксис функции mysql_resu1t():



int mysql_result (int идентификатор_результата, int запись [. mixed поле"]')

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



смещение поля в таблице;

имя поля;

имя поля в формате имя_поля_имя_тдблицы.

В листинге 11.1 используется база данных, изображенная на рис. 11.1.

Листинг 11.1.

Выборка и форматирование данных в базе данных MySQL

<?

@mysql_connect("localhost", "web", "ffttss")

or die("Could not connect to MySQL server!");

@mysql_select_db("company")

or die("Could not select products database!");

// Выбрать все записи из таблицы products

$query = "SELECT * FROM products"; $result = mysql_query($query);

$x = 0;

print "<table>\n";

print "<tr>\n<th>Product ID</th><th>Product Name</th><th>Product Price</th>\n</tr>\n";

while ($x < mysql_numrows($result)) :

$id = mysql_result($result. $x. 'prod_id');

$name = mysql_result($result, $x, 'prod_name');

$price = mysql_result($result. $x, 'prod_price');

print "<tr>\n";

print "<td>$id</td>\n<td>$name</td>\n<td>$price</td>\n";

print "</tr>\n";

$x++;

endwhile;

print "</table>";

mysql_close();

?>

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

Листинг 11.2.

Результат выполнения листинга 11.1

<table>

<tr>

<th>Product ID</th><th>Product Name</th><th>Product Price</th>

</tr>

<tr>

<td>1000pr</td>

<td>apples</td>

<td>1.23</td>

</tr>

<tr>

<td>1001pr</td>



<td>oranges</td>

<td>2.34</td>

</tr>

<tr>

<td>1002pr</td>

<td>bananas</td>

<td>3.45</td>

</tr>

<tr>

<td>1003pr</td>

<td>pears</td>

<td>4.45</td>

</tr>

</table>

Функция mysql_result( ) удобна для работы с относительно небольшими наборами данных, однако существуют и другие функции, работающие намного эффективнее, — а именно, функции mysql_fetch_row( ) и mysql_fetch_array( ). Эти функции описаны в следующих разделах.

mysql_fetch_row()

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

array mysql_fetch_row (int результат)

Использование функции list( ) в сочетании с mysql_fetch_row( ) позволяет сэкономить несколько команд, необходимых при использовании mysql_result( ). В листинге 11.3 приведен код листинга 11.1, переписанный с использованием list( ) и mysql_fetch_row( ).

Листинг 11.3.

Выборка данных функцией mysql_fetch_row( ) <?

@mysql_connect( "localhost", "web", "ffttss") or die("Could not connect to MySQL server!");

@mysql_select_db("company") or die("Could not select products database!");

$query = "SELECT * FROM products"; 

$result = mysql_query($query);

print "<table>\n";

print "<tr>\n<th>Product ID</th><th>Product Name</th><th>

Product Price</th>\n</tr>\n";

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

print "<tr>\n":

print "<td>".$row["prod_id"]."</td>\n<td>".$row["prod_name"]."

</td>\n<td>" .$row["prod_price"]. "</td>\n";



print "</tr>\n"; 

endwhile; 

print "</table>"; 

mysql_close();

?>

Листинг 11. 3 выдает тот же результат, что и листинг 11.1, но использует при этом меньшее количество команд.

my sq l_f etch_array ( )

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

array mysql_fetch_array (int идентификатор результата [, тип_индексации])

В параметре идентификатор_результата передается значение, возвращенное функцией mysql_query( ). Необязательный параметр тип_индексации принимает одно из следующих значений:



MYSQL_ASSOC — функция mysql_fetch_array( ) возвращает ассоциативный массив. Если параметр не указан, это значение используется по умолчанию;

MYSQL_NUM — функция mysql_fetch_array( ) возвращает массив с числовой индексацией;

MYSQL_BOTH — к полям возвращаемой записи можно обращаться как по числовым, так и по ассоциативным индексам.

Листинг 11.4 содержит очередной вариант кода листингов 11.1 и 11.3. На этот раз используется функция mysql_fetch_array( ), возвращающая ассоциативный массив полей.

Листинг 11.4.

Выборка данных функцией mysql_fetch_array( )

<?

@mysql_connect( "local host", "web", "ffttss")

or die("Could not connect to MySQL server!");

@mysql_select_db( "company" )

or die("Could not select products database!");

$query = "SELECT * FROM products";

$result = mysql_query($query);

"<table>\n";

print "<tr>\n<th>Product ID</th><th>Product Name</th> <th>Product Price</th>\n</tr>\n";

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

print "<tr>\n";

print "<td>".$row["prod_id"]."</td>\n <td>".$row["prod_name"]."</td>\n <td>" . $row["prod_price"] . "</td>\n" ;

print "</tr>\n";

endwhile;

print "</table>";

mysql_close();

?>

Листинг 11.4 выдает тот же результат, что и листинги 11.1 и 11.3.

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


Установка


Одна из причин популярности MySQL среди пользователей РНР заключается в том, что поддержка этого сервера автоматически включается в поставку РНР. Таким образом, вам остается лишь проследить за правильной установкой пакета MySQL СУБД MySQL совместима практически с любой серьезной операционной системой, включая FreeBSD, Solaris, UNIX, Linux и различные верии Windows. Хотя лицензионная политика MySQL отличается большей гибкостью в сравнении с другими серверами баз данных, я настоятельно рекомендую ознакомиться с лицензионной информацией, размещенной на сайте MySQL (http://www.mysql.com).

Последнюю версию MySQL можно принять с любого зеркального сайта. Полный список зеркальных сайтов приведен по адресу http://www.mysql.com/downloads/ mirrors.html. На момент написания книги последняя стабильная версия MySQL имела номер 3.22.32, а версия 3.32 находилась на стадии бета-тестирования. Конечно, всегда следует устанавливать последнюю стабильную версию, это в ваших интересах. Посетите ближайший «зеркальный» сайт и загрузите версию, соответствующую вашей операционной системе. В верхней части страницы расположены ссылки на новые версии для различных платформ. Обязательно прочитайте всю страницу, поскольку она завершается ссылками для некоторых специфических ОС.

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



В этой главе была представлена


В этой главе была представлена концепция, особенно важная как для РНР, так и для web-программирования в целом, — применение шаблонов. Глава началась с обзора двух схем; упоминавшихся ранее, — простой замены переменных средствами РНР и логическим делением страницы при помощи включаемых файлов. Затем мы познакомились с третьей схемой применения шаблонов, позволяющей полностью отделить программирование от дизайна страницы. Оставшаяся часть главы была посвящена анализу класса, построенного для реализации шаблонов такого рода. Главу завершает пример практического использования шаблонов в адресной книге на базе Web. В частности, в этой главе рассматривались следующие темы:

для чего нужны шаблоны;
простой шаблон № 1: внедрение РНР в HTML;
простой шаблон № 2: разделение компонентов страницы при помощи включаемых файлов;
нетривиальное использование шаблонов для полного разделения программирования и дизайна;
класс для работы с шаблонами;
регистрация файлов;
регистрация переменных;
подстановка значений переменных в файл;
вывод файла в браузере;
недостатки шаблонов;
адресная книга, расширяющая стандартный класс шаблона за счет применения запросов SQL.
В следующей главе мы продолжим знакомство с разработкой динамических web-приложений. Вы узнаете, как при помощи cookie и отслеживания сеансовых данных наделить ваш web-сайт новыми интерактивными возможностями.

Недостатки системы шаблонов


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



Необоснованные надежды на «идеальное решение»


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



Нетривиальная система шаблонов


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

К счастью, сделать это проще, чем кажется на первый взгляд, — при условии, что до

начала разработки было проведено некоторое предварительное планирование. В листинге 12.1 представлен некий базовый шаблон, созданный на основе материала этой главы.

Листинг 12.1.

Пример шаблона

<html>

<head>

<title>:::::{page_title}:::::</title>

</head>

<body bgcolor="{bg_color}">

Welcome to your default home page. {user_name}!<br>

You have 5 MB and 3 email addresses at your disposal.<br>

Have fun!

</body>

</html>

Обратите внимание на три строки (page_title, bg_color и userjiame), заключенные в фигурные скобки ({ }). Фигурные скобки имеют особый смысл при обработке шаблонов — заключенная в них строка интерпретируется как имя переменной, вместо которого подставляется ее значение. Дизайнер строит страницу по своему усмотрению; все, что от него потребуется, — включать в соответствующие места документа эти ключевые строки. Конечно, программисты и дизайнеры должны заранее согласовать имена всех переменных!

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

class template {

VAR $files = array( );

VAR $variables = array( );

VAR $openi ng_escape = '{';

VAR $closing_escape = '}';

В массиве $files хранятся идентификаторы файлов и содержимое каждого файла. Атрибут $variables представляет собой двухмерный массив для хранения файлового идентификатора (ключа) и всех соответствующих переменных, обрабатываемых в схеме шаблонов. Наконец, атрибуты $opening_escape и $closing_escape задают ограничители для частей шаблона, которые должны заменяться системой. Как было показано в листинге 12.1, в наших примерах в качестве ограничителей будут использоваться фигурные скобки ({ }). Впрочем, вы можете изменить два последних атрибута и выбрать ограничители по своему усмотрению. Главное — проследите за тем, чтобы эти символы не использовались для других целей.


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



Регистрация файлов — регистрация всех файлов, обрабатываемых сценариями шаблонов.

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

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

Вывод файла — вывод обработанных зарегистрированных файлов в браузере.

Применение концепций ООП в РНР рассматривалось в главе 6. Если вы не знакомы с ООП, я рекомендую бегло просмотреть главу 6 перед тем, как читать дальше.


О чем говорилось выше


До настоящего момента я упоминал о двух разных подходах к созданию шаблонов РНР:

внедрение HTML в код РНР;

включение файлов в страницу.

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

Вторая схема во многих ситуациях оказывается гораздо удобнее первой. Тем не менее, хотя структура «заголовок — основная часть — колонтитул» (см. главу 9)

хорошо подходит для структурирования относительно малых сайтов с четко определенным форматом, с увеличением объемов и сложности проекта эти ограничения проявляются все заметнее. Попытки решения этих проблем привели к разработке новой схемы применения шаблонов, более сложной по сравнению с двумя первыми, но и обладающей существенно большей гибкостью. В этой схеме разделяются два главных компонента web-приложения: дизайн и программирование. Подобное деление обеспечивает возможность параллельной разработки (web-дизайн и программирование) без необходимости постоянной координации на протяжении всего рабочего цикла. Более того, оно позволяет в будущем модифицировать один компонент, не влияя на работу другого. В следующем разделе я покажу, как устроена одна из таких схем «нетривиальных шаблонов». Следует помнить, что эта схема существует не только в РНР. Более того, она появилась задолго до РНР и в настоящее время используется в нескольких языках, включая РНР, Perl и Java Server Pages. To, что описано в этой главе, — не более чем адаптация этой схемы применительно к РНР.



Обработка файла


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

Листинг 12.4.

Метод обработки файла

function file_parser($file_id) {

// Сколько переменных зарегистрировано для данного файла?

$varcount = count($this->variables[$file_id]);

// Сколько файлов зарегистрировано?

$keys = array_keys($this->files):

// Если файл $file_id существует в массиве

$this->files

// и с ним связаны зарегистрированные переменные

If ( (in_array($file_id. $keys)) && ($varcount > 0) ) :

// Сбросить $x $x = 0:

// Пока остаются переменные для обработки...

while ($x < sizeof($this->variables[$file_id])) :

// Получить имя очередной переменной $string = $this->variables[$file_id][$x];

// Получить значение переменной. Обратите внимание:

// для получения значения используется конструкция $$.

// Полученное значение подставляется в файл вместо

// указанного имени переменной.GLOBAL $$string:

// Построить точный текст замены вместе с ограничителями

$needle = $this->opening_escape.$string.$this->closing_escape;

// Выполнить замену.

$this->files[$file_id] = str_replace( $needle.

$$string.

$this->files[$file_id]);

// Увеличить $х $x++;

endwhile;

endif;

}

Сначала мы проверяем, присутствует ли указанное имя файла в массиве $this->files. Если файл был зарегистрирован, мы также проверяем, были ли для него зарегистрированы переменные, и если были — значения этих переменных подставляются в содержимое $file_id. Пример:

// Включить класс шаблона include("template. class") ;

$page_title = "Welcome to your homepage!";

$bg_color = "white"; $user_name = "Chef Jacques";

// Создать новый экземпляр класса

$template = new template;

// Зарегистрировать файл "homepage.html",

II

присвоив ему псевдоним "home"

$template->register_file( "home", "homepage.html");

// Зарегистрировать несолько переменных

$template->register_variables("home", "page_titie, bg_color, user_name");

$template->file_parser("home");

Поскольку переменные page_title, bg_color и user_name были зарегистрированы, значения каждой переменной (присвоенные в начале сценария) подставляются в страницу homepage.html, хранящуюся в массиве files (атрибуте объекта-шаблона). На этом предварительная подготовка завершается, остается лишь вывести полученный шаблон в браузере. Эта операция рассматривается в следующем разделе.



Ориентация дизайна на РНР


Одна из главных целей создания шаблонов заключается в том, чтобы по возможности изолировать дизайнера от программного кода при редактировании внешнего вида и поведения страницы. В идеальном случае дизайнер должен обладать некоторыми навыками программирования или, по крайней мере, быть знакомым с общими концепциями — переменными, циклами и условными командами. Дизайнеру, абсолютно не разбирающемуся в них, применение шаблонов практически ничего не даст, кроме относительно бесполезных сведений из области синтаксиса. В общем, независимо от того, захотите вы пользоваться этим типом шаблонов или нет, я настоятельно рекомендую потратить немного времени и обучить дизайнера азам языка РНР... а еще лучше — купить ему эту книгу! От этого выиграют обе стороны, поскольку дизайнер приобретет дополнительные навыки и станет более ценным членом рабочей группы, а у программиста появится новый источник идей. Может, дизайнер и не изобретет ничего выдающегося, но зато он взглянет на ситуацию под новым углом зрения, обычно недоступным для программиста.



Проект: адресная книга


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

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

Прежде всего, необходимо решить, какие данные и в каком формате будут храниться в адресной книге. Конечно, оптимальным носителем информации в данном случае является база данных, поскольку это упростит такие полезные операции, как поиск и сортировка данных. В своем примере я воспользуюсь СУБД MySQL. Определение таблицы выглядит следующим образом:

mysql>CREATE table addressbook (

last_name char(35) NOT NULL,

first_name char(20) MOT NULL,

tel char(20) NOT NULL,

email char(55) NOT NULL );

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

Теперь я возьму на себя роль дизайнера и займусь созданием шаблонов. Для этого проекта нужны два шаблона. Код первого, «родительского» шаблона book.html приведен в листинге 12.8.

Листинг 12.8.

Основной шаблон адресной книги book.html

<html>

<head>

<title>:::::{page_title}:::::</title>

</head>

<body bgcolor="white">

<table cellpadding=2 cellspacing=2 width=600>

<h1>Address Book: {letter}</h1> <tr><td>


<a href="index.php?letter=a">A</a> |

<a href="index.php?letter=b">B</a> | 

<a href="index.php?letter=c">C</a> | 

<a href="index.php?letter=d">D</a> | 

<a href="index.php?letter=e">E</a> | 

<a href="index.php?letter=f">F</a> | 

<a href="index,php?letter=g">G</a> | 

<a href="index.php?letter=h">H</a> | 

<a href="index.php?letter=i">I</a> | 

<a href="index.php?letter=j">J</a> | 

<a href="index.php?letter=k">K</a> | 

<a href="index.php?letter=l">L</a> | 

<a href="index.php?letter=m">M</a> | 

<a href="index.php?letter=n">N</a> | 

<a href="index.php?letter=o">O</a> | 

<a href="index.php?letter=p">P</a> | 

<a href="index.php?letter=q">Q</a> | 

<a href="index.php?letter=r">R</a> | 

<a href="index.php?letter=s">S</a> | 

<a href="index.php?letter=t">T</a> | 

<a href="index.php?letter=u">U</a> | 

<a href="index.php?letter=v">V</a> | 

<a href="index.php?letter=w">W</a> | 

<a href="index.php?letter=x">X</a> | 

<a href="index.php?letter=y">Y</a> | 

<a href="index.php?letter=z">Z</a>

</td></tr>

{rows.addresses}

</table>

</body>

</html>

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



В странице встречаются три имени переменных, заключенных в ограничители: page_title, letter и rows_addresses. Смысл первых двух переменных очевиден: текст в заголовке страницы и буква адресной книги, использованная для выборки текущих адресных данных. Третья переменная относится к дополнительному шаблону (листинг 12.9) и определяет файл конфигурации таблицы, включаемый в основной шаблон. Файлы конфигурации таблиц используются в связи с тем, что в сложных страницах может быть одновременно задействовано несколько шаблонов, в каждом из которых данные форматируются в виде таблиц HTML. Шаблон rows.addresses (листинг 12.9) выполняет вспомогательные функции и вставляется в основной шаблон book.html. Вскоре вы поймете, почему это необходимо.

Листинг 12.9.

Вспомогательный шаблон rows.addresses

<tr><td bgcolor="#c0c0c0">

<b>{last_name},{first_name}</b>

</td></tr>

<tr><td>

<b>{telephone}</b>

</td></tr>

<tr><td>

<b><a href = "mailto:{email}">{email}</a></b>

</td></tr>

В листинге 12.9 встречаются четыре переменных, заключенных в ограничители: last_name, first_name, telephone и emal. Смысл этих переменных очевиден (см. определение таблицы addressbook). Следует заметить, что этот файл состоит только из табличных тегов строк (<tr>...</tr>) и ячеек (<td>...</td>). Дело в том, что этот файл вставляется в шаблон многократно, по одному разу для каждого адреса, прочитанного из базы данных. Поскольку имя переменной rows.addresses в листинге 12.8 включается внутрь тегов <table>...</table>, форматирование HTML будет обработано правильно. Чтобы вы лучше поняли, как работает этот шаблон, взгляните на рис. 12.1 — на нем изображена копия страницы адресной книги. Затем проанализируйте листинг 12.10, содержащий исходный текст этой страницы. Вы увидите, что содержимое файла rows.addresses многократно встречается в странице.



Листинг 12.10.

Исходный текст страницы, изображенной на рис. 12.1

<html>

<head>

<title>:::::Address Book:::::</title>

</head>

<body bgcolor="white">

<table cellpadd1ng=2 cellspacing=2 width=600>

<hl>Address Book: f</hl>

<tr><td>

<a href="index.php?letter=a">A</a> | 

<a href="index.php?letter=b">B</a> | 

<a href="index.php?letter=c">C</a> | 

<a href="index.php?letter=d">D</a> | 

<a href="index.php?letter=e">E</a> | 

<a href="index.php?letter=f">F</a> | 

<a href="index.php?letter=g">G</a> | 

<a href="index.php?letter=h">H</a> | 

<a href="index.php?letter=i">I</a> | 

<a href="index.php?letter=j">J</a> | 

<a href="index.php?letter=k">K</a> | 

<a href="index.php?letter=l">L</a> | 

<a href="index.php?letter=m">M</a> | 

<a href="index.php?1etter=n">N</a> | 

<a href="index.php?letter=o">0</a> | 

<a href="index.php?letter=p">P</a> | 

<a href="index.php?letter=q">Q</a> | 

<a href="index.php?letter=r">R</a> | 

<a href="index.php?letter=s">S</a> | 

<a href="index.php?letter=t">T</a> | 

<a href="index.php?letter=u">U</a> | 

<a href="index.php?letter=v">V</a> | 

<a href="index.php?letter=w">W</a> | 

<a href="index.php?letter=x">X</a> | 

<a href="index.php?letter=y">Y</a> | 

<a href="index.php?letter=z">Z</a>



</td></tr>

<tr><t

bgcolor="#c0c0c0">

<b>Fries.Bobby</b>

</td></tr>

<tr><td>

<b>(212) 563-5678</b>

</td></tr>

<tr><td> "

<b>

<a href="mailto:bobby@fries.com">bobby@fries.com</a>

</b>

</td></tr>

<tr><td bgcolor="#c0c0c0">

<b>Frenchy.Pierre</b>

</td></tr>

<tr><td>

<b>002-(30)-09-7654321</b>

</td></tr>

<tr><td>

<b><a href = "mailto:frenchy@frenchtv.com">
frenchy@frenchtv.com</a></b>

</td></tr>

</table>

</body>

</html>

Как видно из приведенного листинга, в адресной книге хранятся записи двух лиц, фамилии которых начинаются с буквы F: Bobby Fries и Pierre Frenchy. Соответственно в таблицу вставляются данные двух записей.

Дизайнерская часть проекта адресной книги завершена, и я перехожу к роли программиста. Возможно, вас удивит тот факт, что класс tempiate. class (см. листинг 12.7) практически не изменился, если не считать появления одного нового метода — address_sql( ). Код этого метода приведен в листинге 12.11.

Листинг 12.11.

Обработка данных, полученных в результате запроса

class template {

VAR $files = array( );

VAR $variab!es = array( ):

VAR $sql = array();

VAR $opening_escape - '{';

VAR $closing_escape = '}';

VAR $host = "localhost";

VAR $user = "root";

VAR $pswd = "";

VAR $db = "book";

VAR $address table = "addressbook";

function address_sql($file_id, $vanable_name, $letter) {

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

mysql_connect($this->host, $this->user, $this->pswd)

or die("Couldn't connect to MySQL server!");

mysql_select_db($this->db) or die('Couldn't select MySQL database!");

// Обратиться с запросом к базе данных



$query = "SELECT last_name, first_name, tel, email

FROM $this->address_table WHERE lastjiame LIKE '$letter%' ";

$result = mysql_query($query);

// Открыть файл "rows.addresses"

// и прочитать его содержимое в переменную

$fh - fopen("$variable_name", "r");

$file_contents = fread($fh, filesize("rows.addresses") ):

// Заменить имена переменных в ограничителях

// данными из базы.

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

$new_row = $file_contents;

$new_row=str_replace($this->opening_escape.
"last_name".$this->closing_escape. 

$row["last_name"]. $new_row);

$new_row=

str_replace($th1s->opening_escape.
"first_name".$this->closing_escape.

$row["first_name"], $new_row);

$new_row=str_replace($this->opening_escape.
"telephone".$this->closing_escape.

 $row["tel"], $new_row);

$new_row = str_replace($this->opening_escape.
"email".$this->closing_escape,

 $row["email"],

$new_row);

// Присоединить запись к итоговой строке замены

$complete_table .= $new_row;

endwhile;

$sql_array_key = $variable_name;

$this->sql[$sql_array_key] = $complete_table;

// Включить ключ в массив variables для последующего поиска

$this->variables[$file_id][ ] = $variable_name;

// Закрыть файловый манипулятор fclose(lfh);

Комментариев, приведенных в листинге 12.11, вполне достаточно для того, чтобы вы разобрались в происходящем, однако я должен сделать несколько важных замечаний. Во-первых, обратите внимание на то, что файл rows.addresses открывается только один раз. Возможен и другой вариант — многократно открывать и закрывать файл rows.addresses, каждый раз производя замену и присоединяя его содержимое к переменной $complete_table. Впрочем, такое решение будет крайне неэффективным. Потратьте немного времени и разберитесь в том, как новые данные таблицы в цикле присоединяются к переменной $complete_table.



Второе, на что следует обратить внимание при просмотре листинга 12.11, — появление пяти новых атрибутов класса: $host, $user, $pswd, $db и $address_table. В этих атрибутах хранится информация, необходимая для сервера SQL. Полагаю, смысл каждого атрибута понятен без объяснений, а если нет — вернитесь и повторите материал главы 11.

 


Рис. 12.1.

Страница адресной книги

Все, что осталось сделать — написать файл index.php, инициирующий обработку шаблонов, Код этого файла приведен в листинге 12.12. Если щелкнуть на одной из ссылок (index.php?letter=буква) на странице book.html (см. листинг 12.8), загружается страница index.php, которая, в свою очередь, заново строит book.html с включением новой информации.

Листинг 12.12.

Обработчик шаблонов index.php

include("Listing12-11.php"); $page_title = "Address Book";

// По умолчанию загружается страница с фамилиями,

// начинающимися с буквы 'а' if (! isset($letter) ) :

$letter = "а";

endif ;

$tpl = new template;

$tpl->register_file("book", "book.html");

$tpl->register_variables("book", "page_title.letter");

$tpl ->address_sql("book", "rows.addresses", "$letter");

$tpl ->file_parser("book");

$tpl->phnt_fil("book");

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


Расширения класса template


Конечно, класс tempi ate обладает весьма ограниченными возможностями, хотя для проектов, создаваемых на скорую руку, он вполне подходит. Объектно-ориентированные схемы хороши тем, что они позволяют легко наращивать функциональность, не беспокоясь о возможных нарушениях работы существующего кода. Допустим, вы решили создать новый метод, который будет загружать значения для последующей замены из базы данных. Хотя такой метод устроен чуть сложнее, чем метод file_parser( ), производящий простую замену глобальных переменных, его реализация на базе SQL состоит из нескольких строк и легко инкапсулируется в отдельном методе. Более того, мы создадим нечто подобное в проекте адресной книги, завершающем эту главу.

В класс tempi ate можно внести несколько очевидных усовершенствований. Первое — объединение функций register_file( ) и register_variables( ), обеспечивающее автоматическую регистрацию переменных для каждого регистрируемого файла. Конечно, при этом также необходимо реализовать проверку ошибок, чтобы предотвратить регистрацию неверных файлов и переменных.

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

Общие схемы работы с шаблонами были реализованы на нескольких языках и ни в коем случае не являются чем-то принципиально новым. В Web можно найти немало информации о реализации шаблонов. Рекомендую два особенно интересных ресурса — сборники статей, написанных с ориентацией на JavaScript:

http://www.netscape.com/viewsource/long_ssjs/long_ssjs.html;

http://www.netscape.com/viewsource/schroder_template/schroder_template.html.

В следующей статье затронута тема использования шаблонов применительно к Java Server Pages:

http://www-4.ibm.com/software/webservers/appserv/doc/guide/asgdwp.html.

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

PHPLib Base Library: http://phplib.netuse.de;

Richard Hayes's Template Class: http://www.heyes-computing.net;

Fast Template: http://www.thewebmasters.net/php.

На сайте ресурсов РНР, PHPBuilder (http://www.phpbuilder.com), также имеется несколько интересных учебников, посвященных обработке шаблонов. Кроме того, загляните на сайт РНР Classes Repository (http://phpclasses.UpperDesign.com), здесь также можно найти несколько реализаций.



Регистрация файлов


В процессе регистрации содержимое файла сохраняется в массиве с ключом, однозначно идентифицирующим этот файл. Метод register_file( ) открывает и читает содержимое файла, имя которого передается в качестве параметра. Код этого метода приведен в листинге 12.2.

Листинг 12.2.

Метод регистрации файла

function register_file($file_id, $file_name) {

// Открыть $file_name для чтения или завершить программу

// с выдачей сообщения об ошибке.

$fh = fopen($file_name, "r") or die("Couldn't open $file_name!");

// Прочитать все содержимое файла $file_name в переменную.

$file_contents = fread($fh, filesize($file_name));

// Присвоить содержимое элементу массива

// с ключом $file_id. $this->files[$file_id] = $file_contents;

// Работа с файлом завершена, закрыть его.

fclose($fh);

}

Параметр $file_id содержит идентификатор — «псевдоним» для последующих операций с файлом, упрощающий последующие вызовы метода. Идентификатор используется в качестве ключа для индексирования массива $files. Пример регистрации файла:

// Включить класс шаблона

include("tempiate.class"):

// Создать новый экземпляр класса

$template = new template:

// Зарегистрировать файл "homepage.html",

// присвоив ему псевдоним "home"

$template->register_file("home", "homepage.html");



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


После регистрации файлов необходимо зарегистрировать все переменные, которые будут интерпретироваться особым образом. Метод register_variables( ) (листинг 12.3) работает по тому же принципу, что и register_file( ), — он читает имена переменных и сохраняет их в массиве $variables.

Листинг 12.3.

Метод регистрации переменнных

function register_vanables($file_id, $variable_name) {

// Попытаться создать массив,

// содержащий переданные имена переменных

$input_variables - explode(".", $variable_name);

// Перебрать имена переменных

while (Iist($value) = each($input_variables)) :

// Присвоить значение очередному элементу массива

$this->variables $this->variables[$file_id][] = $value:

endwhile;

}

В параметре $file_id передается ранее присвоенный псевдоним файла. Например, в предыдущем примере файлу homepage.html был присвоен псевдоним home. Обратите внимание — при регистрации имен переменных, которые должны особым образом обрабатываться в файле homepage.html, вы должны ссылаться на файл по псевдониму! В параметре $variable_name передаются имена одной или нескольких переменных, регистрируемых для указанного псевдонима. Пример:

// Включить класс шаблона include("tempiate.class");

// Создать новый экземпляр класса $template = new template;

// Зарегистрировать файл "homepage.html",

// присвоив ему псевдоним "home" $template->register_file("home", "homepage.html");

// Зарегистрировать несколько переменных

$template->register_variablest"home", "page_title.bg_color,user_name");



Снижение быстродействия


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



Вывод файла


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

файла создается отдельный метод, приведенный в листинге 12.5, однако в зависимости от ситуации вывод также может интегрироваться с методом f i I e_parser().

Листинг 12.5.

Метод вывода файла в браузере

function pnnt_file($file_id) {

// Вывести содержимое файла с идентификатором

$file_id print $this->files[$file id];

}

Все очень просто — при вызове print_file( ) содержимое файла, представленного ключом $file_id, передается в браузер.

В листинге 12.6 приведен пример использования класса template.

Листинг 12.6. Пример использования класса template

// Включить класс шаблона, include("tempiate.class");

// Присвоить значения переменным

$page_title = "Welcome to your homepage!";

$bg_color = "white"; $user_name = "Chef Jacques":

// Создать новый экземпляр класса $template= new template;

// Зарегистрировать файл "homepage.html" с псевдонимом "home"

$template->register_file("home", "homepage.html");

// Зарегистрировать переменные

$template->register_variables("home", "page_title, bg_color.user_name");

$template->file_parser("home");

// Передать результат в браузер

$template->print_file("home");

Если бы шаблон, приведенный в листинге 12.1, хранился в файле homepage.html в одном каталоге со сценарием из листинга 12.6, то в браузер был бы направлен следующий код HTML:

<html>

<head>

<title>:::::Welcome to your homepage!:::::</title>

</head>

<body bgcolor=white>

Welcome to your default home page, Chef Jacques!<br>

You have 5 MB and 3 email addresses at your disposal.<br>

Have fun!

</body>

</html>

Как видно из приведенного примера, все зарегистрированные переменные были заменены соответствующими значениями. При всей своей простоте класс tempi ate


обеспечивает стопроцентное разделение уровней программирования и дизайна. Полный код класса template приведен в листинге 12.7.
Листинг 12.7.
Полный код класса template
class template {
VAR $files = array( );
VAR $variables = array( );
VAR $opening_escape = '{';
VAR $closing_escape = '}' ;
// Функция: register_file( )
// Назначение: сохранение в массиве содержимого файла.
// определяемого идентификатором $file_id
function register_file($file_id. $file_name) {
// Открыть $file_name для чтения или завершить программу
// с выдачей сообщения об ошибке.
$fh = fopen($file_name, "r") or die("Couldn't open $file_name!");
// Прочитать все содержимое файла $file_name в переменную.
$file_contents = fread($fh, filesize($file_name));
// Присвоить содержимое элементу массива
// с ключом $file_id. $this->files[$file_id] = $file_contents;
// Работа с файлом завершена, закрыть его.
fclose($fh):
} // Функция: register_variables( )
// Назначение: сохранение переменных, переданных
// в параметре $variable_name. в массиве с ключом $file_id.
function register_variables($file_id, $variable_name) {
// Попытаться создать массив.
// содержащий переданные имена переменных
$input_variables = explode(".", $vahable_name);
// Перебрать имена переменных
while (list(, $value) = each($input_variables)) :
// Присвоить значение очередному элементу массива $this->variables $this->variables[$file_id][] = $value:
endwhile;
} // Функция: file_parser( )
// Назначение: замена всех зарегистрированных переменных
// в файле с идентификатором $file_id
function file_parser($file_id) {
// Сколько переменных зарегистрировано для данного файла?
$varcount = count($this->variables[$file_id]):
// Сколько файлов зарегистрировано?
$keys = array_keys($this->files):
// Если файл $file_id существует в массиве $this->files
// и с ним связаны зарегистрированные переменные
if ( (in_array($file_id. $keys)) && ($varcount > 0) ) :


// Сбросить $х $x - 0;
// Пока остаются переменные для обработки...
while ($x < sizeof($this->variables[$file_id])) :
// Получить имя очередной переменной
$string = $this->variables[$file_id][$x];
// Получить значение переменной. Обратите внимание:
// для получения значения используется конструкция $$.
// Полученное значение подставляется в файл вместо
// указанного имени переменной.
GLOBAL $$string;
// Построить точный текст замены вместе с ограничителями
$needle = $this->opemng_escape.$string.$this->closing_escape;
// Выполнить замену.
$this->files[$file_id] = str_replace( $needle, $$string,
$this->files[$file_idj);
// Увеличить $х $x++;
endwhile;
endif;
}
// Функция: print_file()
// Назначение: вывод содержимого файла,
// определяемого параметром $file_id
function print_file($file_id) {
// Вывести содержимое файла с идентификатором $file_id
print $this->files[$file_id];
}
} //END template.class

Что такое cookie?


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

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



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 favorite background 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 (например, Netscape Communicator разрешает создавать до 20 cookie на домен).

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

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

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

mysql>create table user_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 cookie containing your userID has been set on your machine.

Please refresh the page to retrieve your user information";

else:

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



@mysql_select_db("user") or die("Could not select user 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 email address is ".$row[ "email"];

else:

print "Invalid User ID!";

endif;

mysql_close();

endif;

?>

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

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

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


PHP 4 на практике


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

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

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

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



В этой главе был представлен


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

общие сведения о cookie;
работа с cookie в РНР;
создание уникальных идентификаторов;
сценарии регистрации пользователей;
общие сведения о сеансах;
параметры сеансов в файле php.ini;
стандартные сеансовые функции в РНР;
функция session_set_save_handler( );
учет посещений сайта.
Концепция сеанса открывает очень широкие возможности перед разработчиками web-сайтов, ориентированных на пользователя. Настоятельно рекомендую поэкспериментировать с сеансовыми средствами РНР — думаю, вы оцените ту пользу, которую они могут принести.
На этом завершается вторая часть книги. Третья часть, «РНР для профессионалов», открывается обзором интеграции РНР с XML. Не расслабляйтесь, самое интересное впереди.

Компоненты 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 зависит от браузера. Например, Netscape Communicator хранит 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, достаточно открыть их в любом текстовом редакторе. Помните, что некоторые редакторы не обрабатывают завершающие символы новой строки и на месте этих символов в документе могут выводиться квадратики.

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


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


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

Order ID Customer ID Product ID Quantity
2000cu 1002pr 11
2000cu 1000pr 9
2000cu 1000pr 12
2000cu 1001pr 5
2000cu 1002pr 3

Параметр


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

Листинг 13.8.

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

<?

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

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

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

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

$db = "users";

$session table = "user session 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't connect to MySQL server!");

@mysql_select_db($db)

or die("Can't select 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 = "SELECT value 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 = "INSERT INTO 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 = "DELETE FROM $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 = "DELETE FROM $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( ) по мере необходимости.


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


Сеансом

(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

Описание
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( ). Параметр $ключ соответствует имени сеансовой переменной, а параметр $значение — значению, связываемому с заданным ключом
Директива

Описание

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

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

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

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

session.name =PHPRESSID.

Если флаг session.use_cookies установлен, то значение session.name



/p>

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

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

session_start( )

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

session_start( ): boolean session_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 "Your session identification number is ".sessionjd( ):

session_destroy( ):

?>

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

Your session identification 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 $hits times.

?>

Сеансовые переменные можно не только создавать, но и уничтожать. Для этой цели применяется функция 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 not connect to MySQL server!");

@mysql_select_db("users")

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

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

$query = "UPDATE user_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 not connect to MySQL server!");

@mysql_select_db("users")

or die("Could not select company database!");

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

$query = "SELECT page_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( ) обеспечивают очень удобные и эффективные сохранение и загрузку сеансовых данных.


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


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

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

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

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

mysql>create table visitors (

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

->host char(85) NOT NULL.

->timeOfVisit datetime 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-d H:i:S");

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

Sresult = @mysql_query($query); }

// recordUser function viewStats() {

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

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

$query = " SELECT browser, ip. host. TimeofVisit FROM $visitors_table ORDER BY TimeofVisit desc LIMIT 0, $maxNumVisitors";

$result = mysql_query($query);

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

print "<tr bgcolor= \"$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 "<tr bgcolor=\"$row_color\">";

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

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

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

print "<td><font color=\"$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 to My 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>Most recent <?=$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 для конкретной страницы, информация о которой регистрируется в журнале.


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


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

Функция uniqid( ) генерирует уникальный идентификатор.из 13 символов, значение которого основано на текущем времени. Синтаксис функции uniqid( ): int uniqid(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 = "INSERT INTO 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 uponon each 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:



http://www.cookiecentral.com;

http://home.netscape.com/newsref/std/cookie_spec.html;

http://builder.com/Programming/Cookies/ss01.html;

http://www.w3.org/Protocols/rfc2109/rfc2100.

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









session.save_handler = files
session.save_path =/tmp
session_use_cookies =1