Оглавление

Поиск книг в интернет магазине Болеро

Поиск DVD в интернет магазине DVD DOM

PHP 4

Содержание

    • Как читать этот диск
    • Введение
  • Часть I . Основы PHP
    • Глава 1. Знакомство с PHP
    • Глава 2. Переменные и типы данных
    • Глава 3. Выражения, операторы и управляющие конструкции
    • Глава 4. Функции
    • Глава 5. Массивы
    • Глава 6. Объектно-ориентированные возможности PHP
    • Глава 7. Файловый ввод/вывод и файловая система
    • Глава 8. Строки и регулярные выражения
  • Часть II. PHP и Web
    • Глава 9. PHP и динамическое создание страниц
    • Глава 10. Формы
    • Глава 11. Базы данных
    • Глава 12. Шаблоны
    • Глава 13. Cookie и отслеживание сеанса
  • Часть III. PHP для профессионалов
    • Глава 14. PHP и XML
    • Глава 15. JavaScript и COM
    • Глава 16. Безопасность


  • Глава 10. Формы
    • Общие сведения о формах
      • Элементы форм, ориентированные на ввод с клавиатуры
      • Элементы форм, ориентированные на ввод с мыши
      • Все вместе: пример формы
    • Формы и PHP
      • Вводные примеры
    • Проверка ошибок
    • Динамическое конструирование форм
    • Проект: гостевая книга
    • Итоги

ГЛАВА 10

Формы

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

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

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

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

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

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

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

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

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

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

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

Наше знакомство с построением форм начнется с элементов, ориентированных на ввод с клавиатуры. Таких элементов всего два — текстовое поле (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.

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

От описания базовых компонентов форм мы переходим к практическому примеру — построению формы для обработки данных, введенных пользователем. Допустим, вы хотите создать форму, в которой пользователь может высказать мнение о вашем Сайте. Пример такой формы приведен в листинге 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 мы переходим к самому интересному — применению РНР для обработки данных, введенных пользователем в форме.

Формы и РНР

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

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

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

Пример 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.

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

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

Обработка пользовательских данных дает осмысленный результат лишь в том случае, если данные имеют правильную структуру. Проверить достоверность введенных данных невозможно, однако вы можете проверить их целостность (например, убедиться в том, что адрес электронной почты соответствует стандартному шаблону). Хотя для проверки данных часто применяется технология 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 убеждается в том, что поля имени и адреса электронной почты не остались пустыми, а также проверяет правильность синтаксиса вве-, денного адреса. Если в результате каких-либо проверок в форме обнаруживаются ошибки, программа выводит соответствующие сообщения и отображает форму заново — при этом вся введенная ранее информация остается в форме, благодаря чему пользователю будет проще внести исправления. Если вывести пустую форму и предложить пользователю заполнить ее заново, он может отправиться

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

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

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

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

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

Пример 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;

?>

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

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

С первых дней 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!

Итоги

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

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

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


  • Глава 11. Базы данных
    • Что такое SQL?
    • Поддержка баз данных в PHP
    • MySQL
      • Установка
      • Настройка MySQL
    • Стандартные функции PHP для работы с MySQL
      • Простейшая поисковая система
      • Сортировка таблиц
    • ODBC
      • Поддержка ODBC в PHP
      • Microsoft Access и PHP
    • Проект: каталок ссылок
    • Итоги

ГЛАВА 11

Базы данных

Средства эффективного хранения и выборки больших объемов информации внесли огромный вклад в успешное развитие Интернета. Обычно для хранения информации используются базы данных. Работа таких известных сайтов, как 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.

Что такое 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.

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

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

Adabas D | Informix | PostgreSQL
Dbase | Ingres | Solid
Direct MS-SQL | InterBase | Sybase
Empress | mSQL | UNIX dbm
File-Pro (read-only) | MySQL | Velods
FrontBase | ODBC |
IBM DB2 | Oracle (OCI7 и OC18) |

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

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

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 совместима практически с любой серьезной операционной системой, включая 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 подготовила подробную документацию с описанием процесса установки. Я советую внимательно изучить все общие аспекты установки, не ограничиваясь информацией, относящейся непосредственно к вашей операционной системе.

Настройка MySQL

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

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

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

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

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

  1. Установить соединение с сервером MySQL. Если попытка завершается неудачей, вывести соответствующее сообщение и завершить процесс.
  2. Выбрать базу данных сервера MySQL. Если попытка выбора завершается неудачей, вывести соответствующее сообщение и завершить процесс. Допускается одновременное открытие нескольких баз данных для обработки запросов.
  3. Обработать запросы к выбранной базе (или базам).
  4. После завершения обработки запросов закрыть соединение с сервером баз данных.

В примерах этого раздела используются таблицы 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:

@mysql_connect(" local host", "web", "4tf9zzzf") or die("Could not connect to MySQL server!");

В данном примере localhost — имя компьютера, web— имя пользователя, а 4tf9zzzf — пароль. Знак @ перед вызовом функции mysql_connect( ) подавляет все сообщения об ошибках, выдаваемые при неудачной попытке подключения, — они заменяются сообщением, указанным при вызове die( ). Обратите внимание: значение, возвращаемое при вызове rnysql_connect( ), в данном примере не используется. Если в программе используется всего одно соединение с сервером MySQL, это вполне нормально. Но если программа устанавливает соединения с несколькими серверами MySQL на разных хостах, следует сохранить идентификатор соединения, возвращаемый при вызове mysql_connect( ), чтобы адресовать последующие команды нужному серверу MySQL. Пример:

<?

$link1 = @mysql_connect("www.somehost.com", "web", "abcde") or die("Could not connect to

MySQL server!");

$linkl = @mysql_connect("www.someotherhost.com", "usr", "secret") or die("Could not connect

to MySQL server!");

?>

Идентификаторы $link1 и $link2 передаются при последующих обращениях к базам данных с запросами. Вскоре вы узнаете, как именно адресовать запрос нужному серверу при помощи идентификатора соединения.

Функция mysql_pconnect( ) обеспечивает поддержку восстанавливаемых (persistent) соединений. В многопользовательских средах рекомендуется использовать mysql_pconnect( ) вместо mysql_connect( ) для экономии системных ресурсов. По типам параметров и возвращаемого значения функция mysql_pconnect( ) в точности совпадает c mysql_connect( ).

mysql_select_db( )

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

int mysql_select_db (string имя_базы_данных [, int идентификатор_соединения])

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

которой возвращается функцией 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 (см. предыдущую главу) для получения данных, которые в дальнейшем используются для выборки информации из базы.

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

Всем нам неоднократно приходилось пользоваться поисковыми системами в 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:

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

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

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

При выводе данных из базы необходимо предусмотреть возможность их сортировки по различным критериям. В качестве примера рассмотрим результаты, выведенные нашей поисковой системой, — обратим особое внимание на следующие после заголовка 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 | Customer ID | Product ID | Quantity
100003 | 2000cu | 1000pr | 12
100005 | 2000cu | 1002pr | 11
100004 | 2000cu | 1000pr | 9
100002 | 2000cu | 1001pr | 5
100001 | 2000cu | 1002pr | 3

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

Order ID | Customer ID | Product ID | Quantity
100005 | 2000cu | 1002pr | 11
100004 | 2000cu | 1000pr | 9
100003 | 2000cu | 1000pr | 12
100002 | 2000cu | 1001pr | 5
100001 | 2000cu | 1002pr | 3

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

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

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 в РНР».

Поддержка 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.

Microsoft Access и РНР

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

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

  1. Создайте базу данных Access. Предполагается, что вы уже умеете это делать, а если не умеете, но все равно хотите проследить за выполнением этого примера, — воспользуйтесь услугами программы-мастера. Я создал стандартную базу данных для хранения контактных данных при помощи мастера. Обязательно создайте в таблице несколько записей и запомните имя таблицы, оно нам вскоре понадобится!
  2. Сохраните базу данных на своем компьютере.
  3. На следующем шаге мы организуем доступ к базе Access средствами ODBC. Выполните команду Пуск > Настройка > Панельуправления (Start > Settings > Control Panel). Найдите на панели управления значок Источники данных ODBC (32) (ODBC Data Sources (32 bit)). При помощи этого значка запускается Администратор ODBC, предназначенный для настройки различных драйверов и источников данных в вашей системе. Запустите программу, дважды щелкнув на этом значке. Окно Администратора по умолчанию открывается на вкладке Пользовательский DSN (User DSN). На этой вкладке перечисляются источники данных-, которые относятся к конкретному пользователю и могут использоваться только на этом компьютере. В данном примере будет использоваться именно такой источник данных.
  4. Нажмите кнопку Добавить... (Add...) в правой части окна. Открывается новое окно для выбора драйвера, предназначенного для работы с новым источником. Выберите строку Microsoft Access Driver (*.mdb) и нажмите кнопку Finish (Готово).
  5. На экране появляется новое окно Установка драйвера ODBC для Microsoft Access (ODBC Microsoft Access Setup). Найдите в форме текстовое поле Имя источника данных (Data Source Name) и введите в нем имя созданной вами базы данных Access. При желании можете ввести описание в текстовом поле, расположенном непосредственно под полем Имя источника данных.
  6. Нажмите кнопку Выбрать... (Select...) — появляется окно в стиле Проводника Windows. В нем вам предлагается найти базу данных, доступ к которой будет осуществляться средствами ODBC.
  7. Найдите в дереве каталогов свою базу данных и дважды щелкните на ней. Вы снова возвращаетесь к окну Установка драйвера ODBC для Microsoft Access. Путь к выбранной базе данных отображается над кнопкой Выбрать.... Нажмите кнопку ОК.
  8. Готово! Теперь вы можете работать с базой данных 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-браузере

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

Самый простой способ наполнить ваш сайт реальным содержанием — дать пользователю возможность сделать это за вас. Конечно, для ввода данных удобнее всего воспользоваться формой 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;
  • стандартную поддержку баз данных в РНР;
  • вводную информацию о сервере MySQL;
  • стандартные функции РНР для работы с MySQL;
  • реализацию простейшей поисковой системы;
  • сортировку таблиц;
  • общие сведения об ODBC;
  • стандартные функции РНР для работы с ODBC;
  • взаимодействие с Microsoft Access в РНР;
  • хранение пользовательских данных на web-сайте.

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

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


  • Глава 12. Шаблоны
    • О чем говорилось выше
    • Нетривиальная система шаблонов
      • Регистрация файлов
      • Регистрация переменных
      • Обработка файла
      • Вывод файла
      • Расширение класса template
      • Недостатки системы шаблонов
      • Необоснованные надежды на «идеальное решение»
      • Снижение быстродействия
      • Ориентация дизайна на PHP
    • Проект: адресная книга
    • Итоги

ГЛАВА 12

Шаблоны

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

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

О чем говорилось выше

До настоящего момента я упоминал о двух разных подходах к созданию шаблонов РНР:

  • внедрение HTML в код РНР;
  • включение файлов в страницу.

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

Вторая схема во многих ситуациях оказывается гораздо удобнее первой. Тем не менее, хотя структура «заголовок — основная часть — колонтитул» (см. главу 9)

хорошо подходит для структурирования относительно малых сайтов с четко определенным форматом, с увеличением объемов и сложности проекта эти ограничения проявляются все заметнее. Попытки решения этих проблем привели к разработке новой схемы применения шаблонов, более сложной по сравнению с двумя первыми, но и обладающей существенно большей гибкостью. В этой схеме разделяются два главных компонента web-приложения: дизайн и программирование. Подобное деление обеспечивает возможность параллельной разработки (web-дизайн и программирование) без необходимости постоянной координации на протяжении всего рабочего цикла. Более того, оно позволяет в будущем модифицировать один компонент, не влияя на работу другого. В следующем разделе я покажу, как устроена одна из таких схем «нетривиальных шаблонов». Следует помнить, что эта схема существует не только в РНР. Более того, она появилась задолго до РНР и в настоящее время используется в нескольких языках, включая РНР, Perl и Java Server Pages. To, что описано в этой главе, — не более чем адаптация этой схемы применительно к РНР.

Нетривиальная система шаблонов

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

К счастью, сделать это проще, чем кажется на первый взгляд, — при условии, что до начала разработки было проведено некоторое предварительное планирование. В листинге 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 перед тем, как читать дальше.

Регистрация файлов

В процессе регистрации содержимое файла сохраняется в массиве с ключом, однозначно идентифицирующим этот файл. Метод 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");

Обработка файла

После того как файлы и переменные будут зарегистрированы в системе шаблонов, можно переходить к обработке зарегистрированных файлов и замене всех ссылок на переменные с соответствующими значениями. Метод 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 (атрибуте объекта-шаблона). На этом предварительная подготовка завершается, остается лишь вывести полученный шаблон в браузере. Эта операция рассматривается в следующем разделе.

Вывод файла

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

файла создается отдельный метод, приведенный в листинге 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

Расширения класса 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), здесь также можно найти несколько реализаций.

Недостатки системы шаблонов

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

Необоснованные надежды на «идеальное решение»

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

Снижение быстродействия

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

Ориентация дизайна на РНР

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

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

Хотя системы шаблонов хорошо подходят для многих типов 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");

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

Итоги

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

  • для чего нужны шаблоны;
  • простой шаблон № 1: внедрение РНР в HTML;
  • простой шаблон № 2: разделение компонентов страницы при помощи включаемых файлов;
  • нетривиальное использование шаблонов для полного разделения программирования и дизайна;
  • класс для работы с шаблонами;
  • регистрация файлов;
  • регистрация переменных;
  • подстановка значений переменных в файл;
  • вывод файла в браузере;
  • недостатки шаблонов;
  • адресная книга, расширяющая стандартный класс шаблона за счет применения запросов SQL.

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


  • Глава 13. Cookie и отслеживание сеанса
    • Что такое Cookie?
      • Компоненты Cookie
      • Cookie и PHP
    • Уникальные идентификаторы
    • Отслеживание сеанса
      • Назначение пользовательских функций для хранения сеансовых данных
    • Проект: журнал посещений сайта
    • Итоги

ГЛАВА 13

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

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

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

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

Что такое cookie?

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

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

Компоненты cookie

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

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

Хотя при создании cookie используются одни и те же синтаксические правила, формат хранения cookie зависит от браузера. Например, 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.

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.

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

Вероятно, у вас уже возник вопрос: как сгенерировать 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) называется период времени, который начинается с момента прихода пользователя на сайт и завершается, когда пользователь покидает сайт. В течение сеанса часто возникает необходимость в сохранении различных переменных, которые бы «сопровождали» пользователя при перемещениях на сайте, чтобы вам не приходилось вручную кодировать многочисленные скрытые поля или переменные, присоединяемые к URL.

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

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

--enable-trans-id

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

track_vars

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

register_globals

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

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

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

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

Директива | Описание
session.save_handler = files | Определяет способ хранения сеансовых данных на сервере. Возможны три варианта: в файле (files), в общей памяти (mm) или с использованием функций, определяемых пользователем (User). Последний вариант позволяет легко сохранить информацию в любом формате — например, в базе данных
session.save_path =/tmp | Определяет каталог для сеансовых файлов РНР. На платформе Linux обычно используется значение по умолчанию ('/tmp'). На платформе Windows следует указать путь к какому-нибудь каталогу, в противном случае произойдет ошибка
session_use_cookies =1 | При установке этого флага для сохранения идентификатора сеанса на компьютере пользователя используются cookie
session.name =PHPRESSID. | Если флаг session.use_cookies установлен, то значение session.name используется в качестве имени cookie. Имя может состоять только из алфавитно-цифровых символов
session.auto_start = 0 | При установке флага session.auto_start сеанс автоматически инициируется при первоначальном запросе со стороны клиента
session.cookie_lifetime = 0 | Если флаг session.use_cookies установлен, то значение session.cookie_lifetime определяет срок действия отправляемых cookie. Если параметр равен 0, то все cookie становятся недействительными при завершении сеанса
session.cookie_path = / | Если флаг session.use_cookies установлен, то значение session.cookie_path определяет каталог, для которого отправляемые cookie считаются действительными
session.cookie_domain = | Если флаг session.use_cookies установлен, то значение session.cookie_domain определяет домен, для которого отправляемые cookie считаются действительными
session.serialize_handler = php | Имя обработчика, используемого в процессе сериализации данных. В настоящее время определены два возможных значения: php и WDDX
session.gc_probability =1 | Вероятность активизации сборщика мусора РНР (в процентах)
session.gc_maxlifetime=1440 | Промежуток времени (в секундах), по истечении которого данные сеанса считаются недействительными и уничтожаются. Отсчет начинается с момента последнего обращения пользователя в текущем сеансе
session.referer_check = | Если этому параметру присвоено строковое значение, каждый запрос к странице при включенном отслеживании сеанса начинается с проверки того, что заданная строка присутствует в глобальной переменной $HTTP_REFERER. Если строка не найдена, идентификаторы сеансов игнорируются
session.enthropy_fiie = | Ссылка на внешний файл с дополнительной случайной информацией, используемой при генерации идентификаторов сеансов. В системах UNIX для этой цели обычно используются два устройства, /dev/random и /dev/urandom. Устройство /dev/random получает случайные данные от ядра, а устройство /dev/urandom генерирует случайную строку при помощи хэш-алгоритма М05. Короче говоря, /dev/random работает быстрее, a /dev/urandom генерирует «более случайные» строки
session.enthropy_length = 0 | Если флаг session.enthropy_file установлен, то session.enthropyjength определяет количество байт, читаемых из файла session.enthropy_file
session.cache limiter = nocache | Способ управления кэшем для страниц сеанса. В настоящее время определены три возможных значения: nocache, public и private
session.cache_expire =180 | Продолжительность жизни кэшированных страниц сеанса (в минутах)

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

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

session_start( )

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

session_start( ): 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( ) обеспечивают очень удобные и эффективные сохранение и загрузку сеансовых данных.

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

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

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

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

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

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

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

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

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

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

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

<?

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

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

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

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

$db = "users";

$session table = "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( ) по мере необходимости.

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

Статистические сведения о посетителях сайта приносят немалую пользу. Как вы уже знаете, сохранение информации о посетителях широко практикуется на сайтах рекламных 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 для конкретной страницы, информация о которой регистрируется в журнале.

Итоги

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

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

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

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


  • Глава 14. PHP и XML
    • Разметка текста
      • Язык SGML
      • Пришествие HTML
      • XML как неопровержимое свидетельство эволюции
    • Знакомство с синтаксисом XML
      • Определение типа документа(DTD)
    • PHP и XML
      • Подключение пользовательских функций к обработке XML
      • Функции обработки кода XML
      • Другие полезные функции
      • Параметры анализатора XML
      • Преобразование XML в HTML
    • Несколько слов о PHP и XML
    • Итоги

ГЛАВА 14

РНР и XML

Бесспорно, развитие World Wide Web оказало заметное влияние на способы обмена информацией. Вследствие огромных размеров этой электронной сети соблюдение стандартов превратилось из простого удобства в обязательное требование — конечно, если ваша организация собирается в полной мере использовать потенциал Web. Одним из таких стандартов является язык XML (extensible Markup Language) — удобное средство обмена данными между организациями и приложениями.

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

Но прежде чем переходить к непосредственному описанию XML, я расскажу о том, как же развивались концепции, в конечном счете приведшие к возникновению формата XML.

Разметка текста

Как нетрудно предположить по его названию, язык HTML (HyperText MarkUp Language) относится к числу так называемых языков разметки текста (markup languages). Под термином «разметка» понимается общая служебная информация, которая не выводится вместе с документом, но определяет; как должны выглядеть те или иные фрагменты документа. Например, вы можете потребовать, чтобы какое-либо слово выводилось жирным или курсивным шрифтом, вывести отдельный абзац особым шрифтом или оформлять заголовки увеличенным шрифтом. Текстовый редактор, в котором я ввожу этот абзац, тоже использует особую форму разметки для представления тех атрибутов форматирования, которые я выбираю. Таким образом, в нем тоже используется особая разновидность языка разметки. Короче говоря, язык разметки, используемый моим текстовым редактором, представляет собой средство для описания визуального оформления текста в моих документах.

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

  1. Язык определяет синтаксис разметки. Например, в соответствии со спецификацией HTML конструкция <b>text</b> определяет синтаксически правильную разметку текста, а конструкция <xR5t>text</x4rt> считается неправильной из-за несовпадения открывающего и закрывающего тегов.
  2. Язык определяет смысл разметки. Конечно, вы знаете, что команда <b>text</b> выводит слово text жирным шрифтом. В данном случае определяется смысл, связанный с объявлением некоторого компонента документа.

Стремительное развитие Web за последние несколько лет наглядно показывает, что самым популярным языком разметки текста является HTML. Но как появился этот язык? Кто закрепил за тегами <b> и </b> определенный смысл в документе? Чтобы ответить на этот вопрос, необходимо познакомиться с предшественником HTML — SGML (Standard Generalized Markup Language).

Язык SGML

SGML представляет собой международный стандарт обмена электронной информацией между различными аппаратными и программными компонентами. По названию можно предположить, что SGML — это язык. На самом деле это не совсем так, поскольку SGML в действительности определяет формализованный набор правил для создания языков. На базе SGML были созданы два самых популярных языка разметки — HTML и XML. Как вы уже знаете, HTML — плат-форменно- и аппаратно-независимый язык, предназначенный для форматирования и отображения текста. То же самое можно сказать и о XML.

Появление стандарта SGML было обусловлено необходимостью совместного использования данных разными приложениями и операционными системами. Даже в далеких 60-х годах у пользователей компьютеров возникало немало проблем с совместимостью. Проанализировав недостатки многих нестандартных языков разметки, трое ученых из IBM — Чарльз Гольдфарб (Charles Goldfarb), Эд Мо-шер (Ed Mosher) и Рэй Лори (Ray Lorie) — сформулировали три общих принципа, обеспечивающих возможность совместной работы с документами в разных операционных системах:

  • Использование единых принципов форматирования во всех программах, выполняющих обработку документов. Вполне логичное требование — всем нам хорошо известно, как трудно договориться между собой людям, говорящим на разных языках. Наличие единого набора синтаксических конструкций и общей семантики заметно упрощает взаимодействие между программами.
  • Специализация языков форматирования. Благодаря возможности построения специализированного языка на базе набора стандартных правил программист
  • перестает зависеть от внешних реализаций и их представлений о потребностях конечного пользователя
  • Четкое определение формата документа. Правила, определяющие формат документа, задают количество и маркировку языковых конструкций, используемых в документе. Применение стандартного формата гарантирует, что пользователь будет точно знать структуру содержимого документа. Обратите внимание: речь идет не о формате отображения документа, а о его структурном формате. Набор правил, описывающих этот формат, называется «определением типа документа» (document type definition, DTD).

Эти три правила были заложены в основу предшественника SGML — GML (Generalized Markup Language). Исследования и разработка GML продолжались около десяти лет, пока в результате соглашения, заключенного международной группой разработчиков, не появился стандарт SGML.

В 1980-х годах необходимость в общих средствах обмена информацией непрерывно возрастала, и SGML вскоре превратился в отраслевой стандарт (в 1986 году он был принят в качестве стандарта ISO). Даже в наши дни этот стандарт занимает достаточно сильные позиции, поскольку многие организации, работающие с огромными объемами информации, полагаются на SGML как на удобное и надежное средство хранения данных. Чтобы подкрепить сказанное, замечу, что Бюро патентов и товарных знаков США (http://www.uspto.gov), Служба внутренних сборов США (http://www.irs.gov) и Библиотека Конгресса (http://lcweb.loc.gov) используют SGML в своих основных приложениях. Только представьте, какой объем документации проходит через эти организации за год!

Одним из лучших ресурсов Интернета, посвященных SGML, XML и другим языкам раз-метки, является сайт Robin Cover/OASIS XML Cover Pages (http://www.oasis-open.org/cover).

Идея передачи гипертекстовых документов через web-браузер, предложенная Тимом Бернерсом-Ли (Tim Berners-Lee), не требовала многих возможностей, поддерживаемых полной реализацией SGML. В результате появился известный язык разметки HTML.

Пришествие HTML

Концепция World Wide Web идеально соответствовала идее применения обобщенного языка разметки для упрощения обмена информацией в среде, содержащей множество разных аппаратных конфигураций, операционных систем и программных реализаций. Несомненно, Бернерс-Ли учитывал это обстоятельство, поскольку он смоделировал первую версию HTML на основе стандарта SGML. HTML унаследовал некоторые характеристики SGML, в том числе простой обобщенный набор тегов и особую роль угловых скобок. Простые документы в формате HTML можно прочитать в любой компьютерной системе, в которой предусмотрены средства для просмотра текстовых документов. Все остальное — история.

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

Реакцией консорциума W3 (http://www.w3.org) на быстро ухудшающуюся ситуацию стала попытка вернуть развитие HTML на правильный путь — другими словами, вернуться к истокам SGML. Результатом этих усилий стал XML.

XML как неопровержимое свидетельство эволюции

XML воплощает все усилия, предпринятые W3 в области выработки Интернет-стандарта, который бы соответствовал трем главным принципам SGML (см. предыдущий раздел). XML, как и SGML, не является языком; он также представляет собой набор рекомендаций, на базе которых создаются другие языки. Точнее говоря, XML является конгломератом из трех отдельных спецификаций:

  • XML (Extensible Markup Language) — спецификация, определяющая базовый синтаксис XML;
  • XSL (Extensible Style Language) — спецификация, направленная на отделение визуального оформления страницы от ее содержимого за счет применения к документу стилей (style sheets), определяющих конкретные атрибуты форматирования;
  • XLL (Extensible Linking Language) — спецификация, определяющая представление ссылок на другие ресурсы.

XML не только позволяет разработчикам создавать специализированные языки для Интернет-приложений; он также обеспечивает возможность проверки этих документов на соответствие спецификации XML. Более того, XML действительно реализует концепцию данных, не зависящих от реализации, поскольку формат отображаемого документа можно точно описать при помощи XSL. Допустим, вы переформатировали свой web-сайт, чтобы он хранился в формате XML. После этого' вы сможете использовать один стиль для форматирования исходного текста XML на портативном компьютере типа Palm Pilot, а другой — для форматирования на мониторе обычного компьютера. В обоих случаях код XML остается одним и тем же, изменяется только его форматирование в соответствии с используемым устройством.

Примером популярного языка, созданного на базе XML, является WML (Wireless Markup Language).

Знакомство с синтаксисом XML

Для большинства читателей, знакомых с SGML или HTML, структура документов XML не содержит ничего нового. Пример простого документа XML приведен в листинге 14.1.

Листинг 14.1. Пример документа XML

<?xml version="1.0"?>

<!DOCTYPE cookbook SYSTEM "cookbook.dtd">

<cookbook>

<recipe category="italian">

<title>Spaghetti alla Carbonara</title>

<description>This traditional Italian dish is sure to please even the most discriminating

critic.</description>

<ingredients>

<ingredient>2 large eggs</ingredient>

<ingredient>4 strips of bacon</ingredient>

<ingredient>l clove garlic</ingredient>

<ingredient>12 ounces spaghetti</ingredient>

<ingredient>3 tablespoons olive oil</ingredient>

</ingredients>

<process>

<step>Combine oil and bacon in large skillet over medium heat. Cook until bacon is

brown and crisp.</step>

<step>whisk eggs in bowl. Set aside.</step>

<step>Cook pasta in large pot of boiling water to taste, stirring occasionally.

Add salt as necessary.</step>

<step>Drain pasta and return to pot. adding whisked eggs. Stir over medium-low

heat for 2-3 minutes.</step>

<step>Mix in bacon. Season with salt and pepper to taste.</step>

</process>

</recipe>

</cookbook>

Обратите внимание на основные компоненты, из которых состоит документ XML:

  • пролог XML;
  • теги;
  • атрибуты;
  • ссылки на сущности;
  • инструкции по обработке;
  • комментарии.

Пролог XML

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

Поскольку текущая версия XML имеет номер 1.0, все ваши документы XML должны начинаться со строки

<?xml version="1.0">

Следующая строка в листинге 14.1 указывает на внешний DTD. Пока не обращайте на нее внимания — DTD подробно рассматриваются в следующем разделе «Определение типа документа (DTD)»:

<!DOCTYPE cookbook SYSTEM "cookbook.dtd">

Оставшаяся часть листинга 14.1 состоит из элементов, очень похожих на элементы документов HTML. Первый элемент, cookbook, называется корневым элементом (root element), поскольку в эту пару тегов заключены все остальные теги документа. Конечно, вы можете присвоить корневому элементу любое имя по своему усмотрению. Главное, о чем следует помнить, — все остальные элементы должны находиться внутри пары корневых тегов.

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

<?xml version="1.0" standalone="yes">

Присваивание yes атрибуту standalone сообщает механизму обработки XML-кода о том, что документ не импортирует других файлов (например, DTD).

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

Элементы

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

  • Непустые элементы должны содержать как открывающий, так и закрывающий тег. В элементах, которые логически не могут иметь закрывающего тега, используется альтернативная форма синтаксиса <элемент />. Возникает вопрос — у каких элементов нет закрывающего тега? Достаточно вспомнить некоторые теги форматирования HTML — например, <br>, <hr> и <img>, у них нет парных тегов. Теги этого формата могут создаваться и в документах XML
  • Элементы XML должны находиться на правильном уровне вложенности. Документ XML, приведенный в листинге 14.1, синтаксически правилен; другими словами, теги элементов не встречаются там, где их быть не должно. Например, следующий фрагмент недопустим:

<title>Spaghetti alia Carbonara

<ingredients></title>

  • В элементах XML различается регистр символов. Некоторым читателям это наверняка не понравится. Например, в XML теги <tag>, <Tag> и <TAG> считаются разными тегами. Привыкайте поскорее — с непривычки это может свести вас с ума.

Атрибуты

Теги XML, по аналогии с тегами HTML, могут обладать атрибутами. Атрибуты содержат дополнительную информацию о содержании, которая в дальнейшем используется при форматировании или обработке XML. Значения атрибутов присваиваются в формате «имя=значение», и, в отличие от HTML, атрибуты XML должны быть заключены в апострофы или кавычки. В листинге 14.1 встречается пример использования атрибута:

<recipe category="italian">

Атрибут сообщает, что данный рецепт (recipe) относится к категории «итальянской кухни» (italian). Наличие такой информации упрощает дальнейшую группировку и обработку данных.

Ссылки на сущности

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

Чтобы сослаться на некоторую сущность в документе HTML, следует указать ее имя с префиксом «амперсанд» (&) и суффиксом «точка с запятой» (;). Допустим, вы объявили сущность с информацией об авторских правах. После этого на данную сущность можно ссылаться следующим образом:

&Соруright:

При этом строка документа XML может выглядеть так:

<footer>

...прочие данные колонтитула...

&Copyright:

</footer>

Сущности, как и переменные и шаблоны, часто применяются в ситуациях, когда некоторая информация может измениться в будущем или документ содержит множество повторяющихся ссылок. Мы вернемся к проблемам объявления ссылок в разделе «Определение типа документа (DTD)».

Инструкции по обработке

Инструкции по обработке (processing instructions, PI) представляют собой внешние команды, которые выполняются приложением, работающим с документом XML.

В общем случае синтаксис PI выглядит так:

<?приложение инструкции?>

Атрибут приложение указывает, какой программе адресованы последующие инструкции. Например, для выполнения команды РНР в документе XML можно воспользоваться следующей конструкцией:

<?php print "Today's date is:".date("m-d-Y");?>

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

Комментарии

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

<!-комментарии ->

Итак, мы проанализировали структуру типичного документа XML. Но у документов XML существует еще один важный аспект — определение типа документа (DTD).

Определение типа документа (DTD)

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

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

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

<!DOCTYPE имя_корневого_элемента [

...прочие объявления...

] >

Атрибут имя_корневого_элемента соответствует имени корневого элемента в тегах, содержащих весь документ XML. В секции «прочих объявлений» находятся определения элементов, атрибутов и т. д.

Возможно, вы предпочитаете разместить DTD в отдельном файле, чтобы обеспечить модульную структуру программы. Давайте посмотрим, как выглядит ссылка на внешний DTD в документе XML. Задача решается одной простой командой:

<!DOCTYPE имя_корневого_элемента SYSTEM "some_dtd.dtd">

Как и в случае с внутренним объявлением DTD, имя_корневого_элемента должно соответствовать имени корневого элемента в тегах, содержащих весь документ XML. Атрибут SYSTEM указывает на то, что some_dtd.dtd находится на локальном сервере. Впрочем, на файл some_dtd.dtd также можно сослаться по его абсолютному URL. Наконец, в кавычках указывается URL внешнего DTD, расположенного на локальном или на удаленном сервере.

Как же создать DTD для листинга 14.1? Во-первых, мы собираемся создать в документе XML ссылку на внешний DTD. Как упоминалось в предыдущем разделе, ссылка на DTD выглядит так:

<!DOCTYPE cookbook SYSTEM "cookbook.dtd">

Возвращаясь к листингу 14.1, мы видим, что cookbook является именем корневого элемента, a cookbook.dtd — именем DTD-файла. Содержимое DTD показано в листинге 14.2, а ниже приведены подробные описания всех строк.

Листинг 14.2. DTD для листинга 14.1 (cookbook.dtd)

<?xml version="1.0"?>

<!DOCTYPE cookbook [

<!ELEMENT cookbook (recipe+)>

<!ELEMENT recipe (title, description, ingredients, process)>

<!ELEMENT title (#PCDATA)>

<!ELEMENT description (#PCDATA)>

<!ELEMENT ingredients (ingredient+)>

<!ELEMENT ingredient (#PCDATA)>

<!ELEMENT process Cstep+)>

<!ELEMENT step (#PCDATA)>

<!ATTLIST recipe category CDATA #REQUIRED>

] >

Что же означает этот загадочный документ? Несмотря на внешнюю сложность, в действительности он довольно прост. Давайте переберем все содержимое листинга 14.2:

<?xml version="1.0"?>

Перед нами пролог XML, о котором уже говорилось выше.

<!DOCTYPE cookbook [

Вторая строка сообщает, что далее следует DTD с именем cookbook.

<!ELEMENT cookbook (recipe+)>

Третья строка описывает элемент XML, в данном случае — корневой элемент cookbook. После него следует слово recipe, заключенное в круглые скобки. Это означает, что в теги cookbook заключается вложенный тег с именем recipe. Знак + говорит о том, что в родительских тегах cookbook находится одна или несколько пар тегов recipe.

<!ELEMENT recipe (title, description, ingredients. process)>

Четвертая строка описывает тег recipe. В ней сообщается, что в тег recipe входят четыре вложенных тега: title, description, ingredients и process. Поскольку после имен тегов не указываются признаки повторения (см. следующий раздел), внутри тегов recipe должна быть заключена ровно одна пара каждого из перечисленных тегов.

<! ELEMENT title (#PCDATA)>

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

<!ELEMENT ingredients (ingredient+)>

В соответствии с определением элемент ingredients содержит один или несколько тегов с именем ingredient. Обратитесь к листингу 14.1, и вы все поймете.

<! ELEMENT ingredient (#PCDATA)>

Поскольку элемент ingredient соответствует отдельному ингредиенту, вполне логично, что этот элемент содержит простые символьные данные.

<! ELEMENT process (step+)>

Элемент process содержит один или несколько экземпляров элемента step.

<! ELEMENT step (#PCDATA)>

Элемент step, как и элемент ingredient, соответствует отдельному пункту в списке более высокого уровня. Следовательно, он должен содержать символьные данные.

<!ATTLIST recipe category CDATA #REQUIRED>

Обратите внимание: элемент recipe в листинге 14.1 содержит атрибут. Этот атрибут, category, определяет общую категорию, к которой относится рецепт — в приведенном примере это категория «итальянская кухня» (Italian). В определении ATTLIST указывается как имя элемента, так и имя атрибута. Кроме того, отнесение каждого рецепта к определенной категории упрощает классификацию, поэтому атрибут объявляется обязательным (#REQUIRED).

]>

Последняя строка просто завершает определение DTD. Определение всегда должно быть должным образом завершено, иначе произойдет ошибка.

В завершение этого раздела я приведу сводку основных компонентов типичного DTD-файла:

  • объявления типов элементов;
  • объявления атрибутов;
  • ID, IDREF и IDREFS;
  • объявления сущностей.

Некоторые из этих компонентов уже встречались нам в описании листинга 14.2. Далее каждый компонент будет описан более подробно.

Объявления элементов

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

<! ELEMENT описание (#РСDАТА)>

Следующее определение элемента process говорит о том, что он содержит ровно один вложенный элемент с именем step:

<!ELEMENT process (step)>

Впрочем, процессы (process) из одного шага (step) встречаются довольно редко — скорее всего, шагов будет несколько. Чтобы указать, что элемент содержит один или несколько экземпляров вложенного элемента step, следует воспользоваться признаком повторения:

<!ELEMENT process (step+)>

Количество вложенных элементов можно задать несколькими способами. Полный список операторов элементов приведен в табл. 14.1.

Таблица 14.1. Операторы элементов

Признак | Значение
? | Ноль или ровно один экземпляр
* | Ноль или несколько экземпляров
+ | Один или несколько экземпляров
 | Ровно один экземпляр
| | Один из элементов
, | Перечисление элементов

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

<!ELEMENT recipe (title, description, ingredients, process)>

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

Определение элемента уточняется при помощи логических операторов. Предположим, вы работаете с рецептами, в которые всегда входят макароны (pasta) с одним или несколькими типами сыра (cheese) или мяса (meat). В этом случае элемент ingredient определяется следующим образом:

<!ELEMENT ingredient (pasta+, (cheese | meat)+)>

Поскольку элемент pasta обязательно должен присутствовать в элементе ingredient, он указывается с признаком повторения +. Затем следует либо элемент cheese, либо элемент meat; мы разделяем альтернативы вертикальной чертой и заключаем их в круглые скобки со знаком +, поскольку в рецепт всегда входит либо одно, либо другое.

Существуют и другие разновидности определений элементов. Мы рассмотрели лишь простейшие случаи. Тем не менее, приведенного материала вполне достаточно для понимания примеров, приведенных в оставшейся части этой главы.

Объявления атрибутов

Атрибуты элементов описывают значения, связываемые с элементами. Элементы XML, как и элементы HTML, могут иметь ноль, один или несколько атрибутов. Общий синтаксис объявления атрибутов выглядит следующим образом:

<!ATTLIST имя_элемента имя_атри6ута1 тип_данных1 флаг1

Имя_элемента определяет имя элемента, включаемое в тег. Затем перечисляются атрибуты, связанные с данным элементом. Объявление каждого атрибута состоит из трех основных компонентов: имени, типа данных и флага, определяющего особенности данного атрибута. Вместо многоточия (...) могут быть расположены объявления других атрибутов.

Простое объявление атрибута уже встречалось нам в листинге 14.2:

<!ATTLIST recipe category CDATA #REQUIRED>

Тем не менее, как видно из приведенного общего определения, допускается одновременное объявление нескольких атрибутов. Допустим, в дополнение к атрибуту category вы хотите связать с элементом recipe дополнительный атрибут difficulty (сложность приготовления). Оба атрибута объявляются в одном списке:

<!ATTLIST recipe category CDATA #REQUIRED difficulty CDATA #REQUIRED>

Форматировать объявления подобным образом необязательно; тем не менее, многострочные объявления нагляднее однострочных. Кроме того, поскольку оба атрибута являются обязательными, тег reci ре не может ограничиться каким-нибудь одним атрибутом, он должен включать в себя оба атрибута сразу. Например, следующий тег будет считаться неверным: <recipe difficulty="hard">

Почему? Потому что в нем отсутствует атрибут category. Правильный тег должен содержать оба атрибута:

<recipe category="Italian" difficulty="hard">

Особые условия обработки атрибута описываются тремя флагами, перечисленными в табл. 14.2.

Таблица 14.2. Флаги атрибутов

Флаг | Описание
#FIXED | Во всех экземплярах элемента в документе атрибуту может присваиваться только одно конкретное значение
#IMPLIED | Если атрибут не указан в элементе, используется значение по умолчанию
#REQUIRED | Атрибут является обязательным и должен присутствовать во всех экземплярах элемента в документе

Типы атрибутов

Атрибут элемента может объявляться с определенным типом. Типы атрибутов описаны далее.

Атрибуты CDATA

Очень часто атрибуты содержат общие символьные данные. Такие атрибуты называются атрибутами CDATA. Следующий пример уже встречался в начале этого раздела:

<!ATTLIST recipe category COATA #REQUIRED>

Атрибуты ID, IDREF и IDREFS

Идея однозначного представления данных (например, информации о пользователе или товаре, хранящейся в базе данных) посредством идентификаторов неоднократно встречалась в предыдущих главах книги. Идентификаторы также часто используются в XML, поскольку перекрестные ссылки между документами применяются не только в общих задачах обработки данных, но и в World Wide Web (гиперссылки).

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

<!ELEMENT recipe (title, description, ingredients, process)>

<!ATTLIST recipe recipe-id ID #REQUIRED>

<!ELEMENT recipe-ref EMPTY>

<!ATTLIST recipe-ref go IDREF #REQUIRED>

После этого объявление элемента recipe в документе может выглядеть так:

<recipe recipe-id="ital003">

<title>Spaghetti alla Carbonara</title>

Рецепт однозначно определяется идентификатором ital003. Следует помнить, что атрибут redpe-id относится к типу ID, поэтому ital003 не может использоваться в качестве значения атрибута recipe-id другого элемента, в противном случае документ будет считаться синтаксически неверным. Теперь допустим, что позднее вы захотели сослаться на этот рецепт из другого документа — скажем, из списка любимых рецептов пользователя. Именно здесь в игру вступают перекрестные ссылки и атрибут IDREF. Атрибуту IDREF присваивается идентификатор, используемый для ссылок на элемент, — по аналогии с тем, как URL используется для идентификации страницы в гиперссылке. Рассмотрим следующий фрагмент кода XML:

<favoriteRecipes>

<recipe-ref go="ital003">

</favoriteRecipes>

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

Перечисляемые атрибуты

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

<!ATTLIST recipe category (Italian | French | Japanese | Chinese) #REQUIRED difficulty (easy | medium | hard) #REQUIRED)

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

Перечисляемые атрибуты со значением по умолчанию

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

<!ATTLIST recipe category (Italian | French | Japanese | Chinese) "Itaian">

Если атрибут category не задан явно, по умолчанию ему присваивается значение Italian.

Атрибуты ENTITY и ENTITIES

Данные в документах XML не всегда являются текстовыми — документ может содержать и двоичную информацию (например, графику). На такие данные можно ссылаться при помощи атрибута entity. Например, в описании элемента description можно указать атрибут recipePicture с графическим изображением:

<!ATTLIST description recipePicture ENTITY #IMPLIED>

Также можно объявить сразу несколько сущностей, заменив ENTITY на ENTITIES. Значения разделяются пробелами.

Атрибуты NMTOKEN и NMTOKENS

Атрибуты NMTOKEN представляют собой строки из символов, входящих в ограниченный набор. Объявление атрибута с типом NMTOKEN предполагает, что значение атрибута соответствует установленным ограничениям. Как правило, значение атрибута NMTOKEN состоит из одного слова:

<!ATTLIST recipe category NMTOKEN #REQUIRED>

Можно объявить сразу несколько атрибутов, заменив NMTOKEN на NMTOKENS. Значения разделяются пробелами.

Объявления сущностей

Объявление сущности напоминает команду define в некоторых языках программирования, включая РНР. Ссылки на сущности кратко упоминались в предыдущем разделе «Знакомство с синтаксисом XML». На всякий случай напомню, что ссылка на сущность используется в качестве замены для другого фрагмента содержания. В процессе обработки документа XML все вхождения сущности заменяются содержанием, которое она представляет. Существует два вида сущностей: внутренние и внешние.

Внутренние сущности

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

<!ENTITY Copyright "Copyright 2000 YourCompanyName. All Rights Reserved.">

В процессе обработки документа все экземпляры &Соруright заменяются текстом «Copyright 2000 YourCompanyName. All Rights Reserved». Весь код XML в заменяющем тексте обрабатывается так, словно он присутствовал в исходном документе.

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

Внешние сущности

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

<!ENTITY Copyright SYSTEM "http://yoursite.com/administer/copyright.xml">

При последующей обработке документа XML все ссылки &Соруright заменяются содержимым документа copyright.xml. Весь код XML в заменяющем тексте обрабатывается так, словно он присутствовал в исходном документе.

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

<!ENTITY food_picture SYSTEM http://yoursite.com/food/logo.gif>

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

Ресурсы, посвященные XML

Хотя приведенного выше материала вполне достаточно для понимания базовой структуры документов XML, данное описание не является полным. Ниже приведены ссылки на ресурсы Интернета, содержащие более подробную информацию:

  • http://www.w3.org/XML;
  • http://www.xml.com/pub/ArticlesByTopic;
  • http://ww.ibm.com/developer/xml;
  • http://www.oasis-open.org.cover.

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

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

РНР и ХМL

Для работы с XML в РНР используется пакет Джеймса Кларка (James Clark) Expat (XML Parser Toolkit) — cm. http://www.jclark.com/xml. Expat включается в поставку Apache 1.3.7 и более поздних версий, поэтому вам не придется специально загружать его, если вы используете свежую версию Apache. Чтобы воспользоваться функциональными возможностями XML в РНР, необходимо настроить РНР с ключом -with-xml.

Разработку Expat 2.0 в настоящее время ведет Кларк Купер (Clark Cooper). За дополни- тельной информацией обращайтесь по адресу http://expat.sourceforge.net.

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

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

  • открывающие теги;
  • атрибуты;
  • символьные данные;
  • закрывающие теги;
  • инструкции по обработке;
  • синтаксические объявления;
  • внешние ссылки на сущности;
  • необработанные сущности;
  • прочие компоненты (комментарии, объявления XML и т. д.).

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

  1. Определите пользовательские функции. Разумеется, если вы собираетесь постоянно работать с документами XML, эти функции достаточно написать всего один раз и в дальнейшем лишь вносить в них необходимые изменения.
  2. Создайте анализатор (parser) кода XML, который будет использоваться для обработки документа. Анализатор создается вызовом функции xml_parser_create( ).
  3. При помощи стандартных функций зарегистрируйте свои функции в анализаторе XML.
  4. Откройте файл XML, прочитайте содержащиеся в нем данные и передайте их анализатору XML. Обработка данных выполняется простым вызовом xml_parse( )! В процессе своей работы эта функция обеспечивает косвенный вызов всех определенных вами обработчиков.
  5. Уничтожьте анализатор XML, чтобы освободить задействованные им ресурсы.

Задача решается функцией xml_parser_free( ). Смысл всех перечисленных этапов разъясняется в следующем разделе.

Подключение пользовательских функций к обработке XML

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

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

xml_set_character_data_handler()

Функция регистрирует пользовательскую функцию для работы с символьными данными. Синтаксис:

int xml_set_character data_handler(int анализатор, string обработчик_символьных_данных)

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

function обработчик_символьных_данных (int анализатор, string данные) {

...

}

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

xml_set_default_handler( )

Функция регистрирует пользовательскую функцию для всех незарегистрированных компонентов документа XML. В частности, к числу таких компонентов относятся пролог XML и комментарии. Синтаксис:

int xml_set_default_handler(int анализатор, string обработчик_по_умолчанию)

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

function обработчик_по_умолчанию (int анализатор, string данные) {

...

}

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

xml_set_element_handler( )

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

int xml_set_element_handler(int анализатор, string обработчик_открывающих_тегов, string обработчик_закрывающих_тегов)

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

function обработчик_открывающих_тегов (int анализатор, string имя_тега,

string атрибуты[ ]) {

...

}

Первый параметр определяет анализатор XML, второй — имя открывающего тега для анализируемого элемента, а третий содержит массив атрибутов соответствующего тега.

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

function обработчик_закрывающих_тегов (int анализатор, string имя_тега) {

...

}

Первый параметр определяет анализатор XML, второй — имя закрывающего тега для анализируемого элемента.

xml_set_external_entity_ref_handler( )

Функция регистрирует пользовательскую функцию для обработки внешних ссылок на сущности. Синтаксис:

int xml_set_external_entity_ref_handler(int анализатор, string обработчик_внешних_ссылок)

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

function обработчик_внешних_ссылок (int анализатор, string ссылка, string база. string системный_идентификатор, string открытый_идентификатор) {

...

}

Первый параметр определяет анализатор XML. Второй параметр определяет имя ссылки, четвертый — системный идентификатор ссылки на сущность, а пятый — открытый идентификатор ссылки. Третий параметр, база, в настоящее время не используется, однако его объявление все равно обязательно.

xml_set_notation_decl_handler ( )

Функция регистрирует пользовательскую функцию для обработки синтаксических объявлений. Синтаксис:

int xml_set_notation_decl_handler(int анализатор, string обработчик_синтаксических_обьявлений)

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

function обработчик_синтаксических_обьявлений (int анализатор, string ссылка, string база, string системный_идентификатор, string открытый_идентификатор) {

...

}

Первый параметр определяет анализатор XML. Второй параметр определяет имя объявления, четвертый — системный идентификатор, а пятый — открытый идентификатор объявления. Третий параметр, база, в настоящее время не используется, однако его объявление все равно обязательно.

xml_set_object( )

Функция ассоциирует анализатор XML с некоторым объектом. Синтаксис:

void xml_set_object(int анализатор, object &о6ъект)

Первый параметр определяет анализатор XML, а второй содержит ссылку на объект, методы которого будут использоваться для обработки компонентов XML. Таким образом, функция xml_set_object связывает анализатор с объектом. Как правило, она вызывается в конструкторе объекта перед определениями функций-обработчиков:

class xmlDB {

VAR $xmlparser:

function xmlDB( ) {

$this->xmlparser = xml_parser_create();

// Связать анализатор с объектом

xml_set_object($this->xmlparser. &$this);

// Определить функции-обработчики

xml_set_element_handler($this->xmlparser,

"startTag","endTag");

xml_set_character_data($this->xmlparser,"characterData");

}

... Определения функций-обработчиков startTag. endTag. characterData и т.д. ...

} // class xmlDB

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

xml_set_processing_instruction_handler( )

Функция регистрирует пользовательскую функцию для работы с Pi-инструкциями.

Синтаксис:

int xml_set_processing_instruction_handler(int анализатор, string обработчик_инструкций)

Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой при обработке Pi-инструкций. Определение функции-обработчика должно выглядеть так:

function обработчик_инструкций (int анализатор, string приложение, string инструкция) {

...

}

Первый параметр определяет анализатор XML, второй — имя приложения, выполняющего инструкции, а третий — инструкцию, передаваемую приложению.

xml_set_unparsed_entity_decl_handler( )

Функция регистрирует пользовательскую функцию для необработанных внешних ссылок на сущности. Синтаксис:

int xml_set_external_entity_ref_handler(int анализатор, string обработчик_внешних_ссылок)

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

function обработчик_внешних_ссылок (int анализатор, string сущность, string база, string системный_идентификатор. string открытый_идентификатор, string имя_объявления) {

...

}

Первый параметр определяет анализатор XML. Второй параметр определяет имя ссылки, четвертый — системный идентификатор ссылки на сущность, а пятый — открытый идентификатор ссылки. Третий параметр, база, в настоящее время не используется, однако его объявление все равно обязательно. Наконец, последний параметр определяет имя синтаксического объявления.

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

Функции обработки кода XML

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

xml_parser_create( )

Перед обработкой документа XML необходимо предварительно создать анализатор. Синтаксис:

int xml_parser_create([stnng кодировка])

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

  • UTF-8;
  • US-ASCII;
  • ISO-8859-1 (используется по умолчанию).

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

xml_parse()

Функция xml_parse( ) выполняет обработку документа XML. Синтаксис:

int xml_parse(int анализатор, string данные [int завершение])

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

xml_parser_free( )

Функция освобождает ресурсы, выделенные для работы анализатора. Синтаксис:

int xml_parser_free(int анализатор)

Параметр функции определяет анализатор XML.

Другие полезные функции

В РНР также существуют другие функции, упрощающие процесс обработки кода XML.

utf8_decode( )

Функция преобразует данные в кодировку ISO-8859-1. Предполагается, что преобразуемые данные находятся в кодировке UTF-8. Синтаксис:

string utf8_decode(string данные)

Параметр данные содержит преобразуемые данные в кодировке UTF-8.

utf8_encode( )

Функция преобразует данные из кодировки ISO-8859-1 в кодировку UTF-8. Синтаксис:

string utf8_decode(string данные)

Параметр данные содержит преобразуемые данные в кодировке ISO-8859-1.

xml_get_error_code( )

Функция xm1_get_error_code( ) получает код ошибки, возникшей в процессе обработки XML. Код ошибки передается функции xml_error_string( ) (см. ниже) для интерпретации. Синтаксис:

int xml_error_code(int анализатор)

Параметр функции определяет анализатор XML. Пример использования приведен ниже, в описании функции xml_get_current_line_number( ).

xml_error_string( )

Ошибкам, возникающим в процессе анализа кода XML, присваиваются числовые коды. Функция xml_error_string( ) возвращает текстовое описание ошибки по ее коду. Синтаксис:

string xml_error_string(int код)

В параметре функции передается код ошибки (вероятно, полученный при вызове функции xml_get_error_code( )). Пример использования функции приведен ниже, в описании функции xml_get_current_line_number( ).

xml_get_current_line_number( )

Функция возвращает номер текущей строки, обрабатываемой анализатором XML. Синтаксис:

int xml_get_current_line_number(int анализатор)

Параметр функции определяет анализатор XML. Пример использования функции:

while ($line - fread($fh. 4096)) :

if (! xml_parse($xml_parser. $line. feof($fh)));

$err_string - xml_error_string(xml_get_error_code($xml_parser));

$line_number - xml_get_current_line_number($xml_parser);

print "Error! [Line Sline_number]: $err_string";

endif;

endwhile;

Например, если ошибка была обнаружена в шестой строке файла, определяемого манипулятором $fh, сообщение будет выглядеть примерно так:

Error! [Line 6]:mi snatched tag

xml_get_current_column_number( )

Функция xml_get_current_colunin_number( ) может использоваться в сочетании с xml_get_current_line_number( ) для определения точного местонахождения ошибки в документе XML. Синтаксис:

int xml_get_current_column_number(int анализатор)

Параметр функции определяет анализатор XML. Давайте усовершенствуем предыдущий пример:

while ($line = fread($fh. 4096)) :

if (! xml_parse($xml_parser, $line, feof($fh))):

$err_string = xml_error_string(xml_get_error_code($xml_parser));

$line_number = xml_get_current_line_number($xml_parser);

$column_number = xml_get_current_column_number($xml_parser)

print "Error! [Line $line_nuimber, Column $column_number]: $err_string";

endif;

endwhile;

Например, если ошибка была обнаружена в шестой строке файла, определяемого манипулятором $fh, сообщение будет выглядеть примерно так:

Error! [Line 6 Column 2]:mismatched tag

Параметры анализатора XML

В настоящее время в РНР поддерживаются два параметра, влияющих на работу анализатора XML:

  • XML_OPTION_CASE_FOLDING — автоматическое преобразование имен тегов к верхнему регистру;
  • XML_OPTION_TARGET_ENCODING — кодировка документа на выходе анализатора XML. В настоящее время поддерживаются кодировки UTF-8, ISO-8859-1 и US-ASCII.

Для получения текущих значений и модификации этих параметров применяются, соответственно, функции xml_parser_get_option( ) и xml_parser_set_option( ).

xml_parser_get_option( )

Функция xml_parser_get_option( ) получает текущее значение параметра анализатора XML. Синтаксис:

int xml_parser_get_option(int анализатор, int параметр)

Первый параметр функции определяет анализатор XML, а второй — имя интересующего вас параметра. Пример:

$setting = xml_parser_get_option($xml_parser, XML_OPTION_CASE_FOLDING);

print "Case Folding: $setting";

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

Case Folding: 1

xml_parser_set_option( )

Функция xml_parser_set_option() задает значение параметра анализатора XML. Синтаксис:

int xml_parser_set_option(int анализатор, int параметр, mixed значение)

Первый параметр функции определяет анализатор XML, второй — имя интересующего вас параметра, а третий — его новое значение. Пример:

$setting = xml_parser_set_option($xml_parser, XML_OPTION_TARGER_ENCODING."UTF-8"):

В результате выполнения этой команды выходная кодировка документа изменяется с ISO-8859-1 на UTF-8.

Преобразование XML в HTML

Предположим, у вас имеется документ XML bookmarks.xml, содержащий список ссылок. Он выглядит примерно так:

<?xml version="1.0"?>

<website>

<title>Epicurious</title>

<url>http://www.epicurious.com</url>

<description>

Epicurious is a great online cooking resource, providing tutorials.

recipes, forums and more.

</description> </website>

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

Листинг 14.3. Преобразование XML в HTML

Class XMLHTML {

VAR $xmlparser: VAR $tagcolor ="#800000";

VAR $datacolor ="#0000ff";

function XMLHTML( ) {

$this->xmlparser = xml_parser_create();

xml_set_object($this->xmlparser. &$this);

xml_set_element_handler($this->xmlparser, "startTag", "endTag");

xml_set_character_data_handler($this->xmlparser. "characterData");

}

// Функция отвечает за обработку всех открывающих тегов.

function startTag($parser, $tagname, $attributes) {

GLOBAL $tagcolor;

print "<font size=\"-2\" color=\"$this->tagcolor\" face=\"arial,

verdana\ ">&1 t ; $tagname&gt ; </f ont> <br>" ;

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

function characterData($parser. $characterData) {

GLOBAL $datacplor;

print "<font size=\"-2\" color=\"$this->datacolor\" face=\"arial,

verdana\ ">&nbsp; &nbsp; &nbsp; $characterData</font> <br>";

// Функция отвечает за обработку всех закрывающих тегов.

function endTag(Sparser, $tagname) {

GLOBAL Stagcolor;

print "<font size=\"-2\" color=\"$this->tagcolor\" face=\"arial, verdana\">&lt;/

$tagname&gt;</font> <br>";

}

function.parse($fp) {

// xml_parse($this->xm1parser,$data);

// Обработать файл XML

while ( $line = fread($fp. 4096) ) :

// При возникновении ошибки прервать обработку // и вывести сообщение об ошибке.

if ( ! xml_parse($this->xmlparser, $line, feof($fp))) :

die(sprintf("XML error: %s at line %d",

xml_error_sthng(xml_get_error_code($this->xmlparser)),

xml_get_curren_line_number($this->xml parser)));

endif;

endwhile;

}

}

// Открыть файл XML для обработки

$xml_file = "bookmarks. xml";

$fp = f open ($xml_flie, "r");

// Создать новый объект

$xml_parser = new XMLHTML;

// Обработать $xml_file

$xml_parser->parse($fp);

?>

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

<WEBSITE>

<TITLE>

Epicurious

</TITLE>

<URL>

http : //www.epicurious.com

</URL>

<DESCRIPTION>

Epicurious is a great online cooking resource,

providing tutorials, recipes, forums and more.

</DESCRIPTION>

</WEBSITE>

Конечно, результат не такой уж впечатляющий — мы всего лишь добились, чтобы файл XML отображался в браузере. Внеся небольшие изменения в листинг 14.3, можно преобразовать URL в работающие гиперссылки, оформить данные между парой тегов <TITLE>...</TITLE> жирным шрифтом и т. д. Как видно из листинга 14.3, я использую шрифт двух разных цветов, чтобы продемонстрировать возможность форматирования текста в браузере.

Несколько слов о РНР и XML

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

К сожалению, на момент написания книги РНР еще не обладал возможностями, которые бы позволяли работать с XML исключительно средствами РНР. Конечно,

возможности РНР продолжают расширяться, и в будущем эта проблема обязательно будет решена.

Особого внимания в этой области заслуживает XSL-процессор Sablotron, разработанный компанией Ginger Alliance Lts. (http://www.gingerall.com). 12 октября 2000 года было объявлено о том, что РНР 4.03 отныне распространяется с модулем расширения Sablotron для платформ Linux и Windows. Обязательно проследите за дальнейшим развитием событий.

Итоги

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

  • краткое описание языков разметки текста;
  • SGML;
  • общие сведения о XML;
  • синтаксис XML;
  • описание типа документа (DTD);
  • РНР и XML.

В главе 15 рассматриваются две перспективные технологии, JavaScript и СОМ (Component Object Model), и возможности их использования в РНР.


  • Глава 15. JavaScript и COM
    • JavaScript
      • Проверка поддержки JavaScript
      • Динамическое создание временных окон
    • COM
      • Поддержка COM в PHP
      • Запись информации в документ Microsoft Word
      • Дополнительная информация
    • Итоги

ГЛАВА 15

JavaScript и COM

Как неоднократно упоминалось в книге, одной из самых замечательных особенностей РНР является простота его интеграции с другими технологиями. Примеры такой интеграции уже встречались при описании работы с базами данных, ODBC и XML. В этой главе будет показано, как просто организуется работа РНР в комбинации с JavaScript и приложениями на базе СОМ. Ниже приводятся общие сведения о JavaScript и СОМ, подкрепленные примерами их использования в РНР. К концу главы вы узнаете немало полезного об этих замечательных технологиях и о том, как они применяются в РНР.

JavaScript

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

Любой программист с опытом работы на РНР, Pascal или C++ освоит JavaScript без особого труда. Если вы не программировали на этих языках, не огорчайтесь — JavaScript изучается легко. Разработчики JavaScript (как, впрочем, и разработчики РНР) в первую очередь ориентировались на решение реальных, практических задач.

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

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

Проверка поддержки JavaScript

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

get_browser( )

Функция get_browser( ) возвращает информацию о возможностях браузера в виде объекта. Синтаксис:

object get_browser([string агент])

Необязательный параметр агент используется для получения характеристик конкретного браузера. Как правило, функция get_browser( ) вызывается без параметров, поскольку по умолчанию она использует глобальную переменную РНР $HTTP_USER_AGENT.

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

;browcap = extra/browcap.ini

Файл browser.ini был разработан компанией cyScape, Inc. Последняя версия этого файла находится по адресу http://www.cyscape.com/browcap. Загрузите и распакуйте этот файл в каталог на сервере. Запомните имя каталога, оно понадобится вам для обновления параметра browcap в файле php.ini.

В принципе, после загрузки browcap.ini и редактирования файла php.ini вы можете включать в свои программы проверку возможностей браузера. Впрочем, я рекомендую сначала открыть файл browser.ini и ознакомиться с его структурой, а затем просмотреть листинги 15.1 и 15.2. В листинге 15.1 приведен очень простой пример отображения всех возможностей браузера в самом браузере. Листинг 15.2 ограничивается лишь одной возможностью — поддержкой JavaScript.

Листинг 15.1. Отображение всех атрибутов браузера

<?

// Получить информацию о браузере

$browser = get_browser();

// Преобразовать $browser в массив

Sbrowser = (array) Sbrowser;

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

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

if ($value == "") : $value = 0;

endif;

print "$key : $value <br>";

endwhile;

?>

Для браузера Microsoft Internet Explorer 5.0 листинг 15.1 выводит следующий результат:

browser_name_pattern : Mozilla/4\.0 (compatible; MSIE 5\..*)

parent IE 5.0

browser : 5.0

version : 15

majorver : #5

minorver : #5

frames : 1

tables : 1

cookies : 1

backgroundsounds : 1

vbscript : 1

javascript : 1

javaapplets : 1

activexcontrols : 1

win16 : 0

beta : 0

ak : 0

sk : 0

aol : 0

crawler : 0

cdf : 1

В листинге 15.2 приведен простой, но эффективный сценарий, который при помощи файла browcap.ini определяет, включена ли поддержка JavaScript в браузере.

Листинг 15.2. Проверка поддержки JavaScript

<?

$browser = get_browser( );

// Преобразовать $browser в массив $browser = (array) $browser;

if ($browser["javascript"] == 1) :

print "Javascript enabled!";

else :

print "No javascript allowed!";

endif;

?>

Листинг 15.2 проверяет, присутствует ли ключ javascript для заданного браузера. Если ключ присутствует и равен 1, в браузере выводится сообщение о поддержке JavaScript. В противном случае выводится сообщение об ошибке. Конечно, в реальной программе вместо выдачи сообщения следует выполнить какие-нибудь полезные действия.

Следующие два примера показывают, как легко РНР,интегрируется с JavaScript. Листинг 15.3 определяет параметры экрана (разрешение и цветовую глубину) средствами JavaScript и затем выводит их средствами РНР. Листинг 15.4 (см. следующий раздел) показывает, как при помощи шаблона РНР во временном (pop-up) окне, вызванном из кода JavaScript, выводится информация о ссылке, на которой щелкнул пользователь.

Листинг 15.3. Определение цветовой глубины и разрешения экрана

<html>

<head>

<title>Browser Information</title>

</head>

<body>

<script language="Javascriptl.2">

<!--//

document.write('<form method=POST action ="<? echo $PHP_SELF; ?>">');

document.write('<input type=hidden name=version value=' + navigator.appVersion + '>');

document.write('<input type=hidden name=type value=' + navigator.appName + '>');

document.write('<input type=hidden name-screenWidth value=' + screen.width +'>');

document.write('<input type=hidden name=screenHeight value=' + screen.height + '>'};

document.write('<input type=hidden name=browserHeight value=' + window.innerWidth + '>');

document.write('<input type=hidden name=browserWidth value=' + window.innerHeight + '>');

//-->

</script>

<input type="submit" value="Get browser information"><p>

</form>

<?

echo "<b>Browser:</b> $type Version: $version<br>";

echo "<b>Screen Resolution:</b> $screenWidth x $screenHeight pixels.<br>";

if ($browserWidth != 0) :

echo "<b>Browser resolution:</b> $browserWidth x $browserHeight pixels.";

else :

echo "No javascript browser resolution support for Internet Explorer";

endif;

?>

</body>

</html>

Динамическое создание временных окон

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

Для читателей, не имеющих опыта программирования на JavaScript, я включил в программу подробные комментарии. Значение переменной winld, передаваемой сценарию РНР window.php, задается внутри ссылки в основном коде HTML. Когда пользователь щелкает на ссылке, вызывается функция newWindow( ), определенная в JavaScript. Чтобы вы лучше поняли, как это происходит, рассмотрим следующую ссылку:

<а href="#" onClick="newWindow(1):">Contact us</a><br>

Как видите, я просто включаю в href значение "#", поскольку ссылка генерируется обработчиком события onClick в JavaScript. Установка обработчика приводит к тому, что при щелчке на ссылке вызывается функция newWindow( ). Обратите внимание на параметр, передаваемый при вызове этой функции (в приведенном примере — 1). Содержащийся в нем идентификатор используется сценарием РНР для выбора отображаемой информации. Вы можете передать любое число — при условии, что оно соответствует имени файла, отображаемого в сценарии РНР. Внимательно просмотрите листинг 15.4. Чтобы вам было легче ориентироваться, я создал три простых файла *.inc, соответствующих ссылкам в этом листинге.

Листинг 15.4. Динамическое построение временных окон

<html>

<head>

<title>Listing 15-4</title>

<SCRIPT language="Javascript">

// Объявить переменную Javascript

var popWindow;

// Объявить функцию newWindow

function newWindow(winID)

{

// Объявить переменную winURL. Присвоить ей

// имя файла РНР с последующими данными.

var winURL = "Listingl5-5.php?winID=" + winID;

// Если временное окно не существует или закрыто.

// открыть его.

if (! popWindow | popWindow.closed) {

// Открыть новое окно шириной 200 пикселов и высотой

// 300 пикселов, расположенное на расстоянии 150 пикселов

// от левого края и 100 пикселов от верхнего края

// основного окна.

popWindow = window.open(winURL. 'popWindow',

dependent.width=200.height=300.left=150 ,top=100');

}

// Если временное окно уже открыто.

// активизировать его и обновить содержимое

// в соответствии с winURL.

else {

popWindow.focus();

popWindow.location = winURL;

}

}

//-->

</SCRIPT>

</head>

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

<a href="#" onClick="newWindow(1);">Contact Us</a><br>

<a href="#" onClick="newWindow(2):">Driving Directions</a><br>

<a href="#" onClick="newWindow(3);">Weather Report</a><br>

</body>

</html>

Когда пользователь щелкает на одной из ссылок в листинге 15.4, программа создает временное окно и загружает в него содержимое, полученное в результате вызова window.php. Сценарию window.php передается переменная winID, по которой определяется файл, включаемый в сценарий РНР. Сценарий window.php приведен в листинге 15.5.

Листинг 15.5. Сценарий window.php

<html>

<head>

<title>Popup Window Fun</title>

</head>

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

<table width="100%" border="0" cellpadding="0" cellspacing="0">

<tr>

<td>

<?

// Включить файл, имя которого определяется

// переданным параметром.

INCLUDE("$winID.inc");

?>

</td>

</tr>

<tr>

<td>

<a href="#" onClick="parent.self.closet);

">close window</a>

</td>

</tr>

</table>

</body>

</html>

Остается лишь создать файлы для ссылок в листинге 15.4. Поскольку в ссылках передаются три уникальных идентификатора (1, 2 и 3), мы должны создать три файла. Первый файл, содержащий контактную информацию, сохраняется с именем Line:

<td>

<h4>Contact Us</h4>

<table> <tr>

<li>email: <a href="mailto:wj@wjgilmore.com">wj@wjgilmore.com</a> <li>phone: (555) 867 5309 <li>mobile: (555) 555 5555 </ul> </td>

</tr> </table>

Следующий файл (местонахождение) сохраняется с именем 2.inс.

<table>

<tr>

<td>

<h4>Driving Directions</h4>

<ol>

<li>Turn left on 1st avenue.

<li>Enter the old Grant building.

<li>Take elevator to 4th floor.

<li>We're in room 444.

</td>

</tr>

</table>

Последний файл (сводка погоды) сохраняется с именем 3.inc. Обратите внимание на вызов функции РНР, возвращающей текущую дату, — этот пример наглядно показывает, как легко РНР интегрируется с JavaScript: ,

<table>

<tr>

<td>

<h4>Weather Report <?=date("m-d-Y");?></h4>

<b>Today:</b> Birr... Brisk, with blowing and drifting snow.<br><br>

<b>Tonight:</b> Winter Weather Advisory. 7-10 inches snow expected.

</td>

</tr>

</table>

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

Рис. 15.1. Сводка погоды во временном окне

Наше короткое знакомство с интеграцией PHP/JavaScript подходит к концу. Мы рассмотрели несколько простых, но вполне реальных примеров, которые при желании легко адаптируются для более сложных целей. При объединении РНР с JavaScript или любой другой технологией, ориентированной на работу на стороне сервера, необходимо правильно определить возможности браузера, чтобы предотвратить случайные ошибки. Всегда полезно поэкспериментировать с другими технологиями, интегрируемыми с кодом РНР; только проследите за тем, чтобы не отпугнуть пользователей от сайта недоступными возможностями или содержанием, которое невозможно просмотреть.

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

СОМ

Технология СОМ (сокращение от «Component Object Model», то есть «модель составного объекта») обеспечивает взаимодействие между приложениями, работающими на разных языках и платформах. Такое взаимодействие в значительной мере способствует идее построения многократно используемых, легко сопровождаемых и адаптируемых программных компонентов (в последнее время к этим трем принципам проявляется повышенное внимание в области компьютерных технологий). Хотя СОМ обычно рассматривается как спецификация, ориентированная в первую очередь на продукты Microsoft, поддержка СОМ уже реализована во многих языках (например, в РНР, Java, C++ и Delphi) и существует на многих платформах, включая Windows, Linux и Macintosh.

Что же вам даст объединение СОМ с РНР? Во-первых, средства СОМ позволяют напрямую взаимодействовать со многими приложениями Microsoft. Ниже рассмотрен интересный пример — форматирование и вывод в Microsoft Word записей базы данных, полученных из Web. В следующем разделе вы увидите, как легко решается эта задача.

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

РНР содержит несколько стандартных функций для работы с СОМ. Учтите, эти функции поддерживаются только в версии РНР для Windows! Прежде чем переходить к примерам, мы рассмотрим все эти функции.

Поддержка СОМ в РНР

Стандартные функции РНР, предназначенные для работы с СОМ, создают объекты СОМ и используют их свойства и методы. Пожалуйста, не забывайте о том, что эта поддержка присутствует только в версии РНР для Windows. Следующие примеры были протестированы для Microsoft Word 2000. За информацией об объектах, методах и событиях, используемых в программе, обращайтесь на web-сайт MSDN (http://msdn.microsoft.com/library/officedev/off2000/ wotocobjectmodelapplication.htm).

Создание экземпляров объектов СОМ

Экземпляры объектов СОМ создаются вызовом new, как при обычном объектно-ориентированном программировании. Синтаксис:

object new СОМ("обьекг.класс" [, string удаленный_адрес])

Параметр объект.класс определяет модуль СОМ, присутствующий на сервере. Необязательный параметр удаленный_адрес используется в том случае, если объект СОМ создается на удаленном компьютере. Допустим, вы хотите создать экземпляр объекта для приложения MS Word. При этом приложение Microsoft Word запускается так, словно вы запустили его вручную (разумеется, для этого MS Word должен быть установлен на компьютере). Команда имеет следующий синтаксис:

$word=new COM("word.application") or die("Couldn't start Word!");

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

$word->visible = 1:

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

Вызов методов объекта СОМ

Методы объектов СОМ вызываются в типичном для ООП формате, с использованием ссылки из объектной переменной. Синтаксис:

объект->имя_метода([значение, ...])

Объект соответствует экземпляру объекта СОМ, созданному описанным выше способом. Параметр имя_метода определяет имя метода, определенного в классе объект. Необязательный параметр значение позволяет передавать параметры при вызове методов, допускающих (или требующих) дополнительных данных. Как и при вызове обычных функций, параметры разделяются запятыми. Если после создания экземпляра объекта СОМ, представляющего MS Word, вы захотите создать в приложении новый документ, просто вызовите соответствующий метод. Задача решается методом add( ) субкласса Documents экземпляра $word:

$word->Documents->Add( );

Обратите внимание: для вызова методов используется очень логичный синтаксис в стиле ООП. В результате выполнения этой команды в окне приложения MS Word открывается новый документ.

com_get( )

Функция com_get( ) возвращает значение свойства объектов СОМ. Синтаксис:

mixed com_get(resource объект, string свойство)

Первый параметр определяет экземпляр объекта СОМ, а второй — атрибут класса, к которому относится данный экземпляр.

<?

// Создать экземпляр объекта для приложения MS Word

$word=new COM("word.application") or die("Couldn't start Word!");

// Режим CapsLock либо включен (свойство CapsLock = 0),

// либо выключен (свойство CapsLock = 1).

$flag = com_get(Sword->Application.CapsLock)

// Преобразовать значение Sflag (0 или 1) в логическое значение

if ($flag == 1) :

$flag = "YES";

else :

$flag = "NO";

endif;

// Вывести сообщение

print "CAPS Lock activated: $flag";

$word->Quit();

?>

Существует и другое решение — значение атрибута CapsLock можно получить при помощи стандартного для ООП синтаксиса обращения к атрибутам. В предыдущем примере для этого следует заменить строку

$flag = com_get($word->Application,CapsLock)

следующей строкой:

$flag = $word->Application->CapsLock:

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

com_set( )

Функция com_set( ) присваивает атрибуту объекта новое значение:

void com_set(resource объект, string свойство, mixed значение)

Первый параметр определяет экземпляр объекта СОМ, а второй — атрибут класса, к которому относится данный экземпляр. Третий параметр определяет новое значение свойства.

Следующая программа (листинг 15.6) запускает Microsoft Word и активизирует окно приложения. Затем она создает новый документ, добавляет в него строку текста и выбирает режим сохранения документа (атрибут DefaultSaveFormat) в текстовом формате. Результат виден при открытии окна Сохранить как (Save As) — в списке Тип файла (Save As Type) автоматически выбирается строка Только текст (Text Only). После сохранения документа приложение Microsoft Word закрывается.

Листинг 15.6. Выбор типа документа по умолчанию

<?

// Создать экземпляр объекта для приложения MS Word

$word-new COMC'word.application") or die("Couldn't start Word!");

// Активизировать окно MS Word $word->visible = 1;

// Создать новый документ $word->Documents->Add();

// Вставить в документ фрагмент текста

$word->Selection->Typetext("php's com functionality is cool\n");

// Выбрать текстовый режим сохранения

$ok = com_set($word->Application, DefaultSaveFormat, "Text");

// Запросить у пользователя имя и сохранить документ.

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

// в текстовом формате! $word->Documents[l]->Save;

// Выйти из MS Word

$word->Quit();

?>

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

$ok = com_set($word->Application, DefaultSaveFormat, "Text");

следующей строкой:

$word->Application->DefaultSaveFormat = "Text";

Итак, вы получили общее представление об управлении приложениями Windows через поддержку СОМ в РНР. Мы переходим к занимательному примеру, кото-

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

Запись информации в документ Microsoft Word

Допустим, вам потребовалось отформатировать информацию, загруженную из базы данных, в документе Word для построения отчета. Весь процесс автоматизируется всего в нескольких строках кода РНР. Для демонстрационных целей я воспользуюсь таблицей addressbook из проекта адресной книги, приведенного в конце главы 12. Алгоритм работы сценария выглядит следующим образом:

  1. Подключиться к серверу MySQL и выбрать нужную базу данных.
  2. Выбрать все данные из таблицы с сортировкой по фамилиям.
  3. Открыть приложение Microsoft Word и создать новый документ.
  4. Отформатировать и вывести все записи в документе.
  5. Запросить у пользователя имя для сохранения документа.
  6. Закрыть Microsoft Word.

Программный код приведен в листинге 15.7.

Листинг 15.7. Запись информации в документ Microsoft Word <?

<?

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

$host = "localhost";

$user = "root";

$pswd = "";

$db = "book";

$address_table = "addressbook";

mysql_connect($host. $user, $pswd)

or die("Couldn't connect to MySQL server!");

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

// Выбрать из базы данных все записи

$query = "SELECT * FROM $address_table ORDER BY lastjiame";

Sresult = mysql_query($query):

// Создать новый объект COM для приложения MS Word

$word=new COM("word.application") or die("Couldn't start Word!");

// Активизировать окно MS Word $word->visible = 1;

// Открыть пустой документ. $word->Documents->Add( );

// Перебрать записи из таблицы адресов

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

$last_name = $row["last_name"];

$first_name = $row["first_name"];

$tel = $row["tel"];

$email = $row["email"];

// Вывести данные таблицы в открытый документ Word.

$word->Selection->Typetext("$last_name.
$first_name\n"); $word->Selection->Typetext("tel. $tel\n"):
$word->Selection->Typetext("email. $email:\n");

endwhile;

// Запросить у пользователя имя документа.

$word->Documents[l]->Save;

// Выйти из MS Word

$word->Quit();

?>

При всей простоте рассмотренный пример наглядно показывает, как писать приложения РНР для пересылки содержимого базы данных в приложения Windows. Можно написать и более сложное приложение, обеспечивающее синхронизацию данных, полученных из Web, из Microsoft Outlook. Все, что для этого нужно — получить ссылку на объекты, свойства и методы Outlook, после чего можно переходить к экспериментам (обзор объектной модели всех приложений семейства Office приведен по адресу http://www.microsoft.com/officedev/articles/Opg/toc/PGTOC.htm).

Дополнительная информация

Ниже перечислены ссылки на некоторые полезные ресурсы, посвященные СОМ и найденные мной в Интернете:

  • http://msdn.microsoft.com/Library/techart/msdn_comppr.htm;
  • http://www.microsoft.com/com/nets/drgui.asp;
  • http://www.microsoft.com/com/default.asp;
  • http://www.comdeveloper.com.

Итоги

Эта глава в очередной раз показала, как легко РНР интегрируется с внешними технологиями — а именно, JavaScript и COM (Component Object Model). В частности, были рассмотрены следующие темы:

  • общие сведения о JavaScript;
  • проверка поддержки JavaScript в браузерах;
  • получение информации о возможностях браузера;
  • использование временных окон в сочетании с РНР;
  • общие сведения о технологии СОМ;
  • стандартные средства РНР для работы с СОМ;
  • использование поддержки СОМ в РНР для передачи информации из базы данных в Microsoft Word.

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


  • Глава 16. Безопасность
    • Проблемы конфигурации
      • safe_mode
      • safe_mode_exec_dir
      • disable_functions
      • doc_root
      • max_execution_time
      • memory_limit
      • sql.safe_mode
      • user_dir
      • Безопасный режим и работа PHP в режиме модуля Apache
      • Маскировка файлов данных и конфигурационных файлов
    • Безопасность данных
      • Обработка пользовательского ввода
    • Шифрование данных
      • Общие функции шифрования
      • Дополнительная информация
    • Электронная коммерция
      • Verisign
      • Cybercash
      • CCVS
    • Аутентификация пользователя
    • Аутентификация с несколькими пользователями
    • Итоги

ГЛАВА 16

Безопасность

Non sum qualis eram

(Я не такой, каким был раньше).

Гораций

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

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

Применительно к РНР тема безопасности выглядит многогранной, причем некоторые ее аспекты связаны с безопасностью самого сервера. Ведь безопасность сервера во многих отношениях определяет безопасность данных, обрабатываемых сценариям РНР. Я настоятельно рекомендую собрать как можно больше информации о вашем web-сервере и постоянно следить за всеми обновлениями и исправлениями. Вероятно, большинство читателей работает с сервером Apache, поэтому я советую почаще посещать сайт Apache (http://www.apache.org) и замечательный сайт Apache Week (http://www.apacheweek.org). Впрочем, безопасностью сервера дело не ограничивается — РНР также в определенной степени влияет на безопасность системы за счет правильного выбора параметров конфигурации и защищенного программирования.

Последняя глава этой книги состоит из пяти разделов:

  • Проблемы конфигурации.
  • Проблемы программирования.
  • Шифрование данных.
  • Электронная коммерция.
  • Аутентификация пользователей.

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

Проблемы конфигурации

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

safe_mode

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

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

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

  • Функции ввода/вывода (в частности, fopen ( ), filе( ) и include ( )) работают только с файлами, принадлежащими владельцу сценария. Предположим, в безопасном режиме сценарий, принадлежащий пользователю Мэри, вызывает функцию fopen( ). Если функция попытается открыть файл, принадлежащий Джону, ее вызов завершится неудачей. Но если Мэри принадлежит как сценарий, вызывающий fopen( ), так и открываемый файл, все будет нормально.
  • Запуск внешних сценариев функциями popen( ), system( ) или ехес( ) разрешается лишь в том случае, если запускаемый сценарий находится в каталоге, определяемом параметром safe_mode_exec_dir (см. далее).
  • Новые файлы создаются только в каталогах, принадлежащих владельцу сценария.
  • Аутентификация HTTP становится более жесткой, поскольку в ней также учитывается UID аутентифицирующего сценария. Механизм аутентификации пользователей рассматривается в одном из разделов этой главы.
  • Имя пользователя, использованное при подключении к серверу MySQL, должно совпадать с именем владельца файла, вызывающего mysql_connect( ).

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

Таблица 16.1. Функции, выполнение которых ограничивается в безопасном режиме

chgrp | include | require
chmod | link | rmdir
chown | passthru | symlink
exec | popen | system
fopen | readfile | unlink
file | rename |

К сожалению, документация РНР по безопасному режиму не обновлялась с версии 2.0, хотя функциональность безопасного режима практически не изменилась. Документация находится по адресу http://www.php.net/manual/phpfi2.html.

safe_mode_exec_dir

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

disable_functions

В этом параметре через запятую перечисляются имена функций, выполнение которых требуется запретить. Обратите внимание — этот параметр никак не связан с safe_mode. Например, чтобы запретить вызовы функций fopen( ), popen( ) и file( ), достаточно включить в конфигурационный файл следующую строку:

disable_functions = fopen, popen.file

doc_root

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

max_execution_time

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

memory_limit

Параметр определяет максимальный объем памяти (в байтах), используемой сценарием. По умолчанию параметр равен 8 Мбайт (8 388 608 байт).

sql.safe_mode

При включении параметра sql .safejnode игнорируется вся информация, передаваемая функциям mysql_connect( ) и mysql_pconnect( ), а подключения разрешаются только для UID, под которым работает web-сервер.

user_dir

Параметр определяет имя подкаталога в домашнем каталоге пользователя, в котором должны находиться исполняемые сценарии РНР. Например, если параметру user_dir присвоено значение scripts и пользователь с именем Alessia хочет выполнить сценарий somescript.php, он должен создать в своем домашнем каталоге подкаталог с именем scripts и поместить сценарий в этот каталог. К сценарию можно обратиться по URL http://www.yoursite.com/~alessia/somescript.php. Обратите внимание — каталог scripts в URL не включается. Как правило, этот параметр используется в сочетании с параметром конфигурации Apache UserDi г.

Безопасный режим и работа РНР в режиме модуля Apache

Следует помнить, что при работе РНР в режиме модуля Apache безопасный режим недоступен. Это объясняется тем, что модуль РНР работает в составе сервера Apache, поэтому все сценарии РНР работают под тем же UID, что и сам сервер Apache. Поскольку ограничения вызова функций в безопасном режиме основаны на сравнении UID, этот режим полноценно работает только при использовании CGI-версии РНР в сочетании с suExec (http://www.apache.org/docs/ suexec.html). Дело в том, что CGI-версия РНР работает как отдельный процесс, что позволяет динамически изменять UID средствами suExec. Если вас интересует использование РНР в безопасном режиме, вероятно, вам следует остановить свой выбор на комбинации CGI/suExec, хотя за это приходится расплачиваться быстродействием.

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

Маскировка файлов данных и конфигурационных файлов

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

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

Для примера создайте файл и введите в нем какой-нибудь «секретный» текст. Сохраните этот файл в общедоступном каталоге HTML с именем secrets и каким-нибудь экзотическим расширением типа .zkgjg. Разумеется, сервер не распознает это расширение, но все равно попытается передать запрошенные данные. Теперь запустите браузер и введите URL со ссылкой на этот файл. Интересно, правда? К счастью, у этой проблемы существует два простых решения.

Хранение файлов за пределами корневого каталога документов

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

DocumentRoot C:\Program Files\Apache Group\Apache\htdocs # Windows

DocumentRoot /www/apache/home # Другие системы

Предположим, у вас имеется файл с атрибутами доступа (хост, имя пользователя, пароль) к базе данных MySQL. Конечно, этот файл не должен попадаться на глаза посторонним, поэтому вы сохраняете его вне корневого каталога документов. Например, в системе Windows можно воспользоваться каталогом C:/Program FHes/mysecretdata, а в UNIX — каталогом /usr/local/mysecretdata.

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

INCLUDE("С:/Program Files/mysecretdata/mysqlaccess.inc");

Пример для UNIX:

INCLUDE("/usr/local/mysecretdata/mysqlaccess.inc");

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

Настройка файла httpd.conf

Во втором варианте доступ к файлам с конфиденциальной информацией ограничивается по расширению файла, при этом используется параметр FILES файла httpd.conf. Допустим, вы хотите запретить пользователям доступ к файлам с расширением .inc. Для этого достаточно включить в файл httpd.conf следующий фрагмент:

<Files *.inc>

Order allow, deny

Deny from all

</Files>

После редактирования файла перезапустите сервер Apache, после этого все попытки запросить любой файл с расширением .inc в браузере отклоняются сервером. Впрочем, эти файлы все равно могут включаться в сценарии РНР. Кстати говоря, при просмотре файла httpd.conf вы увидите, что этот способ применяется для ограничения доступа к файлам .htaccess. Эти файлы используются для парольной защиты каталогов и рассматриваются в конце главы.

Безопасность данных

Даже после того, как вы обеспечили надежную конфигурацию сервера, необходимо постоянно помнить о потенциальной угрозе для системы безопасности, исходящей из программного кода РНР. Не подумайте, что язык РНР недостаточно надежен — теоретическую брешь в системе безопасности можно создать на любом языке программирования. Тем не менее, учитывая широкое распространение РНР для программирования в распределенных средах с большим количеством пользователей (то есть в Web), вероятность попыток «взлома» ваших программ со стороны пользователей существенно возрастает. Вы должны сами позаботиться о том, чтобы этого не произошло.

Обработка пользовательского ввода

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

Простейший способ борьбы с потенциально опасным пользовательским вводом — обработка полученных данных стандартной функцией escapeshellcmd( ).

escapeshellcmd( )

Функция escapeshellcmd( ) экранирует все сомнительные символы в строке, которые могут привести к выполнению потенциально опасной системной команды:

string escapeshellcmd(string команда)

Чтобы вы лучше представили, к каким последствиям может привести бездумное использование полученных данных, представьте, что вы предоставили пользователям возможность выполнения системных команд — например, `ls -l`. Но если пользователь введет команду `rm -rf *` и вы используете ее для эхо-вывода или вставите в вызов ехес( ) или system( ), это приведет к рекурсивному удалению файлов и каталогов на сервере! Проблемы можно решить предварительной «очисткой» команды при помощи функции escapeshel lcmd( ). В примере `rm -rf *` после предварительной обработки функцией escapeshellcmd( ) строка превращается в \ `rm -rf *\`.

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

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

strip_tags( )

Функция strip_tags( ) удаляет из строки все теги HTML. Синтаксис:

string strip_tags (string строка [, string разрешенные_теги])

Первый параметр определяет строку, из которой удаляются теги, а второй необязательный параметр определяет теги, остающиеся в строке. Например, теги курсивного начертания (<1 >...</ 1>) не причинят особого вреда, но лишние табличные теги (например, <td>...</td>) вызовут настоящий хаос. Пример использования функции strip_tags( ):

$input = "I <i>really</i> love РНР!";

$input = strip_tags($input);

// Результат: $input = "I really love PHP!";

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

Шифрование данных

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

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

Общие функции шифрования

Шифрование данных в Web имеет смысл только в том случае, если сценарии, в которых используются средства шифрования, работают на защищенном сервере. Почему? Поскольку РНР является сценарным языком, работающим на стороне сервера, перед шифрованием данные должны быть отправлены на сервер в простом текстовом формате. Если данные передаются через незащищенное соединение, существует немало способов перехвата этой информации в процессе ее пересылки от пользователя на сервер. За дополнительными сведениями о защите сервера Apache обращайтесь на сайт http://www.apache-ssl.org. Читателям, работающим с другими web-серверами, следует обращаться к документации. Скорее всего, для этих серверов существует хотя бы одно (а может, и больше) решение области безопасности.

md5( )

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

string md5(string строка)

MD5 является алгоритмом «одностороннего» хэширования; это означает, что данные, хэшируемые функцией md5(), восстановить уже невозможно.

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

Допустим, у нас имеется некоторый секретный пароль toystore с хэш-кодом 745e2abd7c52eeldd7cl4aeOd71b9d76. Хэшированное значение сохраняется на сервере и сравнивается с хэш-эквивалентом пароля, введенного пользователем. Даже если злоумышленник получит доступ к зашифрованному паролю, это ни на что не повлияет, поскольку он (теоретически) не сможет восстановить по нему оригинал. Пример хэширования строки:

$val = "secret";

$hash_val = md5 ($val);

// $hash_val = "Clab6fb9182fl6eed935bal9aa830788";

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

crypt( )

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

string cкypt(string строке [, детерминант])

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

Таблица 16.2. Алгоритмы шифрования и длины их детерминантов

Алгоритм | Длина
CRYPT_STD_DES | 2
CRYPT_EXT_OES | 9
CRYPT_MD5 | 12
CRYPT BLOWFISH | 16

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

Листинг 16.1. Применение функции crypt (STD_DES) для хранения и сравнения паролей <?

<?

$user_pass = "123456";

// Выделить первые два символа $user_pass

// и использовать их в качестве детерминанта.

$salt = substr($user_pass. 0, 2);

// Зашифровать и сохранить пароль.

$crypt1 = crypt($user_pass, ;salt);

// $crypt1 = "12tir.zIbWQ3c"

//... пользователь вводит пароль

$entered_pass = "123456";

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

$salt1 = substr($crypt, 0, 2);

// Зашифровать $entered_pass, используя $saltl в качестве детерминанта.

$crypt2 = crypt($entered_pass, $salt1);

// $crypt2 = "12tir.zIbWQ3c";

// Следовательно. $cryptl = $crypt2

?>

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

Как видно из листинга 16.1, $crypt совпадает с $crypt2, но только потому, что мы правильно использовали первые два символа $crypt1 в качестве детерминанта для шифрования $entered_pass. Поэкспериментируйте с этим примером, попробуйте использовать различные значения, и вы убедитесь, что $crypt1 совпадает с $crypt2 лишь при использовании этой процедуры.

mhash( )

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

  1. Зайдите на сайт http://mhash.sourceforge.net и загрузите пакет.
  2. Распакуйте содержимое архива и выполните инструкции, приведенные в документе INSTALL.
  3. Откомпилируйте РНР с ключом -with-mhash.

Как видите, ничего сложного. Впрочем, имеется одно обстоятельство, которое часто вызывает проблемы при компиляции mhash для комбинации РНР/Apache, — многим пользователям приходится конфигурировать mhash следующим образом: " ./configure -disable -pthreads" (вы поймете, о чем идет речь, если прочитаете документ INSTALL). Помните об этом в процессе компиляции.

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

Таблица 16.3. Алгоритмы хэширования, поддерживаемые mhash( )

SHA1 | RIPEMD160 | MD5
GOST | TIGER | SNEFRU
HAVAL |  | CRC32
RIPEMD128 |  |
CRC32B |  |

mcrypt( )

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

CBC

Режим СВС (Cipher Block Chaining) является самым распространенным из всех четырех режимов mcrypt. В отличие от режима ЕСВ (см. ниже), СВС обеспечивает разное шифрование идентичных блоков текста, что затрудняет поиск закономерностей при попытке несанкционированной расшифровки. Если вы не знаете, какой из четырех режимов следует использовать, выбирайте СВС. Впрочем, перед принятием окончательного решения стоит ознакомиться со всеми четырьмя режимами.

СFВ

Режим СFВ (Cipher Feedback) обладает некоторыми характеристиками потоковых шифров, что избавляет от необходимости накопления блоков данных перед шифрованием. Данный режим используется очень редко.

ЕСВ

Режим ЕСВ (Electronic Code Book) шифрует каждый текстовый блок независимым блочным шифром, что несколько снижает его защищенность при шифровании относительно малых блоков обычного текста. Поскольку ЕСВ шифрует два блока простого текста одинаковым шифром, у злоумышленника появляется основа для расшифровки. Следовательно, если у вас нет веских доводов в пользу ЕСВ, вероятно, лучше воспользоваться режимом СВС.

OFB

По многим характеристикам режим OFB (Output Feedback) похож на режим СFВ. Как и СFВ, он используется относительно редко.

Чтобы воспользоваться средствами mcrypt необходимо предварительно принять па-кет по адресу ftp://argeas.cs-net.gr/pub/unix/mcrypt.

Дополнительная информация

В этом разделе описаны лишь те средства, которые в той или иной степени интегрируются в РНР. Впрочем, этим ваши возможности не ограничиваются. Помните о том, что при помощи функций рореn( ) или ехес( ) можно работать с любыми технологиями шифрования, разработанными независимыми фирмами, — например, PGP (http://www.pgpi.org) или GPG (http://www.gnupg.org).

Ниже перечислены некоторые ресурсы Интернета, посвященные криптографии и информационной безопасности:

  • http://jya.com/crypto-free.htm;
  • http://www.io.com/~ritter/LEARNING.HTM;
  • http://www.rsasecurity.com/rsalabs/faq;
  • http://www.cs.auckland.ac.nz/~pgut001/links.html;
  • http://www.thawte.com/support/crypto/contents.html.

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

Электронная коммерция

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

Verisign

Компания Verisign, Inc. (http://www.verisign.com) предоставляет широкий ассортимент коммерческих продуктов и услуг. В РНР предусмотрена поддержка взаимодействия со службой Verisign Payflow Pro.

Для использования средств Verisign РНР необходимо откомпилировать с ключом -with-pfproC-DIR]. Кроме того, в файле php.ini имеется несколько конфигурационных параметров, относящихся к Payflow Pro.

Поддержка Payflow Pro в РНР очень проста в использовании, а непосредственное проведение сделок требует минимальных времени и знаний. Однако простое включение поддержки Verisign при компиляции РНР вовсе не означает, что вы можете пользоваться услугами Verisign! Для этого необходимо предварительно зарегистрироваться на сайте Verisign и принять пакет Verisign SDK. На момент написания книги за подключение к Payflow Pro взимался разовый взнос $249, а также ежемесячная оплата $59.95 (если ежемесячное количество сделок не превышает 5000) или $995 (при неограниченном количестве сделок).

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

Ресурсы Интернета, посвященные Verisign:

  • http://www.verisign.com;
  • http://www.php.net/manual/ref.pfpro.php.

Cybercash

Компания Cybercash, Inc. (http://www.cybercash.com) предлагает разнообразные услуги по проверке кредитных карт и проведению сделок, а также программное обеспечение для тех, кто желает использовать эти услуги в своих web-приложениях.

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

  • Используя библиотеку cyberlib.php, включенную в поставку РНР. Этот вариант предоставляет в ваше распоряжение все необходимое для проведения транзакций (рекомендуется).
  • Взаимодействуя со службой Cybercash при помощи готовых сценариев Perl и С и вызывая их из сценариев РНР (рекомендуется).
  • Переписывая готовые сценарии Perl и С на РНР (не рекомендуется).

Для использования средств Cybercash РНР необходимо откомпилировать с ключом -with-cybercash[=DIR].

Как и в случае с Verisign, помните, что включение поддержки Cybercash при компиляции РНР еще не означает, что вы можете пользоваться этой службой! Услуги Cybercash не бесплатны и могут обойтись довольно дорого (подключение к службе Cybercash Commerce Cash Register в настоящее время стоит $495, ежемесячная оплата составляет $20, а каждая сделка стоит $0,20). Тем не менее, невзирая на все расходы, многие разработчики РНР считают, что Cybercash является одним из лучших решений.

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

Ресурсы Интернета, посвященные Cybercash:

  • http://www.cybercash.com;
  • http://www.php.net/manual/ref.cybercash.php.

CCVS

Технология CCVS (Credit Card Verification System) была разработана RedHat (http://www.redhat.com) для независимой обработки сделок по кредитным картам. Она позволяет напрямую обращаться к агентствам кредитных карт вместо того, чтобы пользоваться услугами третьих сторон (например, Cybercash). Технология CCVS совместима со многими платформами Linux/UNIX и легко адаптируется, поскольку RedHat предоставляет исходные тексты.

Для использования средств CCVS РНР необходимо откомпилировать с ключом -with-ccvs[-DIR].

За дополнительной информацией о CCVS обращайтесь по адресам:

  • http://www.php.net/manual/ref.ccvs.php;
  • http://www.redhat.com/products/ccvs/support/CCVS3.3docs/ProgPHP.html;
  • http://www.redhat.com/products/ccvs.

Аутентификация пользователя

Правильно введенные имя и пароль открывают пользователю доступ к каталогам сервера, недоступным для анонимного доступа. Этот принцип аутентификации обычно называется схемой «запрос/ответ» (challenge/response). Запросом является приглашение к вводу имени и пароля, а ответом — введенные данные. Если введенная комбинация верна, пользователю предоставляется доступ к защищенным каталогам; в противном случае попытка получения доступа отклоняется с выводом соответствующего сообщения.

Как правило, для ввода имени и пароля применяются диалоговые окна, активизируемые вызовом функции header( ) (листинг 16.2).

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

<?

header( 'WWW-Authenticate: Basic realm="Secret Family Recipes"');

header ('HTTP/1.0 401 Unauthorized');

exit;

?>

Выполнение фрагмента из листинга 16.2 всего лишь активизирует окно для ввода данных. Примерный вид этого окна изображен на рис. 16.1.

Рис. 16.1. Окно аутентификации пользователя

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

Экспериментируя со сценариями этого раздела, вы увидите, что окно аутентификации не всегда появляется после обновления страницы. Проблема кроется не в программе, а в том, как окна аутентификации реализованы в браузере. Чтобы вызвать окно, вам придется закрыть и перезапустить браузер.

Листинг 16.3. Проверка глобальных переменных аутентификации в РНР

<?

If ( (! isset ($PHP_AUTH_USER)) || (! isset ($PHP_AUTH_PW)) ):

headert 'WWW-Authenticate: Basic realm="Secret Family Recipes'");

header(''HTTP/1.0 401 Unauthorized');

print "You are attempting to enter a restricted area. Authorization is required.";

exit;

endif;

?>

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

Листинг 16.4. Жесткое кодирование имени и пароля в сценарии

<?

if ( (! isset ($PHP_AUTH_USER)) | (! isset ($PHP_AUTH_PW)) || ($РНР AUTH USER != 'secret') | ($PHP_AUTH_PW !- 'recipes') ) :

header('WWW-Authenticate: Basic realm="Secret Family Recipes'"); header('HTTP/1.0 401 Unauthorized');

print "You are attempting to enter a restricted area. Authorization is required,

exit;

endif;

?>

Аутентификация с несколькими пользователями

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

Хранение информации в текстовом файле

Существует очень простое, но эффективное решение — хранить аутентификацион-ные данные в текстовом файле. В каждой строке файла содержится отдельная пара «имя:пароль»; в,процессе проверки программа последовательно читает и проверяет все строки файла. Примерный вид текстового файла приведен в листинге 16.5.

Листинг 16.5. Типичный текстовый файл с параметрами аутентификации (authenticate.txt)

brian:snaidni00

alessia:aiggaips

gary:9avaj9

chris:poghsawcd

matt:tsoptaes

Как видно из листинга, каждая строка приведенного файла состоит из имени пользователя и пароля, разделенных двоеточием (:). Таким образом, при использовании этого файла существует пять комбинаций «имя/пароль», обеспечивающих доступ к ограниченным ресурсам. Каждый раз, когда пользователь вводит имя и пароль в окне, сценарий открывает текстовый файл и последовательно ищет в нем совпадающую пару. Если совпадение находится, запрашиваемый доступ пользователю предоставляется, а если нет — запрос отклоняется. Процедура аутентификации продемонстрирована в листинге 16.6.

Листинг 16.6. Аутентификация на основе текстового файла <?

<?

$file = "Listing16-5.txt":

$fp = fopen($file, "r"):

$auth_file = fread ($fp, filesize($fp)):

fclose($fp);

$authorized = 0;

// Сохранить строки файла в виде элементов массива

$elements = explode ("\n", $auth_file);

foreach ($elements as $element) {

list ($user, $pw) = split (":", $element);

if (($user == $PHP_AUTH_U$ER) && ($pw = $PHP_AUTH_PW)) :

$authorized = 1;

break ;

endif;

}

if (! $authorized) :

header('WWW-Authenticate: Basic realm="Secret Family Recipes'");

header('HTTP/1.0 401 Unauthorized');

print "You are attempting to enter a restricted area. Authorization is required.";

exit;

else :

print "Welcome to the family's secret recipe collection";

endif;

?>

Хранение информации в базе данных

Хранение аутентификационной информации в базах данных обладает рядом преимуществ, многие из которых рассматривались в главе 11. Простота обновления, масштабируемость и гибкость — лишь некоторые из доводов в пользу хранения больших объемов информации в базе данных. В табл. 16.4 приведено содержимое демонстрационной таблицы user_authenticate. После успешно пройденной аутентификации по идентификатору пользователя можно устанавливать связи с другими таблицами, содержащими разнообразные данные и настройки. Процесс разделения взаимосвязанных данных по таблицам меньшего размера (вместо группировки всей информации в одной большой таблице) называется нормализацией базы данных, о нем кратко говорилось в главе 11.

В примерах этого раздела используется синтаксис MySQL. Программный код достаточно прост и легко адаптируется для других СУБД.

Таблица 16.4. Пример таблицы с аутентификационными данными (user_authenticate)

userid | username | password
url234 | brian | 2b877b4b825b48a9a0950dd5bdlf264d
urlHS | alessia | 6fled002ab5595859014ebf0951522d9
url15932 | gary | 122a2aladf096fe4f93287f9dal8f664
url19042 | chris | 6332e88a4c7dba6f7743d3a7a0c6ea2c
url8930 | matt | 922fe5dl40el9d308f2037404a0536a

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

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

<?

if (!isset($PHP_AUTH_USER)) :

header( 'WWW-Authenticate: Basic realm="Secret Family Recipes'");

header('HTTP/1.0 401 Unauthorized');

exit;

else :

// Создать содинение с базой данных MySQL

mysql_connect ("host", "user", "password")

or die ("Can't connect to database!");

mysql_select_db ("useMnfo")

or die ("Can't select database!");

// Обратиться к таблице user_authenticate

// для поиска совпадающей строки

$query = "select userid from user_authenticate where

username = '$PHP_AUTH_USER' and

password = '$PHP_AUTH_PW'";

$result = mysql_query (Squery):

// Если совпадение не найдено, вывести окно аутентификации

if (mysql_numrows($result) != 1) :

header('WWW-Authenticate: Basic realm="Secret Family Recipes'");

header ('HTTP/ 1.0 401 Unauthorized');

exit;

// Если проверка пройдена, получить идентификатор пользователя

else ;

Suserid = mysql_result (user_authenticate, 0, $result);

endif;

endif;

?>

Итоги

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

  • проблемы конфигурации РНР;
  • безопасный режим;
  • проблемы программирования;
  • шифрование данных;
  • технологии электронной коммерции;
  • аутентификация пользователей.

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


  • Глава 2. Переменные и типы данных
    • Целые числа
    • Восьмеричная и шестнадцатеричная запись
    • Вещественные числа
      • Стандартная запись
      • Научная запись
    • Строковые значения
      • Строковое присваивание
      • Синтаксис встроенной документации
      • Обращение к отдельным символам строк
    • Массивы
      • Одномерные индексируемые массивы
      • Одномерные ассоциативные массивы
      • Многомерные индексируемые массивы
      • Многомерные ассоциативные массивы
      • Смешанное индексирование
    • Объекты
    • Логические величины(истина/ложь)
    • Идентификаторы
    • Переменные
      • Объявление переменных
      • Область видимых переменных
      • Статические переменные
    • Переключение типов
    • Преобразование типов
    • Присваивание
      • Присваивание по значению
      • Присваивание по ссылке
    • Переменные в переменных
    • Стандартные переменные
    • Константы
    • Итоги

Глава 2

Переменные и типы данных

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

  • целые числа;
  • вещественные числа;
  • строки;
  • массивы;
  • объекты;
  • логические величины.

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

Целые числа

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

5

591

52

Восьмеричная и шестнадцатеричная запись

В РНР поддерживается запись целых чисел в восьмеричной (по основанию 8) и шестнадцатеричной (по основанию 16) системах счисления. Восьмеричные числа начинаются с цифры 0, после которой следует серия цифр от 0 до 7. Примеры:

0422

0534

Шестнадцатеричные целые числа имеют префикс 0х или 0Х и могут состоять из цифр от 0 до 9 и букв от а (А) до f (F). Примеры:

0x3FF

0x22abc

Вещественные числа

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

Стандартная запись

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

12.45

98.6

Научная запись

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

Зе8

5.9736е24

Строковые значения

Строкой (string) называется последовательность символов, которая рассматривается как единое целое, но при этом обеспечивает доступ к отдельным символам. Примеры строк:

thesaurus

49ers

abc

&%/$#

Обратите внимание: в РНР не поддерживается символьный тип данных. Строковый тип может рассматриваться как единое представление для последовательностей, состоящих из одного или нескольких символов.

Строковое присваивание

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

Два следующих объявления дают одинаковый результат:

$food = "meatloaf";

$food = 'meatloaf';

Однако результаты следующих объявлений сильно различаются:

$sentence = "My favorite food is $food";

$sentence2 = 'My favorite food is $food';

Переменной $sentence присваивается строка

My favorite food is meatloaf.

Обратите внимание: переменная $food автоматически интерпретируется. С другой стороны, переменной $sentence2 присваивается строка

My favorite food is $food.

В отличие от переменной $sentence, в $sentence2 осталась не интерпретированная переменная $food. Различия обусловлены использованием кавычек и апострофов при присваивании переменным $sentence и $sentence2.

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

Таблица 2.1. Служебные символы в строках

Последовательность | Смысл
\n | Новая строка
\r | Возврат курсора
\t | Горизонтальная табуляция
\\ | Обратная косая черта
\$ | Знак доллара
\" | Кавычка
\[0-7]{1,3} | Восьмеричная запись числа (в виде регулярного выражения)
\x[0-9A-Fa-f]{l,2} | Шестнадцатиричная запись числа (в виде регулярного выражения)

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

$double_list = "item1\nitem2\nitem2";

$single_list = 'item1\nitem2\nitem2';

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

Синтаксис встроенной документации

Второй вариант синтаксиса ограничения строк, представленный в HTML4, называется встроенной документацией (here doc). В этом варианте синтаксиса строка начинается с символов <<<, за которыми следует некоторый идентификатор по вашему выбору, затем строка, присваиваемая переменной. Конструкция заканчивается вторым экземпляром того же идентификатора. Пример:

$paragraph = <<<DELIM

This is a string that

Will be interpreted exactly

As it is written in the

variable assignment,

DELIM;

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

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

К отдельным символам строки можно обращаться как к элементам массива с последовательной нумерацией (см. следующий раздел). Пример:

$sequence_number = "04efgh";

$letter = Ssequence_number[4];

Переменной $ letter будет присвоено значение g. Как вы узнаете из следующего раздела, в РНР нумерация элементов массивов начинается с 0. Соответственно, выражение $sequence_number[l] будет равно 4.

Массивы

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

Одномерные индексируемые массивы

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

Обобщенный синтаксис элементов одномерного массива:

$имя[индекс1];

Одномерные массивы создаются следующим образом:

$meat[0] = "chicken";

$meat[l] = "steak";

$meat[2] = "turkey";

При выполнении следующей команды:

print $meat[1]:

в браузере выводится строка

steak

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

$meat = аrrау("chicken", "steak", "turkey");

Приведенная выше команда pri nt приводит к тому же результату — выводу строки steak.

Чтобы включить новый элемент в конец массива, можно просто присвоить значение переменной массива без указания индекса. Следовательно, массив $meat можно создать еще одним способом:

Smeat[] = "chicken";

$meat[] = "steak";

Smeat[] = "turkey";

Одномерные ассоциативные массивы

Ассоциативные массивы особенно удобны в ситуациях, когда элементы массива удобнее связывать со словами, а не с числами.

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

Spairings["zinfandel"] = "Broiled Veal Chops";

$pairings["merlot"] = "Baked Ham";

$pairings["sauvignon"] = "Prime Rib";

$pairings["sauternes"] = "Roasted Salmon";

Ассоциативный массив заметно экономит время и объем программного кода, необходимого для вывода определенных элементов массива. Допустим, вы хотите узнать, с каким блюдом лучше всего идет «Мерло». Нужная информация выводится простой ссылкой на элемент массива $pairings: print $pairings["merlot"]; // Выводится строка "Baked Ham" Ассоциативные массивы также можно создавать функцией РНР аггау():

Spairings = аrrау(

zinfandel => "Broiled Veal Chops",

merlot => "Baked Ham",

sauvignon => "Prime Rib",

sauternes => "Roasted Salmon");

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

Многомерные индексируемые массивы

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

Обобщенный синтаксис элементов многомерного массива:

$имя[индекс1][индекс2]..[индексN];

Пример ссылки на элемент двухмерного индексируемого массива:

$position = $chess_board[5][4];

Многомерные ассоциативные массивы

Многомерные ассоциативные массивы также существуют в РНР (и приносят определенную пользу). Допустим, в массиве $раirings из предыдущего примера должна храниться информация не только о сорте, но и о производителе вина. Это можно сделать следующим образом:

$pairings["Martinelli"]["zinfandel"] = "Broiled Veal Chops";

$pairings["Beringer"]["merlot"] = "Baked Ham";

$pairings["Jarvis"]["sauvignon"] = "Prime Rib";

$pairings["Climens"]["sauternes"] = "Roasted Salmon";

Смешанное индексирование

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

$Buckeyes["quarterback"] [1] = "Bellisari";

$Buckeyes["quarterback"] [2] = "Moherman":

$Buckeyes["quarterback"] [3] = "Wiley";

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

Объекты

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

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

class appliance {

var power:

function set_power($on_off) {

$this->power = $on_off;

}

}

...

$blender = new appliance;

Определение класса задает атрибуты и функции, связанные с некоторой структурой данных — в данном примере это структура с именем appliance (устройство). У этой структуры имеется всего один атрибут power (мощность). Для изменения этого атрибута создается метод set_power.

Помните: определение класса — всего лишь шаблон, и выполнять операции с ним в программе невозможно; сначала нужно создать объекты на основе этого шаблона. Объекты создаются при помощи ключевого слова new. Например, в приведенном выше фрагменте создается объект $blender класса appliance.

После создания объекта $blender можно задать его мощность при помощи метода

set_power: $blender->set_power("on");

Объектно-ориентированное программирование занимает столь важное место в современных стандартах программирования, что его применение в РНР заслуживает отдельной главы. Реализация ООП в РНР описана в главе 6.

Логические величины (истина/ложь)

Логический тип данных принимает всего два значения: истинное (true) и ложное (false). Логические величины создаются двумя способами: при проверке условий и в виде значений переменных. Обе ситуации достаточно просты.

Сравнения существуют в нескольких формах. Чаще всего они встречаются при использовании оператора = в условной команде if. Пример:

if ($sum == 40) :

...

Результатом проверки является либо истина, либо ложь: переменная $sum либо равна 40, либо не равна. Если переменная $sum равна 40, проверка дает истинный результат. В противном случае результат равен false.

Логические величины также могут определяться явным присваиванием переменной истинного или ложного значения. Пример:

$flag = TRUE;

if ($flag == TRUE) :

print "The flag is true!";

else :

print "The flag is false!";

endif;

Если переменная $flag истинна, выводится первое сообщение, а если ложна — второе сообщение.

Возможен и другой вариант — представление истинных и ложных логических величин в виде значений 1 и 0 соответственно. В этом случае предыдущий пример выглядит так:

$flag = 1;

if ($flag == TRUE) ;

print "The flag is true!";

else :

print "The flag is false!";

endif;

Наконец, существует еще один способ:

$flag = TRUE:

// При выполнении этой команды косвенно

// проверяется условие "if ($flag == TRUE)"

if ($flag) :

print "The flag is true!";

else :

print "The flag is false!";

endif:

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

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

Идентификатор состоит из одного или нескольких символов и начинается с буквы или символа подчеркивания. Идентификатор может содержать только буквы, цифры, символы подчеркивания и другие ASCII-символы с кодами от 127 до 255. Примеры:

Допустимые идентификаторы | Недопустимые идентификаторы
my_function | This&that
Size | !counter
_someword | 4ward

В идентификаторах учитывается регистр символов. Следовательно, переменная с именем $recipe отличается от переменных с именами $Recipe, $rEciPe и $recipE.

Длина идентификаторов не ограничивается. Это удобно, поскольку программист может точно описать смысл идентификатора в его имени.

Идентификатор не может совпадать с каким-либо из стандартных ключевых слов РНР.

Переменные

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

Объявление переменных

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

Имена переменных всегда начинаются со знака доллара, $. Ниже приведены примеры допустимых имен переменных:

$соlоr

$operating_system

$_some_variable

$model

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

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

$sentence = "This is a sentence."; // $sentence интерпретируется как строка

$price = 42.99: // $price интерпретируется как вещественное число

$weight = 185; // $weight интерпретируется как целое число

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

Область видимости переменных

Область видимости (scope) определяется как область доступности переменной в той программе, в которой она была объявлена. В зависимости от области видимости переменные РНР делятся на четыре типа:

  • локальные переменные;
  • параметры функций;
  • глобальные переменные;
  • статические переменные.

Локальные переменные

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

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

$х = 4;

function assignx () {

$х = 0;

print "\$x inside function is $x. <br>";

}

assignx();

print "\$x outside of function is $x. <br>";

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

$х inside function is 0.

$х outside of function is 4.

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

Параметры функций

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

Параметры объявляются в круглых скобках после имени функции. Объявление параметров практически не отличается от объявления типичной переменной:

// Функция умножает переданное значение на 10 и возвращает результат

function x10 ($value) {

$value = $value * 10;

return $value;

}

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

Глобальные переменные

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

$somevar = 15;

function addit() {

GLOBAL $somevar;

$somevar++;

print "Somevar is $somevar";

}

addit();

Будет выведено значение $somevar, равное 16. Допустим, вы забыли включить следующую строку:

GLOBAL $somevar;

В этом случае $somevar будет присвоено значение 1, поскольку эта переменная будет считаться локальной по отношению к функции addit( ). Локальная переменная по умолчанию инициализируется 0, а затем к ней прибавляется 1; таким образом, будет выведено значение 1.

Альтернативный способ объявления глобальных переменных связан с использованием массива РНР $GLOBALS( ). Давайте вернемся к предыдущему примеру и воспользуемся этим массивом для объявления глобальной переменной $somevar: $somevar = 15;

function addit() {

$GLOBALS["somevar"];

$somevar++;

}

addit();

print "Somevar is $somevar";

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

Статические переменные

Последний тип видимости переменных называется статическим. В отличие от переменных, объявленных параметрами и уничтожаемых при выходе из функции, статическая переменная сохраняет свое значение при повторном вызове. Для объявления статической переменной перед ее именем ставится ключевое слово STATIC:

STATIC $somevar;

Рассмотрим пример:

function keep_track() {

STATIC $count = 0;

$count++;

print $count;

print "<br>";

}

keep_track();

keep_track();

keep_track();

Как будут выглядеть результаты работы этого сценария? Если бы переменная $count не была объявлена статической (то есть являлась локальной), результат выглядел бы так:

1

1

1

Но поскольку переменная $count является статической, при каждом вызове функции будет сохраняться ее предыдущее значение, поэтому результат будет таким:

1

2

3

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

Переключение типов

Иногда бывает удобно использовать переменные способами, не предусмотренными при их создании. Допустим, вам захочется прибавить строковое значение "15" к целому числу 12. К счастью, тип переменных РНР может изменяться и без использования механизма явного преобразования. Этот процесс, независимо от того, выполняется ли он прямо или косвенно, называется переключением (juggling) типов. Лучше всего продемонстрировать сказанное на конкретных примерах.

Предположим, вы суммируете две величины — строку и целое число. Как вы думаете, что при этом произойдет? Результат зависит от содержимого строки. Например, при суммировании целого числа со строковым представлением числа будет получено целое число:

$variablel = 1;

$variable2 = "1";

$variable3 = $variablel + $variable2;

// $variable3 присваивается 4.

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

$variablel = 3;

$variable2 = 5.4;

$variable3 = $variablel + $variable2;

// $variablel интерпретируется как вещественное число.

// и $variable3 присваивается 8.4.

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

$variablel = 5;

$variable2 = "100 bottles of beer on the wall";

$variable3 = ;variable1 + $variable2;

// $variable3 присваивается 105

В результате переменной ;variable3 присваивается значение 105. Это происходит из-за того, что лексический анализатор РНР определяет тип по началу строки. Допустим, мы привели переменную $variable2 к виду "There are 100 bottles of beer on the wall". Поскольку алфавитные символы трудно интерпретировать как целое число, строка интерпретируется как 0, и переменной $variable3 присваивается 5.

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

Преобразование типов

Явное приведение переменной к типу, отличному от того, который изначально предназначался для нее, называется преобразованием (casting) типа. Изменение типа может быть как временным, одноразовым, так и постоянным.

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

Таблица 2.2. Операторы преобразования типа переменных

Оператор преобразования типа | Новый тип
(int) или (integer) | Целое число
(real), (double) или (float) | Вещественное число
(string) | Строка
(array) | Массив
(object) | Объект

Простой пример преобразования типов:

$variable1= 13; // $variable1 присваивается целое число 13

$variable2 = (double) $variable1; // $variable2 присваивается 13.0

Хотя переменная $variable1 первоначально содержала целое число 13, преобразование (double) преобразует ее к вещественному типу (поэтому число 13 превращается в 13.0). Полученное значение присваивается переменной $variable2.

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

$variablel = 4.0;

$variable2 = 5;

$variable3 = (int) $variable1 + $variable2; // $variable3 = 9

Следует заметить, что преобразование вещественного типа к целому всегда сопровождается округлением:

$variablel = 14.7:

$variable2 = (int) $varlable1; // $variable2 = 14:

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

$variable1 = 1114;

$array1 = (array) $varable1;

print $array1[0]; // Выводится значение 1114

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

$model = "Toyota";

$new_obj = (object) $model;

Ссылка на исходное строковое значение выглядит так:

print $new_obj->scalar;

Присваивание

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

Присваивание по значению

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

$vehicle = "car";

$amount =10.23;

В результате выполнения этих двух команд по адресу памяти, представленному именем $vehicle, сохраняется строка "car", а по адресу, представленному именем $amount, — значение 10.23.

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

function simple () {

return 5;

}

$return_value = simple();

Функция simple( ) всего лишь возвращает значение 5, которое присваивается некоторой переменной. В данном примере значение 5 будет присвоено переменной $return_value.

Присваивание по ссылке

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

Чтобы присвоить значение по ссылке, укажите перед именем переменной-источника символ & (амперсанд):

$dessert = "cake";

$dessert2 = $Sdessert;

$dessert2 = "cookies";

print "$dessert2 <br>"; // Выводится строка cookies

print Sdessert; // Снова выводится строка cookies

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

Переменные в переменных

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

$recipe = "spaghetti";

Оказывается, строку "spaghetti" можно интерпретировать как имя переменной — для этого в команде присваивания перед именем исходной переменной ставится второй знак $:

$$recipe = "& meatballs";

Эта команда присваивает строку "& meatballs" переменной с именем "spaghetti". Следовательно, следующие две команды выводят одинаковые результаты:

print $recipe $spaghetti;

print $recipe $($recipe);

В обоих случаях будет выведена строка "spaghetti & meatballs".

Стандартные переменные

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

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

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

while (list($var,$value) = each($GLOBALS)) :

echo "<BR>$var => $value";

endwhile;

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

GLOBALS =>

HTTP_GET_VARS => Array

HTTP_COOKIE_VARS => Array

HOSTSIZE => 1000

HOSTNAME => server1.apress.com

LOGNAME => unstrung

HISTFILESIZE => 1000

REMOTEHOST => apress.com

MAIL -> /var/spool/mail/apress

MACHTYPE => 1386

TERM => vt100

HOSTTYPE => i386-linux

PATH =>

/usr/sbin:/sbin:/usr/local /bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/Java/bin

HOME => /root

INPUTRC => /etc/inputrc

SHELL => /bin/csh

USER => nobody

VENDOR => intel

GROUP => root

HOST => server1.apress.com

OSTYPE => linux

PWD => /www/bin

SHLVL => 3_ => /www/bin/httpd

DOCUMENT_ROOT => /usr/local/apress/site.apress

HTTP_ACCEPT => */*

HTTP_ACCEPT_ENCODING => gzip, deflate

HTTP_ACCEPT_LANGUAGE => it.en-us;q=0.5

HTTP_CONNECTION -> Keep-Alive

HTTP_HOST => www.apress.com

HTTP_USER_AGENT => Mozilla/4.0 (compatible; MSIE 5.0: Windows 98;

CNETHomeBuild051099)

REMOTE_ADOR => 127.0.0.1

REMQTE_PORT => 3207

SCRIPT_FILENAME => /usr/local/apress/site.apress/j/environment_vars.php

SERVER_ADDR => 127.0.0.1

SERVER_AOMIN => admin@apress.com

SERVER_NAME => www.apress.com

SERVERJORT => 80

SERVER SIGNATURE =>

Apache/1.3.12 Server at www.apress.com Port 80

SERVER_SOFTWARE => Apache/1.3.12 (Unix) PHP/4.0.1

GATEWAY_INTERFACE => CGI/1.1

SERVER_PROTOCOL => HTTP/1.1

REQUEST_METHOD => GET

QUERY_STRING =>

REQUEST_URI => /j/environment_vars.php

SCRIPT_NAME => /j/environment_vars.php

PATH_TRANSLAETD => /usr/local/apress/site.apress/j/environment_vars.php

PHP_SELF => /j/environment_vars.php

argv => Array

argc => 0

var => argc

value => argc

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

print "Hi! Your IP address is: $REMOTE_ADDR";

IP-адрес выводится в числовой форме (например, 208.247.106.187).

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

print "Your browser is: $HTTP_USER_AGENT";

возвращает информацию следующего вида:

Your browser is: Mozina/4.0 (compatible: MSIE 5.0; Windows 98: CNETHomeBuild051099)

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

Для работы с массивами стандартных переменных необходимо включить директиву track_vars в файл php.ini. В РНР версии 4.0.3 директива track_vars включена постоянно.

Константы

Константой называется именованная величина, которая не изменяется в процессе выполнения программы. Константы особенно удобны при работе с заведомо постоянными величинами — например, числом π (3,141592) или количеством футов в миле (5280).

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

Например, определение числа я в сценарии РНР может выглядеть так:

define("'PI", "3.141592");

Определенную константу можно использовать в программе:

print "The value of pi is". PI."<br>";

$pi2 - 2 * PI:

print "Pi doubled equals $pi2.";

Результат работы этого фрагмента будет таким:

The value of pi is 3.141592.

Pi doubled equals 6.283184.

В этом фрагменте следует обратить внимание на два обстоятельства. Во-первых, в именах констант не указывается знак доллара. Во-вторых, константу невозможно модифицировать (например, присвоить ей величину 2*РI); если константа используется в вычислениях, то результат приходится сохранять в другой переменной.

Итоги

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

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

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


  • Глава 3. Выражения, операторы и управляющие конструкции
    • Выражения
      • Операнды
      • Операторы
    • Управляющие конструкции
      • Проверка условий
      • Альтернативное ограничение блоков
      • while
      • do..while
      • for
      • foreach
      • switch
      • break
      • continue
    • Проект: календарь событий
    • Итоги

ГЛАВА 3

Выражения, операторы и управляющие конструкции

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

Выражения

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

Операнды

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

$а++; // $а - операнд

$sum = $val1 + $val2; // $sum. $val1 и $val2 - операнды

Операторы

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

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

Таблица 3.1. Операторы РНР

Оператор | Ассоциативность | Цель
( ) | - | Изменение приоритета
new | - | Создание экземпляров объектов
! ~ | П | Логическое отрицание, поразрядное отрицание
++ -- | П | Инкремент, декремент
@ | П | Маскировка ошибок
/ * % | Л | Деление, умножение, остаток
+ - . | Л | Сложение, вычитание, конкатенация
<< >> | Л | Сдвиг влево, сдвиг вправо (поразрядный)
< <= > >= | - | Меньше, меньше или равно, больше, больше или равно
== != === <> | - | Равно, не равно, идентично, не равно
& ^ | | Л | Поразрядные операции AND, XOR и OR
&& || | Л | Логические операции AND и OR
?: | П | Тернарный оператор
= += *= /= .= | П | Операторы присваивания
%= &= |= ^= |  |
<<= >>= |  |
AND XOR OR | Л | Логические операции AND, XOR и OR

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

$а = 5;   // Присвоить целое число 5 переменной $а

$а = "5":   // Присвоить строковую величину "5" переменной $а

$sum = 50 + $some_int;  // Присвоить сумму 50 + $some_int переменной $sum

Swine = "Zinfandel";   // Присвоить строку "Zinfandel" переменной $wine

$inventory++:   // Увеличить значение $inventory на 1

Объединяя операторы и операнды, вы получите более сложные выражения для выполнения нетривиальных вычислений. Пример:

$total_cost = $cqst + (Scost * 0.06): // прибавить к цене 6-процентный налог

Приоритет операторов

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

$total_cost = $cost + $cost * 0.06;

Приведенная команда эквивалентна следующей:

$total cost = $cost + ($cost * 0.06);

Это объясняется тем, что оператор умножения обладает более высоким приоритетом по сравнению с оператором сложения.

Ассоциативность операторов

Ассоциативность оператора определяет последовательность выполнения операторов с одинаковым приоритетом (см. табл. 3.1). Выполнение может происходить в двух направлениях: либо слева направо, либо справа налево. При ассоциативности первого типа операции, входящие в выражение, выполняются слева направо. Например, команда

$value = 3*4*5*7*2;

эквивалентна следующей команде:

$value = ((((3 * 4) * 5) * 7) * 2);

Результат вычислений равен 840. Это объясняется тем, что оператор умножения (*) обладает левосторонней ассоциативностью. Операторы с правосторонней ассоциативностью и одинаковым приоритетом обрабатываются справа налево. Например, фрагмент

$с = 5;

$value = $а - $b - $с;

эквивалентен фрагменту

$c = 5;

$value = ($а - ($b - $с));

При обработке этого выражения переменным $value, $a, $b и $с будет присвоено значение 5. Это объясняется тем, что оператор присваивания (=) обладает правосторонней ассоциативностью.

Математические операторы

Математические операторы (табл. 3.2) предназначены для выполнения различных математических операций и часто применяются в большинстве программ РНР. К счастью, их использование обходится без проблем.

Таблица 3.2. Математические операторы

Пример | Название | Результат
$а + $b | Сложение | Сумма $а и $b
$а-$b | Вычитание | Разность $а и $b
$а*$b | Умножение | Произведение $а и $b
$а/$b | Деление | Частное от деления $а на $b
$а % $b | Остаток | Остаток от деления $а на $b

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

Операторы присваивания

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

Таблица 3.3. Операторы присваивания

Пример | Название | Результат
$а = 5; | Присваивание | Переменная $а равна 5
$а += 5; | Сложение с присваиванием | Переменная $а равна сумме $а и 5
$а *= 5; | Умножение с присваиванием | Переменная $а равна произведению $а и 5
$а/=5; | Деление с присваиванием | Переменная $а равна частному отделения $а на 5
$а .= 5; | Конкатенация с присваиванием | Переменная $а равна конкатенации $а и 5

Умеренное использование операторов присваивания обеспечивает более наглядный и компактный код.

Строковые операторы

Строковые операторы РНР (табл. 3.4) обеспечивают удобные средства конкатенации (то есть слияния) строк. Существует два строковых оператора: оператор конкатенации (.) и оператор конкатенации с присваиванием (.=), описанный в предыдущем разделе «Операторы присваивания».

Конкатенацией называется объединение двух и более объектов в единое целое.

Таблица 3.4. Строковые операторы

Пример | Название | Результат
$a = "abc"."def" | Конкатенация | Переменной $а присваивается результат конкатенации $а и $b
$а - "ghijkl" | Конкатенация с присваиванием | Переменной $а присваивается результат конкатенации ее текущего значения со строкой "ghijkl"

Пример использования строковых операторов:

// $а присваивается строковое значение "Spaghetti & Meatballs" $а = "Spaghetti" . "& Meatballs"

// $а присваивается строковое значение "Spaghetti & Meatballs are delicious" $a .= "are delicious";

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

Операторы инкремента и декремента

Удобные вспомогательные операторы инкремента (++) и декремента (--), приведенные в табл. 3.5, делают программу более наглядной и обеспечивают укороченную запись для увеличения или уменьшения текущего значения переменной на 1.

Таблица 3.5. Операторы инкремента и декремента

Пример | Название | Результат
++$а, $а++ | Инкремент | Переменная $а увеличивается на 1
--$а, $а-- | Декремент | Переменная $а уменьшается на 1

Интересный факт: эти операторы могут располагаться как слева, так и справа от операнда. Действия, выполняемые оператором, зависят от того, с какой стороны от операнда он находится. Рассмотрим следующий пример:

$inventory = 15;  // Присвоить Sinventory целое число 15

$old_inv = Sinventory--;  // СНАЧАЛА присвоить $old_inv значение

// Sinventory. а ЗАТЕМ уменьшить Sinventory.

$orig_iinventory = ++inventory;// СНАЧАЛА увеличить Sinventory. а ЗАТЕМ

// присвоить увеличенное значение Sinventory

// переменной $orig_inventory.

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

Логические операторы

Логические операторы (табл. 3.6) наряду с математическими операторами играют важную роль в любом приложении РНР, обеспечивая средства для принятия решений в зависимости от значения переменных. Логические операторы позволяют управлять порядком выполнения команд в программе и часто используются в управляющих конструкциях (таких, как условная команда i f, а также циклы for и while).

Таблица 3.6. Логические операторы

Пример | Название | Результат
$а && $b | Конъюнкция | Истина, если истинны оба операнда ,
$aAND$b | Конъюнкция | Истина, если истинны оба операнда
$а И $b | Дизъюнкция | Истина, если истинен хотя бы один из операндов
$а OR $b | Дизъюнкция | Истина, если истинен хотя бы один из операндов
!$а | Отрицание | Истина, если значение $а ложно
NOT !$a | Отрицание | Истина, если значение $а ложно
$а XOR $b | Исключающая дизъюнкция | Истина, если истинен только один из операндов

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

file_exists("filename.txt") OR print "File does not exist!";

Возможен один из двух вариантов:

  • файл filename.txt существует;
  • будет выведено сообщение: «File does not exist!».

Операторы равенства

Операторы равенства (табл. 3.7) предназначены для сравнения двух величин и проверки их эквивалентности.

Таблица 3.7. Операторы равенства

Пример | Название | Результат
$a==$b | Проверка равенства | Истина, если $а и $b равны
$а != $b | Проверка неравенства | Истина, если $а и $b не равны
$а === $b | Проверка идентичности | Истина, если $а и $b равны и имеют одинаковый тип

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

Операторы сравнения

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

Таблица 3.8. Операторы сравнения

Пример | Название | Результат
$a<$b | Меньше | Истина, если переменная $а меньше $b
$a>$b | Больше | Истина, если переменная $а больше $b
$a <= $b | Меньше или равно | Истина, если переменная $а меньше или равна $b
$a >= $b | Больше или равно | Истина, если переменная $а больше или равна $b
($a-12)?5: -1 | Тернарный оператор | Если переменная $а равна 12, возвращается значение 5, а если не равна — возвращается 1

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

Поразрядные операторы

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

Таблица 3.9. Десятичные числа и их двоичные представления

Десятичное целое | Двоичное представление
2 | 10
5 | 101
10 | 1010
12 | 1100
145 | 10010001
1 452 012 | 1011000100111111101100

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

Таблица 3.10. Поразрядные операторы

Пример | Название | Результат
$а&$b | Конъюнкция | С битами, находящимися в одинаковых разрядах $а и $b, выполняется операция конъюнкции
$а|$Ь | Дизъюнкция | С битами, находящимися в одинаковых разрядах $а и $b, выполняется операция дизъюнкции
$а^$b | Исключающая | С битами, находящимися в одинаковых разрядах $а и $b, выполняется операция исключающей дизъюнкции
~$b | Отрицание | Все разряды переменной $b инвертируются
$а << $b | Сдвиг влево | Переменной $а присваивается значение $b, сдвинутое влево на два бита
$а >> $b | Сдвиг вправо | Переменной $а присваивается значение $b, сдвинутое вправо на два бита

Если вам захочется больше узнать о двоичном представлении и поразрядных операторах, я рекомендую обратиться к обширному электронному справочнику Рэндалла Хайда (Randall Hyde) «The Art of Assembly Language Programming», доступному по адресу http://webster.cs.ucr.edu/Page_asm/Page_asm.html. Это лучший из ресурсов, которые мне когда-либо встречались в Web.

Управляющие конструкции

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

Проверка условий

Управляющие конструкции обычно проверяют условия на истинность или ложность, и в зависимости от результата проверки выполняется то или иное действие. Рассмотрим выражение $а == $b. Это выражение истинно, если $а равно $b, и ложно в противном случае. Результат истинного выражения считается равным 1, а результат ложного выражения равен 0. Рассмотрим следующий фрагмент:

$а = 5;

$b = 5;

print $а == $b;

В результате выводится значение 1. Если изменить $а или $Ь и присвоить переменной значение, отличное от 5, выводится 0.

if

Команда if представляет собой разновидность команды выбора, которая вычисляет значение выражения и в зависимости от того, будет ли полученный результат истинным или ложным, выполняет (или не выполняет) блок программного кода. Существует две общих формы команды i f:

if (выражение) {

блок

}

и

if (выражение) {

блок

}

else {

блок

}

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

if ($cooking_weight < 200) {

print "This is enough pasta (< 200g) for 1-2 people";

}

else {

print "That's a lot of pasta. Having a party perhaps?";

}

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

if ($cooking_weight < 100) print "Are you sure this is enough?";

elseif

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

if (выражение) {

блок

}

elseif (выражение) {

блок

}

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

if ($cooking_weight < 200) {

print "This is enough pasta (< 200g) for 1-2 people";

}

elseif ($cooking_weight < 500) {

print "That's a lot of pasta. Having a party perhaps?"; }

}

else {

print "Whoa! Who are you cooking for, a football team?";

}

Вложенные команды if

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

// Проверить значение $pasta

if ($food == "pasta") {

// Проверить значение $cooking_weight

if ($cooking_weight < 200) {

print "This is enough pasta (< 200g) for 1-2 people";

}

elseif ($cooking_weight < 500) {

print "That's a lot of pasta. Having a party perhaps?";

}

else {

print "Whoa! Who are you cooking for. a football team?";

}

}

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

Вычисление нескольких условий

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

if ($cooking_weight < 0) {

    print "Invalid cooking weight!";

}

if ( ($cooking_weight > 0) && ($cooking_weight < 200) ) {

   print "This is enough pasta (< 200g) for 1-2 people";

}

elseif ( ($cooking_weight > 200) && ($cooking_weight < 500) ) {

   print "That's a lot of pasta. Having a party perhaps?";

}

else {

   print "Whoa! Who are you cooking for, a football team?";

}

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

Альтернативное ограничение блоков

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

if (выражение) :

блок

else :

блок

endif;

Следовательно, две приведенных ниже команды if полностью эквивалентны:

if ($а== $b) {

print "Equivalent values!";

}

if ($a == $b) :

print "Equivalent values!";

endif;

while

Конструкция while предназначена для многократного (циклического) выполнения блока команд. Блок команды while выполняется до тех пор, пока условие цикла остается истинным. Общая форма цикла while выглядит так:

while (выражение) :

блок

endwhile;

Рассмотрим использование цикла while на примере вычисления факториала (n!), где n = 5:

$n = 5;

$nсору = $n;

$factorial = 1; // Установить начальное значение факториала

while ($n > 0) :

$factorial - $n * $factorial;

$n--; // Уменьшить $n на 1

endwhile;

print "The factorial of $ncopy is $factorial.";

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

The factorial of 5 is 120.

В этом примере $n уменьшается в конце каждой итерации. Условие цикла не должно быть истинным в тот момент, когда переменная $n станет равна 0, поскольку величина $factorial умножится на 0 — конечно, этого быть не должно.

В приведенном примере условие цикла следовало бы оптимизировать и привести его к виду $n > 1, поскольку умножать $factorial на 1 бессмысленно — число от этого не изменится. Хотя ускорение работы программы будет ничтожно малым, такие факторы всегда принимаются во внимание с ростом объема и сложности программ.

do. .while

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

do:

блок

while (выражение);

Давайте пересмотрим пример с вычислением факториала и перепишем его с использованием конструкции do. .while:

$n = 5:

$ncopy = $n;

$factorial = 1; // Установить начальное значение факториала

do {

$factorial = $n * $factorial;

$n--: // Уменьшить Sn на 1

} while (Sn > 0);

print "The factorial of Sncopy is $factorial.";

При выполнении этого примера будет получен тот же результат, что и при выполнении его прототипа из предыдущего раздела.

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

for

Цикл for обеспечивает еще одну возможность многократного выполнения блоков. Он отличается от цикла while только тем, что условие изменяется в самой

управляющей конструкции, а не где-то внутри блока команд. Как и в случае с циклом while, цикл выполняется до тех пор, пока проверяемое условие остается истинным. Общая форма конструкции for выглядит так:

for (инициализация: условие; приращение) {

блок

}

Условная часть цикла for в действительности состоит из трех компонентов. Инициализация выполняется всего один раз и определяет начальное значение управляющей переменной цикла. Условие проверяется в начале каждой итерации и определяет, должна ли выполняться текущая итерация или нет. Наконец, приращение определяет изменение управляющей переменной при каждой итерации. Возможно, термин «приращение» в данном случае неточен, поскольку переменная может как увеличиваться, так и уменьшаться в соответствии с намерениями программиста. Следующий пример демонстрирует простейший случай применения цикла for:

for ($i = 10; $1 <- 100: $1 +=10) : // Обратная косая черта предотвращает

print "\$i = $i <br>"; endfor;      // возможную интерполяцию переменной $1

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

$i = 10

$i = 20

$i = 30

$i = 40

$i - 50

$i = 60

$i = 70

$i = 80

$i = 90

$i = 100

В этом примере управляющая переменная $i инициализируется значением 10. Условие заключается в том, что цикл продолжается до тех пор, пока $i не достигнет или не превысит пороговую величину 100. Наконец, при каждой итерации значение $i увеличивается на 10. В результате команда print выполняется 10 раз, каждый раз выводя текущее значение $i. Обратите внимание: для увеличения $i на 10 используется оператор сложения с присваиванием. Для этого есть веские причины, поскольку циклы for в РНР не поддерживают более традиционной записи $i = $i + 10.

Кстати, этот пример можно записать и в другом виде, но с теми же результатами:

for ($i = 10; $i <= 100; print "\$i - $i <br>". $i+=10);

Многие новички не понимают, зачем создавать несколько разновидностей циклов в языке программирования, будь то РНР или какой-нибудь другой язык. Почему нельзя обойтись одной циклической конструкцией? Дело в том, что у цикла for существует несколько специфических особенностей.

Например, вы можете инициализировать несколько переменных одновременно, разделяя команды инициализации запятыми:

for ($x=0,$y=0: $x+$y<10; $x++) :

$У += 2;                    // Увеличить $у на 2

print "\$y = $y <BR>";      // Вывести значение $у

$sum = $x + $y;

print "\surn = $sum<BR>";   // Вывести значение $sum

endfor;

Результат:

$y = 2

$sum = 2

Sy = 4

$sum = 5

$y = 6

$sum = 8

$y = 8

$sum = 11

Этот пример выводит текущие значения $y и суммы $х и $у. Как видно из приведенных результатов, выводится значение $sum = 11, хотя эта сумма выходит за границы условия цикла ($х + $у < 10). Это происходит из-за того, что при входе в данную итерацию переменная $у была равна 6, а переменная $х была равна 2. Значения переменных соответствовали условию цикла, поэтому $х и $у были присвоены новые значения, в результате чего была выведена сумма И. При очередной проверке условия сумма 11 превысила пороговое значение 10 и цикл завершился.

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

$х = 5:

for (: : $х +=2) :

print " $х ";

if ($x == 15) :

break; // Выйти из цикла for

endif;

endfor;

Результат выглядит так:

5 7 9 11 13 15

Хотя циклические конструкции for и while выполняют практически одинаковые функции, считается, что цикл for делает программу более наглядной. Это объясняется тем, что программист при виде команды for немедленно получает всю необходимую информацию о механике и продолжительности цикла. С другой стороны, в командах while приходится тратить лишнее время на поиск обновлений управляющих переменных — в больших программах это может занимать немало времени.

foreach

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

foreach (массив as $элемент) {

блок

}

foreach (массив as $ключ => $элемент) {

блок

}

Например, при выполнении следующего фрагмента:

$menu = аrrау("pasta", "steak", "potatoes", "fish", "fries");

foreach ($menu as $item) {

print "$item <BR>";

}

будет выведен следующий результат:

pasta

steak

potatoes

fish

fries

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

Второй вариант используется при работе с ассоциативными массивами:

$wine_inventory = array {

"merlot" => 15,

"zinfandel" => 17,

"sauvignon" => 32

}

foreach ($wine_inventory as $i => $item_count) {

print "$item_count bottles of $i remaining<BR>";

}

В этом случае результат выглядит так:

15 bottles of merlot remaining

17 bottles of zinfandel remaining

32 bottles of sauvignon remaining

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

switch

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

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

switch (выражение) {

case (условие):

блок

case (условие):

блок

...

default:

блок

}

Проверяемое условие указывается в круглых скобках после ключевого слова switch. Результат его вычисления последовательно сравнивается с условиями в секциях case. При обнаружении совпадения выполняется блок соответствующей секции. Если совпадение не будет обнаружено, выполняется блок необязательной секции default.

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

$user_input = "recipes"; // Команда,выбранная пользователем

switch ($user_input) :

case("search") :

print "Let's perform a search!";

break;

case("dictionary") :

print "What word would you like to look up?";

break;

case("recipes") :

print "Here is a list of recipes...";

break;

default :

print "Here is the menu...";

break;

endswitch;

Как видно из приведенного фрагмента, команда switch обеспечивает четкую и наглядную организацию кода. Переменная, указанная в условии switch (в данном примере — $user_input), сравнивается с условиями всех последующих секций case. Если значение, указанное в секции case, совпадает Со значением сравниваемой переменной, выполняется блок этой секции. Команда break предотвращает проверку дальнейших секций case и завершает выполнение конструкции switch. Если ни одно из проверенных условий не выполняется, активизируется необязательная секция default. Если секция default отсутствует и ни одно из условий не выполняется, команда switch просто завершается и выполнение программы продолжается со следующей команды.

Вы должны помнить, что при отсутствии в секции case команды break (см. следующий раздел) выполнение switch продолжается со следующей команды до тех пор,

пока не встретится команда break или не будет достигнут конец конструкции switch. Следующий пример демонстрирует последствия отсутствия забытой команды break: $value = 0.4;

switch($value) :

case (0.4) :

print "value is 0.4<br>";

case (0.6) :

print "value is 0.6<br>";

break;

case (0.3) :

print "value is 0.3<br>";

break;

default :

print "You didn't choose a value!";

break;

endswitch;

Результат выглядит так:

value is 0.4

value is 0.6

Отсутствие команды break привело к тому, что была выполнена не только команда print в той секции, где было найдено совпадение, но и команда print в следующей секции. Затем выполнение команд конструкции switch прервалось из-за команды switch, следующей за второй командой print.

Выбор между командами switch и if практически не влияет на быстродействие про-граммы. Решение об использовании той или иной конструкции является скорее личным делом программиста.

break

Команда break немедленно прерывает выполнение той конструкции while, for или switch, в которой она находится. Эта команда уже упоминалась в предыдущем разделе, однако прерывание текущего цикла не исчерпывает возможностей команды break. В общем виде синтаксис break выглядит так:

break n;

Необязательный параметр n определяет количество уровней управляющих конструкций, завершаемых командой break. Например, если команда break вложена в две команды while и после break стоит цифра 2, происходит немедленный выход из обоих циклов. По умолчанию значение n равно 1; выход на один уровень может обозначаться как явным указанием 1, так и указанием команды break без параметра. Обратите внимание: команда i f не относится к числу управляющих конструкций, прерываемых командой break. Об этом следует помнить при использовании необязательного параметра п.

Рассмотрим пример использования команды break в цикле foreach:

$arr = array(14, 12, 128, 34, 5);

$magic number = 128:

foreach ($arr as $val) :

if (Sval == $magic_number) :

print "The magic number is in the array!";

break;

endif;

print "val is Sval <br>";

endforeach;

Если значение $magic_number присутствует в массиве $аrr (как в приведенном примере), поиск прерывается. Результат выглядит так:

val is 14

val is 12

The magic number is in the array!

Приведенный пример всего лишь демонстрирует использование команды break. В РНР существует стандартная функция in_array( ), предназначенная для поиска заранее заданной величины в массиве; эта функция подробно описана в главе 5.

continue

Остается рассмотреть еще одну управляющую конструкцию РНР — continue. При выполнении команды continue в цикле пропускаются все оставшиеся команды текущей итерации и немедленно начинается новая итерация. Синтаксис команды continue в общем виде:

continue n;

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

Рассмотрим пример использования команды continue. Допустим, вы хотите сосчитать простые числа в интервале от 0 до некоторой заданной границы. Простоты ради предположим, что у нас имеется функция is_prime(), которая проверяет, является число простым или нет:

$boundary = 558;

for ($i = 0; $i <= $boundary: $i++) :

if (! is_prime($i)) :

continue;

endif;

$prime_counter++;

endfor;

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

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

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

Проект: календарь событий

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

Информация о событиях хранится в обычном текстовом файле и выглядит примерно так:

July 21, 2000|8 p. m.|Cooking With Rasmus|PHP creator Rasmus Lerdorf discusses the wonders of cheese.

July 23, 2000|11 a. m.|Boxed Lunch|Valerie researches the latest ham sandwich making techniques (documentary)

July 31, 2000|2:30p.m.|Progressive Gourmet|Forget the Chardonnay: iced tea is the sophisticated gourmet's beverage of choice.

August 1, 2000|7 p.m.|Coder's Critique|Famed Food Critic Brian rates NYC's hottest new Internet cafes.

August 3, 2000|6 p.m.|Australian Algorithms|Matt studies the alligator's diet.

На рис. 3.1 изображен результат работы сценария РНР, приведенного в листинге 3.1.

Рис. З.1. Примерный вид календаря

Прежде чем переходить к подробному анализу кода, потратьте немного времени на изучение алгоритма:

  1. Открыть файл, содержащий информацию о событиях.
  2. Разделить каждую строку на 4 элемента: дату, время, название и краткое описание мероприятия.
  3. Отформатировать и вывести данные.
  4. Закрыть файл.

Листинг 3.1. Сценарий для вывода содержимого events.txt в браузере

<?

// Приложение: календарь

// Назначение: чтение и анализ содержимого файла

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

// Открыть файловый манипулятор Sevents для файла events.txt

$events - fopen ("events.txt". "r");

print "<table border = 0 width = 250>"

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

print "<h3>Events Calendar:</h3>";

// Читать, пока не будет найден конец файла

while (! feof(Sevents)) :

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

events.txt $event = fgets($events. 4096);

// Разделить компоненты текущей строки на элементы массива

$event_info = explode("|". Jevent);

// Отформатировать и вывести информацию о событии

print "$event_info[0] ( $event_info[1] ) <br>";

print "<b>$event_info[2]</b> <br>";

print "$event_info[3] <br> <br>";

endwhile;

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

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

fclose ($events);

?>

Этот короткий пример убедительно доказывает, что РНР позволяет даже неопытным программистам создавать реальные приложения с минимальными усилиями и затратами времени. Если какие-нибудь из представленных концепций покажутся непонятными, не огорчайтесь — на самом деле они очень просты и будут подробно описаны в следующих главах. А если вам не терпится узнать побольше об этих вопросах, обратитесь к главе 7 «Файловый ввод/вывод и файловая система» и главе 8 «Строки и регулярные выражения» поскольку большая часть незнакомого синтаксиса описана именно там.

Итоги

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

  • операторы;
  • операнды;
  • приоритет операторов;
  • ассоциативность операторов;
  • управляющие структуры (If, while, do. .while, for, foreach, switch, break, continue).

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


  • Глава 4. Функции
    • Что такое функция?
    • Определение и вызов функций
    • Вложенные функции
    • Возврат значений из функции
    • Рекурсивные функции
    • Функции-переменные
    • Построение библиотек функций
    • Итоги

ГЛАВА 4

Функции

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

Данная глава посвящена функциям РНР, их определению и применению на практике. Хотя основное внимание в ней уделяется определению и вызову пользовательских функций, необходимо помнить и о том, что в РНР существуют сотни стандартных функций. Стандартные функции работают точно так же, как пользовательские, и обеспечивают заметную экономию времени при создании новых приложений. Обновленный список стандартных функций РНР можно найти по адресу http://www.php.net.

Что такое функция?

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

Определение и вызов функций

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

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

Определение функции обычно состоит из трех частей:

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

Обобщенный синтаксис функций РНР выглядит так:

function имя_функции ([$параметр1. $параметр2, .... $параметрn]) {

тело функции

}

Имя функции должно подчиняться условиям, приведенным для идентификаторов в главе 2. После имени функции следуют обязательные круглые скобки, в которые заключается необязательный список входных параметров ($параметр1, $параметр2, .... $параметрn). Вследствие относительно либеральных принципов определения переменных в РНР указывать тип входных параметров не нужно. Хотя такой подход имеет свои преимущества, следует помнить, что механизм РНР не проверяет аргументы на соответствие тем типам, которые должны обрабатываться функцией. Случайные ошибки в использовании входных параметров могут привести к неожиданным последствиям (чтобы убедиться в том, что параметр относится к нужному типу, можно проверить его стандартной функцией gettype( )). После закрывающей круглой скобки следуют фигурные скобки, в которые заключается программный код, ассоциируемый с именем функции.

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

function display_copyright() {

print "Copyright &copy; 2001 PHP-Powered Recipes. All Rights Reserved.";

}

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

Рассмотрим разновидность функции display_copyright(), которой при вызове передается параметр. Предположим, вы отвечаете за администрирование нескольких web-сайтов, каждому из которых присвоено отдельное имя. На каждом сайте имеется собственный административный сценарий с несколькими переменными, относящимися к этому сайту; к их числу принадлежит переменная $site_name с именем

сайта. В этом случае функцию display_copyright() можно записать следующим образом:

function display_copyright($site_name) {

print "Copyright &copy; 2001 $site_name. All Rights Reserved.";

}

Переменная $site_name, значение которой присваивается за пределами display_copy-right(), передается функции в качестве параметра. Переданное значение можно использовать и модифицировать в любом месте функции, однако любые изменения будут действовать лишь внутри этой функции. Впрочем, специальные ключевые слова позволяют сделать так, чтобы изменения параметров распространялись и за пределы display_copyright(). Эти ключевые слова были представлены в главе 2, в общем обзоре области видимости переменных и ее отношения к функциям.

Вложенные функции

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

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

function display_copyright($site_name) {

print "Copyright &copy". date("Y"). "$site_name. All Rights Reserved.";

}

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

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

Листинг 4.1. Эффективное использование вложенных функций

function display_footer($site_name) {

function display_copyright($site_name) {

print "Copyright &сору". date("Y").

$site_name. All Rights Reserved.";

print "<center>

<a href = \"\">home</a> | <a href = \"\">recipes</a> | <a href = \"\">events</a><br>

<a href = \"\">tutorials</a> | <a href = \"\">about</a> I <a href = \"\">contact us</a><br>";

display_copyright($site_name);

print "</center>";

}

$site_name = "PHP Recipes":

display_footer($site_name);

display_copyhght($site_name);

Сценарий выводит следующий результат:

home | recipes | events

tutorials | about | contact us

Copyright © 2001 PHP Recipes. All Rights Reserved

Обратите внимание: функцию display_copyright( ) можно вызвать и за пределами display_footer( ) по аналогии стем, как функция display_footer( ) использовалась в предыдущем примере. Концепция защищенных функций в РНР не поддерживается.

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

Возврат значений из функции

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

  • Перед вызовом функции задать значения переменных: $price (цена товара) и $tax (налоговая ставка).
  • Объявить функцию calculate_cost( ). При вызове функция получает два параметра: налоговую ставку и цену товара.
  • Вычислить цену с учетом налога и вернуть ее командой return.
  • Вызвать calculate_cost() и присвоить значение, возвращенное функцией, переменной $total_cost.
  • Вывести соответствующее сообщение.

Листинг 4.2. Создание функции для вычисления налога

$price = 24.99; $tax = .06;

function calculate_cost($tax, $price) {

$sales_tax = $tax;

return $price + ($price * $sales_tax);

}

// Обратите внимание на возврат значения функцией calculate_cost(). $total_cost = calculate_cost ($tax. $price);

// Округлить цену до двух десятичных цифр.

$total_cost = round($total_cost. 2);

print "Total cost: $".$total_cost;

// $total cost = 26.49

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

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

  • Объявить функцию check_limit( ), которая при вызове получает два параметра. Первый параметр, $total_cost, определяет общую сумму счета, накопленную пользователем до настоящего момента. Второй параметр, $credit_limit, определяет максимальную сумму, которую может потратить пользователь.
  • Если накопленная сумма счета превышает предельный размер кредита, функция возвращает ложное значение (0).
  • Если условие команды i f оказывается ложным, работа функции еще не завершена. В этом случае общая сумма не превышает предельного размера кредита, поэтому функция должна вернуть логическую истину.
  • Вызвать функцию check_limit( ) в условии команды if. Проверить, какое значение было возвращено при вызове — истинное или ложное. В зависимости от результата проверки выполняется то или иное действие.

Если при вызове check_limit( ) было получено значение TRUE, мы предлагаем пользователю продолжить закупку. В противном случае пользователь информируется о превышении кредита.

Листинг 4.3. Сравнение текущей суммы счета пользователя с предельным размером кредита

$cost = 1456.22;

$limit = 1000.00;

function check_limit($total_cost. $credit_limit)

if ($total_cost > $credit_limit) :

return 0;

endif;

return 1;

}

if (check_limit($cost. $limit)) :

// Продолжить закупки

print "Keep shopping!";

else :

print "Please lower your total bill to less than $".$limit."!";

endif;

При выполнении листинга 4.3 будет выведено сообщение об ошибке, поскольку значение $cost превышает $limit.

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

  • Объявить функцию best_years( ), вызываемую с одним параметром. Параметр $label определяет сорт вина, для которого пользователь хотел бы узнать три рекомендуемых года.
  • Объявить два массива, $merlot и $zinfandel. В каждом массиве хранится три рекомендуемых года для соответствующего сорта вина.
  • Написать команду return, которая бы использовала особые возможности переменных. Выражение $$label сначала интерпретирует переменную $label, а затем интерпретирует полученное значение как имя другой переменной. В настоящем примере массив merlot возвращается в виде списка, и каждый возвращаемый год занимает свою позицию в списке, для которого вызывалась функция.
  • Вывести сообщение с информацией о рекомендуемых годах.

Листинг 4.4. Возвращение функцией нескольких величин

// Сорт вина, для которого выводятся лучшие годы

$label = "merlot";

// Функция использует массивы и "переменную в переменной"

// для возвращения нескольких значений.

function best_years($label) {

$merlot = array("1987", "1983", "1977");

$zinfandel = array("1992", "1990", "1989");

return $$label;

}

// Функция list( ) используется получения возвращаемых значений.

list ($yr_one, $yr_two. $yr_three) = best_years($label);

print "$label had three particularly remarkable years: $yr_one. $yr_two, and $yr_three.";

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

merlot has three particularly remarkable years: 1987, 1983 and 1977.

Рекурсивные функции

Ситуация, при которой функция многократно вызывает сама себя, пока не будет выполнено некоторое условие, открывает замечательные возможности. При правильном использовании рекурсивные функции уменьшают объем программы и делают ее более выразительной. Рекурсивные функции особенно часто используются при выполнении повторяющихся действий — например, при поиске в файлах/массивах и построении графических изображений (например, фракталов). Классическим примером рекурсивных функций, встречающимся во многих курсах программирования, является суммирование чисел от 1 до N. Программа, приведенная в листинге 4.5, суммирует все целые числа от 1 до 10.

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

function summation ($count) {

if ($count != 0) :

return $count + summation($count-1);

endif;

}

$sum = summation(10);

print "Summation = $sum";

В результате выполнения листинга 4.5 будет выведен следующий результат:

Summation = 55

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

Функции-переменные

Одной из интересных возможностей РНР являются функции-переменные (variable functions), то есть динамические вызовы функций, имена которых определяются во время выполнения программы. Хотя в большинстве web-приложений можно обойтись и без функций-переменных, они значительно сокращают объем и сложность программного кода, а также часто снимают необходимость в условных командах if.

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

$имя_функции( );

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

  • Создать сообщение для итальянского языка в функции с именем italian.
  • Создать сообщение для английского языка в функции с именем english.
  • Передать информацию о выбранном языке в сценарий, присвоив значение переменной $language.

Переменная $language используется для выполнения функции-переменной (в приведенном примере — italian()).

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

// Приветствие на итальянском языке, function italian( ) {

" print "Benvenuti al PHP Recipes.";

}

// Приветствие на английском языке

function english( ) {

print "Welcome to PHP Recipes.";

}

// Выбрать итальянский язык

$language = "italian":

// Выполнить функцию-переменную

$language( );

Листинг 4.6 демонстрирует интересную концепцию функций-переменных и наглядно показывает, что функции-переменные способствуют уменьшению объема программного кода. Если бы не эта возможность, функцию пришлось бы выбирать командой if или switch; это привело бы к заметному увеличению объема программного кода и риску появления дополнительных ошибок при кодировании.

Построение библиотек функций

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

Листинг 4.7. Пример библиотеки функций (array_sorting.inc)

<?

// Файл: array_sorting.inc

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

// Дата: 17 июля 2000 г.

function merge_sort($array. $tmparray, $right, $left) {

...

function bubble_sort($array. $n) {

...

}

function quicksort ($array. $right. $left) {

...

}

?>

Библиотека array_sorting.inc служит накопителем для всех функций сортировки. Это удобно, поскольку функции фактически группируются по своему назначению и при необходимости можно легко найти нужную функцию. Как видно из листинга 4.7, в начало библиотеки обычно включается заголовок из нескольких строк комментария, чтобы при открытии файла библиотеки можно было сразу получить краткую сводку его содержимого. После собственной библиотеки функций можно включить ее в сценарий при помощи команд РНР include( ) и require( ), в результате чего все функции библиотеки становятся доступными. В общем виде синтаксис этих команд выглядит так:

include(путь/имя_файла);

require(путь/имя_файла);

Также существует альтернативный вариант:

include "путь/имя_файла";

require "путь/имя_файла";

где путь определяет относительный или абсолютный путь к файлу. Конструкции include( ) и requirе( ) подробно описаны в главе 9. А пока достаточно запомнить, что эти конструкции используются для включения файла непосредственно в сценарий.

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

Листинг 4.8. Включение библиотечного файла (array_sorting.inc) в сценарий

// Предполагается, что библиотека array_sorting.inc

// находится в одном каталоге со сценарием.

include("array_sorting.inc");

// Теперь вы можете использовать любые функции из array_sorting.inc

$some_array = array (50, 42. 35, 46);

// Использовать функцию bubble_sort()

$sorted_array = bubble_sort($some_array, 1);

Итоги

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

  • определение и вызов функций;
  • вложенные функции;
  • возврат значений из функции;
  • рекурсивные функции;
  • функции-переменные;
  • построение библиотек функций.

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

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


  • Глава 5. Массивы
    • Создание массивов
    • Многомерные массивы
    • Ссылки на многомерные массивы
    • Поиск элементов массива
    • Добавление и удаление элементов
    • Перебор элементов
    • Размер массива
    • Сортировка массивов
    • Другие полезные функции
    • Итоги

ГЛАВА 5

Массивы

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

В этой главе рассматриваются различные средства РНР по работе с массивами. К концу этой главы вы познакомитесь с одномерными и многомерными массивами, сортировкой и перебором элементов, а также с другими возможностями, часто используемыми при работе с массивами. Книгу не стоит рассматривать как подробный справочник по всем существующим функциям, хотя так уж получилось, что в этой главе рассматриваются практически все функции массивов. Последнюю версию списка функций можно найти на домашней странице РНР по адресу http://www.php.net.

Создание массивов

Массив представляет собой совокупность объектов, имеющих одинаковые размер и тип. Каждый объект в массиве называется элементом массива. Создать новый массив в РНР несложно. При объявлении индексируемого массива после имени переменной ставится пара квадратных скобок ([ ]):

$languages [ ] = "Spanish";

// $languages[0] = "Spanish"

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

$languages[ ] = "English"; // $1anguages[l] = "English";

$languagest ] = "Gaelic"; // $languages[2] = "Gaelic";

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

$languages[15] = "Italian";

$languages[22] = "French";

Ассоциативные массивы создаются аналогичным образом:

$languages["Spain"] = "Spanish";

$languages["France"] = "French";

При создании массивов используются три стандартные языковые конструкции:

  • аrrау( );
  • list( );
  • range( ).

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

аггау( )

Функция array( ) получает ноль или более элементов и возвращает массив, состоящий из указанных элементов. Ее синтаксис:

array array ( [элемент1, элемент2...] )

Вероятно, array( ) является всего лишь более наглядной записью для создания массива, используемой для удобства программиста. Ниже показан пример использования array( ) для создания индексируемого массива:

$languages = array ("English". "Gaelic". "Spanish");

// $languages[0] = "English". $languages[1] = "Gaelic",

// $languages[2] = "Spanish"

А вот как array( ) используется при создании ассоциативных массивов:

$languages = array("Spain" => "Spanish",

"Ireland" => "Gaelic".

"United States" => "English");

// $languages["Spain"] = "Spanish"

// $languages["Ireland"] = "Gaelic"

// $languages["United States"] = "English"

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

list( )

Конструкция list( ) похожа на аrrау( ), однако ее главная задача — одновременное присваивание значений, извлеченных из массива, сразу нескольким переменным. Синтаксис команды list( ):

void list (переменная1 [. переменная2 , ...] )

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

профессия и любимый цвет); компоненты записи разделяются вертикальной чертой (|). Типичная строка выглядит так:

Nino Sanzi|Professional Golfer|green

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

// Читать строки, пока не будет достигнут конец файла

while ($line = fgets ($user_file. 4096)) :

// Разделить строку функцией split( ).

// Компоненты присваиваются переменным Sname. $occupation и Scolor.

list ($name, $occupation, $color) = split( "|", $line);

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

print "Name: Sname <br>";

print "Occupation: Soccupation <br>";

print "Favorite color: Scolor <br>";

endwhile;

Каждая строка файла читается, форматируется и выводится в следующем виде:

Name: Nino Sanzi

Occupation: Professional Golfer

Favorite color: green

В приведенном примере применение list( ) зависит от разделения строки на элементы функцией split( ). Элементы, полученные в результате деления, присваиваются, соответственно, переменным $name, $occupation и $color. Дальше все сводится к форматированию данных для вывода в конкретном браузере. Удобные средства лексического анализа текстовых файлов являются одной из сильных сторон РНР. Эта тема подробно рассматривается в главах 7 и 8.

range ( )

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

array range (int нижняя_граница, int верхняя граница)

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

$lottery = range(0,9);

// $lottery = array(0,1,2,3,4,5,6,7,8,9)

Как видно из приведенного фрагмента, в параметрах range( ) был указан интервал от 0 до 9 и массив $lottery был заполнен целыми числами из этого интервала.

Многомерные массивы

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

$chessboard [1] [4] = "King"; // Двухмерный массив

$capitals["USA"] ["Ohio"] = "Columbus": // Двухмерный массив

$streets["USA"]["Ohio"]["Columbus"] = "Harrison"; // Трехмерный массив

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

$desserts = аrrау(

"Fruit Cup" => array (

"calories" => "low",

"served" -> "cold",

"preparation" => "10 minutes"

),

"Brownies" => array (

"calories" -> "high",

"served" => "piping hot",

"preparation" => "45 minutes"

)

);

После создания массива к его элементам можно обращаться по соответствующим ключам:

$desserts["Fruit Cup"]["preparation"] // возвращает "10 minutes"

$desserts["Brownies"]["calories"] // возвращает "high"

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

$desserts["Cake"]["calories"] = "too many";

// Присваивает свойству "calories" объекта "Cake" значение "too many"

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

Ссылки на многомерные массивы

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

print "Brownies are good, but the calories content is ".

$desserts["Brownies"]["calories"];

Во-вторых, ссылку на элемент многомерного массива можно заключить в фигурные скобки ({ }):

print "Brownies are good, but the calories content is

{$desserts[Brownies][calories]}";

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

Поиск элементов массива

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

in_array( )

Функция i n_array ( ) проверяет, присутствует ли в массиве заданный элемент. Если поиск окажется удачным, функция возвращает TRUE, в противном случае возвращается FALSE. Синтаксис функции in_array( ):

bool in_array(mixed элемент, array массив)

Эта функция особенно удобна тем, что вам не приходится в цикле перебирать весь массив в поисках нужного элемента. В следующем примере функция in_array( ) ищет элемент "Russian" в массиве $languages:

$languages = array("English", "Gaelic", "Spanish"):

$exists = in_array("Russian", $languages); // $exists присваивается FALSE

$exists = in_array("English", $languages): // $exists присваивается TRUE

Функция in_array( ) часто встречается в управляющих конструкциях, когда ее возвращаемое значение (TRUE/FALSE) используется для выбора одного из двух вариантов продолжения. В следующем примере функция in_array( ) используется для выбора одного из двух вариантов в условной команде if:

// Ввод данных пользователем

$language = "French"; $email = "wjgilmore@hotmail.com";

// Если язык присутствует в массиве

if (in_array($language. $languages)) :

// Подписать пользователя на бюллетень.

// Обратите внимание: в РНР нет стандартной функции с именем

// subscribe_user(). В данном примере эта функция просто имитирует

// процесс подписки.

subscribe_user($email, $language);

print "You are now subscribed to the $language edition of the newsletter.";

// Язык отсутствует в массиве

else :

print "We're sorry, but we don't yet offer a $language edition of the newsletter".

endif;

Что происходит в этом примере? Допустим, переменные $language и $email содержат данные, введенные пользователем. Вы хотите убедиться в том, что указанный язык поддерживается вашей системой, и используете для этой цели функцию in_array( ). Если название языка присутствует в массиве, пользователь подписывается на бюллетень и получает соответствующее сообщение. В противном случае программа сообщает, что на указанном языке бюллетень не распространяется. Конечно, в настоящей программе пользователь не должен гадать, какие языки поддерживаются вашей программой. Задача решается при помощи раскрывающегося списка — эта тема подробно рассматривается в главе 10. Здесь этот пример всего лишь демонстрирует возможности работы с массивами.

array_keys( )

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

array array_keys (array массив [, mixed искомый_элемент])

Рассмотрим пример использования функции array_keys( ) для получения ключа заданного элемента:

$great_wines = array ("Australia" => "Clarendon Hills 96",

"France" => "Comte George de Vogue 97",

"Austria" => "Feiler Artinger 97");

$great_labels = array_keys($great_wines);

// $great_labels = array("Australia", "France", "Austria");

$great_labels = array_keys($great_wines, "Clarendon Hills 96");

// $great_labels = array("Australia");

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

array_values( )

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

array array_values(array массив)

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

// $great_wines = array ("Australia" => "Clarendon Hills 96",

// "France" => "Comte George de Vogue 97",

// "Austria" => "Feiler Artinger 97");

$great_labels = array_values($great_wines);

// $great_labels = аrrау("Clarendon Hills 96",

// "Comte George de Vogue 97",

// "Feiler Artinger 97");

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

Добавление и удаление элементов

К счастью, в РНР при создании массива не нужно указывать максимальное количество элементов. Это увеличивает свободу действий при операциях с массивами, поскольку вам не приходится беспокоиться о случайном выходе за границы массива, если количество элементов превысит ожидаемый порог. В РНР существует несколько функций для увеличения размеров массива. Некоторые из них были созданы для удобства программистов, привыкших работать с различными типами очередей и стеков (FIFO, FILO и т. д.), что отражается в названиях функций (push, pop, shift и unshift). Но даже если вы не знаете, что такое «очередь» или «стек», не огорчайтесь — в этих функциях нет ничего сложного.

Очередью (queue) называется структура данных, из которой элементы извлекаются в порядке поступления. Стеком (stack) называется структура данных, из которой элементы извлекаются в порядке, обратном порядку их поступления.

array_push( )

Функция array_push( ) присоединяет (то есть дописывает в конец массива) один или несколько новых элементов. Синтаксис функции array_push( ):

int array_push(array массив, mixed элемент [, ...])

Длина массива возрастает прямо пропорционально количеству его элементов. Это продемонстрировано в следующем примере:

$languages = array("Spanish", "English", "French");

array_push($languages, "Russian", "German", "Gaelic");

// $languages = array("Spanish", "English", "French",

// "Russian", "German", "Gaelic")

У функции array_push( ), как и у многих стандартных функций РНР, существует «двойник» — функция аrrау_рор( ), предназначенная для извлечения элементов из массива. Главное различие между этими функциями заключается в том, что array_push( ) может добавлять несколько элементов одновременно, а аrrау_рор( ) удаляет элементы только по одному.

аrrау_рор( )

Результат работы функции аrrау_рор( ) прямо противоположен array_push( ) — эта функция извлекает (то есть удаляет) последний элемент из массива. Извлеченный элемент возвращается функцией. Синтаксис функции аrrау_рор( ):

аrrау_рор(аrrау массив)

При каждом выполнении аrrау_рор( ) размер массива уменьшается на 1. Рассмотрим пример:

$languages = array("Spanish", "English", "French",

"Russian", "German", "Gaelic");

$a_language = array_pop($languages): // $a_language = "Gaelic"

$a_language = array_pop($languages):
//
$a_language = "German"

// $languages = array ("Spanish", "English", "French", "Russian");

Функции array_push( ), и array_pop( ) удобны тем, что с их помощью можно выполнять операции с элементами и управлять размером массива, не беспокоясь о неинициализированных или пустых элементах. Такое решение работает намного эффективнее, чем любые попытки управления этими факторами со стороны программиста.

array_shift( )

Функция array_shift( ) аналогична аrrау_рор( ) с одним отличием: элемент удаляется из начала (левого края) массива. Все остальные элементы массива сдвигаются на одну позицию к началу массива. У функции array_shift( ) такой же синтаксис, как и у аггау_рор( ):

array_shift(array массив)

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

$languages = array("Spanish", "English", "French", "Russian");

$a_language = array_shift($languages); // $a_language = "Spanish";

// $languages = array("English", "French", "Russian");

array_unshift( )

Функция array_unshift( ) дополняет array_shift( ) — новый элемент вставляется в начало массива, а остальные элементы сдвигаются на одну позицию вправо. Синтаксис команды array_unshift( ):

1nt array_unshift(array массив, mixed переменная1 [....переменная2])

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

$languages = array("French", "Italian", "Spanish");

array_unshift($languages, "Russian", "Swahili", "Chinese");

// $languages = array("Russian", "Swahili", "Chinese",

// "French", "Italian", "Spanish");

array_pad( )

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

array arrap_pad(array массив, int размер, mixed значение):

Параметр размер определяет новую длину массива. Параметр значение задает стандартное значение, присваиваемое элементам во всех новых позициях массива. При использовании array_pad( ) необходимо учитывать некоторые обстоятельства:

Если размер положителен, массив дополняется справа, а если отрицателен — слева.

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

Абсолютным значением (модулем) целого числа называется его значение без знака. Например, абсолютное значение чисел 5 и -5 равно 5.

Пример дополнения массива с конца:

$weights = array(1, 3, 5, 10, 15, 25, 50);

$weights = array_pad($weights. 10, 100);

// Результат: $weights = array(1, 3, 5, 10, 15, 25, 50, 100, 100, 100);

Пример дополнения массива с начала:

$weights = array(1, 3, 5, 10, 15, 25, 50);

$weights = array_pad($weights, -10, 100);

// Результат: $weights = array(100, 100, 100, 1, 3, 5, 10, 15, 25, 50);

Неправильная попытка дополнения массива:

$weights = array(1, 3, 5, 10, 15, 25, 50);

$weights = array_pad($weigtits, 3, 100);

// Массив $weights не изменяется:

// $weights = array(1, 3, 5, 10, 15, 25, 50);

Перебор элементов

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

reset( )

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

mixed reset (array массив)

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

$fruits = array("apple", "orange", "banana");

Допустим, указатель текущей позиции в этом массиве установлен на элемент "orange". Команда:

$a_fruit = reset($fruits);

вернет указатель в начало массива, то есть на элемент "apple", и вернет это значение, если результат вызова reset( ) используется в программе. Возможен и упрощенный вариант вызова:

reset($fruits);

В этом случае указатель переходит к первому элементу массива, а возвращаемое значение не используется.

each ( )

Функция each( ) при каждом вызове выполняет две операции: она возвращает пару «ключ/значение», на которую ссылается указатель текущей позиции, и перемещает указатель к следующему элементу. Синтаксис функции each( ):

array each (array массив)

Для удобства each ( ) возвращает ключ и значение в виде массива из четырех элементов; ключами этого массива являются 0, 1, value и key. Возвращаемый ключ ассоциируется с ключами 0 и key, а возвращаемое значение — с ключами 1 и value.

В следующем примере функция each ( ) возвращает элемент, находящийся в текущей позиции:

// Объявить массив из пяти элементов

$spices = array("parsley", "sage", "rosemary", "thyme", "pepper");

// Установить указатель на первый элемент массива

reset($spices);

// Создать массив $a_sp1ce. состоящий из четырех элементов

$a_spice = each($spices);

В результате выполнения приведенного фрагмента массив $a_spice будет содержать следующие пары «ключ/значение»:

  • 0 => 0;
  • 1 => "parsley";
  • key => 0;
  • value => "parsley".

После этого строку "parsley" можно вывести любой из следующих команд:

print $a_spice[1]: print $a_spice["value"];

Функция each() обычно используется в сочетании с list( ) в циклических конструкциях для перебора всех или некоторых элементов массива. При каждой итерации each( ) возвращает либо следующую пару «ключ/значение», либо логическую ложь при достижении последнего элемента массива. Вернемся к массиву $spices; чтобы вывести все элементы на экран, можно воспользоваться следующим сценарием:

// Сбросить указатель текущей позиции

reset($spices);

// Перебрать пары "ключ/значение", ограничиваясь выводом значения

while (list ($key, $val) = each ($spices) ) :

print "$val <br>"

endwhile;

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

Листинг 5.1. Построение таблицы HTML по содержимому массива

// Объявить ассоциативный массив стран и языков $languages = array ("Country" => "Language",

"Spain" => "Spanish",

"USA" => "English",

"France" => "French",

"Russia" => "Russian");

// Начать новую таблицу

print "<table border=l>";

// Переместить указатель к позиции первого элемента

reset ($languages);

// Прочитать первый ключ и элемент

$hdl = key ($languages);

Shd2 = $languages[$hd1];

// Вывести первый ключ и элемент в виде заголовков таблицы

print "<tr><th>$hd1</th><th>$hd2</th></tr>";

next($languages);

// Выводить строки таблицы с ключами и элементами массива

while (list ($ctry,$lang) = each ($languages)) :

print "<tr><td>Sctry</td><td>$lang</td></tr>";

endwhile;

// Завершить таблицу print "</table>";

?>

В результате выполнения этого кода будет построена следующая таблица HTML.

Country | Language
Spain | Spanish
USA | English
France | French
Russia | Russian

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

end( )

Функция end( ) перемещает указатель к позиции последнего элемента массива. Синтаксис функции end( ):

end (array массив)

next( )

Функция next ( ) смещает указатель на одну позицию вперед, после чего возвращает элемент, находящийся в новой позиции. Если в результате смещения

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

mixed next (array массив)

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

prev( )

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

mixed prev (array массив)

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

array_walk( )

Функция array_walk( ) позволяет применить функцию к нескольким (а возможно, всем) элементам массива. Синтаксис функции array_walk( ):

int array_walk(array массив, string имя_функции [, mixed данные])

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

function delete_dupes($element) {

static $last=""; if ($element == $last)

unset($element); else . ''

$last=$element:

}

$emails = array("blah@blah.com", "chef@wjgilmore.com", "blah@blah.com");

sort($emails);

reset($emails);

array_walk($emails,"delete_dupes");

// $emails = array("chef@wjgilmore.com", "blah@blah.com");

array_reverse( )

Функция array_reverse( ) позволяет легко перейти к противоположному порядку элементов, составляющих массив. Синтаксис функции array_reverse( ):

array array_reverse(array массив)

Рассмотрим пример использования функции array_reverse( ):

$us_wireproducers = array ("California", "Oregon", "New York". "Washington");

$us_wine_producers - array_reverse (Sus_wine_producers);

// $us_wine_producers = array ("Washington". "New York", "Oregon". "California");

При вызове array_reverse( ) для ассоциативного массива пары «ключ/значение» сохраняются, изменяется только порядок элементов массива.

array_flip( )

Функция array_flip( ) меняет местами ключи и значения элементов массива. Синтаксис функции array_flip( ):

array array_flip(array массив)

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

$languages = array("Spain" => "Spanish", "France" => "French", "Italy" => "Italian");

$languages = array_flip($languages);

// $languages = array("Spanish" => "Spain", // "French" => "France", // "Italian" => "Italy");

Помните: функция array_flip( ) не изменяет порядок элементов массива. Для этой цели используется функция array_reverse( ).

Размер массива

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

$us_wine_producers = array ("Washington". "New York", "Oregon", "California");

for (Si = 0; Si < sizeof ($us_wine_producers); $i++) :

print "$us_wine_producers[$i]";

endfor;

Поскольку массив $us_wine_producers индексируется целыми числами, мы можем воспользоваться циклом for для циклического увеличения переменной-счетчика ($i) и вывода каждого элемента в массиве.

sizeof( )

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

int sizeof (array массив)

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

$pasta = array("bowties", "angelhair", "rigatoni");

$pasta_size = sizeof($pasta);

// $pasta_size = 3

У функции sizeof ( ) существует и другая, расширенная форма — count ( ) (см. ниже).

count( )

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

int count (mixed переменная)

Единственное различие между sizeof ( ) и count( ) заключается в том, что в некоторых ситуациях count ( ) возвращает дополнительную информацию:

  • если переменная существует и является массивом, count ( ) возвращает количество элементов в массиве;
  • если переменная существует, но не является массивом, функция возвращает значение 1;
  • если переменная не существует, возвращается значение 0.

array_count_values( )

Функция array_count_values( ) является разновидностью sizeof ( ) и count ( ). Вместо общего количества элементов она подсчитывает количество экземпляров каждого значения в массиве. Синтаксис функции array_count_values( ):

array array_count_values(array массив):

В возвращаемом массиве ключами будут значения исходного массива, а значениями — их частоты:

$states = аrrау("ОН", "ОК", "СА", "РА", "ОН", "ОН", "РА", "АК");

$state_freq = array_count_values($states);

Массив $state_freq заполняется следующими ассоциативными парами «ключ/значение»:

$state_freq = аrrау("ОН" => 3, "ОК" => 1, "СА" => 1, "РА" => 2, "АК" => 1);

Сортировка массивов

Сортировка занимает важное место в программировании и часто встречается на практике в таких Интернет-приложениях, как коммерческие сайты (сортировка категорий товаров в алфавитном порядке, сортировка цен) или поисковые системы (сортировка программ по количеству загрузок). В РНР существует девять стандартных функций сортировки (табл. 5.1), причем каждая функция сортирует массив особым образом.

Таблица 5.1. Функции сортировки

Функция | Сортировка | Обратный порядок | Сохранение пар «ключ/значение»
sort | Значение | Нет | Нет
rsort | Значение | Да | Нет
asort | Значение | Нет | Да
arsort | Значение | Да | Да
ksort | Ключ | Нет | Да
krsort | Ключ | Да | Да
usort | Значение | ? | Нет
uasort | Значение | ? | Да
uksort | Ключ | ? | Да

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

Сортировка элементов массива не ограничивается стандартными критериями, поскольку три функции (usort(), uasort( ) и uksort( )) позволяют задать пользовательский критерий и отсортировать информацию произвольным образом.

sort( )

Простейшая функция sort( ) сортирует элементы массива по возрастанию (от меньших к большим). Синтаксис функции sort ( ):

void sort (array массив)

Нечисловые элементы сортируются в алфавитном порядке в соответствии с ASCII-кодами. Следующий пример демонстрирует применение функции sort( ) при сортировке:

// Создать массив городов.

$cities = array("Aprilia", "Nettuno", "Roma", "Venezia", "Anzio");

// Отсортировать города по возрастанию

sort($cities);

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

for (reset($cities); $key = key ($cities); next ($cities)):

print("cities[$key] = $cities[$key] <br>";

endfor;

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

cities[0] = Anzio

cities[1] = Aprilia

cities[2] = Nettuno

cities[3] = Roma

cities[4] = Venezia

Как видите, массив $cities сортируется в алфавитном порядке. Одна из разновидностей этого способа сортировки реализована в функции asort( ), описанной ниже.

rsort( )

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

void rsort (array массив)

Вернемся к массиву $cities из предыдущего примера:

$cities array("Aprilia", "Nettuno", "Roma", "Venezia", "Anzio");

rsort($cities);

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

cities[0] = Venezia

cities[1] = Roma

cities[2] = Nettuno

cities[3] = Aprilia

cities[4] = Anzio

Массив $cities также сортируется, но на этот раз в порядке, обратном алфавитному. Одна из разновидностей этого способа сортировки реализована в функции arsort( ), описанной ниже.

asort( )

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

void asort(array массив)

Вернемся к массиву $cities:

$cities = array("Aprilia", "Nettuno", "Roma", "Venezia", "Anzio");

asort($cities):

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

cities[4] = Anzio

cities[0] = Aprilia

cities[1] = Nettuno

cities[2] = Roma

cities[3] = Venezia

Обратите внимание на индексы и сравните их с приведенными в описании функции sort ( ). Именно в этом и состоит различие между двумя функциями.

arsort( )

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

void arsort (array массив)

Воспользуемся функцией arsort( ) для сортировки массива $cities:

$cities = array("Aprilia", "Nettuno", "Roma", "Venezia", "Anzio");

arsort($cities);

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

cities[3] = Venezia

cities[2] = Roma

cities[l] = Nettuno

cities[0] = Aprilia

cities[4] = Anzio

Обратите внимание на индексы и сравните их с приведенными в описании функции rsort( ). Именно в этом и состоит различие между двумя функциями.

ksort( )

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

void ksort (array массив)

Для примера рассмотрим массив, слегка отличающийся от исходного массива

$cities:

$wine_producers = array ("America" => "Napa Valley",

"Italy" => "Tuscany",

"Australia" => "Ruthgerlen",

"France" => "Loire",

"Chile" => "Rapel Valley");

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

"America" => "Napa Valley"

"Australia" => "Ruthgerlen"

"Chile" => "Rapel Valley"

"France" => "Loire"

"Italy" => "Tuscany"

Сравните с результатами сортировки $wine_producers функцией sort ( ):

"America" => "Napa Valley"

"Australia" => "Tuscany"

"Chile" => "Ruthgerlen"

"France" => "Loire"

"Italy" => "Rapel Valley"

Более чем сомнительный результат!

krsort( )

Функция krsort( ) почти аналогична ksort( ), однако ключи сортируются в обратном порядке. Синтаксис функции krsort( ):

void krsort (array $массив)

Рассмотрим сортировку массива $wi reproducers функцией krsort( ):

$wine_producers = array ("America" => "Napa Valley",

"Italy" => "Tuscany",

"Australia" => "Ruthgerlen",

"France" => "Loire".

"Chile" => "Rapel Valley");

krsort($wine_producers);

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

"Italy" => "Tuscany"

"France" => "Loire"

"Chile" => "Rapel Valley"

"Australia" => "Ruthgerlen"

"America" => "Napa Valley"

Вероятно, описанных выше функций сортировки будет вполне достаточно для большинства случаев. Тем не менее, в некоторых ситуациях может возникнуть необходимость в определении собственных критериев сортировки. В РНР такая возможность реализована в трех стандартных функциях: usort( ), uasort( ) и uksort( ).

usort( )

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

void usort (array массив, string имя_функции)

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

Листинг 5.2. Определение критерия сортировки для функции usort( )

$vocab = аrrау( "Socrates", "Aristophanes", "Plato", "Aeschylus", "Thesmophoriazusae");

function compare_length($str1, $str2) {

// Получить длину двух следующих слов

$length1 = strlen($str1);

$length2 = strlen($str2);

// Определить, какая строка имеет меньшую длину

if ($length1 == $length2) :

return 0;

elseif ($length1 < $length2) :

return -1;

else :

return 1;

endif;

}

// Вызвать usort() с указанием функции compare_length()

// в качестве критерия сортировки

usort ($vocab, "compare_length") :

// Вывести отсортированный список

while (list ($key, $val) = each ($vocab)) {

echo "$val<br>";

}

В листинге 5.2 функция compare_length ( ) определяет критерий сортировки массива. В данном случае это делается посредством сравнения длины передаваемых элементов. Функция-критерий должна получать два параметра, представляющих

сравниваемые элементы массива. Также обратите внимание на то, как эти элементы неявно передаются функции-критерию при вызове usort( ), и на то, что все элементы автоматически сравниваются этой функцией.

Функции uasort( ) и uksort( ) представляют собой разновидности usort( ) с тем же синтаксисом. Функция uasort() сортирует массив по пользовательскому критерию с сохранением ассоциаций «ключ/значение». Функция uksort( ) также сортирует массив по пользовательскому критерию, однако сортируются не значения, а ключи.

Другие полезные функции

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

array_merge( )

Функция arrayjnerge( ) сливает от 1 до N массивов, объединяя их в соответствии с порядком перечисления в параметрах. Синтаксис функции array_merge( ):

array array_merge(array массив1, array массив2, ..., array массивN]

Рассмотрим пример простого объединения массивов функцией arrayjnerge( );

$arr_1 = array("strawberry", "grape", "lemon");

$arr_2 = array("banana", "cocoa", "lime");

$arr_3 = array("peach", "orange");

$arr_4 = array_merge ($arr2, $arr_1, $arr_3):

// $arr_4 = array("banana", "cocoa", "lime", "strawberry", "grape", "lemon", "peach", "orange");

array_slice( )

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

array array_slice(array массив, int смещение [, int длина])

Значения параметров задаются по определенным правилам:

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

array_splice( )

Функция array_spl ice( ) отдаленно напоминает array_slice( ) — она заменяет часть массива, определяемую начальной позицией и необязательной длиной, элементами необязательного параметра-массива. Синтаксис функции array_splice( ):

array_splice(array входной_массив, int смещение, [int длина], [array заменяющий_массив]);

Значения параметров задаются по определенным правилам:

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

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

Удаление всех элементов с пятой позиции до конца массива:

$pasta = array_splice($pasta, 5);

Удаление пятого и шестого элементов:

$pasta = array_splice($pasta. 5, 2);

Замена пятого и шестого элементов новыми значениями:

$pasta = array_splice($pasta, 5, 2, array("element1", "element2"));

Удаление всех элементов, начиная с пятого, до третьего элемента с конца массива:

$pasta = array_splice($pasta, 5, -3);

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

shuffle( )

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

void shuffle(array массив);

Итоги

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

  • создание индексируемых и ассоциативных массивов;
  • многомерные массивы;
  • отображение содержимого многомерных массивов;
  • поиск элементов;
  • добавление и удаление элементов;
  • размер массива;
  • сортировка;
  • другие полезные функции для работы с массивами.

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

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


  • Глава 6. Объектно-ориентированные возможности PHP
    • PHP и ООП
    • Классы, объекты и объявления методов
      • Создание объектов и работа с ними
      • Нарушение инкапсуляции
      • Конструкторы
      • Деструкторы
      • Простое и иерархическое наследование
      • Абстрактные классы
      • Перегрузка методов
    • Функции для работы с классами и объектами
    • Итоги

ГЛАВА 6

Объектно-ориентированные возможности РНР

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

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

  • она проста для понимания — ООП позволяет мыслить категориями повседневных объектов;
  • повышенно надежна и проста для сопровождения — правильное проектирование обеспечивает простоту расширения и модификации объектно-ориентированных программ. Модульная структура позволяет вносить независимые изменения в разные части программы, сводя к минимуму риск ошибок программирования;
  • ускоряет цикл разработки — модульность и здесь играет важную роль, поскольку различные компоненты ОО-программ можно легко использовать в других программах, что уменьшает избыточность,кода и снижает риск внесения ошибок при копировании.

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

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

РНР и ООП

Хотя РНР обладает общими объектно-ориентированными возможностями, он не является полноценным ОО-языком (например, таким, как C++ или Java). В частности, в РНР не поддерживаются следующие объектно-ориентированные возможности:

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

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

Классы, объекты и объявления методов

Классы образуют синтаксическую базу объектно-ориентированного программирования. Их можно рассматривать как своего рода «контейнеры» для логически связанных данных и функций (обычно называемых методами — см. ниже). Класс представляет собой шаблон, по которому создаются конкретные экземпляры, используемые в программе. Экземпляры классов называются объектами.

Чтобы лучше понять связь между классами и объектами, можно представить класс как «чертеж» для создания объектов. По чертежу «изготавливаются» разные объекты, обладающие одними и теми же базовыми характеристиками (например, при строительстве дома — одна дверь, два окна и определенная толщина стены). Тем не менее, каждый объект существует независимо от других — изменение его характеристик никак не влияет на характеристики других объектов; например, в уже построенном доме можно прорубить дополнительное окно. Важно помнить, что у объектов все равно остается общая характеристика — количество окон.

Класс также можно рассматривать как тип данных (см. главу 2), а объект — как переменную (по аналогии с тем, как переменная $counter относится к целому, а переменная $last_name — к строковому типу). Программа может одновременно работать с несколькими объектами одного класса как с несколькими переменными целого типа. Общий формат классов РНР приведен в листинге 6.1.

Листинг 6.1. Объявление классов в РНР

class Class_name {

var $attribute_1;

...

var $attribute_N;

function function1() {

...

}

...

function functionN() {

...

} // end Class_name

Подведем итоги: объявление класса должно начинаться с ключевого слова class (подобно тому, как объявление функции начинается с ключевого слова function). Каждому объявлению атрибута, содержащегося в классе, должно предшествовать ключевое слово van. Атрибуты могут относиться к любому типу данных, поддерживаемых в РНР; их можно рассматривать как переменные с небольшими различиями, о которых вы узнаете в этой главе. После объявлений атрибутов следуют объявления методов, очень похожие на типичные объявления функций.

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

Методы часто используются для работы с атрибутами классов. При ссылках на атрибуты внутри методов используется специальная переменная $this. Синтаксис методов продемонстрирован в следующем примере:

<?

class Webpage {

van $bgcolor;

function setBgColor($color) {

$this->bgcolor = $color;

}

function getBgColor() {

return $this->bgcolor;

}

}

?>

Переменная $this ссылается на экземпляр объекта, для которого вызывается метод. Поскольку в любом классе может существовать несколько экземпляров объектов, уточнение $this необходимо для ссылок на атрибуты, принадлежащие текущему объекту. При использовании этого синтаксиса обратите внимание на два обстоятельства:

атрибут, на который вы ссылаетесь в методе, не нужно передавать в виде параметра функции;

знак доллара ($) ставится перед переменной $this, но не перед именем атрибута (как у обычной переменной).

Создание объектов и работа с ними

Объекты создаются оператором new. Например, объект класса Webpage создается следующей командой:

$home_page = new Webpage;

Новый объект с именем $some_page обладает собственным набором атрибутов и методов, перечисленных в классе Webpage. Для изменения значения атрибута $bgcolor, принадлежащего этому конкретному объекту, можно воспользоваться определенным в классе методом setBgColor( ):

$some_page->setBgColor("black");

Следует помнить, что РНР также позволяет явно получить значение атрибута указанием имен объекта и атрибута:

$some_page->bgcolor;

Однако второй способ противоречит принципу инкапсуляции, и при работе с ООП поступать так не следует. Чтобы понять, почему это так, прочитайте следующий раздел.

Нарушение инкапсуляции

Допустим, вы создали класс, один из атрибутов которого представляет собой массив. Но вместо того чтобы работать с массивом через промежуточные методы (например, предназначенные для создания, удаления, модификации элементов и т. д.), вы в случае необходимости напрямую обращаетесь к массиву. В течение месяца вы уверенно программируете большое «объектно-ориентированное» приложение и благосклонно принимаете хвалу коллег-программистов. Будущее сулит много радостей — премии, оплачиваемый отпуск и даже отдельный кабинет.

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

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

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

Конструкторы

Довольно часто при создании объекта требуется задать значения некоторых атрибутов. К счастью, разработчики технологии ООП учли это обстоятельство и реализовали его в концепции конструкторов. Конструктор представляет собой метод, который задает значения некоторых атрибутов (а также может вызывать другие методы). Конструкторы вызываются автоматически при создании новых объектов. Чтобы это стало возможным, имя метода-конструктора должно совпадать с именем класса, в котором он содержится. Пример конструктора приведен в листинге 6.2.

Листинг 6.2. Использование конструктора

<?

class Webpage {

var $bgcolor;

function Webpage($color) {

$this->bgcolor = $color;

}

}

// Вызвать конструктор класса Webpage

$page = new Webpage("brown");

?>

Раньше создание объекта и инициализация атрибутов выполнялись раздельно. Конструкторы позволяют выполнить эти действия за один этап.

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

$page = new Webpage;

Во-вторых, объект можно создать при помощи конструктора, определенного в классе, — в этом случае вы создаете объект класса Webpage и присваиваете значение его атрибуту bgcolor:

$page = new Webpage("brown");

Деструкторы

Как упоминалось ранее, в РНР отсутствует непосредственная поддержка деструкторов. Тем не менее, вы можете легко имитировать работу деструктора, вызывая функцию РНР unset( ). Эта функция уничтожает содержимое переменной и возвращает занимаемые ею ресурсы системе. С объектами unset( ) работает так же, как и с переменными. Допустим, вы работаете с объектом $Webpage. После завершения работы с этим конкретным объектом вызывается функция

unset($Webpage);

Эта команда удаляет из памяти все содержимое $Webpage. Действуя в духе инкапсуляции, можно поместить вызов unset( ) в метод с именем destroy( ) и затем вызвать его:

$Website->destroy( );

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

Простое и иерархическое наследование

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

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

class Class_name2 extends Class_name1 {

объявления атрибутов;

объявления методов;

}

Ключевое слово extends говорит о том, что класс Class_name2 наследует все характеристики класса Class_name1.

Помимо возможности многократного использования кода, наследование обладает еще одним важным преимуществом — снижается вероятность ошибок при

модификации программы. Например, в иерархии, изображенной на рис. 6.1, изменения в классе «автомобиль» никак не отразятся на коде (и данных) класса «самолет», и наоборот.

Вызов конструктора производного класса не приводит к автоматическому вызову конструктора базового класса.

Рис. 6.1. Иерархия транспортных средств

В листинге 6.3 приведены классы, моделирующие иерархию, изображенную на рис. 6.1.

Листинг 6.3. Представление различных типов транспортных средств при помощи наследования

<?

// Транспортное средство

class Vehicle {

var $model;

var $current_speed;

function setSpeed($mph) {

$this->current_speed = $mph;

}

function getSpeed() {

return $this->current_speed;

}

}

// Автомобиль

class Auto extends Vehicle {

var $fue1_type;

function setFuelType($fuel) {

$this->fuel_type = $fuel;

}

function getFuelType() {

return $this->fuel_type;

}

}

// Самолет

class Airplane extends Vehicle {

var $wingspan;

function setWingSpan($wingspan) {

$this->wingspan = $wingspan;

}

function getWingSpan() {

return $this->wingspan;

}

}

?>

Объекты этих классов создаются следующим образом:

$tractor = new Vehicle;

$gulfstream = new Airplane;

Приведенные команды создают два объекта. Первый объект, $tractor, относится к классу Vehicle. Второй объект, $gulfstream, относится к классу Airplane и потому обладает как общими характеристиками класса Vehicle, так и уточненными характеристиками класса Airplаne.

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

class Airplane extends Vehicle extends Building...

Многоуровневое наследование

С увеличением размеров и сложности программ может возникнуть необходимость в многоуровневом наследовании. Иначе говоря, класс будет наследовать свои свойства от других классов, которые, в свою очередь, будут наследовать от третьих классов и т. д. Многоуровневое наследование развивает модульную структуру программы, обеспечивая простоту сопровождения и более четкую логическую структуру. Скажем, при использовании примера с транспортными средствами в большой программе может появиться необходимость в дополнительном разбиении на субклассы суперкласса Vehicle, продолжающем логическое развитие иерархии. Например, транспортные средства можно дополнительно разделить на наземные, морские и воздушные, чтобы суперкласс специализированных субклассов выбирался в зависимости от среды, в которой перемещается данное транспортное средство. Новый вариант иерархии показан на рис. 6.2.

Краткий пример, приведенный в листинге 6.4, подчеркивает некоторые важные аспекты многоуровневого наследования в РНР.

Листинг 6.4. Многоуровневое наследование

<?

class Vehicle {

Объявления атрибутов...

Объявления методов...

}

class Land extends Vehicle {

  Объявления атрибутов...

  Объявления методов...

}

class Саr extends Land {

  Объявления атрибутов...

  Объявления методов...

}

$nissan = new Car;

?>

Объект $nissan содержит все атрибуты и методы классов Саr, Land и Vehicle. Как видите, программа получается исключительно модульной. Допустим, когда-то в будущем вы захотите добавить в класс Land новый атрибут. Нет проблем: внесите соответствующие изменения в класс Land, и этот атрибут немедленно становится доступным для классов Land и Саr, не влияя на функциональность других классов. Таким образом, модульность кода и гибкость относятся к числу основных преимуществ ООП.

Рис. 6.2. Многоуровневое наследование в иерархии Vehicle

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

Абстрактные классы

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

В РНР отсутствует прямая поддержка абстрактных классов, однако существует простое обходное решение — достаточно определить в «абстрактном» классе конструктор и включить в него вызов die( ). Вернемся к классам из листинга 6.4. Скорее всего, вам никогда не придется создавать экземпляры классов Land и Vehicle, поскольку они не могут представлять физические объекты. Для представления реальных объектов (например, автомобилей) следует создать класс, производный от этих классов. Следовательно, чтобы предотвратить возможное создание объектов классов Land и Vehicle, необходимо включить в их конструкторы вызовы die( ), как показано в листинге 6.5.

Листинг 6.5. Создание абстрактных классов

<?

class Vehicle {

Объявления атрибутов...

function Vehicle() }

die ("Cannot create Abstract Vehicle class!");

}

Объявления других методов...

}

class Land extends Vehicle {

Объявления атрибутов...

function Land() }

die ("Cannot create Abstract Land class!");

}

Объявления других методов. } class Car extends Land {

Объявления атрибутов...

Объявления методов...

}

?>

Попытка создания экземпляра этих абстрактных классов приведет к выдаче сообщения об ошибке и завершению программы.

Перегрузка методов

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

Листинг 6.6. Перегрузка методов

<?

class Page {

var $bgcolor;

var $textcolor;

 function Page() {

// Определить количество переданных аргументов

// и вызвать метод с нужным именем

$name = "Page".func_num_args();

// Call $name with correct number of arguments passed in

if ( func_num_args() == 0 ) :

$this->$name();

else :

$this->$name(func_get_arg(0));

endif;

}

function Page0() {

$this->bgcolor = "white";

$this->textcolor = "black";

print "Created default page";

}

function Page1($bgcolor) {

$this->bgcolor = $bgcolor;

$this->textcolor = "black";

print "Created custom page";

}

}

$html_page - new Page("red");

?>

В этом примере при создании нового объекта с именем $html_page передается один аргумент. Поскольку в классе был определен конструктор по умолчанию (Раgе( )), вызывается именно он. Однако конструктор по умолчанию всего лишь выбирает, какому из конструкторов (Page0( ) или Page1( )) следует передать управление. При выборе конструктора используются функции func_num_args( ) и func_get_arg( ), которые, соответственно, определяют количество аргументов и читают эти аргументы.

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

Функции для работы с классами и объектами

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

get_class_methods( )

Функция get_class_methods( ) возвращает массив имен методов класса с заданным именем. Синтаксис функции get_class_methods( ):

array get_class_methods (string имя_класса)

Простой пример использования get_class_methods( ) приведен в листинге 6.7.

Листинг 6.7. Получение списка методов класса

<?

...

class Airplane extends Vehicle {

var $wingspan;

function setWingSpan($wingspan) {

$this->wingspan = $wingspan;

}

function getWingSpan() {

return $this->wingspan;

}

}

$cls_methods = get_class_methods(Airplane);

// Массив $cls_methods содержит имена всех методов,

// объявленных в классах "Airplane" и "Vehicle"

?>

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

get_class_vars( )

Функция get_class_vars( ) возвращает массив имен атрибутов класса с заданным именем. Синтаксис функции get_class_vars( ):

array get_class_vars (string имя_класса)

Пример использования get_class_vars( ) приведен в листинге 6.8.

Листинг 6.8. Получение списка атрибутов класса функцией get_class_vars( )

<?

class Vehicle {

var $model;

var $current_speed; }

class Airplane extends Vehicle {

var Swingspan; } $a_class = "Airplane";

$attribs = get_class_vars($a_class);

// $attribs = array ( "wingspan", "model", "current_speed")

?>

Массив $attribs заполняется именами всех атрибутов класса Airplane.

get_object_vars( )

Функция get_object_vars( ) возвращает ассоциативный массив с информацией обо всех атрибутах объекта с заданным именем. Синтаксис функции get_object_vars( ):

array get_object_vars (object имя_обьекта)

Пример использования функции get_object_vars( ) приведен в листинге 6.9.

Листинг 6.9. Получение информации о переменных объекта

<?

class Vehicle {

var Swheels;

}

class Land extends Vehicle {

var Sengine;

}

class car extends Land {

var $doors:

function car($doors, $eng, $wheels) {

$this->doors = $doors;

$this->engine = $eng;

$this->wheels = $wheels;

}

function get_wheels() {

return $this->wheels;

}

}

$toyota = new car(2,400,4);

$vars = get_object_vars($toyota);

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

print "$key ==> $value <br>";

endwhile;

// Выходные данные:

// doors ==> 2

// engine ==> 400

// wheels ==> 2

?>

Функция get_object_vars( ) позволяет быстро получить всю информацию об атрибутах конкретного объекта и их значениях в виде ассоциативного массива.

method_exists( )

Функция method_exists( ) проверяет, поддерживается ли объектом метод с заданным именем. Если метод поддерживается, функция возвращает TRUE, в противном случае возвращается FALSE. Синтаксис функции method_exists( ):

bool method_exi sts (object имя_обьекта. string имя_метода)

Пример использования метода method_exists( ) приведён в листинге 6.10.

Листинг 6.10. Проверка поддержки метода объектом при помощи функции method_exists()

<?

class Vehicle {

...

}

class Land extends Vehicle {

var $fourWheel;

function setFourWheel Drive() {

$this->fourWeel = 1;

}

}

// Создать объект с именем $саr

$car = new Land;

// Если метод "fourWheelDrive" поддерживается классом "Land"

// или "Vehicle", вызов method_exists возвращает TRUE;

// в противном случае возвращается FALSE.

// В данном примере method_exists() возвращает TRUE.

if (method_exists($car, "setfourWheelDrive")) :

print "This car is equipped with 4-wheel drive";

else :

print "This car is not equipped with 4-wheel drive";

endif;

?>

В листинге 6.10 функция method_exists ( ) проверяет, поддерживается ли объектом $car метод с именем setFourWheelDrive( ). Если метод поддерживается, функция возвращает логическую истину и фрагмент выводит соответствующее сообщение. В противном случае возвращается FALSE и выводится другое сообщение.

get_class( )

Функция get_class( ) возвращает имя класса, к которому относится объект с заданным именем. Синтаксис функции get_class( ):

string get_class(object имя_объекта);

Пример использования get_class( ) приведен в листинге 6.11.

Листинг 6.11. Получение имени класса функцией get_class( )

<?

class Vehicle {

...

class Land extends Vehicle {

...

}

// Создать объект с именем $саr $car = new Land;

// Переменной $class_a присваивается строка "Land"

$class_a = get_class($car);

?>

В результате переменной $class_a присваивается имя класса, на основе которого был создан объект $саr.

get_parent_class( )

Функция get_parent_class( ) возвращает имя родительского класса (если он есть) для объекта с заданным именем. Синтаксис функции get_parent_dass( ):

string get_parent_class (object имя_обьекта);

Листинг 6.12 демонстрирует использование get_parent_class( ).

Листинг 6.12. Получение имени родительского класса функцией get_parent_class( )

<?

class Vehicle {

...

}

class Land extends Vehicle {

...

}

// Создать объект с именем $саr $саr = new Land;

// Переменной $parent присваивается строка "Vehicle"

$parent = get_parent_dass($car);

?>

Как и следовало ожидать, при вызове get_parent_class( ) переменной $parent будет присвоена строка "Vehicle".

is_subclass_of( )

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

bool is_subclass_of (object объект, string имя_класса)

Использование is_subclass_of( ) продемонстрировано в листинге 6.13.

Листинг 6.13. Использование функции is_subdass_of( )

<?

class Vehicle {

...

}

class Land extends Vehicle {

...

}

$auto = new Land;

// Переменной $is_subclass присваивается TRUE

$is_subclass = is_subclass_of($auto, "Vehicle");

?>

В листинге 6.13 переменной $is_subclass( ) присваивается признак того, принадлежит ли объект $auto к субклассу родительского класса Vehicle. В приведенном фрагменте $auto относится к классу Vehicle; следовательно, переменной $is_subclass( ) будет присвоено значение TRUE.

get_declared_classes( )

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

array get_declared_classes( )

Листинг 6.14. Получение списка классов функцией get_declared_classes( )

<?

class Vehicle {

...

}

class Land extends Vehicle {

...

}

$declared_classes = get_declared_classes();

// $declared_classes = array("Vehicle", "Land")

?>

Итоги

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

  • общие принципы объектно-ориентированного программирования;
  • классы, объекты и методы;
  • простое и иерархическое наследование;
  • абстрактные классы;
  • перегрузка методов;
  • функции для работы с классами и объектами в РНР.

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


  • Глава 7. Файловый ввод/вывод и файловая система
    • Проверка существования и размера файла
    • Открытие и закрытие файлов
    • Запись в файл
    • Чтение из файла
    • Чтение файла в массив
    • Перенаправление файла в стандартный выходной поток
    • Открытие файлового манипулятора процесса
    • Открытие соединения через сокет
    • Запуск внешних программ
      • Обратные апострофы
    • Работа с файловой системой
    • Отображение и изменение характеристик файлов
    • Копирование и переименование файлов
    • Удаление файлов
    • Работа с каталогами
    • Проект 1: простой счетчик обращений
    • Проект 2: построение карты сайта
    • Итоги

ГЛАВА 7

Файловый ввод/вывод и файловая система

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

Проверка существования и размера файла

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

file_exists( ) и is_file( ).

file_exists( )

Функция f ilе_ехists ( ) проверяет, существует ли заданный файл. Если файл существует, функция возвращает TRUE, в противном случае возвращается FALSE. Синтаксис функции file_exists( ):

bool file_exists(string файл)

Пример проверки существования файла:

if (! file_exists ($filename)) :

print "File $filename does not exist!";

endif:

is_file( )

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

bool is_file(string файл)

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

$file = "somefile.txt";

if (is_file($file)) :

print "The file $file is valid and exists!";

else :

print "The file $file does not exist or it is not a valid file!";

endif:

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

filesize( )

Функция filesize( ) возвращает размер (в байтах) файла с заданным именем или FALSE в случае ошибки. Синтаксис функции filesize( ):

int filesize(string имя_файла)

Предположим, вы хотите определить размер файла pastry.txt. Для получения нужной информации можно воспользоваться функцией filesize( ):

$fs = filesize("pastry.txt"); print "Pastry.txt is $fs bytes.";

Выводится следующий результат:

Pastry.txt is 179 bytes.

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

Открытие и закрытие файлов

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

fopen( )

Функция fopen( ) открывает файл (если он существует) и возвращает целое число — так называемый файловый манипулятор (file handle). Синтаксис функции fopen( ):

int fopen (string файл, string режим [, int включение_пути])

Открываемый файл может находиться в локальной файловой системе, существовать в виде стандартного потока ввода/вывода или представлять файл в удаленной системе, принимаемой средствами HTTP или FTP.

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

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

Если параметр задан в виде php://stdin, php://stdout или php://stderr, открывается соответствующий стандартный поток ввода/вывода.

Если параметр начинается с префикса http://, функция открывает подключение HTTP к серверу и возвращает манипулятор для указанного файла.

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

При работе в пассивном режиме сервер ЯР ожидает подключения со стороны клиентов. При работе в активном режиме сервер сам устанавливает соединение с клиентом. По умолчанию обычно используется активный режим.

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

Таблица 7.1. Режимы открытия файла

Режим | Описание
r | Только чтение. Указатель текущей позиции устанавливается в начало файла
r+ | Чтение и запись. Указатель текущей позиции устанавливается в начало файла
w | Только запись. Указатель текущей позиции устанавливается в начало файла, а все содержимое файла уничтожается. Если файл не существует, функция пытается создать его
w+ | Чтение и запись. Указатель текущей позиции устанавливается в начало файла, а все содержимое файла уничтожается. Если файл не существует, функция пытается создать его
a | Только запись. Указатель текущей позиции устанавливается в конец файла. Если файл не существует, функция пытается создать его
a+ | Чтение и запись. Указатель текущей позиции устанавливается в конец файла. Если файл не существует, функция пытается создать его

Если необязательный третий параметр включение_пути равен 1, то путь к файлу определяется по отношению к каталогу включаемых файлов, указанному в файле php.ini (см. главу 1).

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

$file = "userdata.txt"; // Некоторый файл

$fh = fopen($file, "a+") or die("File ($file) does not exist!");

Следующий фрагмент открывает подключение к сайту РНР (http://www.php.net):

$site = "http://www.php.net": // Сервер, доступный через HTTP

$sh = fopen($site., "r");     //Связать манипулятор с индексной страницей Php.net

После завершения работы файл всегда следует закрывать функцией fclose( ).

fclose ( )

Функция fclose( ) закрывает файл с заданным манипулятором. При успешном закрытии возвращается TRUE, при неудаче — FALSE. Синтаксис функции fclose( ):

int fclose(int манипулятор)

Функция fclose( ) успешно закрывает только те файлы, которые были ранее открыты функциями fopen( ) или fsockopen( ). Пример закрытия файла:

$file = "userdata.txt";

if (file_exists($file)) :

$fh = fopen($file, "r");

// Выполнить операции с файлом

fclose($fh);

else :

print "File Sfile does not exist!";

endif;

Запись в файл

С открытыми файлами выполняются две основные операции — чтение и запись.

is_writeable( )

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

bool is_writeable (string файл)

Одно важное обстоятельство: скорее всего, РНР будет работать под идентификатором пользователя, используемым web-сервером (как правило, «nobody»). Пример использования is_writeable( ) приведен в описании функции fwrite( ).

fwrite ( )

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

int fwrite(int манипулятор, string переменная [, int длина])

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

<?

// Информация о трафике на пользовательском сайте

$data = "08:13:00|12:37:12|208.247.106.187|Win98";

$filename = "somefile.txt";

// Если файл существует и в него возможна запись

if ( is_writeable($filename) ) :

// Открыть файл и установить указатель текущей позиции в конец файла

$fh = fopen($filename, "a+");

// Записать содержимое $data в файл

$success - fwrite($fh, $data);

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

fclose($fh); else :

print "Could not open Sfilename for writing";

endif;

?>

Функция fputs( ) является псевдонимом fwrite( ) и может использоваться всюду, где используется fwrite( ).

fputs( )

Функция fputs( ) является псевдонимом fwrite( ) и имеет точно такой же синтаксис. Синтаксис функции fputs( ):

int fputs(int манипулятор, string переменная [, int длина])

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

Чтение из файла

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

is_readable( )

Функция i s_readable( ) позволяет убедиться в том, что файл существует и для него разрешена операция чтения. Возможность чтения проверяется как для файла, так и для каталога. Синтаксис функции is_readable( ):

boo! is_readable (string файл]

Скорее всего, РНР будет работать под идентификатором пользователя, используемым web-сервером (как правило, «nobody»), поэтому для того чтобы функция is_readable( ) возвращала TRUE, чтение из файла должно быть разрешено всем желающим. Следующий пример показывает, как убедиться в том, что файл существует и доступен для чтения:

if ( is_readable($filename) ) :

// Открыть файл и установить указатель текущей позиции в конец файла

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

else :

print "$filename is not readable!";

endif;

fread( )

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

int fread(int манипулятор, int длина)

Манипулятор должен ссылаться на открытый файл, доступный для чтения (см. описание функции is_readable( )). Чтение прекращается после прочтения заданного количества байт или при достижении конца файла. Рассмотрим текстовый файл pastry.txt, приведенный в листинге 7.1. Чтение и вывод этого файла в браузере осуществляется следующим фрагментом:

$fh = fopen('pastry.txt', "r") or die("Can't open file!");

$file = fread($fh, filesize($fh));

print $file;

fclose($fh);

Используя функцию fllesize( ) для определения размера pastry.txt в байтах, вы гарантируете, что функция fread( ) прочитает все содержимое файла.

Листинг 7.1. Текстовый файл pastry.txt

Recipe: Pastry Dough

1 1/4 cups all-purpose flour

3/4 stick (6 tablespoons) unsalted butter, chopped

2 tablespoons vegetable shortening 1/4 teaspoon salt

3 tablespoons water

fgetc( )

Функция fgetc( ) возвращает строку, содержащую один символ из файла в текущей позиции указателя, или FALSE при достижении конца файла. Синтаксис функции fgetc( ):

string fgetc (int манипулятор)

Манипулятор должен ссылаться на открытый файл, доступный для чтения (см. описание функции is_readable( ) ранее в этой главе). В следующем примере продемонстрированы посимвольное чтение и вывод файла с использованием функции fgetc( ):

$fh = fopen("pastry.txt", "r"); while (! feof($fh)) :

$char = fgetc($fh):

print $char; endwhile;

fclose($fh);

fgets( )

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

string fgets (int манипулятор, int длина)

Чтение прекращается при выполнении одного из следующих условий:

  • из файла прочитано длина — 1 байт;
  • из файла прочитан символ новой строки (включается в возвращаемую строку);
  • из файла прочитан признак конца файла (EOF).

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

$fh = fopen("pastry.txt", "r");

while (! feof($fh));

$line = fgets($fh, 4096);

print $line. "<br>";

endwhile;

fclose($fh):

fgetss( )

Функция fgetss( ) полностью аналогична fgets( ) за одним исключением — она пытается удалять из прочитанного текста все теги HTML и РНР:

string fgetss (Int манипулятор, int длина [, string разрешенные_теги])

Прежде чем переходить к примерам, ознакомьтесь с содержимым листинга 7.2 — этот файл используется в листингах 7.3 и 7.4.

Листинг 7.2. Файл science.html

<html>

<head>

<title>Breaking News - Science</title>

<body>

<h1>Alien lifeform discovered</h1><br>

<b>August 20. 2000</b><br>

Early this morning, a strange new form of fungus was found growing in the closet of W. J. Gilmore's old apartment refrigerator. It is not known if powerful radiation emanating from the tenant's computer monitor aided in this evolution.

</body>

</html>

Листинг 7.З. Удаление тегов из файла HTML перед отображением в браузере

<?

$fh = fopen("science.html", "r");

while (! feof($fh)) :

print fgetss($fh, 2048);

endwhile;

fclose($fh);

?>

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

Breaking News - Science Alien lifeform discovered August 20. 2000 Early this morning, a strange new form of fungus was found growing in the closet of W. J. Gilmore's old apartment refrigerator. It is not known if powerful radiation emanating from the tenant's computer monitor aided in this evolution.

В некоторых ситуациях из файла удаляются все теги, кроме некоторых — например, тегов разрыва строк <br>. Листинг 7.4 показывает, как это делается.

Листинг 7.4. Выборочное удаление тегов из файла HTML

<?

$fh = fopenC'science.html", "r");

$allowable = "<br>";

while (! feof($fh)) :

print fgetss($fh. 2048, $allowable);

endwhile;

fclose($fh);

?>

Результат:

Breaking News - Science Alien lifeform discovered August 20. 2000 Early this morning, a strange new form of fungus was found growing in the closet of W. J. Gilmore's old apartment refrigerator. It is not known if powerful radiation emanating from the tenant's computer monitor aided in this evolution.

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

Чтение файла в массив

Функция file( ) загружает все содержимое файла в индексируемый массив. Каждый элемент массива соответствует одной строке файла. Синтаксис функции filе ( ):

array file (string файл [, int включение_пути])

Если необязательный третий параметр включение_пути равен 1, то путь к файлу определяется по отношению к каталогу включаемых файлов, указанному в файле php.ini (см. главу 1). В листинге 7.5 функция file( ) используется для загрузки файла pastry.txt (см. листинг 7.1).

Листинг 7.5. Загрузка файла pastry.txt функцией file( )

<?

$file_array = file( "pastry.txt" );

while ( list( $line_num. $line ) = eacht($file_array ) ):

print "<b>Line $line_num:</b> ", htmlspecialchars($line ), "<br>\n"

endwhile;

?>

Каждая строка массива выводится вместе с номером:

Line 0: Recipe: Pastry Dough

Line 1: 1 1/4 cups all-purpose flour

Line 2: 3/4 stick (6 tablespoons) unsalted butter, chopped

Line 3: 2 tablespoons vegetable shortening

Line 4: 1/4 teaspoon salt

Line 5: 3 tablespoons water

Перенаправление файла в стандартный выходной поток

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

int readfile (string файл [, int включение_пути])

Функция возвращает количество прочитанных байтов. Файл может находиться в локальной файловой системе, существовать в виде стандартного потока ввода/вывода или представлять файл в удаленной системе, принимаемой средствами HTTP или FTP. Параметр файл задается по тем же правилам, что и в функции fopen( ).

Предположим, у вас имеется файл latorre.txt, содержимое которого вы хотите вывести в браузере:

Restaurant "La Тоrrе." located in Nettuno, Italy, offers an eclectic blend of style. history, and fine seafood cuisine. Within the walls of the medieval borgo surrounding the city, one can dine while watching the passersby shop in the village boutiques. Comfort coupled with only the freshest seafare make La Torre one of Italy's finest restaurants.

При выполнении следующего фрагмента все содержимое latorre.txt направляется в стандартный выходной поток:

<?

$restaurant_file = "latorre.txt";

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

readfile($restaurant_filе);

?>

Открытие файлового манипулятора процесса

popen( )

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

int popen (string команда, string режим)

Параметр команда определяет выполняемую системную команду, а параметр режим описывает режим доступа:

<?

// Открыть файл "spices.txt" для записи

$fh = fopen("spices.txt","w");

// Добавить несколько строк текста

fputs($fh, "Parsley, sage, rosemary\n");

fputs($fh, "Paprika, salt, pepper\n");

fputs($fh, "Basil, sage, ginger\n");

// Закрыть манипулятор

fclose($fh);

// Открыть процесс UNIX grep для поиска слова Basil в файле spices.txt

$fh - popen("grep Basil < spices.txt", "r");

// Вывести результат работы grep

fpassthru($fh);

?>

Результат выглядит так:

Basil, sage, ginger

Функция fpassthru( ) является аналогом функции passthru( ), рассматриваемой в разделе «Запуск внешних программ» этой главы.

pclose( )

После выполнения всех операций файл или процесс необходимо закрыть. Функция pclose( ) закрывает соединение с процессом, заданным манипулятором, по аналогии с тем, как функция fclose( ) закрывает файл, открытый функцией fopen( ). Синтаксис функции pclose( ):

int pclose (int манипулятор}

В параметре манипулятор передается манипулятор, полученный ранее при успешном вызове рореn( ).

Открытие соединения через сокет

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

fsockopen( )

Функция fsockopen( ) устанавливает сокетное соединение с сервером в Интернете

через протокол TCP или UDP. Синтаксис функции fsockopen( ):

int fsockopen (string узел, int порт [, int код_ошибки [, string текст_ошибки [, int тайм-аут]]])

Необязательные параметры код_ошибки и текст_ошибки содержат информацию, которая будет выводиться в случае неудачи при подключении к серверу. Оба параметра должны передаваться по ссылке. Третий необязательный параметр, тайм-аут, задает продолжительность ожидания ответа от сервера (в секундах). В листинге 7.6 продемонстрировано применение функции fsockopen( ) для получения информации о сервере. Однако перед рассмотрением листинга 7.6 необходимо познакомиться еще с одной функцией — socket_set_blocking( ).

UDP (User Datagram Protocol) — коммуникационный протокол, не ориентированный на соединение.

socket_set_blocking( )

Функция socket_set_b1ocki ng( ) позволяет установить контроль над тайм-аутом для операций с сервером:

socket_set_blocking(int манипулятор, boolean режим)

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

Листинг 7.6. Использование функции fsockopen() для получения информации о сервере

<?

function getthehost($host.$path) {

// Открыть подключение к узлу

$fp - fsockopen($host, 80, &$errno, &$errstr, 30);

// Перейти в блокирующий режим

socket_set_blocking($fp, 1),

// Отправить заголовки

fputs($fp,"GET $path HTTP/1.1\r\n");

fputs ($fp, "Host: $host\r\n\r\n"); $x = 1;

// Получить заголовки

while($x < 10) :

$headers = fgets ($fp, 4096);

print $headers;

$x++;

endwhile;

// Закрыть манипулятор

fclose($fp);

}

getthehost("www. apress.com", "/");

?>

В результате выполнения листинга 7.6 выводится следующий результат:

НТТР/1.1 200 OK Server: Microsoft-IIS/4.0 Content-location:

http://www.apress.com/0efault.htm Date: Sat. 19 Aug 2000 23:03:25 GMT

Content-Type: text/html Accept-Ranges: bytes Last-Modified: Wed. 19 Jul

2000 20:25:06 GMT ETag: "f0a61666dbff1bf1:34a5" Content-Length: 1311

pfsockopen( )

Функция pfsockopen( ) представляет собой устойчивую (persistent) версию fsockopen( ). Это означает, что соединение не будет автоматически разорвано по завершении сценария, в котором была вызвана функция. Синтаксис функции pfsockopen( ):

int pfsockopen (string узел, int порт [, int код_ошибки [, string текст _ошибки [, int тайм-аут]]])

В зависимости от конкретных целей вашего приложения может оказаться удобнее использовать pfsockopen( ) вместо fsockopen( ).

Запуск внешних программ

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

ехес( )

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

string exec (string команда [, string массив [, int возврат]])

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

Листинг 7.7 показывает, как использовать функцию ехес( ) для выполнения системной функции UNIX ping.

Листинг 7.7. Проверка связи с сервером с применением функции ехес( )

<?

exec("ping -с 5 www.php.net", $ping);

// В Windows - exec("ping -n 5 www.php.net. $ping);

for ($i=0; $i< count($ping);$i++) :

print "<br>$ping[$i]";

endfor;

?>

Результат:

PING www.php.net (208.247.106.187): 56 data bytes

64 bytes from 208.247.106.187: icmp_seq=0 ttl=243 time=66.602 ms

64 bytes from 208.247.106.187: icmp_seq=1 ttl=243 time=55.723 ms

64 bytes from 208.247.106.187: icmp_seq=2 ttl=243 time=70.779 ms

64 bytes from 208.247.106.187: icmp_seq=3 ttl=243 time=55.339 ms

64 bytes from 208.247.106.187: icmp_seq=4 ttl=243 time=69.865 ms

-- www.php.net ping statistics --

5 packets transmitted. 5 packets received. 0% packet loss

round-trip min/avg/max/stddev - 55.339/63.662/70.779/6.783 ms

Обратные апострофы

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

$output = `ls`;

print "<pre>$output</pre>";

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

Внутренний параметр ping -с 5 (-п 5 в системе Windows) задает количество опросов сервера.

Если вы хотите просто вернуть неформатированные результаты выполнения команды, воспользуйтесь функцией passthru( ), описанной ниже.

passthru( )

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

void passthru(string команда [, int возврат])

Если при вызове passthru( ) передается необязательный параметр возврат, этой переменной присваивается код возврата выполненной команды.

escapeshellcmd( )

Функция escapeshellcmd( ) экранирует все потенциально опасные символы, которые могут быть введены пользователем (например, на форме HTML), для выполнения команд exec( ), passthru( ), system( ) или рореn( ). Синтаксис:

string escapeshellcmd (string команда)

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

$user_input = `rm -rf *`; // Удалить родительский каталог и все его подкаталоги

ехес($user_input); // Выполнить $user_input !!!

Если не предпринять никаких мер предосторожности, такая команда приведет к катастрофе. Впрочем, можно воспользоваться функций escapeshellcmd( ) для экранирования пользовательского ввода:

$user_input = `rm - rf *`; // Удалить родительский каталог и все его подкаталоги

ехес( escapeshellcmd($user_input)); // Экранировать опасные символы

Функция escapeshellcmd( ) экранирует символ *, предотвращая катастрофические последствия выполнения команды.

Безопасность является одним из важнейших аспектов программирования в среде Web, поэтому я посвятил целую главу этой теме и ее отношению к программированию РНР. За дополнительной информацией обращайтесь к главе 16.

Работа с файловой системой

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

basename( )

Функция basename( ) выделяет имя файла из переданного полного имени. Синтаксис функции basename( ):

string basename(string полное_имя)

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

$path = "/usr/local/phppower/htdocs/index.php"; $file = basename($path); // $file = "index.php"

Фактически эта функция удаляет из полного имени путь и оставляет только имя файла.

getlastmod( )

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

int getlastmod(void)

Возвращаемое значение соответствует формату даты/времени UNIX, и для его форматирования можно воспользоваться функцией date( ). Следующий фрагмент выводит дату последней модификации страницы:

echo "Last modified: ".date( "H:i:s a". getlastmod( ) );

stat( )

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

array stat(string имя_файла)

В элементах массива возвращается следующая информация:

0 Устройство

1 Индексный узел (inode)

2 Режим защиты индексного узла

3 Количество ссылок

4 Идентификатор пользователя владельца

5 Идентификатор группы владельца

6 Тип устройства индексного узла

7 Размер в байтах

8 Время последнего обращения

9 Время последней модификации

10 Время последнего изменения

11 Размер блока при вводе/выводе в файловой системе

12 Количество выделенных блоков

Таким образом, если вы хотите узнать время последнего обращения к файлу, обратитесь к элементу 8 возвращаемого массива. Рассмотрим пример:

$file - "datafile.txt";

list($dev, $inode, $inodep, $nlink, $uid, $gid, $inodev, $size, $atime, $mtime, $ctime,

$bsize) = stat($file);

print "$file is $size bytes. <br>";

print "Last access time: $atime <br>";

print "Last modification time: $mtime <br>";

Результат:

popen.php is 289 bytes.

Last access time: August 15 2000 12:00:00

Last modification time: August 15 2000 10:07:18

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

Отображение и изменение характеристик файлов

У каждого файла в системах семейства UNIX есть три важные характеристики:

  • принадлежность группе;
  • владелец;
  • разрешения (permissions).

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

Если у вас нет опыта работы в операционных системах UNIX, информацию о характеристиках файловой системы UNIX можно получить по адресу http://sunsite.auc.dk/linux-newbie/FAQ2.htm. Темы принадлежности группе, владения и разрешений рассматриваются в разделе 3.2.6.

chgrp( )

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

int chgrp (string имя_файла, mixed группа)

filegroup( )

Функция filegroup( ) возвращает идентификатор группы владельца файла с заданным именем или FALSE в случае ошибки. Синтаксис функции filegroup( ):

int filegroup (string имя_файла)

chmod( )

Функция chmod( ) изменяет разрешения файла с заданным именем. Синтаксис функции chmod( ):

int chmod (string имя_файла, int разрешения)

Разрешения задаются в восьмеричной системе. Специфика задания параметра функции chmod ( ) продемонстрирована в следующем примере:

chmod("data_file.txt", g+r); // He работает

chmod("data_file.txt", 766); // Не работает

chmod("data_file.txt", 0766); // Работает

fileperms( )

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

int fileperms (string имя_файла)

chown( )

Функция chown( ) пытается сменить владельца файла. Право изменения владельца файла предоставляется только привилегированному пользователю. Синтаксис функции chown( ):

int chown (string имя_файла, mixed пользователь)

fileowner( )

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

int fileowner (string имя_файла)

Копирование и переименование файлов

К числу других полезных системных функций, которые могут выполняться в сценариях РНР, относятся копирование и переименование файлов на сервере. Эти операции выполняются двумя функциями: сору( ) и rename( ).

сору( )

Скопировать файл в сценарии РНР ничуть не сложнее, чем при помощи команды UNIX ср. Задача решается функцией РНР сору( ). Синтаксис функции сору( ):

int copy (string источник, string приемник)

Функция сору( ) пытается скопировать файл источник в файл приемник; в случае успеха возвращается TRUE, а при неудаче — FALSE. Если файл приемник не существует, функция сору( ) создает его. Следующий пример показывает, как создать резервную копию файла при помощи функции сору( ):

$data_file = "datal.txt";

copy($data_file. $data_file'.bak') or die("Could not copy $data_file");

rename ( )

Функция rename( ) переименовывает файл. В случае успеха возвращается TRUE, a при неудаче — FALSE. Синтаксис функции rename( ):

bool rename (string старое_имя, string новое_имя)

Пример переименования файла функцией rename( ):

$data_file = "datal.txt";

rename($data file, $datafile'.old') or die ("Could not rename $data file");

Удаление файлов

unlink( )

Функция unlink( ) удаляет файл с заданным именем. Синтаксис:

int unlink (string файл)

Если вы работаете с РНР в системе Windows, при использовании этой функции иногда возникают проблемы. В этом случае можно воспользоваться описанной выше функцией system( ) и удалить файл командой DOS del:

system ("del filename.txt");

Работа с каталогами

Функции РНР позволяют просматривать содержимое каталогов и перемещаться по ним. В листинге 7.8 изображена типичная структура каталогов в системе UNIX.

Листинг 7.8. Типичная структура каталогов

drwxr-xr-x 4 root wheel 512 Aug 13 13:51 book/

drwxr-xr-x 4 root wheel 512 Aug 13 13:51 code/

-rw-r--r-- 1 root wheel 115 Aug 4 09:53 index.html

drwxr-xr-x 7 root wheel 1024 Jun 29 13:03 manual/

-rw-r--r-- 1 root wheel 19 Aug 12 12:15 test.php

dirname( )

Функция dirname( ) дополняет basename( ) — она извлекает путь из полного имени файла. Синтаксис функции dirname( ):

string dirname (string путь)

Пример использования dirname( ) для извлечения пути из полного имени:

$path = "/usr/locla/phppower/htdocs/index.php";

$file = dirname($path); // $file = "usr/local/phppower/htdocs"

Функция dirname( ) иногда используется в сочетании с переменной $SCRIPT_FILENAME для получения полного пути к сценарию, из которого выполняется команда:

$dir - dirname($SCRIPT_FILENAME);

is_dir( )

Функция is_dir( ) проверяет, является ли файл с заданным именем каталогом:

bool is_dir (string имя_файла)

В следующем примере используется структура каталогов из листинга 7.8:

$isdir = is_dir("index.html"); // Возвращает FALSE

$isdir = is_dir("book");  // Возвращает TRUE

mkdir()

Функция mkdir( ) делает то же, что и одноименная команда UNIX, — она создает новый каталог. Синтаксис функции mkdir( ):

int mkdir (string путь, int режим)

Параметр путь определяет путь для создания нового каталога. Не забудьте завершить параметр именем нового каталога! Параметр режим определяет разрешения, назначаемые созданному каталогу.

opendir( )

Подобно тому как функция fopen( ) открывает манипулятор для работы с заданным файлом, функция opendir( ) открывает манипулятор для работы с каталогом. Синтаксис функции opendir( ):

int opendir (string путь)

closedir( )

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

void closedir(int манипулятор_каталога)

readdir( )

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

string readdir(int манипулятор_каталога)

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

$dh = opendir(' . );

while ($file = readdir($dh)) :

print "$file <br>"; endwhile;

closedir($dh);

chdir( )

Функция chdir( ) работает так же, как команда UNIX cd, — она осуществляет переход в каталог, заданный параметром. Синтаксис функции chdir( ):

int chdir (string каталог)

В следующем примере мы переходим в подкаталог book/ и выводим его содержимое:

$newdir = "book";

chdir($newdir) or die("Could not change to directory ($newdir)"); $dh = opendir(' . ');

print "Files:";

while ($file = readdir($dh)) ;

print "$file <br>";

endwhile;

closedir($dh);

rewinddir( )

Функция rewlnddir( ) переводит указатель текущей позиции в начало каталога, открытого функцией opendir( ). Синтаксис функции rewinddir( ):

void rewinddir (int нанипулятор_каталога)

Проект 1: простой счетчик обращений

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

  1. Присвоить переменной $access имя файла, в котором будет храниться значение счетчика.
  2. Использовать функцию filе( ) для чтения содержимого $access в массив $visits. Префикс @ перед именем функции подавляет возможные ошибки (например, отсутствие файла с заданным именем).
  3. Присвоить переменной $current_visitors значение первого (и единственного) элемента массива $visits.
  4. Увеличить значение $current_visitors на 1.
  5. Открыть файл $access для записи и установить указатель текущей позиции в начало файла.
  6. Записать значение $current_visitors в файл $access.
  7. Закрыть манипулятор, ссылающийся на файл $access.

Листинг 7.9. Простой счетчик обращений

<?

// Сценарий: простой счетчик обращений

// Назначение: сохранение количества обращений в файле

$access = "hits.txt"; // Имя файла выбирается произвольно

$visits = @file($access); // Прочитать содержимое файла в масссив

$current_visitors = $visits[0]; // Извлечь первый (и единственный) элемент

++$current_visitors; // Увеличить счетчик обращений

$fh = fopen($access. "w"); // Открыть файл hits.txt и установить

// указатель текущей позиции в начало файла

@fwrite($fh, $current_visitors);// Записать новое значение счетчика

// в файл "hits.txt"

fclose($fh);  // Закрыть манипулятор файла "hits.txt"

?>

Проект 2: построение карты сайта

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

  1. Объявить служебные переменные для хранения родительского каталога, имени графического файла с изображением папки, названия страницы и флага серверной ОС (Windows или другая система).
  2. Объявить функцию display_directory( ), которая читает содержимое каталога и форматирует его для вывода в браузере.
  3. Построить путь к каталогу объединением имени, передаваемого в переменной $dir1, с $dir.
  4. Открыть каталог и прочитать его содержимое. Отформатировать имена каталога и файлов и вывести их в браузере.
  5. Если текущий файл является каталогом, рекурсивно вызвать функцию display_di rectory( ) и передать ей имя нового каталога для вывода. Вычислить отступ, используемый при форматировании вывода.

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

Листинг 7.10. Программа sitemap.php

// Файл: sitemap.php

// Назначение: построение карты сайта

// Каталог, с которого начинается построение карты

$beg_path = "C:\Program FilesVApache Group\Apache\htdocs\phprecipes";

// Файл с графическим изображением папки.

// Путь должен задаваться Относительно* корневого каталога сервера Apache

$folder_location = "C:\My Documents\PHP for Programmers\FINAL CHPS\graphics\folder.gif";

// Текст в заголовке окна $page_name = "PHPRecipes SiteMap";

// В какой системе будет использоваться сценарий - Linux или Windows?

// (0 - Windows; 1 - Linux)

$usingjinux = 0;

// Функция: display_directory

// Назначение: чтение содержимого каталога, определяемого параметром

// $dir1, с последующим форматированием иерархии каталогов и файлов.

// Функция может вызываться рекурсивно.

function display_directory ($dir1, $folder_location, $using_linux, $init_depth) {

// Обновить путь

$dir.= $dir1;

Sdh = opendir($dir);

while($file = readdir($dh)) :

// Элементы каталогов "." и ".." не выводятся.

if ( ($file != ".") && ($file != "..") ) :

if ($using_linux == 0 ) :

$depth = explode("\\", $dir): else :

$depth = explode("/", $dir); endif ; $curtent_depth = sizeof( $depth);

// Построить путь по правилам используемой операционной системы. if ($using_linux == 0) :

$tab_depth = $current_deptn - $init_depth;

$file = $dir. "\\", $file; else :

$file = $dir. "/",$file; endif;

// Переменная $file содержит каталог? if ( is dir($file) ) :

$х = 0;

// Вычислить отступ

while ( $х < ($tab_depth * 2) ) :

print "&nbsp;";

$х++; endwhile;

print "<img src=\"$folder_location\" alt=\"[dir]\">

".basename($file)."<br>";

// Увеличить счетчик &nbsp;

// Рекурсивный вызов функции display_directory()

display_directory($file, $folder_location, $using_linux, $init_depth);

// He каталог

else :

// Построить путь по правилам используемой

// операционной системы.

if ($using_linux == 0) :

$tab_depth = ($current_depth - $init_depth) - 2; $x = 0;

// Вычислить отступ

while ( $x < (($tab_depth * 2) + 5) ) :

print "&nbsp;";

$x++;

endwhile:

print "<a href =\ "" .$dir."\\".basename($file)."\">".basename($file)."</a> <br>";

else :

print "<a href = \"".$dir."/".basename($file)."\">".basename($file)."</a> <br>";

endif:

endif; // Is_dir(file) endif: // If ! "." or ".."

endwhile;

// Закрыть каталог closedir($dh);

<html >

<head>

<title> <? print "$page_name"; ?> </title>

</head>

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

<?

// Вычислить начальный отступ

if ($using_linux == 0) :

$depth = explode("\\", $beg_path);

else :

$depth = explode("/", $beg_path);

endif:

$init_depth = sizeof($depth);

display_directory ($beg_path, $folder_location, $using_linux, $init_depth);

?>

</body>

</html>

На рис. 7.1 изображен результат выполнения сценария для каталога с несколькими главами этой книги.

Рис. 7.1. Вывод структуры каталога на сервере с использованием сценария sitemap.php

Итоги

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

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

Материал этой главы подготовил почву для следующей главы, «Строки и регулярные выражения», поскольку при разработке web-приложений обработка строк и операции ввода/вывода очень тесно связаны.


  • Глава 8. Строки и регулярные выражения
    • Регулярные выражения
      • синтаксис регулярных выражений(POSIX)
    • Функции PHP для работы с регулярными выражениями(POSIX-совместимые)
    • Синтаксис регулярных выражений в стиле Perl
      • Метасимволы
      • Модификаторы
    • Функции PHP для работы с регулярными выражениями(Perl-совместимые)
    • Другие строковые функции
      • Дополнение и сжатие строк
      • Определение длины строки
      • Сравнение двух строк
      • Обработка строковых данных без применения регулярных выражений
      • Преобразование строк и файлов к формату HTML и наоборот
      • Преобразование HTML в простой текст
      • Преобразование строки к верхнему и нижнему регистру
    • Проект: идентификация браузера
    • Итоги

ГЛАВА 8

Строки и регулярные выражения

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

Поиск по шаблону позволяет не только находить определенные фрагменты текста, но и заменять их другими фрагментами. Одним из стандартных примеров поиска по шаблону являются команды поиска/замены в текстовых редакторах — например, в MS Word, Emacs и в моем любимом редакторе vi. Всем пользователям UNIX хорошо известны такие программы, как sed, awk и grep; богатство возможностей этих программ в значительной степени обусловлено средствами поиска по шаблону. Механизмы поиска по шаблону решают четыре основные задачи:

  • поиск строк, в точности совпадающих с заданным шаблоном;
  • поиск фрагментов строк, совпадающих с заданным шаблоном;
  • замену строк и подстрок по шаблону;
  • поиск строк, с которыми заданный шаблон не совпадает.

Появление Web породило необходимость в более быстрых и эффективных средствах поиска данных, которые бы позволяли пользователям со всего мира находить нужную информацию среди миллиардов web-страниц. Поисковые системы, онлайновые финансовые службы и сайты электронной коммерции — все это стало бы абсолютно бесполезным без средств анализа гигантских объемов данных в этих секторах. Действительно, средства обработки строковой информации являются жизненно важной составляющей практически любого сектора, так или иначе связанного с современными информационными технологиями. В этой главе основное внимание посвящено средствам обработки строк в РНР. Мы рассмотрим некоторые стандартные строковые функции (в языке их больше 60!), а из приведенных определений и примеров вы получите сведения, необходимые для создания web-приложений. Но прежде чем переходить к специфике РНР, я хочу познакомить вас с базовым механизмом, благодаря которому становится возможным поиск по шаблону. Речь идет о регулярных выражениях.

Регулярные выражения

Регулярные выражения лежат в основе всех современных технологий поиска по шаблону. Регулярное выражение представляет собой последовательность простых и служебных символов, описывающих искомый текст. Иногда регулярные выражения бывают простыми и понятными (например, слово dog), но часто в них присутствуют служебные символы, обладающие особым смыслом в синтаксисе регулярных выражений, — например, <(?)>.*<\/.?>.

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

Синтаксис регулярных выражений (POSIX)

Структура регулярных выражений POSIX чем-то напоминает структуру типичных математических выражений — различные элементы (операторы) объединяются друг с другом и образуют более сложные выражения. Однако именно смысл объединения элементов делает регулярные выражения таким мощным и выразительным средством. Возможности не ограничиваются поиском литерального текста (например, конкретного слова или числа); вы можете провести поиск строк с разной семантикой, но похожим синтаксисом — например, всех тегов HTML в файле.

Простейшее регулярное выражение совпадает с одним литеральным символом — например, выражение g совпадает в таких строках, как g, haggle и bag. Выражение, полученное при объединении нескольких литеральных символов, совпадает по тем же правилам — например, последовательность gan совпадает в любой строке, содержащей эти символы (например, gang, organize или Reagan).

Оператор | (вертикальная черта) проверяет совпадение одной из нескольких альтернатив. Например, регулярное выражение php | zend проверяет строку на наличие php или zend.

Квадратные скобки

Квадратные скобки ([ ]) имеют особый смысл в контексте регулярных выражений — они означают «любой символ из перечисленных в скобках». В отличие от регулярного выражения php, которое совпадает во всех строках, содержащих литеральный текст php, выражение [php] совпадает в любой строке, содержащей символы р или h. Квадратные скобки играют важную роль при работе с регулярными выражениями, поскольку в процессе поиска часто возникает задача поиска символов из заданного интервала. Ниже перечислены некоторые часто используемые интервалы:

  • [0-9] — совпадает с любой десятичной цифрой от 0 до 9;
  • [a-z] — совпадает с любым символом нижнего регистра от а до z;
  • [A-Z] — совпадает с любым символом верхнего регистра от А до Z;
  • [a -Z] — совпадает с любым символом нижнего или верхнего регистра от а до Z.

Конечно, перечисленные выше интервалы всего лишь демонстрируют общий принцип. Например, вы можете воспользоваться интервалом [0-3] для обозначения любой десятичной цифры от 0 до 3 или интервалом [b-v] для обозначения любого символа нижнего регистра от b до v. Короче говоря, интервалы определяются совершенно произвольно.

Квантификаторы

Существует особый класс служебных символов, обозначающих количество повторений отдельного символа или конструкции, заключенной в квадратные скобки. Эти служебные символы (+, * и {...}) называются квантификаторами. Принцип их действия проще всего пояснить на примерах:

  • р+ означает один или несколько символов р, стоящих подряд;
  • р* означает ноль и более символов р, стоящих подряд;
  • р? означает ноль или один символ р;
  • р{2} означает два символа р, стоящих подряд;
  • р{2,3} означает от двух до трех символов р, стоящих подряд;
  • р{2,} означает два и более символов р, стоящих подряд.

Прочие служебные символы

Служебные символы $ и ^ совпадают не с символами, а с определенными позициями в строке. Например, выражение р$ означает строку, которая завершается символом р, а выражение ^р — строку, начинающуюся с символа р.

  • Конструкция [^a-zA-Z] совпадает с любым символом, не входящим в указаные интервалы (a-z и A-Z).
  • Служебный символ . (точка) означает «любой символ». Например, выражение р.р совпадает с символом р, за которым следует произвольный символ, после чего опять следует символ р.

Объединение служебных символов приводит к появлению более сложных выражений. Рассмотрим несколько примеров:

  • ^.{2}$ — любая строка, содержащая ровно два символа;
  • <b>(.*)</b> — произвольная последовательность символов, заключенная между <Ь> и </Ь> (вероятно, тегами HTML для вывода жирного текста);
  • p(hp)* — символ р, за которым следует ноль и более экземпляров последовательности hp (например, phphphp).

Иногда требуется найти служебные символы в строках вместо того, чтобы использовать их в описанном специальном контексте. Для этого служебные символы экранируются обратной косой чертой (\). Например, для поиска денежной суммы в долларах можно воспользоваться выражением \$[0-9]+, то есть «знак доллара, за которым следует одна или несколько десятичных цифр». Обратите внимание на обратную косую черту перед $. Возможными совпадениями для этого регулярного выражения являются $42, $560 и $3.

Стандартные интервальные выражения (символьные классы)

Для удобства программирования в стандарте POSIX были определены некоторые стандартные интервальные выражения, также называемые символьными классами (character classes). Символьный класс определяет один символ из заданного интервала — например, букву алфавита или цифру:

  • [[:alpha:]] — алфавитный символ (aA-zZ);
  • [[:digit:]]-цифра (0-9);
  • [[:alnum:]] — алфавитный символ (aA-zZ) или цифра (0-9);
  • [[:space:]] — пропуски (символы новой строки, табуляции и т. д.).

Функции РНР для работы с регулярными выражениями (POSIX-совместимые)

В настоящее время РНР поддерживает семь функций поиска с использованием регулярных выражений в стиле POSIX:

  • еrеg( );
  • еrеg_rерlасе( );
  • eregi( );
  • eregi_replace( );
  • split( );
  • spliti( );
  • sql_regcase( ).

Описания этих функций приведены в следующих разделах.

ereg( )

Функция еrеg( ) ищет в заданной строке совпадение для шаблона. Если совпадение найдено, возвращается TRUE, в противном случае возвращается FALSE. Синтаксис функции ereg( ):

int ereg (string шаблон, string строка [, array совпадения])

Поиск производится с учетом регистра алфавитных символов. Пример использования ereg( ) для поиска в строках доменов .соm:

$is_com - ereg("(\.)(com$)", $email):

// Функция возвращает TRUE, если $email завершается символами ".com"

// В частности, поиск будет успешным для строк

// "www.wjgilmore.com" и "someemail@apress.com"

Обратите внимание: из-за присутствия служебного символа $ регулярное выражение совпадает только в том случае, если строка завершается символами .com. Например, оно совпадет в строке "www.apress.com", но не совпадет в строке "www.apress.com/catalog".

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

Листинг 8.1. Вывод элементов массива $regs

$url = "http://www.apress.com";

// Разделить $url на три компонента: "http://www". "apress" и "com"

$www_url = ereg("^(http://www)\.([[:alnum:]+\.([[:alnum:]]+)". $url, $regs);

if ($www_url) : // Если переменная $www_url содержит URL

echo $regs[0]; // Вся строка "http://www.apress.com"

print "<br>";

echo $regs[l]; // "http://www"

print "<br>";

echo $regs[2]; // "apress"

print "<br>";

echo $regs[3]; // "com" endif;

При выполнении сценария в листинге 8.1 будет получен следующий результат:

http://www.apress.com http://www apress com

ereg_replace( )

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

string ereg_replace (string шаблон, string замена, string строке)

Функция ereg_replace( ) работает по тому же принципу, что и ereg( ), но ее возможности расширены от простого поиска до поиска с заменой. После выполнения замены функция возвращает модифицированную строку. Если совпадения

отсутствуют, строка остается в прежнем состоянии. Функция ereg_replace( ), как и еrеg( ), учитывает регистр символов. Ниже приведен простой пример, демонстрирующий применение этой функции:

$copy_date = "Copyright 1999":

$copy_date = ereg_replace("([0-9]+)". "2000", $copy_date);

print $copy_date: // Выводится строка "Copyright 2000"

У средств поиска с заменой в языке РНР имеется одна интересная возможность — возможность использования обратных ссылок на части основного выражения, заключенные в круглые скобки. Обратные ссылки похожи на элементы необязательного параметра-массива совпадения функции еrеg( ) за одним исключением: обратные ссылки записываются в виде \0, \1, \2 и т. д., где \0 соответствует всей строке, \1 — успешному совпадению первого подвыражения и т. д. Выражение может содержать до 9 обратных ссылок. В следующем примере все ссылки на URL в тексте заменяются работающими гиперссылками:

$url = "Apress (http://www.apress.com");

$url = ereg_replace("http://(([A-Za-z0-9.\-])*)", "<a href=\"\\0\">\\0</a>", $url);

print $url;

// Выводится строка:

// Apress (<a href="http://www.apress.com">http://www.apress.com</a>)

eregi( )

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

int eregi (string шаблон, string строка [, array совпадения])

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

$password = "abc";

if (! eregi("[[:alnum:]]{8.10}, $password) :

print "Invalid password! Passwords must be from 8 through 10 characters in length.";

endif;

// В результате выполнения этого фрагмента выводится сообщение об ошибке.

// поскольку длина строки "abc" не входит в разрешенный интервал

// от 8 до 10 символов.

eregi_replace( )

Функция eregi_replасе( ) работает точно так же, как ereg_replace( ), за одним исключением: поиск производится без учета регистра символов. Синтаксис функции ereg_replace( ):

string eregi_replace (string шаблон, string замена, string строка)

split( )

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

array split (string шаблон, string строка [, int порог])

Необязательный параметр порог определяет максимальное количество элементов, на которые делится строка слева направо. Если шаблон содержит алфавитные символы, функция spl it( ) работает с учетом регистра символов. Следующий пример демонстрирует использование функции split( ) для разбиения канонического IP-адреса на триплеты:

$ip = "123.345.789.000"; // Канонический IP-адрес

$iparr = split ("\.", $ip) // Поскольку точка является служебным символом.

// ее необходимо экранировать.

print "$iparr[0] <br>"; // Выводит "123"

print "$iparr[1] <br>"; // Выводит "456"

print "$iparr[2] <br>"; // Выводит "789"

print "$iparr[3] <br>"; // Выводит "000"

spliti( )

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

array spliti (string шаблон, string строка [, int порог])

Разумеется, регистр символов важен лишь в том случае, если шаблон содержит алфавитные символы. Для других символов выполнение spliti( ) полностью аналогично split( ).

sql_regcase( )

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

string sql_regcase (string строка)

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

$version = "php 4.0";

print sql_regcase($version);

// Выводится строка [Pp][Hh][Pp][ ][44][..][00]

Синтаксис регулярных выражений в стиле Perl

Perl (http://www.perl.com) давно считается одним из самых лучших языков обработки текстов. Синтаксис Perl позволяет осуществлять поиск и замену даже для самых сложных шаблонов. Разработчики РHР сочли, что не стоит заново изобретать уже изобретенное, а лучше сделать знаменитый синтаксис регулярных выражений Perl доступным для пользователей РНР. Так появились функции для работы с регулярными выражениями в стиле Perl.

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

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

/food/

Обратите внимание: строка food заключена между двумя косыми чертами. Как и в стандарте POSIX, вы можете создавать более сложные шаблоны при помощи квантификаторов:

/fo+/

Этот шаблон совпадает с последовательностью fo, за которой могут следовать дополнительные символы о. Например, совпадения будут обнаружены в строках food, fool и fo4. Рассмотрим другой пример использования квантификатора:

/fo{2,4}/

Шаблон совпадает с символом f, за которым следуют от 2 до 4 экземпляров символа о. К числу потенциальных совпадений относятся строки fool , fooool и foosball .

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

Метасимволы

Одной из интересных особенностей Perl является использование метасимволов при поиске. Метасимвол [Следует отметить, что авторское толкование термина «метасимвол» противоречит не только всем традициям, по и официальной документации РНР. — Примеч. перев.] представляет собой алфавитный символ с префиксом \ — признаком особой интерпретации следующего символа. Например, метасимвол \d может использоваться при поиске денежных сумм:

/([d]+)000/

Комбинация \d обозначает любую цифру. Конечно, в процессе поиска часто возникает задача идентификации алфавитно-цифровых символов, поэтому в Perl для них был определен метасимвол \w:

/<([\w]+)>/

Этот шаблон совпадает с конструкциями, заключенными в угловые скобки, — например, тёгами HTML. Кстати, метасимвол \W имеет прямо противоположный смысл и используется для идентификации символов, не являющихся алфавитно-цифровыми.

Еще один полезный метасимвол, \b, совпадает с границами слов:

/sa\b/

Поскольку метасимвол границы слова расположен справа от текста, этот шаблон совпадет в строках salsa и lisa, но не в строке sand. Противоположный метасимвол, \В, совпадает с чем угодно, кроме границы слова:

/sa\B/

Шаблон совпадает в таких строках, как sand и Sally, но не совпадает в строке salsa.

Модификаторы

Модификаторы заметно упрощают работу с регулярными выражениями. Впрочем, модификаторов много, и в табл. 8.1 приведены лишь наиболее интересные из них. Модификаторы перечисляются сразу же после регулярного выражения — например, /string/i.

Таблица 8.1. Примеры модификаторов

Модификатор | Описание
m | Фрагмент текста интерпретируется как состоящий из нескольких «логических строк». По умолчанию специальные символы ^ и $ совпадают только в начале и в конце всего фрагмента. При включении «многострочного режима» при помощи модификатора m^ и $ будут совпадать в начале и в конце каждой логической строки внутри фрагмента
s | По смыслу противоположен модификатору m — при поиске фрагмент интерпретируется как одна строка, а все внутренние символы новой строки игнорируются
i | Поиск выполняется без учета регистра символов

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

  • http://www.php.net/manual/pcre.pattern.modifiers.php;
  • http://www.php.net/manual/pcre.pattern.syntax.php;
  • http://www.perlcom/pub/doc/manual/html/pod/perlre.html;
  • http://www.codebits.com/p5be;
  • http://www.metronet.com/1/perlinfo/doc/FMTEYEWTK/regexps.html.

Функции РНР для работы с регулярными выражениями (Perl-совместимые)

В РНР существует пять функций поиска по шаблону с использованием Perl-совместимых регулярных выражений:

  • preg_match( );
  • preg_match_all( );
  • preg_replace( );
  • preg_split( );
  • preg_grep( ).

Эти функции подробно описаны в следующих разделах.

preg_match( )

Функция pregjnatch( ) ищет в заданной строке совпадение для шаблона. Если совпадение найдено, возвращается TRUE, в противном случае возвращается FALSE. Синтаксис функции pregjnatch( ):

int pregjnatch (string шаблон, string строка [, array совпадения})

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

$linе = "Vi is the greatest word processor ever created!";

// Выполнить поиск слова "Vi" без учета регистра символов:

if (preg_match("/\bVi\b\i", $line, $matcn)) :

print "Match found!";

endif;

// Команда if в этом примере возвращает TRUE

preg_match_all( )

Функция preg_match_all( ) находит все совпадения шаблона в заданной строке.

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

Int preg_match_all (string шаблон, string строка, array совпадения [, int порядок])

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

  • PREG_PATTERN_ORDER — используется по умолчанию, если параметр порядок не указан. Порядок, определяемый значением PREG_PATTERN_ORDER, на первый взгляд выглядит не совсем логично: первый элемент (с индексом 0) содержит массив совпадений для всего регулярного выражения, второй элемент (с индексом 1) содержит массив всех совпадений для первого подвыражения в круглых скобках и т. д.;
  • PREG_SET_ORDER — порядок сортировки массива несколько отличается от принятого по умолчанию. Первый элемент (с индексом 0) содержит массив с текстом, совпавшим со всеми подвыражениями в круглых скобках для первого найденного совпадения. Второй элемент (с индексом 1) содержит аналогичный массив для второго найденного совпадения и т. д.

Следующий пример показывает, как при помощи функции preg_match_al( ) найти весь текст, заключенный между тегами HTML <b>...</b>:

$user_info = "Name: <b>Rasmus Lerdorf</b> <br> Title: <b>PHP Guru</b>";

preg_match_all ("/<b>(.*)<\/b>/U", Suserinfo. $pat_array);

print $pat_array[0][0]." <br> ".pat_array[0][l]."\n":

Результат:

Rasmus Lerdorf

PHP Guru

preg_replace( )

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

mixed preg_replace (mixed шаблон, mixed замена, mixed строка [, int порог])

Необязательный параметр порог определяет максимальное количество замен в строке. Интересный факт: параметры шаблон и замена могут представлять собой масивы. Функция preg_replace( ) перебирает элементы обоих массивов и выполняет замену по мере их нахождения.

preg_split( )

Функция preg_spl it( ) аналогична split( ) за одним исключением — параметр шаблон может содержать регулярное выражение. Синтаксис функции preg_split( ):

array preg_split (string шаблон, string строка [, int порог [, int флаги]])

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

$user_info="+wj+++Gilmore+++++wjgi]more@hotmail.com

+++++++Columbus+++OH";

$fields = preg_split("/\+{1.}/", $user_info);

while ($x < sizeof($fields)):

print $fields[$x]. "<br>";

$x++;

endwhile;

Результат:

WJ

Gilmore

wjgilmore@hotmail.com

Columbus

OH

preg_grep( )

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

array preg_grep (string шаблон, array массив)

Пример использования функции preg_grep( ) для поиска в массиве слов, начинающихся на р:

$foods = array ("pasta", "steak", "fish", "potatoes");

// Поиск элементов, начинающихся с символа "р".

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

$p_foods = preg_grep("/p(\w+)/", $foods):

$х = 0;

while ($x < sizeof($p_foods)) :

print $p_foods[$x]. "<br>";

$Х++;

endwhile;

Результат:

pasta

potatoes

Другие строковые функции

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

Дополнение и сжатие строк

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

chop ( )

Функция chop( ) возвращает строку после удаления из нее завершающих пропусков и символов новой строки. Синтаксис функции chop( ):

string chop(string строка)

В следующем примере функция chop( ) удаляет лишние символы новой строки:

$header = "Table of Contents\n\n";

$header = chop($header);

// $header = "Table of Contents"

str_pad( )

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

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

Если необязательный параметр дополнение не указан, строка дополняется пробелами. В противном случае строка дополняется заданными символами. По умолчанию строка дополняется справа; тем не менее, вы можете передать в параметре тип_дополнения константу STR_PAD_RIGHT, STR_PAD_LEFT или STR_PAD_BOTH, что приведет к дополнению строки в заданном направлении. Пример демонстрирует дополнение строки функцией str_pad( ) с параметрами по умолчанию:

$food = "salad";

print str_pad ($food, 5): // Выводит строку "salad

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

$header = "Table of Contents";

print str_pad ($header, 5, "=+=+=", STR_PAD_BOTH);

// В браузере выводится строка =+=+= Таbе of Contents=+=+="

trim ( )

Функция trim( ) удаляет псе пропуски с обоих краев строки и возвращает полученную строку. Синтаксис функции trim( ):

string trim (string страна]

К числу удаляемых пропусков относятся и специальные символы \n, \r, \t, \v и \0.

ltrim( )

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

string ltrim (string строка)

Функция удаляет те же специальные символы, что и функция trim( ).

Определение длины строки

Длину строки в символах можно определить при помощи функции strlen( ). Синтаксис .функции strlen( ):

int strlen (string строка)

Следующий пример демонстрирует определение длины строки функцией strlen( ):

$string = "hello";

$length = strlen($string);

// $length = 5

Сравнение двух строк

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

  • strcmp( );
  • strcasecmp( );
  • strspn( );
  • strcspn( ).

Все эти функции подробно описаны в следующих разделах.

strcmp( )

Функция strcmp( ) сравнивает две строки с учетом регистра символов. Синтаксис функции strcmp( ):

int strcmp (string строка1, string строка2)

После завершения сравнения strcmp( ) возвращает одно из трех возможных значений:

  • 0, если строка1 и строка2 совпадают;
  • < 0, если строка1 меньше, чем строка2;
  • > 0, если строка2 меньше, чем строка1.

В следующем фрагменте сравниваются две одинаковые строки:

$sthng1 = "butter";

$string2 = "butter";

if ((strcmp($string1. $string2)) == 0) :

print "Strings are equivalent!"; endif;

// Команда if возвращает TRUE

strcasecmp( )

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

int strcasecmp (string cтpoкa1, string строка2)

В следующем фрагменте сравниваются две одинаковые строки:

$string1 = "butter";

$string2 = "Butter";

if ((strcmp($string1, $string2)) == 0) :

print "Strings are equivalent!";

endif;

// Команда if возвращает TRUE

strspn( )

Функция strspn( ) возвращает длину первого сегмента строки1, содержащего символы, присутствующие в строке2. Синтаксис функции strspn( ):

int strspn (string строка1, string строка2)

Следующий фрагмент показывает, как функция strspn( ) используется для проверки пароля:

$password = "12345";

if (strspn($password, "1234567890") != strlen($password)) :

print "Password cannot consist solely of numbers!";

endif:

strcspn( )

Функция strcspn( ) возвращает длину первого сегмента строки1, содержащего символы, отсутствующие в строке2. Синтаксис функции strcspn( ):

int strcspn (string строка1, string строка2)

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

$password = "12345";

if (strcspn($password, "1234567890") == 0) :

print "Password cannot consist solely of numbers!";

endif;

Обработка строковых данных без применения регулярных выражений

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

strtok( )

Функция strtok( ) разбивает строку на лексемы по разделителям, заданным вторым параметром. Синтаксис функции strtok( ):

string strtok (string строка, string разделители)

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

$info = "WJ Gi1more:wjgilmore@hotmail.com | Columbus, Ohio";

// Ограничители - двоеточие (:), вертикальная черта (|) и запятая (.) $tokens = ":|,";

$tokenized = strtok($info, $tokens);

// Вывести элементы массива $tokenized

while ($tokenized) :

echo "Element = $tokenized<br>";

// Обратите внимание: при последующих вызовах strtok

// первый аргумент не передается

$tokenized = strtok($tokens);

endwhile;

Результат:

Element = WJGilmore

Element = wjgilmore@hotmail.com

Element = Columbus

Element = Ohio

parse_str( )

Функция parse_str( ) выделяет в строке пары «переменная-значение» и присваивает значения переменных в текущей области видимости. Синтаксис функции parse_str( ):

void parse_str (string строка)

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

$url = "fname=wj&lname=gilmore&zip=43210";

parse_str($url);

// После выполнения parse_str( ) доступны следующие переменные:

// $fname = "wj":

// $lname = "gilmore";

// $zip = "43210"

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

Работа с формами HTML в РНР описана в главе 10.

explode ( )

Функция explode( ) делит строку на элементы и возвращает эти элементы в виде массива. Синтаксис функции explode( ):

array explode (string разделитель, string строка [, int порог])

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

$info = "wilson | baseball | indians";

$user = explode("|", $info);

// $user[0] = "wilson";

// $user[1] = "baseball";

// $user[2] = "Indians";

Функция explode( ) практически идентична функции регулярных выражений POSIX split( ), описанной выше. Главное различие заключается в том, что передача регулярных выражений в параметрах допускается только при вызове split( ).

implode ( )

Если функция explode( ) разделяет строку на элементы массива, то ее двойник — функция implode( ) - объединяет массив в строку. Синтаксис функции implode( ):

string implode (string разделитель, array фрагменты)

Формирование строки из массива продемонстрировано в следующем примере:

$ohio_cities = array ("Columbus", "Youngstown", "Cleveland", "Cincinnati");

$city_string = implode("l", $ohio_cities);

// $city_string = "Columbus | Youngstown | Cleveland | Cincinnati";

У implode( ) имеется псевдоним — функция join( ).

strpos ( )

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

int strpos (string строка, string подстрока [, int смещение])

Необязательный параметр offset задает позицию, с которой должен начинаться поиск. Если подстрока не найдена, strpos( ) возвращает FALSE (0).

В следующем примере определяется позиция первого вхождения даты в файл журнала:

$log = "

206.169.23.11:/www/:2000-08-10

206.169.23.11:/www/logs/:2000-02-04

206.169.23.11:/www/img/:1999-01-31";

// В какой позиции в журнале впервые встречается 1999 год?

$pos = strpos($log, "1999");

// $pos = 95. поскольку первый экземпляр "1999"

// находится в позиции 95 строки, содержащейся в переменной $log

strrpos( )

Функция strrpos( ) находит в строке последний экземпляр заданного символа. Синтаксис функции strrpos( ):

int strpos (string строка, char символ)

По возможностям эта функция уступает своему двойнику — функции strpos( ), поскольку она позволяет искать только отдельный символ, а не всю строку. Если во втором параметре strrpos( ) передается строка, при поиске будет использован только ее первый символ.

str_replace( )

Функция str_replace( ) ищет в строке все вхождения заданной подстроки и заменяет их новой подстрокой. Синтаксис функции str_replace( ):

string str_replace (string подстрока, string замена, string строка)

Функция substr_replace( ), описанная ниже в этом разделе, позволяет провести заме ну лишь в определенной части строки. Ниже показано, как функция str_replace( ) используется для проведения глобальной замены в строке.

Если подстрока ни разу не встречается в строке, исходная строка не изменяется:

$favorite_food = "My favorite foods are ice cream and chicken wings";

$favorite_food = str_replace("chicken_wings", "pizza", $favohte_food);

// $favorite_food = "My favorite foods are ice cream and pizza"

strstr( )

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

string strstr (string строка, string подстрока)

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

$url = "http://www.apress.com"; $domain - strstr($url, ".");

// $domain = ".apress.com"

substr( )

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

string substr (string строка, int начало [, int длина])

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

  • если параметр начало положителен, возвращаемая подстрока начинается с позиции строки с заданным номером;
  • если параметр начало отрицателен, возвращаемая подстрока начинается с позиции (длина строки - начало);
  • если параметр длина положителен, в возвращаемую подстроку включаются все символы от позиции начало до позиции начало+длина. Если последняя величина превышает длину строки, возвращаются символы до конца строки;
  • если параметр длина отрицателен, возвращаемая подстрока заканчивается на заданном расстоянии от конца строки.

Помните о том, что параметр начало определяет смещение от первого символа строки; таким образом, возвращаемая строка в действительности начинается с символа с номером (начало + 1).

Следующий пример демонстрирует выделение части строки функцией substr( ):

$car = "1944 Ford"; Smodel = substr($car, 6);

// Smodel = "Ford"

Пример с положительным параметром длина:

$car = "1944 Ford";

$model = substr($car, 0, 4);

// $model = "1944"

Пример с отрицательным параметром длина:

$car = "1944 Ford";

$model = substr($car, 2, -5);

// $model = "44"

substr_count( )

Функция substr_count( ) возвращает количество вхождений подстроки в заданную строку. Синтаксис функции substr_count( ):

int substr_count (string строка, string подстрока)

В следующем примере функция substr_count( ) подсчитывает количество вхождений подстроки ain:

$tng_twist = "The rain falls mainly on the plains of Spain";

$count = substr_count($tng_twist, "ain");

// $count = 4

substr_replace( )

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

string substr_replace (string строка, string замена, int начало [, int длина])

Параметры начало и длина задаются по определенным правилам:

  • если параметр начало положителен, замена начинается с заданной позиции;
  • если параметр начало отрицателен, замена начинается с позиции (длина строки -начало);
  • если параметр длина положителен, заменяется фрагмент заданной длины;
  • если параметр длина отрицателен, замена завершается в позиции (длина строки -длина).

Простая замена текста функцией substr_replace( ) продемонстрирована в следующем примере:

$favs = " 's favorite links";

$name = "Alessia";

// Параметры "0, 0" означают, что заменяемый фрагмент начинается

// и завершается в первой позиции строки.

$favs - substr_replace($favs, $name, 0, 0);

print $favs:

Результат:

Alessia's favorite links

Преобразование строк и файлов к формату HTML и наоборот

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

Преобразование текста в HTML

Быстрое преобразование простого текста к формату web-браузера — весьма распространенная задача. В ее решении вам помогут функции, описанные в этом разделе.

nl2br( )

Функция nl2br( ) заменяет все символы новой строки (\n) эквивалентными конструкциями HTML <br>.

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

string nl2br (string строка)

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

// Текстовая строка, отображаемая в редакторе.

$text_recipe = "

Party Sauce recipe:

1 can stewed tomatoes

3 tablespoons fresh lemon juice

Stir together, server cold.";

// Преобразовать символы новой строки в <br>

$htinl_recipe = nl2br($text_recipe)

При последующем выводе $html_recipe браузеру будет передан следующий текст в формате HTML:

Party Sauce recipe:<br>

1 can stewed tomatoes<br>

3 tablespoons fresh lemon juice<br>

Stir together, server cold.<br>

htmlentities( )

Функция htmlentities( ) преобразует символы в эквивалентные конструкции HTML. Синтаксис функции htmlentities:

string htmlentities (string строка)

В следующем примере производится необходимая замена символов строки для вывода в браузере:

$user_input = "The cookbook, entitled Cafe Francaise' costs < $42.25.";

$converted_input = htmlentities($user_input);

// $converted_input = "The cookbook, entitled 'Caf&egrave;

// Frac&ccediliaise' costs &lt; 42.25.";

Функция htmlentities( ) в настоящее время работает только для символов кодировки ISO-8559-1 (ISO-Latin-1). Кроме того, она не преобразует пробелы в &nbsp;, как следовало бы ожидать.

htmlspecialchars( )

Функция htmlspecialchars( ) заменяет некоторые символы, имеющие особый смысл в контексте HTML, эквивалентными конструкциями HTML. Синтаксис функции htmlspecialchars( ):

string htmlspecialchars (string строка)

Функция html special chars( ) в настоящее время преобразует следующие символы:

& преобразуется в &amp;; " " преобразуется в &quot;;

< преобразуется в &lt;; > преобразуется в &gt;.

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

Следующий пример демонстрирует удаление потенциально опасных символов функцией htmlspeclalchars( ):

$user_input = "I just can't get «enough» of PHP & those fabulous cooking recipes!";

$conv_input = htmlspecialchars($user_input);

// $conv_input = "I just can't &lt;&lt;enough&gt;&gt; of PHP &amp those fabulous cooking

recipes!"

Если функция htmlspecialchars( ) используется в сочетании с nl2br( ), то последнюю следует вызывать после htmlspecialchars( ). В противном случае конструкции <br>, сгенерированные при вызове nl2br( ), преобразуются в видимые символы.

get_html_translation_table ( )

Функция get_html_translation_table( ) обеспечивает удобные средства преобразования текста в эквиваленты HTML Синтаксис функции get_htrril_translation_table( ):

string get_html_translation_table (int таблица)

Функция get_html_translation_table( ) возвращает одну из двух таблиц преобразования (определяется параметром таблица), используемых в работе стандартных функций htmlspecialchars( ) и htmlentities( ). Возвращаемое значение может использоваться в сочетании с другой стандартной функцией, strtr( ) (см. далее), для преобразования текста в код HTML.

Параметр таблица принимает одно из двух значений:

  • HTML_ENTITIES;
  • HTML_SPECIALCHARS.

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

$string = "La pasta e il piatto piu amato in Italia";

$translate = get_html_translation_table(HTML_ENTITIES);

print strtr($string, $translate);

// Специальные символы преобразуются в конструкции HTML

// и правильно отображаются в браузере.

Кстати, функция array_flip( ) позволяет провести преобразование текста в HTML в обратном направлении и восстановить исходный текст. Предположим, что вместо вывода результата strtr( ) в предыдущем примере мы присвоили его переменной $translated string.

В следующем примере исходный текст восстанавливается функцией array_flip( ):

$translate = array_flip($translate);

$translated_string - "La pasta &eacute; il piatto pi&uacute; amato in Italia";

$original_string = strtr($translated_string, $translate);

// $original_string = "La pasta e il piatto piu amato in Italia";

strtr( )

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

string strtr (string строка, string источник, string приемник)

Если строки источник и приемник имеют разную длину, длинная строка усекается до размеров короткой строки.

Существует альтернативный синтаксис вызова strtr( ) с двумя параметрами; в этом случае второй параметр содержит ассоциативный массив, ключи которого соответствуют заменяемым подстрокам, а значения — заменяющим подстрокам. В следующем примере теги HTML заменяются XML-подобными конструкциями:

$source = array("<title>" => "<h1>". "</title> => "</h1>");

$string = "<h1>Today In PHP-Powered News"</h1>";

print strtr($string, $source);

// Выводится строка "<title>Today in PHP-Powered News</title>"

Преобразование HTML в простой текст

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

strip_tags( )

Функция strip_tags( ) удаляет из строки все теги HTML и РНР, оставляя в ней только текст. Синтаксис функции strip_tags( ):

string strip_tags (string строка [, string разрешенные_тerи])

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

Ниже приведен пример удаления из строки всех тегов HTML функцией strip_tags( ):

$user_input = "I just <b>love</b> РНР and <i>gourment</i> recipes!";

$stripped_input = strip_tags($user_input);

// $stripped_input = "I just love PHP and gourmet recipes!";

В следующем примере удаляются не все, а лишь некоторые теги:

$input = "I <b>love</b> to <a href = \"http://www.eating.com\">eat!</a>!";

$strip_input = strip_tags ($user_input, "<a>");

// $strip_input = "I love to <a href = \"http://www.eating.com\">eat!</a>!";

Удаление тегов из текста также производится функцией fgetss( ), описанной в главе 7.

get_meta_tags( )

Хотя функция get_meta_tags( ) и не имеет прямого отношения к преобразованию текста, зто весьма полезная функция, о которой следует упомянуть. Синтаксис функции get_meta_tags( ):

array get_meta_tags (string имя_файла/URL [, int включение_пути])

Функция get_meta_tags( ) предназначена для поиска в файле HTML тегов МЕТА.

Теги МЕТА содержат информацию о странице, используемую главным образом поисковыми системами. Эти теги находятся внутри пары тегов <head>...</head>. Применение тегов МЕТА продемонстрировано в следующем фрагменте (назовем его example.html, поскольку он будет использоваться в листинге 8.2):

<html>

<head>

<title>PHP Recipes</title>

<meta name="keywords" content="gourmet. PHP, food. code, recipes, chef, programming, web">

<meta name="description" content="PHP Recipes provides savvy readers with the latest in PHP

programming and gourmet cuisine!">

<meta name="author" content="WJ Gilmore">

</head>

Функция get_meta_tags( ) ищет в заголовке документа теги, начинающиеся словом МЕТА, и сохраняет имена тегов и их содержимое в ассоциативном массиве. В листинге 8.2 продемонстрировано применение этой функции к файлу example.html.

Листинг 8.2. Извлечение тегов МЕТА из файла HTML функцией get_meta_tags( )

$meta_tags = get_meta_tags("example.html"):

// Переменная $meta_tags содержит массив со следующей информацией:

// $meta_tags["keywords"] = "gourmet. PHP. food. code, recipes, chef, programming. Web":

// $meta_tags["description"] = "PHP Recipes provides savvy readers with the latest in PHP

programming and gourmet cuisine";

// $meta_tags["author"] = "WJ Gilmore";

Интересная подробность: данные тегов МЕТА можно извлекать не только из файлов, находящихся на сервере, но и из других URL.

Теги МЕТА и их использование превосходно описаны в статье Джо Берна (Joe Burn) «So, You Want a Meta Command, Huh?» на сайте HTML Goodies: http://htmlgoodies.earthweb.com/tutors/meta.html.

Преобразование строки к верхнему и нижнему регистру

В РНР существует четыре функции, предназначенных для изменения регистра строки:

  • strtolower( );
  • strtoupper( );
  • ucfirst( );
  • ucwords( ).

Все эти функции подробно описаны ниже.

strtolower( )

Функция strtolower( ) преобразует все алфавитные символы строки к нижнему регистру. Синтаксис функции strtolower( ):

string strtolower(string строка)

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

$sentence = "COOKING and PROGRAMMING PHP are my TWO favorite pastimes!";

$sentence = strtolower($sentence);

// После вызова функции $sentence содержит строку

// "cooking and programming php are my two favorite pastimes!"

strtoupper( )

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

string strtoupper (string строка)

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

$sentence = "cooking and programming PHP are my two favorite pastimes!";

$sentence = strtoupper($sentence);

// После вызова функции $sentence содержит строку

// "COOKING AND PROGRAMMING PHP ARE MY TWO FAVORITE PASTIMES!"

ucfirst( )

Функция ucfirst( ) преобразует к верхнему регистру первый символ строки — при условии, что он является алфавитным символом. Синтаксис функции ucfirst( ):

string ucfirst (string строка)

Неалфавитные символы функцией не изменяются. Преобразование первого символа строки функцией ucfirst( ) продемонстрировано в следующем примере:

&sentence = "cooking and programming PHP are my two favorite pastimes!";

$sentence = ucfirst($sentence);

// После вызова функции $sentence содержит строку

// "Cooking and programming PHP are mу two favorite pastimes!"

ucwords( )

Функция ucwords( ) преобразует к верхнему регистру первую букву каждого слова в строке. Синтаксис функции ucwords( ):

string ucwords (string строка")

Неалфавитные символы функцией не изменяются. «Слово» определяется как последовательность символов, отделенная от других элементов строки пробелами. В следующем примере продемонстрировано преобразование первых символов слов функцией ucwords( ):

$sentence = "cooking and programming PHP are my two favorite pastimes!";

$sentence = ucwords($sentence);

// После вызова функции $sentence содержит строку

// "Cooking And Programming PHP Are My Two Favorite Pastimes!"

Проект: идентификация браузера

Каждый программист, пытающийся создать удобный web-сайт, должен учитывать различия в форматировании страниц при просмотре сайта в разных браузерах и операционных системах. Хотя консорциум W3 (http://www.w3.org) продолжает публиковать стандарты, которых должны придерживаться программисты при создании web-приложений, разработчики браузеров любят дополнять эти стандарты своими маленькими «усовершенствованиями», что в конечном счете вызывает хаос и путаницу. Разработчики часто решают эту проблему, создавая разные страницы для каждого типа браузера и операционной системы — при этом объем работы значительно увеличивается, но зато итоговый сайт идеально подходит для любого пользователя. Результат — хорошая репутация сайта и уверенность в том, что пользователь посетит его снова.

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

Приведенный ниже проект (sniffer.php) показывает, как использовать функции РНР для работы с регулярными выражениям с целью получения информации по запросам. Программа определяет тип и версию браузера и операционной системы, после чего выводит полученную информацию в окне браузера. Но прежде чем переходить к непосредственному анализу программы, я хочу представить один из главных ее компонентов — стандартную переменную РНР $HTTP_USER_AGENT. В этой переменной в строковом формате хранятся различные сведения о браузере и операционной системе пользователя — именно то, что нас интересует. Эту информацию можно легко вывести на экран всего одной командой:

<?

echo $HTTP USER_AGENT;

?>

При работе в Internet Explorer 5.0 на компьютере с Windows 98 результат будет выглядеть так:

Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)

Для Netscape Navigator 4.75 выводятся следующие данные:

Mozilla/4.75 (Win98; U)

Sniffer.php извлекает необходимые данные из $HTTP_USER_AGENT при помощи функций обработки строк и регулярных выражений. Алгоритм программы на псевдокоде:

  • Определить две функции для идентификации браузера и операционной системы: browser_info( ) и opsys_info( ). Начнем с псевдокода функции browser_info( ).
  • Определить тип браузера, используя функцию егед( ). Хотя эта функция работает медленнее упрощенных строковых функций типа strstr( ), в данном случае она удобнее, поскольку регулярное выражение позволяет определить версию браузера.
  • Воспользоваться конструкцией if/elseif для идентификации следующих браузеров и их версий: Internet Explorer, Opera, Netscape и браузер неизвестного типа.
  • Вернуть информацию о типе и версии браузера в виде массива.
  • Функция opsys_info( ) определяет тип операционной системы. На этот раз используется функция strstr( ), поскольку тип ОС определяется и без применения регулярных выражений.
  • Воспользоваться конструкцией if/elseif для идентификации следующих систем: Windows, Linux, UNIX, Macintosh и неизвестная операционная система.
  • Вернуть информацию об операционной системе.

Листинг 8.3. Идентификация типа браузера и операционной системы клиента

<?

/*

Файл : sniffer.php

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

Автор: В. Дж. Гилмор

Дата : 24 августа 2000 г.

*/

// Функция: browser_info

// Назначение: Возвращает тип и версию браузера

function browser_info ($agent) {

// Определить тип браузера

// Искать сигнатуру Internet Explorer

if (ereg('MSIE ([0-9].[0-9]{1,2})', $agent, $version))

$browse_type = "IE";

$browse version = $version[1];

// Искать сигнатуру Opera

elseif (ereg( 'Opera ([0-9].[0-9]{1,2})'. $agent, $version)):

$browse_type = "Opera":

$browse_version = $version[1]:

// Искать сигнатуру Netscape. Проверка браузера Netscape

// *должна* выполняться после проверки Internet Explorer и Opera,

// поскольку все эти браузеры любят сообщать имя

// Mozilla вместе с настоящим именем.

elseif (ereg( 'Mozilla/([0-9].[0-9]{1,2})'. $agent, $version)) :

$browse_type = "Netscape";

$browse_version = $version[1];

// Если это не Internet Explorer, Opera или Netscape.

// значит, мы обнаружили неизвестный браузер,

else :

$browse_type = "Unknown";

$browse_version = "Unknown";

endif:

// Вернуть тип и версию браузера в виде массива

return array ($browse_type, $browse_version);

} // Конец функции browser_info

// Функция: opsys_info

// Назначение: Возвращает информацию об операционной системе пользователя

function opsys_info($agent) {

// Идентифицировать операционную систему

// Искать сигнатуру Windows

if ( strstr ($agent. 'win') ) :

$opsys = "windows";

// Искать сигнатуру Linux

elseif ( strstr($agent, 'Linux') ) :

$opsys = "Linux";

// Искать сигнатуру UNIX

elseif ( strstr (Sagent, 'Unix') ) :

$opsys = "Unix";

// Искать сигнатуру Macintosh

elseif ( strstr ($agent, 'Mac') ) :

$opsys = "Macintosh";

// Неизвестная платформа else :

$opsys = "Unknown";

endif;

// Вернуть информацию об операционной системе

return $opsys;

} // Конец функции opsys_info

// Сохранить возвращаемый массив в списке

list ($browse_type. $browse_version) = browser_info ($HTTP_USER_AGENT); Soperating_sys = opsysjnfo ($HTTP_USER_AGENT);

print "Browser Type: $browse_type <br>";

print "Browser Version: $browse_version <br>";

print "Operating System: $operating_sys <br>":

?>

Вот и все! Например, если пользователь работает в браузере Netscape 4.75 на компьютере с системой Windows, будет выведен следующий результат:

Browser Type: Netscape

Browser Version: 4.75

Operating System: Windows

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

Итоги

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

  • общие сведения о регулярных выражениях в стилях POSIX и Perl;
  • стандартные функции РНР для работы с регулярными выражениями;
  • изменение длины строки;
  • определение длины строки;
  • альтернативные функции РНР для обработки строковой информации;
  • преобразование простого текста в HTML и наоборот;
  • изменение регистра символов в строках.

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


  • Глава 9. PHP и динамическое создание страниц
    • Простые ссылки
    • Файловые компоненты(шаблоны)
      • include( ) и require( )
      • Функции
      • Построение компонентов
      • Заголовок
      • Колонтитул
      • Основная часть
      • Все вместе: заголовок, колонтитул и основная часть
      • Оптимизация шаблонов
    • Проект: генератор страниц
    • Итоги

ГЛАВА 9

РНР и динамическое создание страниц

Многие читатели в любой книге о компьютерах пролистывают все, что не представляет непосредственного интереса, и переходят к тому, что они действительно хотят знать. Лично я поступаю именно так. Впрочем, в этом нет ничего страшного — редко встречаются технические книги, которые необходимо читать от корки до корки. А может, вы именно так и поступили — пропустили восемь начальных глав и взялись за эту главу, потому что у нее было самое интересное название? Да и кому захочется тратить время на подробности, когда на работе «горит» очередной проект?

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

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

Простые ссылки

По ссылкам пользователь может переходить как на обычные страницы HTML, так и на страницы, содержащие код РНР:

<а href = "date.php"><View today's date</a>

Если щелкнуть на ссылке, в браузере будет загружена страница с именем date.php. Просто, не правда ли? Развивая приведенный пример, можно воспользоваться переменной для построения динамической ссылки:

<?

$link = "date.php";

print "<а href = \"$link\">View today's date</a> <br>\n"

?>

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

Если необходимость экранировать кавычки вас раздражает, просто включите режим magic_quotes_gpc в файле php.ini. В результате все апострофы, кавычки, обратные косые черты и нуль-символы. в тексте автоматически экранируются!

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

<?

// Создать массив разделов

$contents - array("tutorials", "articles", "scripts", "contact");

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

for ($i = 0; $i < sizeof($contents; $i++)

print " &#149; <a href = \"".$contents[$i].".php\">".$contents[$i]."</a><br>\n";

// &#149; - специальное обозначение точки-маркера endfor;

?>

Файловые компоненты (шаблоны)

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

Как правило, общие фрагменты содержания/кода (то есть шаблоны) сохраняются в отдельных файлах. При построении web-документа вы просто «включаете» эти файлы в соответствующие места страницы. В РНР для этого существуют две функции: include( ) и require( ).

include( ) и require( )

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

опытом программирования на других языках (например, С, C++ или Java), хорошо знакомы с концепцией библиотек функций и их использованием в программах для расширения функциональных возможностей.

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

Функции

В РНР существуют четыре функции для включения файлов в сценарии РНР:

  • include( );
  • include_once( );
  • require( );
  • require_once( ).

Несмотря на сходство имен, эти функции решают разные задачи.

include( )

Функция include( ) включает содержимое файла в сценарий. Синтаксис функции include( ):

include (file файл]

У функции include( ) есть одна интересная особенность — ее можно выполнять условно. Например, если вызов функции включен в блок команды if. то файл включается в программу лишь в том случае, если условие i f истинно. Если функция includeO используется в условной команде, то она должна быть заключена в фигурные скобки или в альтернативные ограничители. Сравните различия в синтаксисе листингов 9.1 и 9.2.

Листинг 9.1. Неправильное использование include( )

if (some_conditional)

include ('text91a.txt'); else

include ('text91b.txt');

Листинг 9.2. Правильное использование include( )

if (some_conditional) :

include ('text91a.txt');

else :

include ('text91b.txt');

endif;

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

print "this is an invalid include file";

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

<?

print "this is an invalid include file";

?>

include_once( )

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

include_once (file файл)

require ( )

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

require (file файл)

Тем не менее, между функциями require( ) и include( ) существует одно важное различие. Файл, определяемый параметром require( ), включается в сценарий независимо от местонахождения require( ) в сценарии. Например, при вызове requi ге( ) в блоке if при ложном условии файл все равно будет включен в сценарий!

Во многих ситуациях бывает удобно создать файл с переменными и другой информацией, которая используется в масштабах сайта, и затем подключать его по мере необходимости. Хотя имя этого файла выбирается произвольно, я обычно называю его init.tpl (сокращение от «initializaion.template»). В листинге 9.3 показано, как выглядит очень простой файл init.tpl. В листинге 9.4 содержимое init.tpl включается в сценарий командой require( ).

Листинг 9.3. Пример инициализационного файла <?

<?

$site_title = "РНР Recipes";

$contact_email = "wjgilmore@hotmail.com";

$contact_name = "WJ Gilmore";

?>

Листинг 9.4. Использование файла init.tpl

<? require ('init.tpl ');?>

<html>

<head>

<title><? print $site_title; ?></title>

</head>

<body>

<? print "Welcome to $site_title. For questions, contact <a href =

\"mai1 to:$contact_email\">$contact_name</a>."; ?>

</body>

</html>

Передача URL при вызове require( ) допускается лишь при включенном режиме «URL fopen wrappers» (этот режим включен по умолчанию).

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

require_once( )

Функция require_once( ) гарантирует, что файл будет включаться в сценарий всего один раз. После вызова requi rе_оnсе( ) все дальнейшие попытки включения того же файла игнорируются. Синтаксис функции requiге_оnсе( ):

require_once(file файл)

Если не считать дополнительной проверки, в остальном эта функция аналогична

require( ).

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

Построение компонентов

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

Заголовок

Заголовочный файл (вроде приведенного в листинге 9.5) присутствует практически в каждом из моих web-сайтов с поддержкой РНР. В этом файле содержится

информация, действующая на уровне всего сайта, — например, заголовок, контактные данные и некоторые компоненты кода HTML-страницы.

Листинг 9.5. Пример файла заголовка

<?

// Файл: header.tpl

// Назначение: заголовочный файл для сайта PhpRecipes .

// Дата: 22 августа 2000 г.

$site_name = "PHPRecipes";

$site_email= "wjgnmore@hotrnail.com";

$site_path = "http://localhost/phprecipes";

?>

<html>

<head>

<title> <? print $site_name; ?> </title>

</head>

<body bgcolor="#7b8079"
text="#ffffff" link="fe7d387" alink="#e7d387"
vlink="#e7f0e4">

<table width="95%" cellpadding="0" cellspacing="0" border="1">

<tr>

<td valign = "top">

PHPRecipes

</td>

<td valign = "top" align="right">

<?

// Вывести текущую дату и время

print date ("F d, h:i a");

?>

</td>

</tr>

</table>

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

<Files "*.tpl">

Order allow,deny

Allow from 127.0.0.1

Deny from all

</Files>

РНР и проблемы безопасности сайтов подробно описаны в главе 16.

Колонтитул

Колонтитулом (footer) обычно называется информация, расположенная в нижней части страниц сайта, — контактные данные, ссылки и информация об авторских правах. Эту информацию можно разместить в отдельном файле и включать в качестве шаблона так же, как это делается с заголовком. Допустим, c наступлением нового года вам потребовалось изменить информацию об авторских правах и привести ее к виду «Copyright © 2000-2001». Есть два пути: потратить канун Рождества на лихорадочное редактирование сотен статических страниц или воспользоваться шаблоном наподобие приведенного в листинге 9.6. Одно простое изменение — и вы можете возвращаться к праздничным хлопотам.

Листинг 9.6. Пример файла колонтитула (footer.tpl)

<table width="95%" cellspacing="0" cellpadding="0" border="1">

<tr><td valign="top" align="middle">

Copyright &copy; 2000 PHPRecipes. All rights reserved.<br>

<a href = "mailto:<?=$site_email;?>">contact</a> |

<a href = "<?=$site_path:?>/

privacy.php">your privacy</a>

</td></tr>

</table>

</body>

</html>

Обратите внимание на использование глобальной переменной $site_email в файле колонтитула. Значение этой переменной действует в масштабах всей страницы, а мы предполагаем, что файлы header.tpl и footer.tpl будут включены в одну итоговую страницу. Также обратите внимание на присутствие пути $site_path в ссылке Privacy (Конфиденциальность). Я всегда включаю в шаблоны полные пути ко всем ссылкам — если бы URL ссылки состоял из одного имени privacy.php, то файл колонтитула был бы жестко привязан к конкретному каталогу.

Основная часть

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

Листинг 9.7. Пример основной части страницы (index_body.tpl)

<table width="95%" cellspacing="0" cellpadding="0" border="1">

<tr>

<td valign="top" width="25%>

<a href = "<?=$site_path;?>/tutorials.php">tutorials</a>

<br>

<a href = "<?=$site_path:?>/articles.php">articles</a>

<br>

<a href = "<?=$site_path;?>/scripts.php">scripts</a>

<br>

<a href = "<?=$site_path;?>/contact.php">contact</a>

<br>

</td>

<td valign="top" width="75%">

Welcome to PHPRecipes. the starting place for PHP scripts, tutorials,

and information about gourmet cooking!

</td>

</tr>

</table>

Все вместе: заголовок, колонтитул и основная часть

Вероятно, мое настроение лучше всего выражается фразой полковника «Ганнибала» Смита (Джордж Пеппард) из знаменитого сериала «Команда А»: «Люблю, когда все становится на свои места». Я испытываю нечто подобное, когда разрозненные шаблоны объединяются и образуют полный web-документ. Комбинируя три секции документа: header.tpl, index_body.tpl и footer.tpl, — можно быстро построить простейшую страницу вроде той, что приведена в листинге 9.8.

Листинг 9.8. Построение страницы index.php включением нескольких файлов

<?

// Файл: index.php

// Назначение: домашняя страница PHPRecipes

// Дата: 23 августа 2000 г.

// Вывести заголовок

include ("header.tpl");

// Вывести основную часть

include ("index_body.tpl");

// Вывести колонтитул

include ("footer.tpl");

?>

Ну как? Три простые команды — и перед вами готовая страница. Текст итоговой страницы приведен в листинге 9.9.

Листинг 9.9. Страница HTML, построенная в листинге 9.8 (index.php)

<html>

<head>

<title> PHPRecipes </title>

</head>

<body bgcolor="#7b8079"
text="#ffffff" link="#e7d387" alink="#e7d387"
vlink="#e7f0e4">

<table width = "95%" cellpadding="0" cellspacing="0" border="1">

<tr>

<td valign = "top">

PHP Recipes

</td>

<td valign = "top" align="right">

August 23, 03:17 pm

</td>

</tr>

</table><table width="95%" cellspacing="0" cellpadd1ng="0" border="1">

<tr>

<td valign="top" width="25%">

<a href = "htfp://localriost/phprecipes/tutorials.php">tutorials

</a> <br>

<a href = "http://localhost/phprecipes/articles.php">articles

</a> <br>

<a href = "http://localhost/phprecipes/scripts.php">scripts</a>

<br>

<a href = "http://localhost/phprecipes/contact.php">contact</a>

<br>

</td>

<td valign="top" width="75%">

Welcome to PHPRecipes, the starting place for PHP scripts, tutorials,

and gourmet cooking tips and recipes!

</td>

</tr>

</table><table width="95%" cellspacing="0" cellpadding="0" border="1">

<tr><td valign="top" align="middle">

Copyright &copy; 2000 PHPRecipes. All rights reserved.<br>

<a href = "mailto:wj@hotmail .com">contact</a> |

<a href = "http://localhost/phprecipes/privacy.php">your privacy</a>

</td></tr>

</table>

</body>

</html>

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

Рис. 9.1. Внешний вид страницы, построенной в листинге 9.8

Оптимизация шаблонов

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

Листинг 9.10. Оптимизированный шаблон сайта (site_init.tpl)

<?

// Файл: site_init.tpl

// Назначение: инициализационный файл PhpRecipes

// Дата: 22 августа 2000 г.

$site_name = "PHPRecipes";

$site_email = "wjgilmore@hotmail.com";

$site_path = "http://localhost/phprecipes/";

function show_header($site_name) {

<html>

<head>

<title> <? print $site_name: ?> </title>

</tiead>

<body bgcolor="#7b8079"
text="#ffffff" link»"#e7d387" alink="#e7d387"
vlink="#e7f0e4">

This is the header

<hr>

function show footer ()

?>

<hr>

This Is the footer

</body>

</html>

<?

}

?>

Листинг 9.11. Применение инициализационного файла

<?

// Включить инициализационный файл

include("site_init.tpl");

// Вывести заголовок

show header ($site_name);

?>

// Содержимое основной части This is some body information

<?

// Вывести колонтитул Show_footer( );

?>

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

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

Ссылки для загрузки различных статических файлов строятся динамически. В обобщенной форме ссылка выглядит так:

<а href = "<?=$site_path:?>/static.php?content=$content">Static Page Name</a>

Начнем с создания статических страниц. Для простоты я ограничусь тремя страницами, содержащими информацию о сайте (листинг 9.12), рекламу (листинг 9.13) и контактные данные (листинг 9.14).

Листинг 9.12. Информация о сайте (about.html)

<h3>About PHPRecipes</h3>

What programmer doesn't mix all night programming with gourmet cookies. Here at PHPRecipes. hardly a night goes by without one of our coders mixing a little bit of HTML with a tasty plate of Portobello Mushrooms or even Fondue. So we decided to bring you the best of what we love most: PHP and food!

<p>

That's right, readers. Tutorials, scripts, souffles and more. <i>0nly</i> at PHPRecipes.

Листинг 9.13. Рекламная информация (advert_info.html)

<h3>Advertising Information</h3>

Regardless of whether they come to learn the latest PHP techniques or for brushing up on how

to bake chicken, you can bet our readers are decision makers. They are the Industry

professionals who make decisions about what their company purchases.

For advertising information, contact <a href - "mailto:ads@phprecipes.com

">ads@phprecipes.com</a>.

Листинг 9.14. Контактные данные (contact.html)

<h3>Contact Us</h3>

Have a coding tip? <br>

Know the perfect topping for candied yams?<br>

Let us know! Contact the team at <a href= "mailto:theteam@phprecipes.com">team@phprecipes.com</a>.

Переходим к построению страницы static.php, которая выводит запрашиваемую статическую информацию. В этот файл (см. листинг 9.15) включаются компоненты страниц нашего сайта и инициализационный файл site_init.tpl.

Листинг 9.15. Общий вывод статических страниц (static.php)

<?

// Файл: static.php

// Назначение: отображение запрашиваемых статических страниц.

// ВНИМАНИЕ: предполагается, что файл "site_init.tpl" и все

// статические файлы находятся в том же каталоге.

// Загрузить функции и переменные include("site_init.tpl"):

// Вывести заголовок show_header($site_name);

// Вывести запрашиваемое содержание include("$content.html"):

// Вывести колонтитул show footer( );

?>

Теперь все готово к построению основного сценария. Просто включите в страницу

следующие ссылки:

<а href = "static.php?content=about">Static Page Name</a></br>

<a href = "static.php?content=advert_info">Advertising Information</a></br>

<a href = "static.php?content=about">Contact Us</a></br>

Если щелкнуть на любой из этих ссылок, в браузере загружается соответствующая статическая страница, внедренная в static.php!

Итоги

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

  • обработка URL;
  • построение динамического содержания;
  • включение и построение базовых шаблонов.

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

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


Введение

Серьезно, а существовала ли разумная жизнь до возникновения Web? Тот, кто вырос в наше время, может в этом усомниться. Пришествие Интернета заложило основу для развития коммуникационных технологий, не имеющих аналогов в истории человечества. И молодежь, и пожилые люди идут в Интернет за покупками, знаниями и общением. Всего за несколько лет произошла масса событий: талантливые предприниматели зарабатывали целые состояния, возникали и рушились корпоративные империи, процветала экономика целых стран — и все благодаря провидческой идее Тима Бернерса-Ли и его коллег о том, что в один прекрасный день мир объединится при помощи гиперссылок.

Конечно, за последние десять лет Всемирная паутина прошла долгий путь — из вспомогательного инструмента научных исследований она превратилась в конгломерат, в котором задействованы прямо-таки головокружительные объемы информации. Возможно, самым важным фактором в развитии Web стала именно простота публикации информации в Web. Даже обладая минимальными познаниями, человек может установить на своем компьютере текстовый редактор, FTP-клиент и web-браузер и открыть свое «представительство» в мире электронной информации.

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


Как читать этот диск

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

В сценарном языке РНР воплотились совместные усилия разработчиков со всего мира, в результате чего был создан исключительно богатый и мощный язык web-программирования. В шестнадцати главах этого диска рассматриваются многие аспекты, обусловившие популярность языка. Книга начинается с подробного описания общих концепций и синтаксических конструкций языка, после чего читатель знакомится с аспектами, относящимися непосредственно к web-программированию, — такими, как динамическое построение страниц, создание ссылок и взаимодействие с базами данных. Затем мы рассмотрим некоторые нетривиальные вопросы применения РНР в web-программировании, включая XML, JavaScript, СОМ и безопасность.

Книга разделена на три части. В первую часть «Основы РНР» входят главы с 1 по 8.

В главе 1 «Знакомство с РНР» приводятся общие сведения о языке РНР, его истории и ключевых возможностях. В ней описан процесс установки и настройки РНР и Apache. Глава завершается обзором базовых синтаксических элементов РНР, необходимых для написания базового сценария.

В главе 2 «Переменные и типы данных» описаны различные типы данных, поддерживаемые РНР, правила выбора имен переменных и особенности хранения информации.

Глава 3 «Выражения, операторы и управляющие конструкции» дополняет материал главы 2. Из нее вы узнаете, как выполняются операции со значениями переменных. В этой главе также представлены основные синтаксические структуры и средства для создания больших и сложных сценариев.

В главе 4 «Функции» описываются некоторые аспекты построения модульного кода РНР, подходящего для многократного использования.

Глава 5 «Массивы» посвящена самой распространенной структуре данных — массивам. В ней приведены общие сведения и примеры создания одномерных и многомерных массивов, как индексируемых, так и ассоциативных.

В главе 6 «Объектно-ориентированные возможности РНР» особо выделены аспекты РНР, относящиеся к объектно-ориентированному программированию. Хотя РНР и не является объектно-ориентированным языком, поддержка некоторых базовых концепций РНР заметно упрощает эффективное программирование и сопровождение кода.

Глава 7 «Файловый ввод/вывод и файловая система» содержит сведения об одной из самых выдающихся возможностей РНР — обработке файлов данных. Кроме того, в ней приведена информация о выполнении средствами РНР операций с каталогами на сервере.

В главе 8 «Строки и регулярные выражения» операции со строковыми данными рассматриваются в контексте стандартных функций РНР и регулярных выражений. Читатель знакомится с двумя разновидностями синтаксиса регулярных выражений — POSIX-совместимыми и Peri-совместимыми выражениями.

Вторая часть «РНР и Web» построена на фундаменте материала первых восьми глав. В нее входят главы с 9 по 13.

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

В главе 10 «Формы» рассматриваются средства РНР для работы с формами HTML при сборе, отображении и обработке пользовательского ввода.

Глава 11 «Базы данных» посвящена обширной поддержке операций с базами данных. Основное внимание уделяется СУБД MySQL, на примере которой показано, как РНР используется для организации эффективного интерфейса между базой данных и Web.

В главе 12 «Шаблоны» описана нетривиальная схема построения страниц по шаблонам.

Глава 13 «Cookie и отслеживание сеанса» показывает, как в РНР организуются сохранение персональных настроек и сбор данных о посетителях сайта.

В третьей части «РНР для профессионалов» представлены более сложные стороны РНР. Эта часть включает главы с 14 по 16.

Глава 14 «РНР и XML» знакомит читателя с XML и показывает, как использовать РНР для анализа и преобразования документов XML.

Глава 15 «JavaScript и СОМ» показывает, как при помощи JavaScript сделать интерфейс web-программ более удобным и интерактивным. Кроме того, в этой главе рассматривается возможность взаимодействия с СОМ-объектами Windows.

В главе 16 «Безопасность» описаны некоторые аспекты безопасности в Web и их реализация в РНР. В этой главе рассматриваются проблемы конфигурации РНР, защищенное программирование, шифрование данных, проблемы электронной коммерции и аутентификация пользователей.