САМОУЧИТЕЛЬ PHP 4

         

Неявное изменение формы


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

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

<?session_start()?>

<form action=aaa method=post>

</form>

А вот почти дословно то, что выдается в браузере (Internet Explorer) после запуска этого сценария и выбора в меню пункта Просмотр в виде HTML:

<form action="aaa" method="post">

<INPUT TYPE=HIDDEN NAME="PHPSESSID" VALUE="0a717e848e91db11b524a">

</form>

Как видим, PHP добавил в форму скрытое поле с нужным именем и значением. Он также заключил в  кавычки значения атрибутов тэга <form> (прав­да, я сам не ожидал увидеть такой эффект, когда опробовал этот сценарий). Что же, кавычки так кавычки, хуже от этого не будет….



Неявное изменение гиперссылок


Похоже, что вы уже начали думать о том, как же это все-таки неудобно — везде вставлять участки кода <?=SID?>, и, пропусти вы их в одном месте, придется долго искать ошибку? Что же, законный повод для беспокойства, но, к счастью, разработчики PHP уберегли нас и от этой напасти.

Вы не поверите, но, если в какой-нибудь гипессылке вы по ошибке пропустите <?=SID?>, PHP вставит его за вас автоматически. Причем так, чтобы это никак не повредило другим параметрам, возможно, уже присутствующим в URL. Если вы в шоке, то запустите следующий сценарий в браузере, а затем наведите мышь на гиперссылку и посмотрите в строке состояния, какой адрес имеет ссылка:

<?session_start()?>

<body>

<a href=/path/to/something.php>Click here!</a><br>

<a href=/path/to/something.html?a=aaa&b=bbb>Click here!</a><br>

<a href=/>Click here!</a><br>



</body>

Вот адреса этих ссылок с точки зрения браузера:

http://www.somehost.ru/path/to/something.php?PHPSESSID=8114536a920bfb01f

http://www.somehost.ru/path/to/something.html?a=aaa&b=bbb&PHPSESSID=86a20

http://www.somehost.ru/?PHPSESSID=8114536a920bfb2a

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

на который он указывает.

Описанная только что возможность работает лишь в том случае, если в настройках PHP установлен в значение истина параметр session.use_trans_sid.

Он как раз и включен по умолчанию.

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



Немного теории


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

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

У каждого Cookie есть определенное время жизни, по истечение которого он автоматически уничтожается. Существуют также Cookies, которые "живут"

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

[E84] ).

Каждый Cookie устанавливается сценарием на сервере.  Для этого он должен послать браузеру специальный заголовок вида:

Set-cookie: äàííûå

Однако в PHP этот процесс скрыт за функцией SetCookie(), которую мы сейчас рассмотрим, так что нам нет смысла вдаваться в детали.

Пожалуй, из теории осталось только добавить, что сценарии с других серверов, а также расположенные в другом каталоге, не будут извещены о "чу­жих"

Cookies. Для них их как словно и нет. И это правильно с точки зрения безопасности — кто знает, насколько секретна может быть информация, сохраненная в Cookies? А вдруг там хранится номер кредитной карточки или пароль доступа к Пентагону?..



Несколько слов о флажках checkbox


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

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

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

Листинг 33.5. Гарантированная установка значений флажков

<?

if(@$Go) {

  foreach($Known as $k=>$v)

    if($v) echo "Вы знаете язык $k!<br>";

      else echo "Вы не знаете языка $k. <br>";

}

?>

<form action=lang.php method=post>

Какие языки программирования вы знаете?<br>

<input type=hidden name=Known[PHP] value=0>

  <input type=checkbox name= Known[PHP] value=1>PHP<br>

<input type=hidden name=Known[Perl] value=0>

  <input type=checkbox name= Known[Perl] value=1>PHP<br>

<input type=submit name=Go value="Go!">

</form>

Теперь в случае, если пользователь не выберет какой-нибудь из флажков, браузер отправит сценарию пару Known[язык]=0, сгенерированную соответствующим скрытым полем, и в массиве $Known создастся соответствующий элемент. Если пользователь выбрал

флажок, эта пара также будет послана, но сразу же после нее последует пара Known[язык]=1, которая "перекроет" предыдущее значение.

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

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



Независимый переключатель (checkbox)


<input type=checkbox

  name=èìÿ

  value=çíà÷åíèå

  [checked]

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



Ноль или более совпадений


Наиболее важный из них — звездочка *. Она обозначает, что предыдущий символ может быть повторен ноль или более раз (то есть, возможно, и ни разу). Например, выражение a-*- соответствует строке, в которой есть буква a, затем — ноль или более минусов и, наконец, завершающий минус.

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

так много символов, как это возможно. К примеру, для строки a---b найдется подстрока a--- (звездочка "заглотила"

2 минуса), а не a- (звездочка захватила 1 минус). Это — так называемая "жадность"

квантификатора, и в PHP нет, к сожалению, возможности "убавить ему аппетит".

Язык PCRE, â îòëè÷èå îò RegEx, позволяет ограничивать "жадность" квантификаторов.



Ноль или одно совпадение


И уж чтобы совсем облегчить жизнь, иногда используют еще один квантификатор — знак вопроса ?. Он обозначает, что предыдущий символ может быть повторен ноль или один (но не более!) раз. Например, выражение

[a-zA_Z]+\r?\n определяет строки, в которых последнее слово прижато к правому краю строки. Если мы работаем в Unix, то там в конце строки символ \r обычно отсутствует, тогда как в текстовых файлах Windows каждая строка заканчивается парой \r\n. Для того чтобы сценарий правильно работал в обоих системах, мы должны учесть эту особенность — возможное наличие \r перед концом строки.



Нулевой ключ


А что, если в массиве встретится ключ 0 (хотя для массивов имен это, согласитесь, маловероятно)? Давайте еще раз посмотрим на первый цикл перебора:

for(Reset($Names); ($k=key($Names)); Next($Names))

  echo "Âîçðàñò $k— {$Names[$k]} ëåò\n";

В этом случае выражение ($k=key($Names)), естественно, будет равно нулю, и цикл оборвется, чего бы нам совсем не хотелось.

Именно по

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



О чем эта книга


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

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

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

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

Возможно, многие детали (даже важные) я опустил, если они не относятся к категориям приемов:

r    которые наиболее часто применяются;

r    без которых нельзя обойтись в Web-программировании.

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



О сравнении строк и инструкции if-else


Теперь я хотел бы рассмотреть одно тонкое место в интерпретаторе PHP, касающееся немного неправильной работы со строками. Заключается оно вот в чем. Если мы используем операторы сравнения ==

и !=

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

Листинг 12.1. Внимание! Опасное место!

$one=1   // ÷èñëî îäèí

$zero=0  // присваиваем ÷èñëî íоëü

if($one=="") echo 1    // î÷åâèäíî, íå ðàâíî— íå âûâîäèò 1

if($zero=="") echo 3   // Âíèìàíèå! Âîïðåêè îæèäàíèÿì ïå÷àòàåò 3!

if(""==$zero) echo 4   // È ýòî òîæå íå ïîìîæåò!..

if("$zero"=="") echo 5 // Не работает в некоторых версиях PHP 3

if(strval($zero)=="") echo 6; // Âîò òåïåðü ïðàâèëüíî — íå âûâîäèò 6

if($zero==="") echo 7  // Самый лучший способ, но не действует в PHP 3

Получается, что в операциях сравнения пустая строка "" прежде всего

трактуется как 0 (ноль) и уж затем — как "пусто"? Это звучит довольно парадоксально, но это действительно так. Операнды сравниваются как строки только в том случае, если они оба — строки, в противном случае идет числовое сравнение. При этом пустая строка воспринимается как 0, впрочем, как и любая другая, которую интерпретатору не удалось перевести в число.




В первых версиях PHP 3 при присоединении к числовому нулю пустой строки этот ноль не менял типа, не становился строкой "0". Видимо, срабатывала какая-то оптимизация, и PHP просто пропускал этот бессмысленный, на его взгляд, шаг. Проведенные мной тесты показывают, что в PHP версии 3.0.12 и старше эта ошибка исправлена, но все же иногда нужно иметь ее в виду, особенно, если сценарии должны быть хорошо переносимыми.

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

Впрочем, это не распространяется на новый оператор PHP версии 4 === (тройное равенство, или оператор эквивалентности). Его использование заставляет интерпретатор всегда сравнивать величины и по значению, и по их типу. Итак, с точки зрения PHP 0=="", но 0!==="". Если вы не собираетесь программировать на PHP версии, ниже третьей, рекомендую всегда использовать === вместо strval(), как это было сделано в листинге 12.1.

Существует одна стандартная ошибка, которую делают многие. Вот в чем она состоит. Есть такая функция — strpos($str,$what), которая возвращает позицию подстроки $what в строке $str или false, если подстрока не найдена. Пусть нам нужно проверить, встречается ли в некоторой строке $str подстрока <? (и напечатать "это PHP-программа", если встречается). Как мы знаем, вариант

if(strpos($str,"<?")!=false)

  echo "ýòî PHP-ïðîãðàììà";

не годится,

если <? находится[E51]  в самом начале строки (в этом случае не будет выдано наше сообщение, хотя подстрока в действительности найдена, и функция возвратила 0, а не false).

Если вы еще собираетесь работать с PHP версии 3, указанную проблему можно решить так:

if(strval(strpos($str,"<?"))!="")

  echo "ýòî PHP-ïðîãðàììà";

Конечно, выглядит это немного "накручено", зато действительно работает. Приятно отметить, что в PHP версии 4 проблема решается гораздо более изящным образом:

if(strpos($str,"<?")!===false)

  echo "ýòî PHP-ïðîãðàììà";

Рекомендую всегда применять последний способ.



Обратите внимание, что мы используем оператор !=== именно с константой false, а не с пустой строкой "". Дело в том, что для этого оператора false!==="", в то время как, разумеется, false=="".


О текстовых и бинарных файлах


Во многих (да что там — практически во всех) языках программирования для работы с текстовыми файлами применяется некоторый трюк. Вот в чем он заключается. Не секрет, что в Unix-системах для отделения одной строки файла от другой используется один специальный символ — его принято обозначать \n.

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

 Windows по историческим причинам для разделения строк применяется не один, а сразу два символа, следующих подряд, — \r\n. Для того чтобы языки программирования были лучше переносимы с одной операционной системы на другую, при чтении текстовых файлов эта комбинация \r\n

преобразуется "на лету" в один символ \n, так что программа и не замечает, что формат файла не такой, как в Unix. В результате этой деятельности, если мы, например, прочитаем содержимое всего текстового файла в строку, то длина такой строки наверняка окажется меньше физического размера файла — ведь из нее "съелись"

некоторые символы \r.

Это относится к системе Windows и MacOS (кстати, в последней применяется комбинация не \r\n, а наоборот — \n\r, что довольно-таки забавно). При записи строки в текстовый файл происходит в точности наоборот: один \n становится на диске парой \r\n.

Впрочем, практически во всех языках программирования вы можете и отключить режим автоматической трансляции \r\n

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


Так как PHP был написан целиком на Си, а Си использует трансляцию символов перевода строк, то описанная техника работает и в PHP. Однако тут есть один очень опасный момент. Дело в том, что разработчики PHP в официальной документации к функции fopen() старательно умалчивают о том, что èíòåðïðåòàòîð может работать с файлами в режиме трансляции символа перевода строки. Так вот, я возьму на себя смелость заявить, что такая возможность в действительности существует, а тесты подтвердили, что ее можно корректно использовать как в Windows и MacOS, так и в Unix. Подробнее об этом мы поговорим при рассмотрении функции fopen().

Если файл открыт в режиме бинарного чтения/записи, то PHP совершенно все равно, что вы читаете или пишете. Вы можете совершенно спокойно считать содержимое какого-нибудь бинарного файла (например, GIF-ðèñóíêà) в обычную строковую переменную, а потом записать эту строку в другой файл, и при этом информация нисколько не исказится. Правда, при чтении текстового файла в Windows вы получите символы \r\n

в конце строки вместо одного \n, если не предпримете некоторых действий, а откроете файл, как об этом написано в документации. Об этом речь ниже.


Объектно-ориентированное программирование на PHP


В последние 10 лет идея объектно-ориентированного программирования (ООП), кардинально новая идеология написания программ, все более занимает умы программистов. И это неудивительно. В самом деле, сейчас происходит (а точнее, уже произошло, особенно после выхода стандарта на С++ от 98-го года и изобретения таких языков, как Java и Delphi) примерно то же, что произошло в начале 80-х годов при появлении идеи структурного программирования.

Объектно-ориентированные программы более просты и мобильны, их легче модифицировать и сопровождать, чем их "традиционных" собратьев. Кроме того, похоже, сама идея объектной ориентированности при грамотном ее использовании позволяет программе быть даже более защищенной от различного рода ошибок, чем это задумывал программист в момент работы над ней. Однако ничего не дается даром: сами идеи ООП довольно трудны для восприятия "с нуля", поэтому до сих пор очень большое количество программ (различные системы Unix, Apache, Perl, да и сам PHP) все еще пишутся на старом добром "объектно-неориентированном" Си. Что ж, очень жаль. Ощущение жалости усиливается, если посмотреть на исходные тексты этих программ, поражающие своей многословностью...

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

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



Object


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


Объект какой-то структуры. Обычно эта структура будет уточняться.



Обновление записей


update Òàáëèöà set(ÈìÿÏîëÿ1='çí1', ÈìÿÏîëÿ1='çí2', ...) where Âûðàæåíèå

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



Обработчик Apache для шаблонизатора


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

Листинг 30.13. Обработчик шаблонизатора: TemplateHandler.php

<?

// Проверяем, не пытается ли пользователь запустить обработчик

// напрямую, минуя Apache.

$FileName=strtr(__FILE__,"\\","/");

$ReqName=ereg_Replace("\\?.*","",strtr(getenv("REQUEST_URI"),"\\","/"));

if(eregi(quotemeta($ReqName),$FileName)) {

  // Выводим сообщение об ошибке.

  include "TemplateHandler.err";

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

  $f=fopen("TemplateHandler.log","a+");

  fputs($f,date("d.m.Y H:i.s")." $REMOTE_ADDR — Access denied\n");

  fclose($f);

  // Завершаем работу.

  exit;

}

// Все в порядке — корректируем переменные окружения в соответствии

// с запрошенным пользователем адресом.

@putenv("REQUEST_URI=".

  $GLOBALS["HTTP_ENV_VARS"]["REQUEST_URI"]=

  $GLOBALS["REQUEST_URI"]=

  getenv("QUERY_STRING")

);

@putenv("QUERY_STRING=".

  $GLOBALS["HTTP_ENV_VARS"]["QUERY_STRING"]=

  $GLOBALS["QUERY_STRING"]=

  ereg_Replace("^[^?]*\\?","",getenv("QUERY_STRING"))

);

// Подключаем библиотекаря.

$INC[]=getcwd();

include "Librarian.phl";

// Переходим в каталог со страницей.

chdir(dirname($SCRIPT_FILENAME));

// Загружаем шаблонизатор.

Uses("Template");

// Выводим содержимое главного блока страницы.

echo RunUrl($SCRIPT_NAME);

?>



Обработчики Apache


Итак, что же такое обработчик Apache? На самом деле мы постоянно сталкиваемся с одним из классических примеров обработчика. Да-да, вы уже догадались: это сам PHP. Если чуть углубиться в теорию, то обработчиком

называется сценарий (возможно, встроенный в сам сервер, как это происходит с PHP), который запускается сервером при попытке пользователя открыть ту или иную страницу определенного типа.

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

Но как же сопоставить идентификатор обработчика тому сценарию, который содержит его код? У сервера Apache для этого есть специальная директива под названием Action. Где задается эта директива? В любом файле конфигурации Apache. Конечно, удобнее всего это делать в файле .htaccess, расположенном в корневом каталоге виртуального хоста, чтобы изменения распространились сразу на весь сервер. Мы уже рассматривали такую стратегию выше, только теперь все будет чуточку сложнее.

Вот требуемые директивы. Поместим их, как водится, в главный .htaccess-файл хоста.

# Сначала связываем имя обработчика с конкретным файлом.

# Знак "?" говорит серверу, что исходный URL запроса следует

# передать сценарию методом GET, т. е. через QUERY_STRING.

Action libhandler "/lib/libhandler.php?"

# Теперь уведомляем сервер, документы какого типа мы желаем

# "пропускать" через наш обработчик.

AddHandler libhandler .html .htm

В этом фрагменте есть два тонких места.

r    Путь к сценарию обработчика всегда

указывается как абсолютный URL без указания имени хоста и порта. Мы не можем задать здесь ни путь к файлу, ни даже относительный URL. По той причине, чтобы позволить одному обработчику "передавать эстафету" другому. В самом деле, ведь это и происходит в нашем примере: Apache сначала определяет, что документ нужно "пропустить" через обработчик libhandler, а т. к. он представляет собой ни что иное, как сценарий на PHP, то и через интерпретатор PHP. В деталях затронутый процесс чуть сложнее, но мы не будем в него углубляться.


r    После URL сценария в директиве Action следует знак ?. Зачем он? Это связано с механизмом, который использует Apache для того, чтобы определить конечный обработчик для того или иного документа. Когда пользователь посылает серверу URL, который, как Apache "знает", подходит под одну из команд Action, к этому URL слева просто присоединяется второй параметр директивы, и все начинается сначала — до тех пор, пока не будет найден последний обработчик. Например, если пользователь ввел /dir/file.html, то благодаря директиве Action указанный адрес преобразуется в /lib/libhandler.php?/dir/file.html. Это — ни что иное, как адрес PHP-сценария с параметром, который будет передан программе, как обычно, через переменную окружения QUERY_STRING.

Теперь сервер знает, что все документы с расширением html и htm нужно обрабатывать при помощи сценария, расположенного по адресу /lib/libhandler.php. Точнее, при каждой попытке открыть страницы с указанными расширениями Apache будет запускать наш сценарий и в числе обычных переменных окружения отправлять ему несколько специальных, содержащих первичную информацию о запросе, переданном пользователем. Мы сейчас рассмотрим эти переменные на практике. Если вас интересует их полный список, попробуйте распечатать массив $GLOBALS или воспользоваться функцией phpinfo(), вставив ее первой и единственной командой обработчика libhandler.php.



Вы, возможно, спросите, почему же мы не добавили в список расширений, на которые будет "реагировать" сценарий, еще одно — php? Давайте посмотрим, что бы произошло, поступи мы так. Предположим, пользователь хочет загрузить страницу /a.php. Apache "видит", что расширение у нее — php, и запускает обработчик с именем /lib/libhandler.php. Точнее, он "сваливает" всю работу на сценарий libhandler.php. Еще точнее — перенаправляет сервер по адресу /lib/libhandler.php?a.php! И тут же зацикливается, потому что у этого сценария расширение — также php. Итак, сервер начинает вызывать сценарий снова и снова, все удлиняя его URL — до тех пор, пока не "поймет": что-то неверно, и пора аварийно завершаться, о чем и сообщает в файлах журнала. О том, как решить эту проблему, рассказано в самом конце главы.



Ну вот, у нас уже почти все готово. Осталось только написать сам код обработчика. Это не так уж и сложно. Но прежде давайте вспомним, зачем мы вообще связались с обработчиками. Для автоматической загрузки библиотекаря перед выполнением того или иного сценария, помните? Что же, вот пример (листинг 29.5).



Мы подразумеваем, что обработчик libhandler.php находится в том же самом каталоге, что и библиотекарь с большинством модулей. Это довольно удобно, поскольку позволяет нам задавать путь к каталогу с модулями лишь в единственном месте — в директиве Action файла .htaccess, да и то в виде относительного URL. Оцените, насколько это проще для будущих модификаций сайта.

Листинг 29.5. Обработчик /lib/libhandler.php с подключением библиотекаря

<?

// Прежде всего, устанавливаем свои каталоги поиска модулей.

// Это, по нашей договоренности, — текущий в данный момент каталог.

$INC[]=getcwd();

// Ïðîâåðÿåì, íå ïûòàåòñÿ ëè ïîëüçîâàòåëü çàïóñòèòü îáðàáîò÷èê íàïðÿìóþ,

// ìèíóÿ Apache — íàïðèìåð, ïóòåì íàáîðà â áðàóçåðå àäðåñà

// /lib/libhandler.php. Òàê êàê àäðåñ, ââåäåííûé ïîëüçîâàòåëåì,

// âñåãäà ïåðåäàåòñÿ â ïåðåìåííîé îêðóæåíèÿ REQUEST_URI, òî íóæíî



// "áèòü òðåâîãó", åñëè ïåðåäàííàÿ ñòðîêà àäðåñà âñòðå÷àåòñÿ

// â èìåíè ôàéëà îáðàáîò÷èêà (ïðè÷åì â ëþáîì ðåãèñòðå ñèìâîëîâ).

// Ìû íå çàáûëè îòрåçàòü â ýòîé ñòðîêå ÷àñòü ïîñëå ?, ïîòîìó ÷òî

// îíà áóäåò ìåøàòü ïðè ñðàâíåíèè ñ èìåíåì ôàéëà.

// Ê ñîæàëåíèþ, ïîõîæå, ýòî åäèíñòâåííûé ïåðåíîñèìûé ìåæäó îïåðàöèîííûìè

// ñèñòåìàìè ñïîñîá ïðîâåðêè ëåãàëüíîñòè çàïóñêà îáðàáîò÷èêà.



$FileName=strtr(__FILE__,"\\","/");

$ReqName=ereg_Replace("\\?.*","",strtr(getenv("REQUEST_URI"),"\\","/"));

if(eregi(quotemeta($ReqName),$FileName)) {

  // Выводим сообщение об ошибке

  include "libhandler.err";

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

  $f=fopen("libhandler.log","a+");

  fputs($f,date("d.m.Y H:i.s")." $REMOTE_ADDR - Access denied\n");

  fclose($f);

  // Завершаем работу

  exit;

}

// Все в порядке — корректируем переменные окружения в соответствии

// с запрошенным пользователем адресом.

@putenv("REQUEST_URI=".

  $GLOBALS["HTTP_ENV_VARS"]["REQUEST_URI"]=

  $GLOBALS["REQUEST_URI"]=

  getenv("QUERY_STRING")

);

@putenv("QUERY_STRING=".

  $GLOBALS["HTTP_ENV_VARS"]["QUERY_STRING"]=

  $GLOBALS["QUERY_STRING"]=

  ereg_Replace("^[^?]*\\?","",getenv("QUERY_STRING"))

);

// Подключаем библиотекарь

include "librarian.phl";

   // Здесь можно выполнить еще какие-нибудь действия...

   // . . .

// Запускаем тот сценарий, который был запрошен пользователем

chdir(dirname($SCRIPT_FILENAME));

include $SCRIPT_FILENAME;

?>

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

<head><title>Доступ запрещен!</title></head>

<body>

<h2>Доступ запрещен!</h2>

Пользователь сделал попытку нелегально вызвать обработчик Apache,

отвечающий за автоматическое подключение библиотекаря. Так как это

свидетельствует о его желании нелегально проникнуть на сервер,

попытка была пресечена. Информация о пользователе записана

в файл журнала.

</body>

В результате

мы пришли к тому, что теперь все документы с расширениями html и htm рассматриваются как сценарии на PHP. Они запускаются уже после того, как подключен библиотекарь, так что могут пользоваться функцией Uses().



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


Обработка ошибок


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

int mysql_errno([int $link_identifier])

Функция возвращает номер последней зарегистрированной ошибки. Идентификатор соединения $link_identifier можно не указывать, если за время работы сценария было установлено только одно соединение.

string mysql_error([int $link_identifier])

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



Общая структура книги


Книга состоит из пяти частей, содержащих в общей сложности 33 главы, и двух приложений. Непосредственное описание языка PHP начинается с третьей части. Это объясняется необходимостью прежде узнать кое-что о CGI (Common Gateway Interface— Общий шлюзовой интерфейс) —первая часть, а также выбрать подходящий инструментарий и Web-сервер для программирования — вторая часть. В четвертой части разобраны наиболее полезные стандартные функции языка. Пятая часть посвящена различным приемам программирования на PHP с множеством примеров. Приложения содержат техническую информацию, которая может иногда пригодиться Web-программисту.

Теперь чуть подробнее о каждой части книги. В первой рассматриваются теоретические аспекты программирования в Web, а также основы того механизма, который позволяет писать программы в Сети. Если вы уже знакомы с этим материалом (например, занимались программированием на Perl или других языках), можете ее смело пропустить. Вкратце я опишу, на чем базируется Web, что такое интерфейс CGI, как он работает на низком уровне, как используются возможности языка HTML при программировании Web, как происходит взаимодействие CGI и HTML и многое другое. В принципе, вся теория по Web-программированию коротко изложена именно в этой части книги. Так как CGI является независимым от платформы интерфейсом, материал не "привязан" к конкретному языку (хотя в примерах используется Си как наиболее универсальный язык). Если вы не знаете языка Си, не стоит отчаиваться: немногочисленные примеры на этом языке не настолько сложны, чтобы в них можно было "запутаться". К тому же, каждое действие подробно комментируется. Большинство описанных идей будет повторно затронуто в последующих главах, посвященных уже языку PHP.

Вторая часть книги довольно небольшая и состоит из разного рода дополнительной информации, связанной по большей части с серверным програм­мным обеспечением Apache. Сервер Apache — один из самых популярных в мире, на нем построено около двух третей хостов Интернета (по крайней мере, на настоящий момент). Главное его достоинство — простое и в то же время универсальное конфигурирование, что позволяет создавать довольно сложные и большие серверы на его основе. Думаю, вряд ли в ближайшее время кто-либо будет серьезно использовать PHP под управлением какого-то другого сервера, нежели Apache. Основное внимание во второй части уделено установке и использованию Apache для Windows, поскольку, как мы увидим ниже, это очень сильно облегчает программирование и отладку сценариев. Не секрет, что подчас выбор неверного и неудобного инструментария только из-за того, что "им пользуются все", является серьезной помехой при программировании. Именно из-за этого многие "закаленные" Web-программисты "старого образца" не принимают PHP всерьез. Вторая часть книги призвана раз и навсегда решить эту проблему.


Третья часть целиком посвящена основам PHP. Язык PHP — сравнительно молодой, но в то же время удивительно удобный и гибкий язык для программирования Web. С помощью него можно написать 99% программ, которые обычно требуются в Интернете. Для оставшегося 1% придется использовать Си или Perl (или другой универсальный язык). Впрочем, даже это необязательно: вы сильно облегчите себе жизнь, если интерфейсную оболочку будете разрабатывать на PHP, а ядро — на Си, особенно, если ваша программа должна работать быстро, например, если вы пишете поисковую систему. Последняя тема в этой книге не рассматривается, поскольку требует довольно большого опыта низкоуровневого программирования на языке Си, а потому не вписывается в концепцию данной книги.

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

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

В приложениях приведена дополнительная информация, касающаяся Web-программирования. В Приложении 1 содержится полный перевод на русский язык комментариев в файле конфигурации Apache httpd.conf. Она может очень пригодиться вам, если вы собираетесь тесно взаимодействовать с этим сервером в своих сценариях. Приложение 2 включает аналогичный перевод комментариев, сопровождающих файл конфигурации интерпретатора PHP. Оно призвано помочь лучше систематизировать сведения о конфигурировании PHP, полученные из других глав книги (и увидеть реальный пример использования многих описанных директив).


Общий синтаксис определения функции


В общем виде синтаксис определения функции таков:

function èìÿ_ôóíêöèè(àðã1[=зн1], àðã2[=зн2], ... àðãN[=знN])

{  îïåðàòîðû_òåëà_ôóíêöèè;

}

Имя функции должно быть уникальным с точностью до регистра букв. Это означает, что, во-первых, имена MyFunction, myfunction и даже MyFuNcTiOn будут считаться одинаковыми, и, во-вторых, мы не можем переопределить уже определенную функцию (стандартную или нет — не важно), но зато можем давать функциям такие же имена, как и переменным в программе (конечно, без знака $ в начале). Список аргументов, как легко увидеть, состоит из нескольких перечисленных через запятую переменных, каждую из которых мы должны будем задать при вызове функции (впрочем, когда для этой переменной присвоено через знак равенства значение по умолчанию (обозначенное =знM), ее можно будет опустить; см. об этом чуть ниже). Конечно, если у функции не должно быть аргументов вовсе (как это сделано у функции time()), то следует оставить пустые скобки после ее имени, например:

function SimpleFunction() { ... }

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



Обзор обработчиков


Всего существует 6 функций, связанных с сессиями, которые PHP вызывает в тот или иной момент работы механизма обработки сессий. Им передаются различные параметры, необходимые для работы. Сейчас я перечислю все эти функции вместе с их описаниями.

bool handler_open(string $save_path, string $session_name)

Функция вызывается, когда вызывается session_start(). Обработчик должен взять на себя всю работу, связанную с открытием базы данных для группы сессий с именем $session_name. В параметре $save_path передается то, что было указано при вызове session_save_path()

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

bool handler_close()

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

string handler_read(string $sid)

Вызов обработчика происходит, когда нужно прочитать данные сессии с идентификатором $sid

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

имя1=значение1;имя2=значение2;имя3=значение3;...;

Здесь имяN задает имя очередной переменной, зарегистрированной в сессии, а значениеN— результат вызова функции Serialize() для значения этой переменной. Например, запись может иметь следующий вид:

foo|i:1;count|i:10;

Она говорит о том, что из временного хранилища были прочитаны две целые переменные, первая из которых равна 1, а вторая — 10.

string handler_write(string $sid, string $data)

Этот обработчик предназначен для записи данных сессии с идентификатором $sid во временное хранилище — например, открытое ранее обработчиком handler_open(). Параметр $data задается в точно таком же формате, который был описан выше. Фактически, чаще всего действия этой функции сводятся к записи в базу данных строки $data без каких-либо ее изменений.

bool handler_destroy(string $sid)

Обработчик вызывается, когда сессия с идентификатором $sid

должна быть уничтожена.

bool handler_gc(int $maxlifetime)


Данный обработчик — особенный. Он вызывается каждый раз при завершении работы сценария. Если пользователь окончательно "покинул"

сервер, значит, данные сессии во временном хранилище можно уничтожить. Этим и должна заниматься функция handler_gc(). Ей передается в параметрах то время (в секундах), по прошествии которого PHP принимает решение о необходимости "почистить перышки", или "собрать мусор" (garbage collection) — т. е., это максимальное время существования сессии.

Как же должна работать рассматриваемая функция? Очень просто. Например, если мы храним данные сессии в базе данных, мы просто должны удалить из нее все записи, доступ к которым не осуществлялся более, чем $maxlifetime секунд. Таким образом, "застарелые"

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



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


Одинаковые ключи


Первый недостаток довольно фундаментален: мы не можем одновременно перебирать массив в двух вложенных циклах или функциях. Причина вполне очевидна: второй вложенный for "испортит"

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



Одно или более совпадений


Возможно, вы заметили некоторую неуклюжесть в предыдущем примере.

В самом деле, фактически мы составляли выражение, которое ищет строки с a и одним или более минусом. Можно было бы записать его и так: a--*, но лучше воспользоваться специальным квантификатором, который как раз и обозначает "одно или более совпадений"— символом плюса +. С его помощью можно было бы выражение записать лаконичнее: a-+, что буквально и читается как "a и один или более минусов". Вот пример выражения, которое определяет, есть ли в строке английское слово, написанное через дефис: [a-zA-Z]+-[a-zA-Z]+.



Операции инкремента и декремента


Для операций $a+=1 и $b-=1 в связи с их чрезвычайной распространенностью в PHP ввели, как и в Си, специальные операторы. Итак:

r    $a++ — увеличение переменной $a на 1;

r    $a-- — уменьшение переменной $a на 1.

Как и в языке Си, эти операторы увеличивают или уменьшают значение переменной, а [В. О.35] в выражении возвращают значение переменной $a до изменения. Например:

$a=10;

$b=$a++;

echo "a=$a, b=$b"; // âûâåäåò a=11, b=10

Как видите, сначала переменной $b присвоилось значение переменной $a, а уж затем последняя была инкрементирована. Впрочем, выражение, значение которого присваивается переменной $b, может быть и сложнее — в любом случае, инкремент $a произойдет только после его вычисления.

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

изменения. Вот пример:

$a=10;

$b=--$a;

echo "a=$a, b=$b"; // âûâåäåò a=9, b=9

Операторы инкремента и декремента на практике применяются очень часто. Например, они встречаются практически в любом цикле for.



Операции эквивалентности


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

$a=10;

$b="10";

if($a==$b) echo "a è b ðàâíû";

И это несмотря на то, что переменная $a представляет собой число, а $b — строку. Впрочем, данный пример показывает, каким PHP может быть услужливым, когда нужно. Давайте теперь посмотрим, какой казус может породить эта "услужливость".

$a=0;  // íîëü

$b=""; // ïóñòàÿ ñòðîêà

if($a==$b) echo "a è b ðàâíû";

Хотя $a и $b явно не равны даже в обычном понимании этого слова, программа заявит, что они совпадают. Почему так происходит? Дело в том, что если один из операндов логического оператора может трактоваться как число, то оба операнда трактуются как числа. При этом пустая строка превращается в 0, который затем и сравнивается с нулем. Неудивительно, что оператор echo срабатывает.

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

$a=0;  // íîëü

$b=""; // ïóñòàÿ ñòðîêà

if($a===$b) echo "a è b ðàâíû";

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

$a=array(’a’=>’aaa’);

$b=array(’b’=>’bbb’);

if($a==$b)  echo "Ñ èñïîëüçîâàíèåì == a=b<br>";

if($a===$b) echo "Ñ èñïîëüçîâàíèåì === a=b<br>";

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

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



Операции над массивами


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



Операции присваивания


Основным из этой группы операций является оператор присваивания =. Еще раз напомню, что он не обозначает "равенство", а говорит интерпретатору, что значение правого выражения должно быть присвоено переменной слева. Например:

$a = ($b = 4) + 5;

После этого $a равно 9, а $b равно 4.

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

Помимо этого основного оператора, существует еще множество комбинированных— по одному на каждую арифметическую, строковую и другую операцию. Например:

$a = 10;

$a +=[В. О.34]  4;         // ïðèáàâèòü ê $a 4

$s = "Hello";

$s .= " world!"; // òåïåðü â $s "Hello world!"

Думаю, не стоит особо на них задерживаться.



Операции сравнения


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

r    a == b — истина, если a равно b.

r    a != b — истина, если a не равно b.

r    a < b  — истина, если a меньше b.

r    a > b  — аналогично больше.

r    a <= b — истина, если a меньше либо равно b.

r    a >= b — аналогично больше либо равно.

Следует отметить, что в PHP сравнивать можно только скалярные (то есть строки и числа) переменные. Для массивов и объектов этого делать нельзя. Их даже нельзя сравнивать на равенство (при помощи оператора ==), но при выполнении такой операции PHP не выдает предупреждения. Так что удивившись как-то раз, почему это два совершенно разных массива при сравнении их с помощью == оказываются вдруг одинаковыми, вспомните, что перед сравнением оба операнда преобразуются в слово array, которое потом и сравнивается.



Оператор альтернативы


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

Но в языке RegEx есть возможность задавать альтернативы не одиночных символов, а сразу их групп. Это делается при помощи оператора |.

Вот несколько примеров его работы.

r

Выражение 1|2|3 полностью эквивалентно выражению [123], но сопоставление происходит несколько медленнее.

r    Выражению aaa|^a|z$|zzz соответствуют строки, в которых есть подстрока aaa, либо которые начинаются на a, либо оканчиваются на z, либо, наконец, содержат подстроку zzz.

r    Выражению abc1|abc22|abc333 соответствуют строки, в которых встречаются подстроки abc1, abc22 или abc333 (а возможно, и все три одновременно).



Оператор отключения ошибок


Есть и еще один аргумент за то, чтобы всегда использовать полный контроль ошибок. Это— существование в PHP оператора @. Если этот оператор поставить перед любым выражением, то все ошибки, которые там возникнут, будут проигнорированы. Например:

if(!@filemtime("notextst.txt"))

  echo "Файла не существует!";

Попробуйте убрать оператор @ — тут же получите сообщение: "Файл не найден", а только после этого — вывод оператора echo. Однако с оператором @ предупреждение будет подавлено, что нам и требовалось.

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

// Îáíîâèòü ôàéë, åñëè åãî íå ñóùåñòâóåò èëè îí î÷åíü ñòàðûé

if(!file_exists($fname) || filemtime($fname)<time()-60*60)

  MyFunctionForUpdateFile($fname);

Сравните со следующим фрагментом:

// Îáíîâèòü ôàéë, åñëè åãî íå ñóùåñòâóåò èëè îí î÷åíü ñòàðûé

if(@filemtime($fname)<time()-60*60)

  MyFunctionForUpdateFile($fname);

Всегда помните об операторе @. Он крайне удобен. Подумайте, стоит ли рисковать, устанавливая слабый контроль ошибок при помощи Error_reporting(), если его и так можно локально установить при помощи @? По-моему, нет.



Описание шаблонизатора


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



Определение границ строки


list imageTTFBBox(int $size, int $angle, string $fontfile, string $text)

Эта функция ничего не выводит в изображение, а просто определяет, какой размер и положение заняла бы строка текста $text

размера $size, выведенная под углом $angle в какой-нибудь рисунок. Параметр $fontfile, как и в функции imageTTFText(), задает абсолютный путь к файлу шрифта, который будет использован при выводе.

Возвращаемый список содержит всю информацию о размерах строки в формате, похожем на тот, что выдает функция imageTTFText(). Однако порядок точек в нем отличается (табл. 23.2).

Таблица 23.2.

Ñîäåðæèìîå ñïèñêà, âîçâðàùàåìîãî ôóíêöèåé

Индексы

Что содержится

0 и 1

(x,y) левого нижнего угла

2 и 3

(x,y) правого нижнего угла

4 и 5

(x,y) правого верхнего угла

4 и 5

(x,y) левого верхнего угла



Определение констант


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

void define(string $name, string $value, bool $case_sen=true);

Определяет новую константу с именем, переданным в $name, и значением $value. Если необязательный параметр $case_sen равен true, то в дальнейшем в программе регистр букв константы учитывается, в противном случае— не учитывается (по умолчанию, как мы видим, регистр учитывается). Созданная константа не может быть уничтожена или переопределена.

Например:

define("pi",3.14);

define("str","Test string");

echo sin(pi/4);

echo str;

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



Определение параметров изображения


Как только картинка создана и получен ее идентификатор, GD становится совершенно все равно, какой формат она имеет и каким путем ее создали. То есть все остальные действия с картинкой происходят через ее идентификатор, вне зависимости от формата, и это логично— ведь в памяти изображение все равно хранится в распакованном виде (наподобие BMP), а значит, информация о ее формате нигде не используется. Так что вполне возможно открыть PNG-изображение с помощью imageCreateFromPng() и сохранить ее на диск функцией imageJpeg(), уже в другом формате. В дальнейшем можно в любой момент времени определить размер загруженной картинки, воспользовавшись функциями imageSX() и imageSY():

int imageSX(int $im)

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

int imageSY(int $im)

Возвращает высоту картинки в пикселах.

int imageColorsTotal(int $im)

Эту функцию имеет смысл применять только в том случае, если вы работаете с изображениями, "привязанными" ê êîíêðåòíîé ïàëèòðå — например, с файлами GIF. Она возвращает текущее количество цветов в палитре. Как мы вскоре увидим, каждый вызов imageColorAllocate()

увеличивает размер палитры. В то же время известно, что если при небольшом размере палитры GIF-картинка сжимается очень хорошо, то при переходе через степень двойки (например, от 16 к 17 цветам) эффективность сжатия заметно падает, что ведет к увеличению размера (так уж устроен формат GIF). Если мы не хотим этого допустить и собираемся вызывать imageColorAllocate()

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



Определение типа файла


bool file_exists(string $filename)

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

$fname="/etc/passwd";

if(!file_exists($fname)

  $f=fopen($fname,"w");

else

  $f=fopen($fname,"r");

Дело в том, что между вызовом file_exists()

и открытием файла в режиме w проходит некоторое время, в течение которого другой процесс может "вклиниться"

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

string filetype(string $filename)

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

r

file — обычный файл;

r    dir — каталог;

r    link — символическая ссылка;

r    fifo — fifo-канал;

r    block — блочно[E67] -ориентированное устройство;

r    char — символьно-ориентированное устройство;

r    unknown — неизвестный тип файла.

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

bool is_file(string $filename)

Возвращает true, если $filename — обычный файл.

bool is_dir(string $filename)

Возвращает true, если $filename — каталог.

bool is_link(string $filename)

Возвращает true, если $filename — символическая ссылка.



Определение типа переменной


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

r is_integer($a)

Возвращает true, если $a — целое число.

r    is_double($a)

Возвращает true, если $a — действительное число.

r    is_string($a)

Возвращает true, если $a является строкой.

r    is_array($a)

Возвращает true, если $a является массивом.

r    is_object($a)

Возвращает true, если $a объявлена как объект.

r    is_boolean($a)

Возвращает true, если $a определена как логическая переменная.

r    gettype($a)

Возвращает строки, соответственно, со значениями: array, object, integer, double, string,

boolean или unknown type в зависимости от типа переменной.

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



Определение возможности доступа


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

открытиями файлов.

bool is_readable(string $filename)

Возвращает true, если файл может быть открыт для чтения.

bool is_writeable(string $filename)

Возвращает true, если в файл можно писать.

bool is_executable(string $filename)

Возвращает true, если файл — исполняемый.



Основы регулярных выражений в формате RegEx


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



Открытие файла


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

int fopen(string $filename, string $mode, bool $use_include_path=false)

Открывает файл с именем $filename в режиме $mode и возвращает дескриптор открытого файла. Если операция "провалилась", то, как это принято, fopen() возвращает false. Впрочем, мы можем не особо беспокоиться, проверяя выходное значение на ложность— вполне подойдет и проверка на ноль, потому что дескриптор 0 в системе соответствует стандартному потоку ввода, а он, очевидно, никогда не будет открыт функцией fopen() (во всяком случае, пока не будет закрыт нулевой дескриптор, а это делается крайне редко). Необязательный параметр $use_include_path

говорит PHP о том, что, если задано относительное имя файла, его следует искать также и в списке путей, используемом инструкциями include

и require. Обычно этот параметр не используют.

Параметр $mode может принимать следующие значения:

r    r — файл открывается только для чтения. Если файла не существует, вызов регистрирует ошибку. После удачного открытия указатель файла устанавливается на его первый байт, т. е. на начало;

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

их, при необходимости увеличив размер файла. Будьте внимательны;


r    w — создает новый пустой файл. Если на момент вызова уже был файл с таким именем, то он предварительно уничтожается. В случае неверно заданного имени файла вызов, как нетрудно догадаться, "проваливается";

r    w+ — аналогичен r+, но если файла изначально не существовало, создает его. После этого с файлом можно работать как в режиме чтения, так и записи. Если файл существовал до момента вызова, его содержимое удаляется;

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

r    a+ — открывает файл в режиме чтения и записи, указатель файла устанавливается на конец файла, при этом содержимое файла не уничтожается. Отличается от a тем, что если файла изначально не существовало, то он создается. Этот режим полезен, если вам нужно что-то дописать в файл (например, в журнал), но вы не знаете, создан ли уже такой файл;

Но это еще не полное описание параметра $mode. Дело в том, что в конце любой из строк r, w, a, r+, w+ и a+ может находиться еще один необязательный символ — b или t. Если указан b (или не указан вообще никакой), то файл открывается в режиме бинарного чтения/записи. Если же это t, то для файла устанавливается режим трансляции символа перевода строки, т. е. он воспринимается как текстовый.



О режиме t

нет ни слова в документации PHP (во всяком случае, на момент написания этих строк), однако, как показывают мои тесты, он работает на всех системах.

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

Листинг 15.1. Сценарий test.php:

работа с текстовыми файлами



<?

// Получает в параметрах строку и возвращает через пробел коды

// символов, из которых она состоит

function MakeHex($st)

{  for($i=0; $i<strlen($st); $i++) $Hex[]=sprintf("%2X",ord($st[$i]));

   return join(" ",$Hex);

}

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

$f=fopen("test.php","r");  // бинарный режим

echo MakeHex(fgets($f,100)),"<br>\n";

$f=fopen("test.php","rt"); // текстовый режим

echo MakeHex(fgets($f,100)),"<br>\n";

?>

Первая строчка файла test.php состоит всего из двух символов — это < и ?. За ними должен следовать маркер конца строки. Сценарий показывает, как выглядит этот маркер, т. е. состоит ли он из одного или двух символов.

Запустим этот сценарий в Unix. Мы получим две одинаковые строки, которые выводят операторы echo:

3C 3F 0A

3C 3F 0A

Отсюда следует, что в этой системе физический конец строки обозначается одним символом — кодом 0x0A, или \n (коды 0x3C и 0x3F соответствуют символам < и ?). В то же время, если запустить сценарий в Windows, мы получим такой результат:

3C 3F 0D 0A

3C 3F 0A

Как видите, бинарное и текстовое чтение дали разные результаты! В последнем случае произошла трансляция маркера конца строки.

Как уже говорилось, можно предварять имя файла строкой http:// или ftp://, при этом прозрачно будет осуществляться доступ к файлу с удаленного хоста.

В случае HTTP-доступа PHP открывает соединение с указанным сервером, а также посылает ему нужные заголовки: Host и GET[E65] . После чего при помощи файлового дескриптора из удаленного файла можно читать обычным образом — например, посредством все той же функции fgets().

Если же вы открываете FTP-файл, то в него можно производить либо запись, либо читать из него, но не и то и другое одновременно. Кроме того, FTP-сервер должен поддерживать пассивный режим передачи (впрочем, большинство серверов его поддерживают). Не забудьте также указать логин и пароль, как это сделано в примерах ниже.



Дам небольшой совет: не используйте обратные слэши \ в именах файлов, как это принято в DOS и Windows. Просто забудьте про этот архаизм. Поможет вам в этом PHP, который незаметно в нужный момент переводит прямые слэши / в обратные (разумеется, если вы работаете под Windows). Если же вы все-таки не можете обойтись без обратного слэша, не забудьте его удвоить, потому что в строках он воспринимается как спецсимвол:

$fp = fopen ("c:\\windows\\hosts", "r");

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

Вот несколько примеров:

// Открывает файл на чтение

$f = fopen("/home/user/file.txt", "r") or die("Ошибка!");

// Открывает HTTP-соединение на чтение

$f = fopen("http://www.php.net/", "r") or die("Ошибка!");

// Открывает FTP-соединение с указанием логина и пароля для записи

$f = fopen("ftp://user:password@example.com/", "w") or die("Ошибка!");


Отмена действия спецсимволов


Если же нужно вставить в выражение один из управляющих символов, но только так, чтобы он "не действовал", достаточно предварить его обратным слэшем. К примеру, если мы ищем строку, содержащую подстроку a*b, то мы должны использовать для этого выражение a\*b (опять же, в PHP эта строка будет записываться как "a\\*b"), поскольку символ * является уп­равляющим (вскоре мы рассмотрим, как он работает).

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

$a="a\*b"

но можем

$a="a\\*b"

В последнем случае в строке $a оказывается a\*b. Так как регулярные выражения в PHP представляются именно в виде строк, то необходимо постоянно помнить это правило.

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



Отправка и перекодирование писем


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

r    вставку заголовка From

в письмо, если он еще не присутствует в сообщении;

r    преобразование  письма в нужную кодировку кириллицы;

r    вставку соответствующего значения в заголовок Content-type, чтобы письмо было "понятно" любой почтовой программе;

r    поддержку функций мини-шаблонизатора, который мы уже написали.

В листинге 32.2 приведен исходный код функции. Как обычно, мы помещаем функцию в отдельный модуль библиотекаря (библиотекарь описан в главе 29). Этот модуль будет использовать возможности, предоставляемые библиотекой Minitemplate.phl.

Листинг 32.2. Функция PostMail(): Mail.phl

<?

Uses("Minitemplate");

// Кодировка по умолчанию для исходного текста.

define("DefaultCode","w");

// Функция возвращает строку $st, переведенную из кодировки

// $from в кодировку $to. Возможные значения этих параметров:

// w[indows]  — windows-1251

// k[oi8-r]   — koi8-r

// m[ac]      — x-mac-cyrillic

// i[so]      — iso-8859-5

// t[ranslit] — translit ("английскими"

буквами — "русские" слова)

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

// параметр $from не может равняться "t", потому что трудно

// восстанавливать текст из транслита (хотя эта задача и разрешима).

// Функция полезна и сама по себе, но все-таки чаще всего ее

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

// ее в этот модуль.

function EncodeString($st,$to,$from=DefaultCode)

{ // Оставляем только первые буквы названий кодировок

  $from=strtolower(substr($from,0,1));

  $to  =strtolower(substr($to,0,1));

  // Пытаемся воспользоваться встроенной в PHP функцией


  if($to!="t") return convert_cyr_string($st,$from,$to);

  // Иначе нужно преобразовать строку в Translit, что придется

  // делать "вручную" — при помощи strtr().

  // Сначала заменяем "односимвольные" фонемы.

  $st=strtr($st,"абвгдеёзийклмнопрстуфхъыэ",

                "abvgdeeziyklmnoprstufh'ie");

  $st=strtr($st,"АБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЭ",

                "ABVGDEEZIYKLMNOPRSTUFH'IE");

  // Затем — "многосимвольные".

  $st=strtr($st,array(

    "ж"=>"zh",  "ц"=>"ts", "ч"=>"ch", "ш"=>"sh",

    "щ"=>"shch","ь"=>"",   "ю"=>"yu", "я"=>"ya",

    "Ж"=>"ZH",  "Ц"=>"TS", "Ч"=>"CH", "Ш"=>"SH",

    "Щ"=>"SHCH","Ь"=>"",   "Ю"=>"YU", "Я"=>"YA"

  ));

  // Возвращаем результат.

  return $st;

}

// Значения параметра Content-tyep charset в зависимости от

// односимвольного названия кодировки.

global $CoderCharset;

$CoderCharset["w"]="windows-1251";

$CoderCharset["i"]="iso-8859-5";

$CoderCharset["k"]="koi8-r";

$CoderCharset["m"]="x-mac-cyrillic";

$CoderCharset["t"]="koi8-r";

// Разделитель тела и заголовков (таких как From: и т. д.) в письме.

define("MailDivider","~StartOfMail");

// Посылает письмо $msg по заданному адресу $to, перед этим

// преобразовав его в кодировку $encTo. Проставляет поле

// charset и правильно обрабатывает имя получателя (если

// в теле письма уже указано "To: Вася", то в результате

// получается "To: Вася <vasya@pupkin.ru>"). Если работа происходит



// в Win32, то письмо не посылается, а создается отладочный файл,

// в котором будет содержаться текст письма.

// Письмо должно состоять из заголовков и тела, разделенных

// маркером ~StartOfMail.

function SendMail($to,$msg,$encTo=DefaultCode,$encFrom=DefaultCode)

{ global $CoderCharset;

  // Перекодируем

  $msg=EncodeString($msg,$encTo,$encFrom); // тело письма

  $head="";                                // заголовки

  // Если есть заголовки, выделяем их.

  if(strpos($msg,MailDivider)!==false) {

    $regs=split(MailDivider."\r?\n?",$msg,2); // тело и заголовки

    $head=trim($regs[0]);

    $msg=$regs[1];

  }

  // Работаем с заголовками. Разбиваем их на строки.

  if($head) $Lines=split("[\r\n]+",$head); else $Lines=array();

  $HasContType=0;   // число найденных заголовков Content-type

  $chs="charset=$CoderCharset[$encTo]";

  $subject="";

  for($i=0; $i<count($Lines); $i++) {

    $l=&$Lines[$i];

    // Проставляем текущую кодировку у письма. Для этого

    // проверяем, задан ли в нем заголовок Content-type и,

    // если задан, то модифицируем его, а если нет —

    // добавляем этот заголовок в начало и конец письма.

    if(eregi("^Content-type:",$l)) {

      if(eregi("charset *=",$l))

        $l=eregi_Replace("charset *= *[^;,\n]+",$chs,$l);

      else

        $l.="; $chs";

      $HasContType++;

    }

    // Проверяем значение поля "to" в письме — там может быть имя

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

    if(eregi("^to:([^\r\n]*)",$l,$regs)) {

      $to=trim($regs[1])." <$to>";

      $l="";

    }

    // Проверяем заголовок Subject. В некоторых верcиях PHP

    // передача пустого второго параметра в функцию mail()

    // приводит к нежелательным последствиям. Указывая в заголовке

    // значение Subject из письма, мы решаем проблему.

    if(eregi("^subject:([^\r\n]*)",$l,$regs)) {



      $subject=trim($regs[1]);

    }

  }

  // Нет заголовка Content-type — добавляем его в конец.

  if(!$HasContType) $Lines[]="Content-type: text/plain; $chs";

  // Соединяем строки опять вместе.

  $head=ereg_Replace("\n\n+","\n",join("\n",$Lines));

  // Посылаем письмо.

  $Result=@mail($to,$subject,$msg,$head)!=0;

  // В Windows параллельно ведем журнал писем (для отладки).

  if(getenv("COMSPEC")) {

    if(!@is_dir("debug")) mkdir("debug",0755);

    $f=fopen("debug/_debug_mail.txt","a+");

    fputs($f,"> to: $to\n");

    fputs($f,"$head\n--------\n");  

    fputs($f,"$msg\n-----------------------------------------\n\n");

    fclose($f);

  } 

  return $Result;

}

// Функция PostMail() "разворачивает" шаблон $msg, делая доступным для

// него переменные из массива $Vars (см. описание функций

// ExpandTemplate() и ExpandFile()). Затем она переводит результирующий

// текст в кодировку, заданную в $encTo (сам текст при этом

// рассматривается в кодировке $encFrom), и посылает его по электронной

// почте по адресу $to. Если строка $msg начинается с префикса

// file:, за которым следует имя файла, то шаблон письма загружается из

// этого файла при помощи ExpandFile(). В противном случае в качестве

// шаблона рассматривается сам параметр $msg.

function PostMail($to,$msg,$encTo=DefaultCode,

                  $Vars=false,$encFrom=DefaultCode)

{ if(eregi("^file:(.*)(\n|\$)",$msg,$P))

    $Text=ExpandFile(trim($P[1]),$Vars);   

  else

    $Text=ExpandTemplate($msg,$Vars);

  // Посылаем письмо.

  return SendMail($to,$Text,$encTo,$encFrom);

}

?>

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



Термин "транслит" (сокращение от "транслитерация") означает такую кодировку кириллицы, при которой все "русские" буквы контекстно заменяются на записанные в соответствии с английской транскрипцией. Например, vot stroka, zapisannaya translitom. Эта кодировка особенно полезна для пользователей Unix, которые забыли установить у себя "русскую" таблицу символов.


Отрицательные группы


Иногда (когда альтернативных символов много) бывает довольно утомительно перечислять их всех в квадратных скобках. Особенно обидно выходит, если нас устраивает любой символ, кроме нескольких (например, кроме > и <). В этом случае, конечно, не стоит указывать 254 символа, вместо этого лучше воспользоваться конструкцией [^<>], которая обозначает любой символ, кроме тех, которые перечислены после [^ и до ]. Например, выражение a[^ \t\n\r]b "срабатывает"

на все строки, содержащие буквы a и b, разделенные любым не пробельным символом.

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



Ðàáîòà


Библиотека GD имеет некоторые возможности по работе с текстом и шрифтами. Шрифты представляют собой специальные ресурсы, имеющие собственный идентификатор, и чаще всего загружаемые из файла или встроенные в GD. Каждый символ шрифта может быть отображен лишь в моноцветном[E106][DK107]  режиме, т. е. "рисованные"

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


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

Результат— это просто набор данных, и  количество вошедших в него записей можно узнать через mysql_num_rows(). Например, если в предыдущем примере при выборке из таблицы оказалось, что в таблице имеются записи о 10 людях старше 30 лет, то мы в идентификаторе результата получим "ссылку" на 10 "строчек". Òåïåðü ìы можем считать в программу на PHP любую из них с помощью специальных функций, которые будут описаны ниже.

Каждая запись — это список значений полей, а именно, тех полей и в том же порядке, которые были указаны в запросе select ... from Таблица на месте многоточия (если там была звездочка, то все поля). Таким образом, результат — это такой своеобразный двумерный массив: первый индекс — номер записи и второй — имя поля. Можете называть его прямоугольной таблицей или матрицей данных — как угодно.[E138] [DK139] 

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




Обычно в таблице содержится довольно много записей с разными значениями полей. Встает проблема выбора одной конкретной записи из этого массива. В рассмотренном нами примере таблицы с информацией о гражданах, пожалуй, запись можно однозначно идентифицировать по фамилии человека. Ну а если встретятся однофамильцы? Тогда по имени. А если же и имена совпадут? Ну, тогда…...

Вы видите, насколько это все неудобно. Поэтому во избежание недоразумений подобного рода в таблицу вводят еще одно вспомогательное поле (колонку), назвав ее, скажем, id. Этот самый id уникален у каждой записи, поэтому мы можем, зная id нужного нам человека, тут же получить его данные. Кроме того, если нам понадобится, например, зафиксировать в таблице еще и родственные связи людей (кто является чьим отцом, например), мы можем завести в ней еще одно поле— parent_id, в котором будет храниться id родителя конкретного человека. Таким образом, описанная техника оказывается довольно удобной.

Пусть теперь мы хотим добавить в таблицу сведения о еще одном человеке. Логично было бы, чтобы его id проставлялся автоматически. Возникает вопрос: как нам этот id вычислить? В самом деле, мы же не знаем, какие id в таблице в данный момент "свободны"... Можно использовать для этой цели текущее время в секундах. Но вдруг именно в данную секунду кто-то еще точно так же добавляет в базу данных [E144] запись? Можно, конечно, взять максимальный id, прибавить к нему единичку и занести в таблицу. Но где гарантия, что, опять же в этот момент другой администратор не проделал ту же операцию — до того, как вы добавили свою информацию, но после того, как определили максимальный id?

Как раз для решения этой проблемы и предназначена в MySQL возможность под названием AUTO_INCREMENT. [E145] [DK146] А именно, при создании таблицы мы можем какое-нибудь ее поле (в нашем случае как раз id) объявить так:

ÈìÿÏîëÿ int auto_increment primary key

Немного длинновато, но это стоит того! Теперь любая операция INSERT автоматически проставит у добавленной записи поле ИмяПоля так, чтобы оно было уникально во всей таблице — MySQL это гарантирует. В простейшем случае — просто увеличит на 1 некий внутренний счетчик, глобальный для всей таблицы, и занесет его новое значение в нужное поле записи. Причем гарантируется, что никакие проблемы с совместным доступом к таблице просто не могут возникнуть, как это произошло бы, используй мы "кустар­ные"

способы.

Получить только что вставленный идентификатор можно при помощи функции mysql_insert_id().

int mysql_insert_id([int $link_identifier])

Функция возвращает непосредственно перед ее вызовом сгенерированный идентификатор [E147] записи для автоинкрементного поля после выполнения команды insert. Вызывать ее разумно только сразу после выполнения инструкции insert, например, в таком контексте:

mysql_query("insert into Òàáëèöà(ïîëå1, ïîëå2) values('aa','bb')");

$id=mysql_insert_id();

Теперь к только что вставленной записи можно обратиться, используя идентификатор $id:

$r=mysql_query("select * from Òàáëèöà where id=$id");

$Row=mysql_fetch_array($r);




Вот и подходит к концу наш разговор о функциях поддержки MySQL в PHP. Мы познакомились с большинством функций, которые встроены в PHP для взаимодействия с этой СУБД. Мы еще вернемся к MySQL в части V книги.

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

int mysql_list_fields(string $dbname, string $tblname [,int $link])

Функция mysql_list_fields() возвращает информацию об указанной таблице $tblname в базе данных $dbname, используя идентификатор соединения $link, если он задан (в противном случае — последнее открытое соединение). Возвращаемое значение — идентификатор результата, который может быть проанализирован обычными средствами, либо при помощи функций mysql_field_flags(), mysql_field_len(), mysql_field_name()

и

mysql_field_type(). В случае ошибки возвращается –1,

текст сообщения ошибки может быть получен обычным способом.

int mysql_list_tables(string $database [,int $link_identifier])

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



Параметры по умолчанию


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

function MySort(&$Arr, $NeedLoOrder=1)

{ ... ñîðòèðóåì â çàâèñèìîñòè îò $NeedLoOrder...

}

Теперь, имея такую функцию, можно написать в программе:

MySort($my_array,0);   // ñîðòèðóåò в ïîðÿäêå возрастания

MySort($my_array);     // âòîðîé àðãóìåíò çàäàåòñÿ ïî óìîë÷àíèþ!

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

аргумент. Вот, например, неверное описание:

// Ошибка!

function MySort($NeedLoOrder=1, &$Arr)

{

  ... сортируем в зависимости от $NeedLoOrder...

}

MySort(,$my_array); // Îøèáêà!

Это вам не Бейсик!



Параметры результата


int mysql_num_rows(int $result)

Функция mysql_num_rows() возвращает число записей в результате запроса. Таким образом, функция позволяет определить вертикальную размерность "двумерного массива результата".

int mysql_num_fields(int $result)

Эта функция возвращает число полей в одной строке результата, т. е., число колонок в результате $result. В силу сказанного, функция позволяет определить горизонтальную размерность "двумерного массива результата".



Параметры шрифта


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

int imageFontHeight(int $font)

Возвращает высоту в пикселах каждого символа в заданном шрифте.

int imageFontWidth(int $font)

Возвращает ширину в пикселах каждого символа в заданном шрифте.



Path


Параметр path=путь обычно описывает каталог (точнее, URI), в котором расположен сценарий, установивший Cookie. Как мы видим, этот параметр также можно установить вручную, записав в него не только каталог, а вообще все, что угодно. Однако при этом следует помнить: указав хост, отличный от хоста сценария, или путь, отличный от URI каталога (или родительского каталога) сценария, мы тем самым никогда больше не увидим наш Cookie в этом сценарии.



Прямой перебор массивов применялся столь


Прямой перебор массивов применялся столь часто, что разработчики PHP решили в четвертой версии языка добавить специальную инструкцию перебора массива — foreach. Мы уже рассматривали ее ранее. Вот как с ее помощью можно перебрать и распечатать наш массив людей:

foreach($Names as $k=>$v) echo "Âîçðàñò $k — $v\n";

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



Есть и еще одна причина предпочесть этот вид перебора "связке" цикла for с eaсh(). Дело в том, что при применении foreach мы указываем имя перебираемого массива $Names только в одном месте, так что когда вдруг потребуется это имя изменить, нам достаточно будет поменять его только один раз. Наоборот, использование Reset()

и each() заставит нас в таком случае изменять название переменной в двух местах, что потенциально может привести к ошибке. Представьте, что произойдет, если мы случайно изменим операнд each(), но сохраним параметр Reset()!


Перечисления и множества


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

ENUM(value1,value2,value3,...)

В отличие от всех остальных типов, множества означают, что в соответствующем поле может содержаться не одно, а сразу несколько значений (value1, value2 и т. д., ò. å. — множество значений). Формат задания данных такого типа имеет следующий вид:

SET(value1,value2,value3,...)

Значений в множестве может быть не сколько угодно, а не более 64 штук. Иногда это сильно мешает при программировании.



Передача функций "по ссылке"


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

function A($i) { echo "a $i\n"; }

function B($i) { echo "b $i\n"; }

function C($i) { echo "c $i\n"; }

$F="A";  // èëè $F="B" èëè $F="C"

$F(10);  // âûçîâ ôóíêöèè, èìÿ êîòîðîé õðàíèòñÿ â $F

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

// Ñðàâíåíèå áåç ó÷åòà ðåãèñòðà ñèìâîëîâ ñòðîê

function FCmp($a,$b)

{ return strcmp(tolower($a),tolower($b))

}

$a=array("b"=>"bbb", "a"=>"Aaa", "d"=>"ddd);

uasort($a,"FCmp"); // Сортировка без учета регистра символов

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

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



Передача параметров методом GET


Тут все просто. Все параметры передаются единой строкой (а именно, точно такой же, какая была задана в URL после ?) в переменной QUERY_STRING. Единственная проблема — то, что все данные поступят URL-кодирован­ными. Так что нам понадобится функция декодирования. Но это отдельная тема, пока мы не будем ее касаться.

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

Листинг 3.2. Работа с переменными окружения

#include <stdio.h>  // Âêëþ÷àåì ôóíêöèè ââîäà/âûâîäà

#include <stdlib.h> // Âêëþ÷àåì ôóíêöèþ getenv()

void main(void) {

// ïîëó÷àåì çíà÷åíèå ïåðåìåííîé îêðóæåíèÿ REMOTE_ADDR

  char *RemoteAddr = getenv("REMOTE_ADDR");

// ... è åùå QUERY_STRING

  char *QueryString = getenv("QUERY_STRING");

// ïå÷àòàåì çàãîëîâîê

  printf("Content-type: text/html\n\n");

// ïå÷àòàåì äîêóìåíò

  printf("<html><body>");

  printf("<h1>Çäðàâñòâóéòå. Ìû çíàåì î âàñ âñå!</h1>");

  printf("Âàø IP-àäðåñ: %s<br>",RemoteAddr);


  printf("Âîò ïàðàìåòðû, êîòîðûå Âû óêàçàëè: %s",QueryString);

  printf("</body></html>");

}

Откомпилируем сценарий и поместим его в "CGI-каталог". Теперь в адресной строке введем:

http://www.myhost.com/cgi-bin/script.cgi?a=1&b=2

Мы получим примерно такой документ:

Çäðàâñòâóéòå. Ìû çíàåì î Âàñ âñå!

Âàø IP-àäðåñ: 192.232.01.23

Âîò ïàðàìåòðû, êîòîðûå Âû óêàçàëè: a=1&b=2


Передача параметров методом POST


В отличие от метода GET, здесь параметры передаются сценарию не через переменные окружения, а через стандартный поток ввода (в Си он называется stdin). То есть программа должна работать так, будто никакого сервера не существует, а она читает данные, которые вводит пользователь с клавиатуры. (Конечно, на самом деле никакой клавиатуры нет и быть не может, а заправляет всем сервер, который "изображает из себя" клавиатуру.)

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

подразумевает также возможность передачи данных через URL-строку. Эти данные будут, как обычно, помещены в переменную окружения QUERY_STRING.

Но как же узнать, сколько именно данных переслал пользователь методом POST? До каких пор нам читать входной поток? Для этого служит переменная окружения CONTENT_LENGTH, в которой хранится строка с десятичным представлением числа переданных байтов данных (разумеется, перед использованием ее надо перевести в обычное число).

Модифицируем предыдущий пример так, чтобы он принимал POST-данные, а также выводил и GET-информацию, если она задана:

Листинг 3.3. Получение данных POST

#include <stdio.h>

#include <stdlib.h>

void main(void) {

// èçâëåêàåì çíà÷åíèÿ ïåðåìåííûõ îêðóæåíèÿ

  char *RemoteAddr = getenv("REMOTE_ADDR");

  char *ContentLength = getenv("CONTENT_LENGTH");

  char *QueryString = getenv("QUERY_STRING");

// âû÷èñëÿåì äëèíó äàííûõ — ïåðåâîäèì ñòðîêó â ÷èñëî


  int NumBytes = atoi(ContentLength);

// âûäåëÿåì â ñâîáîäíîé ïàìÿòè áóôåð íóæíîãî ðàçìåðà

  char *Data = (char *)malloc(NumBytes + 1);

// ÷èòàåì äàííûå èç ñòàíäàðòíîãî ïîòîêà ââîäà

  fread(Data, 1, NumBytes, stdin);

// äîáàâëÿåì íóëåâîé êîä â êîíåö ñòðîêè

// (â Ñè íóëåâîé êîä ñèãíàëèçèðóåò î êîíöå ñòðîêè)

  Data[NumBytes] = 0;

// âûâîäèì çàãîëîâîê

  printf("Content-type: text/html\n\n");

// âûâîäèì äîêóìåíò

  printf("<html><body>");

  printf("<h1>Çäðàâñòâóéòå. Ìû çíàåì î âàñ âñå!</h1>");

  printf("Âàø IP-àäðåñ: %s<br>",RemoteAddr);

  printf("Êîëè÷åñòâî áàéòîâ äàííûõ: %d<br>",NumBytes);



  printf("Âîò ïàðàìåòðû, êîòîðûå Âû óêàçàëè: %s<br>",Data);

  printf("À âîò òî, ÷òî ìû ïîëó÷èëè ÷åðåç URL: %s",

    QueryString);

  printf("</body></html>");

}

Странслируем этот сценарий и запишем то, что получилось, под именем script.cgi в каталог, видимый извне как /cgi-bin/. Откроем в браузере следующий HTML-файл с формой:

Листинг 3.4. POST-форма

<html><body>

<form action=/cgi-bin/script.cgi?param=value method=post>

Name1: <input type=text name="name1"><br>

Name2: <input type=text name="name2"><br>

<input type=submit value="Çàïóñòèòü ñöåíàðèé!">

</form>

</body></html>

Теперь, если набрать в полях ввода какой-нибудь текст и нажать кнопку, получим HTML-страницу, сгенерированную сценарием, например, следующего содержания:

Çäðàâñòâóéòå. Ìû çíàåì î âàñ âñå!

Âàø IP-àäðåñ: 136.234.54.2

Êîëè÷åñòâî áàéòîâ äàííûõ: 23

Âîò ïàðàìåòðû, êîòîðûå Âû óêàçàëè: name1=Vasya&name2=Petya

À âîò òî, ÷òî ìû ïîëó÷èëè ÷åðåç URL: param=value

Как можно заметить, обработка метода POST устроена сложнее, чем GET. Тем не менее, метод POST используется чаще, особенно если нужно передавать большие объемы данных или "закачивать"

файл на сервер (эта возможность также поддерживается протоколом HTTP и HTML).


Передача параметров по ссылке


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

function Test($a)

{ echo "$a\n";

  $a++;

  echo "$a\n";

}

. . .

$num=10;

Test($num);

echo $num;

Что происходит перед началом работы функции Test()

(которая, кстати, не возвращает никакого значения, т.е. является в чистом виде подпрограммой или процедурой) — как выражаются программисты на Паскале? Все начинается с того, что создается переменная $a, локальная для данной функции (про локальные переменные мы поговорим позже), и ей присваивается значение 10 (то, что было в $num). После этого значение 10 выводится на экран, величина $a

инкрементируется, и новое значение (11) опять печатается. Так как тело функции закончилось, происходит возврат в вызвавшую программу. А теперь вопрос: что будет напечатано при последующем выводе переменной $num?

А напечатано будет 10 (и это несмотря на то, что в переменной $a

до возврата из функции было 11!) Ясно, почему это происходит: ведь $a — лишь копия $num,

а изменение копии, конечно, никак не отражается на оригинале.

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

(переданной ей в параметрах), достаточно при передаче аргумента функции перед его именем поставить & (листинг 11.4):

Листинг 11.4. Передача параметров по ссылке (первый способ)

function Test($a)

{ echo "$a\n";

  $a++;

  echo "$a\n";

}

$num=10;      // $num=10

Test(&$num);  // à òåïåðü $num=11!

echo $num;    // âûâîäèò 11!

Такой способ передачи параметров исторически называется "передачей по ссылке", в этом случае аргумент не является копией переменной, а "ссы­лается"

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


Чтобы не забывать каждый раз писать & перед переменной, передавая ее функции, существует и другой, более привычный для программистов на Си++ синтаксис передачи по ссылке. А именно, можно символ & перенести прямо в заголовок функции, вот так (листинг 11.5):

Листинг 11.5. Передача параметров по ссылке (второй способ)

function Test(&$a)

{ echo "$a\n";

  $a++;

  echo "$a\n";

}

....

$num=10;      // $num=10

Test($num);   // à òåïåðü $num=11!

echo $num;    // âûâîäèò 11!

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

и, к тому же, убережет вас от множества ошибок, связанных с пропуском & в программе.



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


Передача параметров "вручную"


Давайте будем включать параметры прямо в URL, в строку параметров. Таким образом, если запустить в браузере

http://www.somehost.com/script.cgi?name=Vasya&age=20

мы получим страницу с нужным результатом:

<html><body>

Ïðèâåò, Vasya! ß çíàþ, Âàì 20 ëåò!

</body></html>

Заметьте, что мы разделяем параметры символом &, а также используем знак равенства =. Это неспроста. Сейчас мы обсудим, почему.



Перехват обращений к несуществующим страницам


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

/forum/Computers-01-04-01.html

Хотя файла Computers-01-04-01.html нет и в помине, обработчик может перехватить запрос к нему и определить, что речь идет о новостях в разделе "Компьютеры"

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

Обычно для подобных целей используют специальный модуль Apache — mod_rewrite. К сожалению, по статистике далеко не все хостинг-провайдеры соглашаются устанавливать его на свои серверы. В то же время механизм ActionAddHandler работает всегда и везде, где установлен Apache.

Надо заметить, что в примере из листинга 29.5 мы никак не перехватываем обращения к несуществующим страницам. Что происходит, если пользователь все же введет неправильный адрес? Очевидно, вызов include, стоящий в предпоследней строчке, завершится неуспешно, а PHP выведет сообщение об ошибке. Наверное, в реальной программе нужно как-то обрабатывать эту ситуацию, — например, при помощи проверки существования запрошенного файла.



Переменное число параметров


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

Листинг 11.6. Переменное число параметров функции

function myecho()

{  for($i=0; $i<func_num_args(); $i++) {

     for($j=0; $j<$i; $j++) echo "&nbsp;"; // выводим отступ

     echo func_get_arg($i)."<br>\n";       // выводим элемент

   }

}

// отображаем строки "лесенкой"

myecho("Меркурий", "Венера", "Земля", "Марс");

Обратите внимание на то, что при описании myecho()

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

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

r    int func_num_args()

Возвращает общее

число аргументов, переданных функции при вызове.

r    mixed func_get_arg(int $num)

Возвращает значение аргумента с номером $num, заданного при вызове функции. Нумерация, как всегда, отсчитывается с нуля.

r    list func_get_args()

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

Перепишем наш пример с применением последней функции (листинг 11.7):

Листинг 11.7. Использование fuct_get_args()

function myecho()

{  foreach(func_get_args() as $v) {

     for($j=0; $j<@$i; $j++) echo "&nbsp;";

     echo "$v<br>\n";

     @$i++;

   }

}

// выводим строки "лесенкой"

myecho("Меркурий", "Венера", "Земля", "Марс");

Мы используем здесь цикл foreach

для перебора аргументов, а также оператор отключения ошибок @, чтобы PHP не "ругался" на то, что переменная $i не определена при первом "обороте"

цикла.