Вы находитесь на странице: 1из 572

Содержание данной книги находится в конце документа.

Предисловие

В последнее время в связи с бурным развитием сети Интернет в программировании


начинает все более резко выделяться отдельная отрасль. Поначалу она не могла даже
и сравниться по своей сложности с другими областями программистского ремесла, не
"дотягиваясь" не только до системного, но даже и до прикладного программирования.
Речь идет, конечно, о программировании сценариев для Web, или, как часто говорят,
Web-программировании. В наши дни, однако, роль этой отрасли в структуре Интер-
нета все более возрастает, соответственно растет и средняя оценка сложности сцена-
риев. Многие системы (например, поисковые) по объему кода приближаются к раз-
меру исходных кодов серьезных пакетов прикладных программ.
Представляю, как эти слова тут же вызовут бурю протеста со стороны прикладных и
системных программистов, лишь мельком глянувших на программирование в Web.
"Как, — заявят они, — неужели написание простейших программ на "бейсикоподоб-
ных" интерпретаторах вообще можно назвать серьезным программированием? Да с
этим же справится любой начинающий изучать программирование студент, потому
что эта область не вносит и не может внести каких-либо новшеств, не "изобретает"
алгоритмов, и, кстати, в ней нет ничего творческого. Да и вообще, скука-то, наверное,
какая..." Обычно с такими людьми можно спорить часами. Действительно, какую бы
задачу им ни привели, они начинают утверждать, что решить ее очень просто, хотя на
самом деле это в контексте Web, мягко говоря, оказывается не совсем так.
Что ж, отчасти такие люди правы. Поначалу все мы так считали, пока не столкнулись
вплотную с тем, что называется Web-программированием. Да, в большинстве своем
все программы удивляют своей кажущейся простотой. Но везде есть "подводные кам-
ни", и Web-программирование особенно ярко это доказывает. Обычно на написание
сценариев уходят не месяцы и годы, а дни и недели. Но особо сложные сценарии мо-
гут потребовать значительно большего времени на разработку. Наконец, на первый
взгляд работа Web-программиста кажется на редкость скучной. Но...
Все это обстоит именно таким образом, если вы программируете, что называется,
"для себя", и при этом не пытаетесь каким-либо образом "автоматизировать" и упро-
стить этот процесс. Действительно, можно получать удовольствие от написания при-
кладных программ (особенно нетривиальных), даже если их никто, кроме автора и
его ближайших знакомых, потом не увидит. Здесь привлекает сам процесс. Вот этим-
то и отличается программирование в Web: нельзя писать сценарии "для себя", это
занятие действительно покажется (а возможно, так оно и есть) скучным. Зато если вы
создали программу, прекрасно работающую в Интернете, через которую "проходят"
сотни человек в день, и к тому же с удобным и оригинальным интерфейсом — вот
тут-то и начинает вам нравиться ваша профессия.
2 Предисловие

Лу Гринзо, один из программистов IBM, говорил: "Все программисты немного


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

Чего хочет программист


от своей профессии
Давайте попробуем разобраться, чего хочет Web-программист, когда он выбирает
свою профессию. Возможно, он считает, что эта стезя довольно прибыльна? Но день-
ги приходят, что называется, "сами собой" с накоплением опыта и получением опре-
деленных навыков, по мере того, как человек становится профессионалом. Так про-
исходит с любой профессией, а не только с программированием. Кстати, как я
немного выше упоминал, никто из профессиональных программистов не работает
исключительно за деньги, основной стимул — это все-таки интерес к работе.
Идем дальше. Может быть, ему нужна известность? Конечно, этот фактор не является
третьестепенным, учитывая то, что известность — гарантия, что программист всегда
легко сможет найти работу. Однако, как и деньги, слава и известность также не бы-
вают самими по себе — их необходимо заслужить. И, к тому же, много ли вы знаете
известных имен программистов, действительно заслуживших свое признание практи-
кой (Билл Гейтс не в счет, потому что он уже давно этим не занимается)? Правиль-
но — ни одного. Разве что, может быть, кто-нибудь вспомнит доблестных создателей
игры Doom, ставшей уже историей.
Но есть нечто такое, на что я уже намекал, и именно этим Web-программирование (да
и вообще любая работа, происходящая в Web) резко отличается в лучшую сторону от
всех доселе известных видов программирования. Вы можете быть очень хорошим
прикладным или системным программистом. Однако вряд ли ваши программы будет
использовать такое количество людей, которое ежедневно посещает даже и не самую
популярную страничку в Интернете, "подкрепленную" Web-сценарием. Вряд ли вы
получите такое количество отзывов, приобретете такое число бесплатных тестеров,
усердно шлющих вам гору писем с сообщениями о неточностях и ошибках в вашем
продукте, а также с отзывами и предложениями. А ведь, как известно, заметить
ошибку в программе означает "отрубить ей голову". Наконец, иногда приятно отме-
тить для себя, что сценарий, написанный вами несколько лет назад, о котором вы
почти уже и забыли думать, продолжает исправно работать "сам по себе", без всякого
человеческого вмешательства.
Предисловие 3

Временные затраты
Да, я уже слышу очередные протесты "системщиков". Конечно, операционная систе-
ма — безусловный "долгожитель" на множестве компьютеров. Вместе с тем, согласи-
тесь, написать работоспособную ОС, действительно пригодную для использования
(без всяких там оговорок) — довольно тяжелая работа, если не сказать большего. Под
силу ли это одиночке? Сомневаюсь, что с ней в приемлемые сроки справится не то
чтобы один, а десяток или даже пятьдесят человек.
Проведем несложные расчеты. На одной из конференций представитель фирмы Sun
Microsystems заявил (видимо, в качестве порицания), что исходный текст последних
версий Windows насчитывает порядка 50 миллионов строк. Думаю, он не очень силь-
но ошибся в своей оценке (как мы увидим, даже если он завысил цифру хоть в 10 раз,
все равно результат будет неутешительный). В сумме это составляет около
50 млн´20 байт=1000 Мбайт (из расчета в среднем 20 символов в строке). Предпо-
ложим, программист может печатать со скоростью 30 символов в минуту (разумеет-
ся, скорость собственно печати значительно выше, но ведь прежде чем что-то наби-
рать, нужно сначала все спланировать и разработать). Таким образом, работая
непрерывно, он в этом темпе создаст ОС за 1000 Мбайт/(30/60 мин)/
/3600 с=555 555 часов, что составит 555 555/24=23 148 дня или ровным счетом
23 148/365=63 года непрерывной круглосуточной работы! А ведь мы значительно
завысили реальную скорость печати, да и, к тому же, нельзя 24 часа заниматься толь-
ко тем, что набирать программу на клавиатуре.
Ко всему прочему, нужно еще компилировать программу, исправлять ошибки, еще
раз компилировать и так до бесконечности (как это может показаться непривычному
человеку). Наконец, "Нет ошибок в данной трансляции", но вдруг — логическая
ошибка, и начинай все заново?.. Допустим даже ОС будет занимать не 50 миллионов
строк, а только 5 миллионов. Предположим, что в команде не один, а 1000 человек. И
пусть рабочий день программиста составляет 6 часов непрерывной работы. Итак, мы
получим, что на написание нашей ОС этой командой уйдет
555 555/10/1000´(24/6)=222 дня, или около семи месяцев. Что ж... Вполне неплохо,
но какой ценой…?.. К тому же совершенно неизвестно, получится ли в конце концов
система, которая кому-то будет нужна. Представляете, полгода работы — и все на-
прасно?!
Разумеется, в системном и прикладном программировании существуют и другие на-
правления. Например, можно написать какую-нибудь полезную программу, вроде
текстового процессора или браузера. Кстати, вы знаете достоверно, сколько человек
писало Internet Explorer? Лучше бы и я этого не знал...
И вот мы вернулись к тому, с чего начинали: чем же так привлекательна профессия
Web-программиста. Все-таки понять это в полной мере можно, лишь достаточно по-
работав в этой области. Самое привлекательное в ней то, что результат своей работы
можно видеть через довольно короткий срок.
4 Предисловие

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


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

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

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


Книга состоит из пяти частей, содержащих в общей сложности 33 главы, и двух при-
ложений. Непосредственное описание языка PHP начинается с третьей части. Это
объясняется необходимостью прежде узнать кое-что о CGI (Common Gateway Inter-
face — Общий шлюзовой интерфейс) —первая часть, а также выбрать подходящий
инструментарий и Web-сервер для программирования — вторая часть. В четвертой
части разобраны наиболее полезные стандартные функции языка. Пятая часть посвя-
щена различным приемам программирования на PHP с множеством примеров. При-
ложения содержат техническую информацию, которая может иногда пригодиться
Web-программисту.
Теперь чуть подробнее о каждой части книги. В первой рассматриваются теоретиче-
ские аспекты программирования в Web, а также основы того механизма, который
позволяет писать программы в Сети. Если вы уже знакомы с этим материалом (на-
пример, занимались программированием на Perl или других языках), можете ее сме-
ло пропустить. Вкратце я опишу, на чем базируется Web, что такое интерфейс CGI,
как он работает на низком уровне, как используются возможности языка HTML при
программировании Web, как происходит взаимодействие CGI и HTML и многое дру-
гое. В принципе, вся теория по Web-программированию коротко изложена именно в
этой части книги. Так как CGI является независимым от платформы интерфейсом,
материал не "привязан" к конкретному языку (хотя в примерах используется Си как
наиболее универсальный язык). Если вы не знаете языка Си, не стоит отчаиваться:
немногочисленные примеры на этом языке не настолько сложны, чтобы в них можно
было "запутаться". К тому же, каждое действие подробно комментируется. Большин-
ство описанных идей будет повторно затронуто в последующих главах, посвященных
уже языку PHP.
Вторая часть книги довольно небольшая и состоит из разного рода дополнительной
информации, связанной по большей части с серверным программным обеспечением
Apache. Сервер Apache — один из самых популярных в мире, на нем построено око-
ло двух третей хостов Интернета (по крайней мере, на настоящий момент). Главное
его достоинство — простое и в то же время универсальное конфигурирование, что
позволяет создавать довольно сложные и большие серверы на его основе. Думаю,
6 Предисловие

вряд ли в ближайшее время кто-либо будет серьезно использовать PHP под управле-
нием какого-то другого сервера, нежели Apache. Основное внимание во второй части
уделено установке и использованию Apache для Windows, поскольку, как мы увидим
ниже, это очень сильно облегчает программирование и отладку сценариев. Не секрет,
что подчас выбор неверного и неудобного инструментария только из-за того, что "им
пользуются все", является серьезной помехой при программировании. Именно из-за
этого многие "закаленные" Web-программисты "старого образца" не принимают PHP
всерьез. Вторая часть книги призвана раз и навсегда решить эту проблему.
Третья часть целиком посвящена основам PHP. Язык PHP — сравнительно молодой,
но в то же время удивительно удобный и гибкий язык для программирования Web. С
помощью него можно написать 99% программ, которые обычно требуются в Интер-
нете. Для оставшегося 1% придется использовать Си или Perl (или другой универ-
сальный язык). Впрочем, даже это необязательно: вы сильно облегчите себе жизнь,
если интерфейсную оболочку будете разрабатывать на PHP, а ядро — на Си, особен-
но, если ваша программа должна работать быстро, например, если вы пишете поис-
ковую систему. Последняя тема в этой книге не рассматривается, поскольку требует
довольно большого опыта низкоуровневого программирования на языке Си, а потому
не вписывается в концепцию данной книги.
Четвертая часть может быть использована не только как своеобразный учебник, но
также и в справочных целях — ведь в ней рассказано о большинстве стандартных
функций, встроенных в PHP. Я группировал функции в соответствии с их назначени-
ем, а не в алфавитном порядке, как это иногда бывает принято в технической литера-
туре. Что ж, думаю, книга от этого только выиграла. Содержание части во многих
местах дублирует документацию, сопровождающую PHP, но это ни в коей мере не
означает, что она является лишь ее грубым переводом. Наоборот, я пытался взглянуть
на "кухню" Web-программирования, так сказать, свежим взглядом, еще помня свои
собственные ошибки и изыскания. Конечно, все функции PHP описать невозможно
(потому что они добавляются и совершенствуются от версии к версии), да этого и не
требуется, но львиная доля предоставляемых PHP возможностей все же будет нами
рассмотрена.
Пятая часть книги целиком посвящена различным приемам программирования на
PHP. Она насыщена всевозможными примерами программ и библиотек, облегчаю-
щими работу программиста. Если первые три части, да и четвертая в известной мере,
касались Web-программирования в основном теоретически, то здесь как раз основной
упор сделан на практику. Как известно, грамотное программирование и написание
повторно используемого кода может сильно облегчить жизнь, поэтому один из пер-
вых приемов, рассматриваемых в пятой части — это написание системы управления
модулями и библиотеками. Кроме того, вряд ли вы станете разрабатывать сайты в
одиночку — скорее всего, в вашей команде будет дизайнер, HTML-верстальщик и
представители других профессий. Поэтому на передний план выходит техника отде-
ления кода от шаблона страницы сценария, чему также уделяется довольно много
внимания. Дополнительно рассматриваются: загрузка (upload) файлов, реализация
почтовых шаблонов, техника разделенных вычислений и т. д.
Предисловие 7

В приложениях приведена дополнительная информация, касающаяся Web-


программирования. В Приложении 1 содержится полный перевод на русский язык
комментариев в файле конфигурации Apache httpd.conf. Она может очень приго-
диться вам, если вы собираетесь тесно взаимодействовать с этим сервером в своих
сценариях. Приложение 2 включает аналогичный перевод комментариев, сопровож-
дающих файл конфигурации интерпретатора PHP. Оно призвано помочь лучше сис-
тематизировать сведения о конфигурировании PHP, полученные из других глав книги
(и увидеть реальный пример использования многих описанных директив).
ЧАСТЬ I. ОСНОВЫ WEB-ПРОГРАММИРОВАНИЯ

ЧАСТЬ II. ВЫБОР И НАСТРОЙКА ИНСТРУМЕНТАРИЯ. WEB-СЕРВЕР


APACHE

ЧАСТЬ III. ОСНОВЫ ЯЗЫКА PHP

ЧАСТЬ IV. СТАНДАРТНЫЕ ФУНКЦИИ PHP

ЧАСТЬ V. ПРИЕМЫ ПРОГРАММИРОВАНИЯ НА PHP

ЧАСТЬ VI. ПРИЛОЖЕНИЯ


ЧАСТЬ I.
ОСНОВЫ
WEB-ПРОГРАММИРОВАНИЯ
Глава 1

Принципы работы
Интернета

Протоколы передачи данных


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

Иногда я буду называть Интернет Сетью с большой буквы, в отличие от "сети"


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

Необходимость некоторой стандартизации возникла чуть ли не с самого момента воз-


никновения компьютерных сетей. Действительно, подчас одной сетью объединены
компьютеры, работающие под управлением не только различных операционных сис-
тем, но нередко имеющие и совершенно различную архитектуру процессора, органи-
зацию памяти и т. д. Именно для того, чтобы обеспечивать возможность передачи
между такими компьютерами, и предназначены всевозможные протоколы. Давайте
рассмотрим этот вопрос чуть подробнее.
Разумеется, для разных целей существуют различные протоколы. К счастью, нам не
нужно иметь представление о каждом из них — достаточно знать только тот, который
мы будем использовать в Web-программировании. Таковым для нас является прото-
кол TCP (Transmission Control Protocol — Протокол управления передачей данных), а
12 Часть I. Основы Web-программирования

точнее, протокол HTTP (Hypertext Transfer Protocol — Протокол передачи гипертек-


ста), базирующийся на TCP. Протокол HTTP как раз и задействуется браузерами и
Web-серверами.
Заметьте, что уже в самом начале первой главы я упомянул о том, что один протокол
может использовать в своей работе другой. В мире Интернета эта ситуация является
совершенно обычной. Чаще всего каждый из протоколов, участвующих в передаче
данных по сети, реализуется в виде отдельного и по возможности независимого про-
граммного обеспечения или драйвера. Среди них существует некоторая иерархия,
когда один протокол является всего лишь "надстройкой" над другим, тот, в свою оче-
редь — над третьим, и т. д. до самого "низкоуровневого" драйвера, работающего уже
непосредственно на физическом уровне с сетевыми картами или модемами. На
рис. 1.1 приведена примерная схема того, что происходит при отправке запроса брау-
зером пользователя на некоторый Web-сервер в Интернете. Прямоугольниками обо-
значены программные компоненты: драйверы протоколов и программы-абоненты
(последние выделены жирным шрифтом), направление передачи данных указано
стрелками. Конечно, в действительности процесс гораздо более сложен, но нам сей-
час нет необходимости на этом останавливаться.
Обратите внимание, что в пределах каждой системы протоколы на схеме расположе-
ны в виде "стопки", один над другим. Такая структура обуславливает то, что часто
семейство протоколов обмена данными в сети Интернет называют стеком TCP/IP
(стек в переводе с английского как раз и обозначает "стопку").

Машина клиента Машина сервера

Браузер Web-сервер

TCP TCP

IP IP

... ...

Физический Интернет Физический


протокол протокол

Рис. 1.1. Организация обмена данными в Интернете


Каждый из протоколов в идеале "ничего не знает" о том, какой протокол "стоит над
ним". Например, протокол IP (который обеспечивает несколько более простой сервис
по сравнению с TCP) не использует возможности протокола TCP, а TCP, в свою оче-
Глава 1. Принципы работы Интернета 13

редь, "не догадывается" о существовании протокола HTTP (именно его задействует


браузер и понимает Web-сервер, на схеме протокол HTTP не обозначен).
Применение такой организации позволяет заметно упростить ту часть операционной
системы, которая отвечает за поддержку работы с сетью. А я тем временем прошу вас
не пугаться. Нас будет интересовать в конечном итоге всего лишь протокол самого
высокого уровня, "возвышающийся" над всеми остальными протоколами, т. е. HTTP
и то, как он взаимодействует с протоколом TCP.

Семейство TCP/IP
Как мы уже знаем, в сети Интернет в качестве основного выбирается протокол TCP,
хотя, конечно, этот выбор обусловлен скорее историческими причинами, нежели его
действительными преимуществами (впрочем, преимуществ у TCP также предоста-
точно). Он ни в коей мере не претендует на роль низкоуровневого — наоборот, в
свою работу он вовлекает другие протоколы, например, IP (в свою очередь, IP также
базируется на услугах, предоставляемых некоторыми другими протоколами). Прото-
колы TCP и IP настолько сильно связаны, что принято объединять их в одну группу
под названием семейство TCP/IP (в него включается также протокол UDP, который
мы рассматривать не будем). Ниже приводятся основные особенности протокола
TCP, входящего в семейство.
r Корректная доставка данных до места назначения гарантируется — разумеется,
если такая доставка вообще возможна. Даже если связь не вполне надежна (на-
пример, на линии помехи оттого, что в кабель попала вода, замерзшая зимой и ра-
зорвавшая оболочку провода), "потерянные" фрагменты данных посылаются сно-
ва и снова до тех пор, пока вся информация не будет передана.
r Передаваемая информация представлена в виде потока — наподобие того, как
осуществляется обмен с файлами практически во всех операционных системах.
Иными словами, мы можем "открыть" соединение и затем выполнять с ним те же
самые операции, к каким мы привыкли при работе с файлами. Таким образом,
программы на разных машинах (возможно, находящихся за тысячи километров
друг от друга), подключенных к Интернету, обмениваются данными так же не-
принужденно, как и расположенные на одном компьютере.
r TCP/IP устроен так, что он способен выбрать оптимальный путь распространения
сигнала между передающей и принимающей стороной, даже если сигнал проходит
через сотни промежуточных компьютеров. В последнем случае система выбирает
путь, по которому данные могут быть переданы за минимальное время, основыва-
ясь при этом на статистическую информацию работы сети и так называемые таб-
лицы маршрутизации.
r При передаче данные разбиваются на фрагменты — пакеты, которые и доставля-
ются в место назначения по отдельности. Разные пакеты вполне могут следовать
различными маршрутами в Интернете (особенно если их путь пролегает через де-
14 Часть I. Основы Web-программирования

сятки серверов), но для всех них гарантирована правильная "сборка" в месте на-
значения (в нужном порядке). Как уже упоминалось, принимающая сторона в слу-
чае обнаружения "недосдачи" пакета запрашивает передающую систему, чтобы та
передала его еще раз. Все это происходит незаметно для программного обеспече-
ния, эксплуатирующего TCP/IP.
В Web-программировании нам вряд ли придется работать с TCP/IP напрямую (разве
что в очень экзотических случаях) — обычно можно использовать более высокоуров-
невые "языки", например, HTTP, служащий для обмена информацией между серве-
ром и браузером. Собственно, этому протоколу посвящена значительная часть книги.
Его мы рассмотрим подробно чуть позже. А пока давайте поговорим еще немного о
том, что касается TCP/IP, чтобы не возвращаться к этому впоследствии.

Адресация с Сети
Машин в Интернете много, это факт. Так что вопрос о том, как можно их эффективно
идентифицировать в пределах этой сети, оказывается далеко не праздным. Кроме
того, практически все современные операционные системы работают в многозадач-
ном режиме (поддерживают одновременную работу нескольких программ). Это зна-
чит, что возникает также вопрос о том, как нам идентифицировать конкретную сис-
тему или программу, желающую обмениваться данными через Сеть. Эти две задачи
решаются стеком TCP/IP при помощи IP-адреса и номера порта. Давайте посмотрим,
как.

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


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

IP-адрес
Любой компьютер, подключенный к Интернету и желающий обмениваться информа-
цией со своими "сородичами", должен иметь некоторое уникальное имя, или IP-
адрес. Вот уже 30 лет (думаю, и в ближайшее десятилетие тоже) IP-адрес выглядит
примерно так:
127.12.232.56
Как мы видим, это — четыре 8-разрядных числа (то есть принадлежащих диапазону
от 0 до 255 включительно), соединенные точками. Не все числа допустимы в записи
IP-адреса: ряд из них используется в служебных целях (например, адрес 127.0.0.1
выделен для обращения к локальной машине — той, на которой был произведен за-
Глава 1. Принципы работы Интернета 15

прос, а число 255 соответствует широковещательной рассылке в пределах текущей


подсети). Мы не будем здесь обсуждать эти исключения детально.
Возникает вопрос: ведь компьютеров в Интернете миллионы (а скоро будут миллиар-
ды). Как же мы, простые пользователи, запросив IP-адрес машины, в считанные се-
кунды с ней соединяемся? Как "оно" (и что это за "оно"?) узнает, где на самом деле
расположен компьютер и устанавливает с ним связь, а в случае неверного адреса аде-
кватно на это реагирует? Вопрос актуален, поскольку машина, с которой мы собира-
емся связаться, вполне может находиться за океаном, и путь к ней пролегает через
множество промежуточных серверов.
В деталях вопрос определения пути к адресату довольно сложен. Однако достаточно
нетрудно представить себе общую картину, точнее, некоторую ее модель. Предполо-
жим, что у нас есть 1 миллиард компьютеров (давайте завысим цифры), каждый из
которых напрямую соединен с 11 (к примеру) другими через кабели. Получается эта-
кая паутина из кабелей, не так ли? Кстати, это объясняет, почему одна из наиболее
популярных служб Интернета, базирующаяся на протоколе HTTP, названа WWW
(World Wide Web, или Всемирная паутина).

Следует заметить, что в реальных условиях, конечно же, компьютеры не со-


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

Итак, мы сидим за компьютером номер 1 и желаем соединиться с машиной


somehost с таким-то IP-адресом. Мы даем нашему компьютеру запрос: выясни-ка у
своих соседей, не знают ли они чего о somehost. Он рассылает в одиннадцать сторон
этот запрос (считаем, что это занимает 0,1 с, т. к. все происходит практически одно-
временно — размер запроса не настолько велик, чтобы сказалась задержка передачи
данных), и ждет, что ему ответят.
Что же происходит дальше? Нетрудно догадаться. Каждый из компьютеров окруже-
ния действует по точно такому же плану. Он спрашивает у своих десятерых соседей,
не слышали ли они чего о somehost. Это, в свою очередь, занимает еще 0,1 с. Что же
мы имеем? Всего за 0,2 с проверено уже 11´10=
=110 компьютеров. Но это еще не все, ведь процесс нарастает лавинообразно. Не-
трудно подсчитать, что за время порядка 1 секунды мы "разбудим" 10 в десятой сте-
пени машин, т. е. в 10 раз больше, чем мы имеем!
16 Часть I. Основы Web-программирования

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

В действительности дело обстоит куда сложнее. Отличия от представленной


схемы частично заключаются в том, что компьютеру совсем не обязательно
"запрашивать" всех своих соседей — достаточно ограничиться только некото-
рыми из них. Для убыстрения доступа все возможные IP-адреса делятся на че-
тыре группы — так называемые адреса подсетей классов A, B, C и D. Но для
нас сейчас это не представляет никакого интереса, поэтому не будем задержи-
ваться на деталях. О TCP/IP можно написать целые тома (что и делается).

Доменное имя
И все-таки обычным людям довольно неудобно работать с IP-представлением адреса.
Действительно, куда как проще запомнить символьное имя, чем набор чисел. Чтобы
облегчить простым пользователям работу с Интернетом, придумали систему DNS
(Domain Name System — Система имен доменов).

Общемировая DNS представляет собой распределенную базу данных, способ-


ную преобразовать доменные имена машин в их IP-адреса. Это не так-то про-
сто, учитывая, что скоро Интернет будет насчитывать десятки миллионов ком-
пьютеров. Поэтому мы не будем в деталях рассматривать то, как работает
служба DNS, а займемся больше практической стороной вопроса.

Итак, при использовании DNS любой компьютер в Сети может иметь не только IP-
адрес, но также и символическое имя. Выглядит оно примерно так:
www.somehost.msu.su
То есть, это набор слов (их число произвольно), опять же соединенных точкой. Каж-
дое такое сочетание слов называется доменом N-го уровня (например, su — домен
первого уровня, msu.su — второго, somehost.msu.su — третьего и т. д.)
Вообще говоря, полное DNS-имя выглядит немного не так: в его конце обязательно
стоит точка, например:
www.somehost.msu.su.
Именно такое (вообще-то, и только такое) представление является правильным, но
браузеры и другие программы часто позволяют нам опускать завершающую точку. В
принятой нами терминологии будем называть эту точку доменом нулевого уровня,
или корневым доменом.
Глава 1. Принципы работы Интернета 17

Интересно, и почему так популярна в компьютерной технике точка? В именах


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

Нужно заметить, что одному и тому же IP-адресу вполне может соответствовать сразу
несколько доменных имен. Каждое из них ведет в одно и то же место — к единствен-
ному IP-адресу. Благодаря протоколу HTTP 1.1 (мы вскоре кратко рассмотрим его
особенности) Web-сервер, установленный на машине и откликающийся на какой-либо
запрос, способен узнать, какое доменное имя ввел пользователь, и соответствующим
образом среагировать, даже если его IP-адресу соответствует несколько доменных
имен. В последнее время HTTP 1.1 применяется практически повсеместно — не то,
что несколько лет назад, поэтому все больше и больше серверов используют его в
качестве основного протокола для доступа к Web.
Интересен также случай, когда одному и тому же DNS-имени сопоставлены несколь-
ко разных IP-адресов. В этом случае служба DNS автоматически выбирает тот из ад-
ресов, который, по ее мнению, ближе всего расположен к клиенту, или который давно
не использовался, или же наименее загружен (впрочем, последняя оценка может быть
весьма и весьма субъективна). Эта возможность часто задействуется, когда Web-
сервер становится очень большим (точнее, когда число его клиентов начинает пре-
вышать некоторый предел) и его приходится обслуживать сразу нескольким компью-
терам. Такая схема используется, например, на сайте компании Netscape.
Как же ведется поиск по DNS-адресу? Для начала он преобразуется специальными
DNS-серверами, раскиданными по всему миру, в IP-адрес. Давайте посмотрим, как
это происходит. Пусть клиентом выдан запрос на определение IP-адреса машины
www.host.ru. (еще раз обратите внимание на завершающую точку! — это не конец
предложения). Чтобы его обработать, первым делом посылается запрос к так назы-
ваемому корневому домену (точнее, к программе — DNS-серверу, запущенному на
этом домене), который имеет имя "." (на самом деле его база данных распределена
по нескольким компьютерам, но для нас это сейчас несущественно). Запрос содержит
команду: вернуть IP-адрес машины (точнее, IP-адрес DNS-сервера), на котором рас-
положена информация о домене ru. Как только IP-адрес получен, по нему происходит
аналогичное обращение с просьбой — определить адрес, соответствующий домену
host внутри домена ru внутри корневого домена ".".
В конце у предпоследней машины запрашивается IP-адрес поддомена www в домене
somehost.ru.
Важно, что каждый домен "знает" все о своих поддоменах, а те, в свою очередь — о
своих, т. е. система имеет некоторую иерархичность. Корневой домен, как мы уже
заметили, принято называть доменом нулевого уровня, домен ru. (в нашем приме-
ре) — первого, host.ru. — второго уровня, ну и т. д. При изменении доменов неко-
торого уровня об этом должны узнать все домены, родительские по отношению к не-
му, для чего существуют специальные протоколы синхронизации. Нам сейчас нет
18 Часть I. Основы Web-программирования

нужды вникать, как они действуют — скажу только, что распространяются сведения
об изменениях не сразу, а постепенно, спустя некоторое время, задаваемое админист-
ратором DNS-сервера, и рассылкой также занимаются DNS-серверы.
Просто? Не совсем. Представьте, какое бы произошло столпотворение на корневом
домене ".", если бы все запросы на получение IP-адреса проходили через него. Чтобы
этого избежать, практически все машины в Сети кэшируют информацию о DNS-
запросах, обращаясь к корневому домену (и доменам первого уровня — ru, com
и т. д.) лишь изредка для обновления этого кэша. Например, пусть пользователь, под-
ключенный через модем к провайдеру, впервые соединяется с машиной
www.host.ru. В этом случае будет передан запрос корневому домену, а затем, по
цепочке, поддомену host и, наконец, домену www. Если же пользователь вновь обра-
тится к www.host.ru., то сервер провайдера сразу же вернет ему нужный IP-адрес,
потому что он сохранил его в своем кэше запросов ранее. Подобная технология по-
зволяет значительно снизить нагрузку на DNS-серверы в Интернете. В то же время у
нее имеются и недостатки, главный из которых — вероятность получения ложных
данных, например, в случае, если хост host.ru. только что отключился или сменил
свой IP-адрес. Так как кэш обновляется сравнительно редко, мы всегда можем столк-
нуться с такой ситуацией.
Конечно, не обязательно, чтобы все компьютеры, имеющие различные доменные
имена, были разными или даже имели уникальные IP-адреса: вполне возможна си-
туация, когда на одной и той же машине на одном и том же IP-адресе располагаются
сразу несколько доменных имен.

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

Порт
Итак, мы ответили на первый поставленный вопрос — как адресовать отдельные ма-
шины в Интернете. Теперь давайте поглядим, как нам быть с программным обеспе-
чением, использующим Сеть для обмена данными.
До сих пор мы расценивали машины, подключенные к Интернету, как некие недели-
мые сущности. Так оно, в общем-то, и есть (правда, с некоторыми оговорками) с точ-
ки зрения протокола IP. Но TCP использует в своей работе несколько другие понятия.
А именно, для него отдельной сущностью является процесс — программа, запущен-
ная где-то на компьютере в Интернете. Именно между процессами, а не между ма-
шинами, и осуществляется обмен данными в терминах протокола TCP. Мы уже
знаем, как идентифицируются отдельные компьютеры в Сети. Осталось рассмотреть,
как же TCP определяет тот процесс, которому нужно доставить данные.
Глава 1. Принципы работы Интернета 19

Пусть на некоторой системе выполняется программа (назовем ее Клиент), которая


хочет через Интернет соединиться с какой-то другой программой (Сервером) на дру-
гой машине в Сети. Для этого должен выполняться ряд условий, а именно:
r программы должны "договориться" о том, как они будут друг друга идентифици-
ровать;
r программа Сервер должна находиться в режиме ожидания, что сейчас к ней кто-
то подключится;
Давайте остановимся на первом пункте чуть подробнее. Термин "договориться" тут
не совсем уместен (примерно так же милиция "договаривается" с только что задер-
жанным бандитом о помещении его в тюрьму). На самом деле программа Сервер, как
только она запускается, говорит драйверу TCP, что она собирается использовать для
обмена данными с Клиентами некоторый идентификатор, или порт, целое число в
диапазоне от 0 до 65 535 (именно такие числа могут храниться в ячейке памяти раз-
мером в 2 байта). TCP регистрирует это в своих внутренних таблицах — разумеется,
только в том случае, если какая-нибудь другая программа уже не "заняла" нужный
нам порт (в последнем случае происходит ошибка). Затем Сервер переходит в режим
ожидания поступления запросов, приходящих на этот порт. Это означает, что любой
Клиент, который собирается вступить в "диалог" с Сервером, должен знать номер его
порта. В противном случае TCP-соединение невозможно: куда передавать данные,
если не знаешь, к кому подключиться?
Теперь посмотрим, какие действия предпринимает Клиент. Он, как мы условились,
знает следующее:
r IP-адрес машины, на которой запущен Сервер;
r номер порта, который использует Сервер.
Как видим, этой информации вполне достаточно, поэтому Клиент посылает драйверу
TCP команду на соединение с машиной, расположенной по заданному IP-адресу с
указанием нужного номера порта. Поскольку Сервер "на том конце" готов к этому, он
откликается, и соединение устанавливается.
Только что я употребил слово "откликается", подразумевая, что Сервер отправляет
какое-то сообщение Клиенту о том, что он готов к обмену данными. Но вспомним,
что для TCP существует только два понятия для идентификации процесса: адрес и
порт. Так куда же направлять "отклик" Сервера? Очевидно, последний должен каким-
то образом узнать, какой порт будет использовать Клиент для приема сообщений от
него (ведь мы знаем, что принимать данные можно, только зарезервировав для этого
у TCP номер порта). Эту информацию ему как раз и предоставляет драйвер TCP на
машине Клиента, который непосредственно перед установкой соединения выбирает
незанятый порт из списка свободных на данный момент портов на клиентском ком-
пьютере и "присваивает" его процессу Клиент. Затем драйвер информирует о номере
порта Сервер в первом же сообщении о желании установить соединение. Собственно,
это и составляет смысл такого сообщения.
20 Часть I. Основы Web-программирования

Как только обмен "приветственными" сообщениями закончен (его еще называют


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

Терминология
Далее в этой книге я буду часто применять некоторые термины, связанные с "сущно-
стями" в сети Интернет. Чтобы не было разногласий, сразу условимся, что я буду по-
нимать под конкретными понятиями. Перечисляю их в том порядке, в котором, на
мой взгляд, они идут по логике вещей, чтобы ни одно предыдущее слово не "цепля-
лось" за следующее. Это — порядок "от простого к сложному". Собственно, именно
по этому принципу построена вся книга, которую вы держите в руках.

Сервер
Сервер — любой отдельно взятый компьютер в Интернете, который позволяет дру-
гим машинам, грубо говоря, использовать себя в качестве "посредника" при передаче
данных. Также все серверы участвуют в вышеописанной "лавине" поиска компьютера
по ее IP-адресу, на многих хранится какая-то информация, доступная или нет извне.
Сервер — это именно машина ("железо"), а не логическая часть Сети, он может
иметь несколько различных IP-адресов (не говоря уже о доменных именах), так что
вполне может выглядеть из Интернета как несколько независимых систем.
Только что я сказал, что сервер — это "железо". Пожалуй, это слишком механистиче-
ский подход. Мы можем придерживаться и другой точки зрения, тоже в некоторой
степени правильной. Отличительной чертой сервера является то, что он использует
один-единственный стек TCP/IP, т. е. на нем запущено только по одному "экземпля-
ру" драйверов протоколов. Пожалуй, это будет даже правильнее, хотя в настоящее
время оба определения почти эквивалентны (просто современный компьютер не на-
столько мощен, чтобы на нем могли бы функционировать одновременно две операци-
онные системы, а следовательно, и несколько стеков TCP/IP). Посмотрим, как будет с
этим обстоять дело в будущем.
У термина "сервер" есть и еще одно, совершенно другое, определение — это про-
грамма (в терминологии, TCP — процесс), обрабатывающая запросы клиентов. На-
пример, приложение, обслуживающее пользователей WWW, называется Web-
сервером. Как правило, из контекста будет ясно, что конкретно имеется в виду. Все
же, чтобы не путаться, иногда я такие программы буду называть сетевыми демона-
ми.
Глава 1. Принципы работы Интернета 21

Узел
Любой компьютер, подключенный к Интернету, имеет свой уникальный IP-адрес. Нет
адреса — нет узла. Узел — совсем не обязательно сервер (типичный пример — кли-
ент, подключенный через модем к провайдеру). Вообще, мы можем дать такое опре-
деление: любая сущность, имеющая уникальный IP-адрес в Интернете, называется
узлом. С этой (логической) точки зрения Интернет можно рассматривать, как множе-
ство узлов, каждый из которых потенциально может связаться с любым другим. За-
метьте, что на одной системе может быть расположено сразу несколько узлов, если
она имеет несколько IP-адресов. Например, один узел может заниматься только дос-
тавкой и рассылкой почты, второй — исключительно обслуживанием WWW, а на
третьем работает DNS-сервер.
Помните, мы говорили о том, что TCP использует термин "процесс", и каждый про-
цесс для него однозначно идентифицируется IP-адресом и номером порта. Так вот,
этот самый IP-адрес и есть узел.

Порт
Некоторое число, которое идентифицирует программу, желающую принимать данные
из Интернета. Таким образом, порт — вторая составляющая адресации TCP. Любая
программа, стремящаяся передать данные другой, должна знать номер порта, кото-
рый закреплен за последней. Например, традиционно Web-серверу выделяется порт с
номером 80, поэтому, когда вы набираете какой-нибудь адрес в браузере, запрос идет
именно на порт 80 указанного узла.

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

Сам термин "сетевой демон" возник на базе устоявшейся терминологии Unix.


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

Впрочем, к Web-программированию написание сетевых демонов не имеет почти ни-


какого отношения, поскольку это — удел системного программирования. И все же
22 Часть I. Основы Web-программирования

скажу о них пару слов, т. к. эта область иногда довольно близко примыкает к Web-
программированию. Написание сетевых демонов — дело непростое и, к тому же,
обычно требует полного контроля над "железом" сервера. Фирмы, "продающие" вир-
туальные хосты в Интернете (хостинг-провайдеры), не позволяют этого делать из
соображений безопасности, а также из-за того, что такая программа постоянно рабо-
тает на компьютере и отнимает процессорное время. Поскольку у многих нет своего
собственного узла в Сети (а это стоит обычно около 100—200 долларов в месяц),
возможность создавать такие программы доступна далеко не всем, потому мы не бу-
дем касаться ее в этой книге.

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

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

Виртуальный хост
Это — хост, не имеющий уникального IP-адреса в Сети, но, тем не менее, доступный
указанием какого-нибудь дополнительного адреса (например, его DNS-имени). В по-
следнее время число виртуальных хостов в Интернете постоянно возрастает, что свя-
зано с повсеместным распространением протокола HTTP 1.1. С точки зрения Web-
браузера (вернее, с точки зрения пользователя, который этим браузером пользуется)
виртуальный хост выглядит так же, как и обычный хост — правда, его нельзя адресо-
вать по IP-адресу.
К сожалению, все еще существуют версии браузеров, не поддерживающие протокол
HTTP 1.1, которые соответственно не могут быть использованы для обращения к та-
ким ресурсам.

Понятие "виртуальный хост" не ограничивается только службой Web. Многие


другие сервисы имеют свои понятия о виртуальных хостах, совершенно не свя-
Глава 1. Принципы работы Интернета 23

занные с Web и протоколом HTTP 1.1. Сервер sendmail службы SMTP (Simple
Mail Transfer Protocol — Простой протокол передачи почты) также использует
понятие "виртуальный хост", но для него это — лишь синоним главного, основ-
ного хоста, на котором запущен сервер. Например, если хост syn.com являет-
ся синонимом для microsoft.com, то адрес E-mail my@syn.com на самом де-
ле означает my@microsoft.com. Примечательно, однако, что виртуальный
хост и в этом понимании не имеет уникального IP-адреса.

Хостинг-провайдер (хостер)
Организация, которая может создавать хосты (виртуальные или обычные) в Интерне-
те и продавать их различным клиентам, обычно за определенную плату. Существует
множество хостинг-провайдеров, различающихся по цене, уровню обслуживания,
поддержке telnet-доступа (то есть доступа в режиме терминала к операционной сис-
теме машины) и т. д. Они могут оказывать услуги по регистрации доменного имени в
Интернете, а могут и не оказывать. При написании этой книги я рассчитывал, что
читатель собирается воспользоваться услугами такого хостинг-провайдера, который
предоставляет возможность использования PHP (их сейчас большинство). Если вы
еще не выбрали хостинг-провайдера и только начинаете осваивать Web-
программирование, не беда: во второй части книги подробно рассказано, как можно
установить и настроить собственный Web-сервер на любом компьютере с установ-
ленной операционной системой Windows. (Это можно сделать даже на той самой ма-
шине, на которой будет работать браузер — ведь драйверу протокола TCP совершен-
но безразлично, где выполняется процесс, к которому будет осуществлено
подключение, хоть даже и на том же самом компьютере.) Используя этот сервер, вы
сможете немного потренироваться. Кроме того, он незаменим при отладке тех про-
грамм, которые вы в будущем планируете разместить на настоящем хосте в Интерне-
те.

Хостинг
Те услуги, которые предоставляют клиентам хостинг-провайдеры.

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

HTML-документ
Файл, содержащий данные в формате HTML.
24 Часть I. Основы Web-программирования

Страница (или HTML-страница)


Адресуемая из Интернета минимальная единица текстовой информации службы
World Wide Web, которая может быть затребована у Web-сервера и отображена в
браузере. Часто страница представлена отдельным HTML-документом, однако в по-
следнее время число таких страниц постоянно сокращается — чаще они генерируют-
ся автоматически "на лету" какой-нибудь программой и тут же отсылаются клиенту.
Например, гостевая книга, в который пользователь может оставить текстовое сооб-
щение, — пример страницы, не являющейся HTML-документом в обычном смысле.
Язык HTML (Hypertext Markup Language — Язык разметки гипертекста) позволяет
вставлять в страницы ссылки на другие страницы. Щелкнув кнопкой мыши на поле
ссылки, пользователь может переместиться к тому или иному документу. Впрочем,
подразумевается, что читатель более-менее знаком с языком HTML, а потому в этой
книге о нем дается минимум сведений — в основном только те, которые касаются
форм.

Web-программирование
Этот термин будет представлять для нас особый интерес, потому что является темой
книги, которую вы держите в руках, уважаемый читатель. Давайте же наконец про-
ставим все точки над "i".
Только что упоминалось, что страница и HTML-документ — вещи несколько разные,
а также то, что существует возможность создания страниц "на лету" при запросе
пользователя. Разработка программ, которые занимаются формированием таких
страниц, и есть Web-программирование. Все остальное (в том числе, администриро-
вание серверов, разграничение доступа для пользователей и т. д.) не имеет к Web-
программированию никакого отношения. Фактически, для работы Web-программиста
требуется только наличие правильно сконфигурированного и работающего хостинга
(возможно, купленного у хостинг-провайдера, в этом случае уж точно среда будет
настроена правильно), и это все.
По большому счету эта книга посвящена именно Web-программированию, за исклю-
чением второй части и Приложений. Во второй части рассказано о том, как за мини-
мальное время настроить "домашний" хостинг на своей собственной машине, пусть
даже и не подключенной к Интернету, т. е. стать "сам себе хостером". Это не так бес-
полезно, как может показаться, и вскоре вы поймете, почему.

Между прочим, представленная терминология довольно-таки спорная — в раз-


ных публикациях используются различные термины. Например, однажды я ви-
дел, как хостом называлась любая сущность, имеющая уникальный IP-адрес в
Интернете. Лично я с этим не согласен и буду называть эту сущность узлом.
Глава 1. Принципы работы Интернета 25

World Wide Web и URL


В наше время одной из самых популярных "служб" Интернета является World Wide
Web, Web или WWW (все три термина совершенно равносильны). Действительно,
большинство серверов Сети поддерживают WWW и связанный с ним протокол пере-
дачи HTTP (Hypertext Transfer Protocol — Протокол передачи гипертекста). Служба
привлекательна тем, что позволяет организовывать на хостах сайты — хранилища
текстовой и любой другой информации, которая может быть просмотрена пользова-
телем в интерактивном режиме.
Я думаю, каждый хоть раз в жизни набирал какой-нибудь "адрес" в браузере. Он на-
зывается URL (Universal Resource Locator — Универсальный идентификатор ресурса)
и обозначает в действительности нечто большее, нежели чем просто адрес. Для чего
же нужен URL? Почему недостаточен лишь один DNS-адрес?
Ответ довольно-таки очевиден. Действительно, каждый Web-сайт обычно хранит в
себе множество документов. Следовательно, нужно иметь механизм, который бы по-
зволял пользователю ссылаться на конкретный документ внутри указанного хоста.
В общем случае URL выглядит примерно так:
http://www.somehost.com:80/path/to/document.html
Давайте рассмотрим чуть подробнее каждую логическую часть этого URL.

Протокол
Часть URL, предваряющая имя хоста и завершающаяся двумя косыми чертами (в
нашем примере http://), указывает браузеру, какой высокоуровневый протокол
нужно использовать для обмена данными с Web-сервером. Обычно это HTTP, но мо-
гут поддерживаться и другие протоколы. Например, протокол HTTPS позволяет пере-
давать информацию в специальном зашифрованном виде, чтобы злоумышленники не
могли ее перехватить, — конечно, если Web-сервер способен с ним работать. Нужно
заметить, что все подобные протоколы базируются на сервисе, предоставляемом TCP,
и по большей части представляют собой лишь набор текстовых команд. В следующей
главе мы убедимся в этом утверждении, разбирая, как работает протокол HTTP.

Имя хоста
Следом за протоколом идет имя узла, на котором размещается запрашиваемая стра-
ница (в нашем примере — www.somehost.com). Это может быть не только доменное
имя хоста, но и его IP-адрес. В последнем случае, как нетрудно заметить, мы сможем
обращаться только к узлам (невиртуальным хостам), потому что лишь они однознач-
но идентифицируются указанием их IP-адреса.
26 Часть I. Основы Web-программирования

Порт
Сразу за именем хоста через двоеточие может следовать (а может и быть опущен)
номер порта. Исторически сложилось, что для протокола HTTP стандартный номер
порта — 80 (или 81). Именно это значение используется браузером, если пользова-
тель явно не указал номер порта. Как мы знаем, порт идентифицирует постоянно ра-
ботающую программу на сервере (или, как ее нередко называют, сетевой демон), в
частности, порт 80 связывается с Web-сервером, который и осуществляет обработку
HTTP-запросов клиентов и пересылает им нужные документы. Существуют и другие
демоны, например, FTP и Telnet, но к ним нельзя подключиться с помощью браузера.

Путь к странице
Наконец, мы дошли до последней части адресной строки — пути к файлу страницы (в
нашем примере это /path/to/document.html). Как уже упоминалось, совершенно
не обязательно, чтобы эта страница действительно присутствовала, — вполне типич-
на ситуация, когда страницы создаются "на лету" и не представлены отдельными
файлами в файловой системе сервера. Например, сайт новостей может использовать
виртуальные пути типа /Y/M/N.html для отображения всех новостей за число N ме-
сяца M года Y, так что пользователь, набрав в браузере адрес наподобие
http://новострой_сервер/2000/
10/20.html, сможет прочитать новости за 20 октября 2000 года. При этом файла с
именем 20.html физически нет, существует только виртуальный путь к нему, а всю
работу по генерации страницы берет на себя программное обеспечение сервера (в
последней части этой книги я расскажу, как такое программное обеспечение можно
написать).
Есть и другой механизм обработки виртуальных путей, когда запрошенные файлы
представляют собой статические объекты, но располагаются где-то в другом месте. С
точки зрения программного обеспечения путь к документу отсчитывается от некото-
рого корневого каталога, который указывает администратор сервера. Практически все
серверные программы позволяют создавать псевдонимы для физических путей. На-
пример, если мы вводим:
http://www.somehost.com/cgi-bin/something
отсюда не следует, что существует каталог cgi-bin, — это может быть лишь имя
псевдонима, ссылающегося на какую-то другую каталог.
Расширение html (от HyperText Markup Language — Язык разметки гипертекста)
принято давать документам со страницами Web. HTML представляет собой язык, на
котором задается расположение текста, рисунков, гиперссылок и т. д. Кроме html
часто встречаются и другие форматы данных: gif, jpg — для изображений, cgi,
pl — для сценариев (программ, запускаемых на сервере) и т. д. Вообще говоря, сер-
вер можно настроить таким образом, чтобы он корректно работал с любыми расши-
рениями, например, никто не запрещает нам сконфигурировать его так, чтобы файлы
Глава 1. Принципы работы Интернета 27

с расширением htm также рассматривались как HTML-документы (что часто и дела-


ется).

Браузеру совершенно все равно, какое расширение у запрошенного объекта —


он ориентируется по другому признаку.
Глава 2

Интерфейс CGI

Термин CGI (Common Gateway Interface — Общий шлюзовой интерфейс) обозначает


набор соглашений, которые должны соблюдаться Web-серверами при выполнении
ими различных Web-приложений. Вскоре мы расшифруем его смысл гораздо более
подробно. Фактически, до недавнего времени все Web-программирование представ-
ляло собой программирование CGI-приложений. В последнее время ситуация изме-
нилась. И хотя CGI все еще остается негласным стандартом для Web-приложений,
механизм работы CGI-программ несколько обновился.
В этой и следующей главах мы будем разбирать основы традиционного CGI-
программирования, не касаясь напрямую PHP. В качестве языка для примеров вы-
бран Си, поскольку его компиляторы можно найти практически в любой операцион-
ной системе, и по той причине, что он "наиболее красиво" показывает, почему… его
не следует использовать в Web-программировании. Да-да, это не опечатка. Вскоре вы
поймете, что я хотел сказать.

Что такое CGI?


Итак, мы набираем в нашем браузере
http://www.somehost.com:80/path/to/document.ext
Мы ожидаем, что сейчас получим HTML-документ (или документ другого форма-
та — например, рисунок). Иными словами, мы рассчитываем, что на хосте в каталоге
/path/to/ расположен файл document.ext, который нам сейчас и доставят (пере-
даст его, кстати, Web-сервер, подключенный к порту 80 на сервере).
Однако на самом деле ситуация несколько иная. По двум причинам.
r Путь /path/to/, ровно как и файл document.ext на хосте может вообще не
существовать. Ведь администратор сервера имеет возможность задать псевдоним
(alias) для любого объекта на сервере. Кроме того, даже если и не назначено ника-
кого псевдонима, все равно имеется возможность так написать программы для
Web-сервера, что они будут "перехватывать" каждое обращение к таким путям и
соответствующим образом реагировать на это (пример рассмотрен в главе 1 ).
r Файл document.ext может быть вовсе не текстовым документом, а программой,
которая в ответ на наш запрос молниеносно запустится, не менее стремительно
Глава 2. Интерфейс CGI 29

выполнится и возвратит пользователю результаты своей работы, хотя бы в том же


HTML-формате (или, разумеется, в любом другом, — например, это может быть
изображение). Пользователь и не догадается, что на самом деле произошло. Для
него все равно, загружает ли он документ или невольно запускает программу. Бо-
лее того, он никак не сможет узнать, что же на самом деле случилось.
Последний пункт особенно впечатляющ. Если вы прониклись его идеей, значит, вы
поняли в общих чертах, что такое CGI. Как раз CGI обеспечивает все то, что выгля-
дит так прозрачно для пользователя. Традиционно программы, работающие в соот-
ветствии с соглашениями CGI, называют сценариями — скорее всего из-за того, что в
большинстве случаев их пишут на языках-интерпретаторах, подобных Basic (напри-
мер, на Perl или PHP).
Задумаемся на мгновенье. Мы получили довольно мощный механизм, который по-
зволяет нам, в частности, формировать документы "на лету". К примеру, пусть нам
нужно, чтобы в каком-то документе проставлялись текущая дата и время. Разумеется,
мы не можем заранее прописать их в документе — ведь в зависимости от того, когда
он будет загружен пользователем, эта дата должна меняться. Зато мы можем напи-
сать сценарий, который вычислит дату, вставит ее в документ и затем передаст его
пользователю, который даже ничего и не заметит!
Однако в построенной нами модели не хватает одного звена. Действительно, предпо-
ложим, нам нужно, чтобы время в нашей странице проставлялось на основе часового
пояса пользователя. Но как сценарий узнает, какой часовой пояс у региона, в котором
живет этот человек (или какую-нибудь другую информацию, которую может предос-
тавить пользователь)? Видимо, должен быть какой-то механизм, который позволит
пользователю не только получать, но также и передавать информацию серверу (в
данном случае, например, поправку времени в часах относительно Москвы). И это
тоже обеспечивает CGI. Но вернемся прежде снова к URL.

Секреты URL
Помните, я выше описывал, как выглядит URL? Каюсь, приврал. На самом деле URL
имеет более "длинный" вид:
http://www.somehost.com:80/path/to/document.ext?parameters
Как нетрудно заметить, может существовать еще строка parameters, следующая
после вопросительного знака. В некоторой степени эта строка аналогична командной
строке ОС. В ней может быть все, что угодно, она может быть любой длины (однако
следует учитывать, что некоторые символы должны быть URL-закодированы, см.
ниже). Вот как раз эта-то строка и передается CGI-сценарию.
30 Часть I. Основы Web-программирования

На самом деле существуют некоторые ограничения на длину строки парамет-


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

Вернемся к нашему предыдущему примеру. Теперь пользователь может указать свой


часовой пояс сценарию, например, так:
http://www.somehost.com/script.cgi?time=+5
Сценарий с именем script.cgi, после того как он запустится и получит эту строку
параметров, должен ее проанализировать (например, создать переменную time и
присвоить ей значение +5, т. е. 5 часов вперед) и дальше работать как ему нужно.
Обращаю ваше внимание на то, что принято параметры сценариев указывать именно
в виде переменная=значение.
А если нужно передать несколько параметров (например, не только часовой пояс, но
и имя пользователя)? Сделаем это следующим образом:
http://www.somehost.com/script.cgi?time=+5&name=Vasya
Опять же, принято разделять параметры с помощью символа &. Будем в дальнейшем
придерживаться этого соглашения, поскольку именно таким образом поступают брау-
зеры при обработке форм. (Видели когда-нибудь на странице несколько полей ввода
и переключателей, а под ними кнопку "Отправить"? Это и есть форма, с ее помощью
можно автоматизировать процесс передачи данных сценарию). Ну и, разумеется, сце-
нарий опять же должен адекватно среагировать на эти параметры: провести разбор
строки, создать переменные и т. д. Обращаю ваше внимание на то, что все эти дейст-
вия придется программировать вручную, если мы хотим воспользоваться языком Си.
Так вот, такой способ посылки параметров сценарию (когда данные помещаются в
командную строку URL) называется методом GET. Фактически, даже если не переда-
ется никаких параметров (например, при загрузке статической страницы), все равно
применяется метод GET. Все? Нет, не все. Существует еще один распространенный
способ (не менее распространенный) — метод POST, но давайте прежде рассмотрим,
на каком языке "общаются" браузер и сервер.

Заголовки и метод GET


Задумаемся на минуту, что же происходит, когда мы набираем в браузере строку
somestring и нажимаем <Enter>. Браузер посылает серверу запрос somestring?
Нет, конечно. Все немного сложнее. Он анализирует строку, выделяет из нее имя сер-
вера и порт (а также имя протокола, но нам это сейчас не интересно), устанавливает
соединение с Web-сервером по адресу сервер:порт и посылает ему что-то типа сле-
дующего:
GET somestring HTTP/1.0\n
Глава 2. Интерфейс CGI 31

...другая информация...
\n\n
Здесь \n означает символ перевода строки, а \n\n — два обязательных символа но-
вой строки, которые являются маркером окончания запроса (точнее, окончания заго-
ловков запроса). Пока мы не пошлем этот маркер, сервер не будет обрабатывать наш
запрос.
Как видим, после GET-строки могут следовать и другие строки с информацией, раз-
деленные символом перевода строки. Их обычно формирует браузер. Такие строки
называются заголовками (headers), и их может быть сколько угодно. Протокол HTTP
как раз и задает правила формирования и интерпретации этих заголовков.
Вот мы и начинаем знакомство с протоколом HTTP. Как видите, он представляет со-
бой ни что иное, как просто набор заголовков, которыми обмениваются сервер и
браузер, и еще пару соглашений насчет метода POST, которые мы вскоре рассмотрим.
Не все заголовки обрабатываются сервером — некоторые просто пересылаются за-
пускаемому сценарию с помощью переменных окружения. Переменные окружения
представляют собой именованные значения параметров, которые операционная сис-
тема (точнее, процесс-родитель) передает запущенной программе. Программа может
с помощью специальных функций (их мы рассмотрим в следующей главе на приме-
рах) получить значение любой установленной переменной окружения, указав ее имя.
Именно так и должен поступать CGI-сценарий, когда захочет узнать значение того
или иного заголовка запроса. К сожалению, набор передаваемых сценарию заголов-
ков ограничен стандартами, и некоторые заголовки нельзя получить из сценария ни-
каким способом (ему просто недоступна соответствующая переменная окружения).
Такие случаи мы будем оговаривать особо.

Если быть до конца честным, то все-таки системный администратор может на-


строить сервер так, чтобы он посылал сценарию и те заголовки, которые по
стандарту не передаются. Однако это выходит далеко за рамки Web-
программирования, поэтому мы не будем останавливаться на этом вопросе.

Ниже приводятся некоторые заголовки запросов с их описаниями, а также имена пе-


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

GET
r Формат: GET сценарий?параметры HTTP/1.0
r Переменные окружения: REQUEST_URI; в переменной QUERY_STRING сохраняется
значение параметры, в переменной REQUEST_METHOD — ключевое слово GET.
32 Часть I. Основы Web-программирования

Этот заголовок является обязательным (если только не применяется метод POST) и


определяет адрес запрашиваемого документа на сервере. Также задаются параметры,
которые пересылаются сценарию (если сценарию ничего не передается, или же это
обычная статическая страница, то все символы после знака вопроса и сам знак опус-
каются). Вместо строки HTTP/1.0 может быть указан и другой протокол — напри-
мер, HTTP/1.1. Именно его соглашения и будут учитываться сервером при обработке
данных, поступивших от пользователя, и других заголовков.
Строка сценарий?параметры задается в том же самом формате, в котором она вхо-
дит в URL. Неплохо было бы назвать эту строку как-нибудь более реалистично, чтобы
учесть возможность присутствия в ней командных параметров. Такое название дейст-
вительно существует и звучит как URI (Universal Resource Identifier — Универсаль-
ный идентификатор ресурса). Очень часто его смешивают с понятием URL (вплоть до
того, что это происходит даже в официальной документации по стандартам HTTP).
Давайте договоримся, что в будущем я всегда буду называть словом URL полный
путь к некоторой Web-странице вместе с параметрами, и условимся, что под словом
URI будет пониматься его часть, расположенная после имени (или IP-адреса) хоста и
номера порта.

POST
r Формат: POST сценарий?параметры HTTP/1.0
r Переменная окружения: REQUEST_URI; в переменной QUERY_STRING сохраняется
значение параметры, в переменной REQUEST_METHOD — слово POST.
Этот заголовок используется при передаче данных методом POST. Вскоре мы рас-
смотрим этот метод подробнее, а пока скажу лишь, что он отличается от метода GET
тем, что данные можно передавать не только через командную строку, но и в конце
всех заголовков.

Content-type
r Формат: Content-Type: application/x-www-form-urlencoded
r Переменная: CONTENT_TYPE
Данный заголовок идентифицирует тип передаваемых данных. Обычно для этого ука-
зывается значение application/x-www-form-urlencoded, что означает формат, в
котором все управляющие символы (отличные от алфавитно-цифровых и других ото-
бражаемых) специальным образом кодируются. Это тот самый формат передачи, ко-
торый используется методами GET и POST. Довольно распространен и другой формат,
и называется он multipart/form-data. Мы разберем его, когда будем обсуждать
вопрос, касающийся загрузки файлов на сервер.
Хочу обратить ваше внимание на то, что сервер никак не интерпретирует рассматри-
ваемый заголовок, а просто передает его сценарию через переменную окружения.
Глава 2. Интерфейс CGI 33

User-Agent
r Формат: User-Agent: Mozilla/4.5 [en] (Win95; I)
r Переменная окружения: HTTP_USER_AGENT
Уточняет версию браузера (в данном случае это Netscape Navigator).

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

Вы, наверное, подумали, что слово referer пишется по-английски с двумя бук-
вами "r". Да, вы правы. Однако те, кто придумывал стандарт HTTP, этого, ви-
димо, не знали. Так что не позволяйте столь досадному факту ввести себя в
заблуждение, когда будете в сценарии использовать переменную окружения
HTTP_REFERER.

Content-length
r Формат: Content-length: длина
r Переменная окружения: CONTENT_LENGTH
Заголовок содержит строку, являющуюся десятичным представлением длины данных
в байтах, передаваемых методом POST. Если задействуется метод GET, то этот заго-
ловок отсутствует, и значит, переменная окружения не устанавливается.

Cookie
r Формат: Cookie: значения_Cookies
r Переменная окружения: HTTP_COOKIE
Здесь хранятся все Cookies в URL-кодировке (о Cookies мы подробнее поговорим в
следующей главе).
34 Часть I. Основы Web-программирования

Accept
r Формат: Accept: text/html, text/plain, image/gif, image/jpeg
r Переменная окружения: HTTP_ACCEPT
В этом заголовке браузер перечисляет, какие типы документов он "понимает". Пере-
числение идет через запятую. К сожалению, в последнее время браузеры стали не-
сколько небрежны и часто присылают в этом заголовке значение */*, что обозначает
любой тип.
Существует еще множество заголовков запроса (часть из них востребуются только
протоколом HTTP 1.1), но мы не будем на них задерживаться.

Эмуляция браузера через telnet


Между прочим, при передаче запроса браузер "притворяется" пользователем, кото-
рый запустил telnet-клиента (программу, которая, грубо говоря, умеет подключаться к
заданному IP-адресу и порту, посылать по нему то, что набирается на клавиатуре, и
отображать на экране поступающие "снаружи" данные) и вводит строки заголовков
вручную — т. е., в текстовом виде. Например, вместо того чтобы набрать в браузере
http://www.somehost.com/, попробуйте в командной строке ОС (Unix, Windows
95/98/NT/2000 или любой другой) выполнить следующие команды (вместо <Enter>
нажимая соответствующую клавишу):
telnet www.somehost.com 80<Enter>
GET /index.html HTTP/1.0<Enter>
<Enter>
Вы увидите, как перед вами промелькнут строки HTML-документа index.html.
Очень рекомендую проделать описанную процедуру, чтобы избавиться от духа мис-
тицизма при упоминании о протоколе HTTP. Все это не так сложно, как иногда может
показаться.

Если у вас указанная процедура не удалась, и сервер все время шлет сообще-
ние "Bad Request", то проверьте регистр символов, в котором вы набираете
команды. Все буквы должны быть заглавными, а название протокола
HTTP/1.0 — идти без пробелов.

Посмотрим теперь, как работает сервер. А происходит все следующим образом: он


считывает все заголовки запроса и дожидается маркера "\n\n" (или, что то же самое,
"пустого" заголовка), а как только его получает, начинает разбираться — что же ему
за информация пришла, и выполнять соответствующие действия.
С помощью заголовков реализуются такие механизмы, как контроль кодировок,
Cookies, метод POST и т. д. Если же сервер не понимает какого-то заголовка, он его
Глава 2. Интерфейс CGI 35

либо пропускает, либо жалуется отправителю (в зависимости от воли администрато-


ра, который настраивал сервер).

Метод POST
Мы подошли к сути метода POST. А что, если мы в предыдущем примере зададим
вместо GET слово POST и после последнего заголовка (маркера \n\n) начнем переда-
вать какие-то данные? В этом случае сервер их воспримет и также передаст сцена-
рию. Только нужно не забыть проставить заголовок Content-length в соответствии
с размером данных, например:
POST /script.cgi HTTP/1.0\n
Content-length: 5\n
\n
Test!
Сервер начнет обработку запроса, не дожидаясь передачи данных после маркера кон-
ца заголовков. Иными словами, сценарий запустится сразу же после отправки \n\n, а
уж ждать или не ждать, пока придет строка Test! длиной 5 байтов — его дело.
Последнее означает, что сервер никак не интерпретирует POST-данные (точно так же,
как он не интерпретирует некоторые заголовки), а пересылает их непосредственно
сценарию. Но как же сценарий узнает, когда данные кончаются, т. е. когда ему пре-
кращать чтение информации, поступившей от браузера? В этом ему поможет пере-
менная окружения Content-Length, и именно на нее следует ориентироваться. Чуть
позже мы рассмотрим этот механизм подробнее.
Зачем нужен метод POST? В основном для того, чтобы передавать большие объемы
данных. Например, при загрузке файлов через Web (см. ниже) или при обработке
больших форм. Кроме того, метод POST часто используют для эстетических целей:
дело в том, что при применении GET, как вы, наверное, уже заметили, URL сценария
становится довольно длинным и неизящным, а POST-запрос оставляет URL без изме-
нения.

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


Ранее упоминалось, что и в методе GET, и в методе POST данные доставляются в
URL-кодированном виде. Что это значит?
Уж не знаю, откуда взялась эта дурная традиция (может, из стремления сохранить
совместимость с древними программами, которыми вот уже лет 20 никто не пользу-
ется), но почему-то все Интернет-сервисы — начиная от
E-mail и заканчивая Web — как-то очень "не любят" байты со значениями, превы-
шающими 127. Поэтому применяется изощренный способ перекодировки, который
все символы в диапазонах 0 .. 32 и 128 .. 256 представляет в URL-кодированном ви-
де. Например, если нам нужно закодировать символ с шестнадцатеричным кодом 9E,
36 Часть I. Основы Web-программирования

это будет выглядеть так: %9E. Помимо этого, пробел представляется символом плюс
(+). Так что будьте готовы к тому, что вашим сценариям будут передаваться данные
именно в таком виде.
В частности, все буквы кириллицы преобразуются в подобную абракадабру (соответ-
ственно, размер данных увеличивается примерно в 3 раза!). Поэтому программы
должны всегда быть готовы перекодировать информацию туда-сюда-обратно.
Но это только пол-беды. Дело в том, что существует еще такая неприятная проблема,
как кодировки символов кириллицы. И неприятно не столько то, что они существуют,
сколько то, что они все не подчиняются никакому единому логическому правилу, в
отличие он ASCII. Если при этом текст, который пришел, допустим, в кодировке
KOI-8-R, просматривают в WIN-кодировке, получается редкостная путаница.
Казалось бы, чего сложного — выполнить автоматическое перекодирование в чита-
бельный вид полученного текста (кстати говоря, относительно часто этот текст даже
снабжен указанием, в какой же он кодировке). Однако, насмотревшись на разнооб-
разные программные продукты, складывается такое впечатление, что эта проблема
сложнее, чем создание искусственного интеллекта! А дело все в том, что "интеллек-
туальные" серверы вместо того, чтобы присылать и принимать текст всегда в фикси-
рованной кодировке и переложить эту проблему на плечи браузеров, зачем-то сами
занимаются перекодировкой. И браузеры в своем большинстве — тоже. Так что ино-
гда бывает, что текст приходит "зашифрованным" с помощью каких-то двух экзоти-
ческих кодировок, что окончательно его портит.

Существуют даже специальные программы, которые пытаются раскодировать


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

Что может быть глупее? А все по той причине, что нет строгого стандарта на кирил-
лицу и что, якобы, где-то в мире существуют браузеры, которые не умеют перекоди-
ровать информацию. Скажите на милость, зачем они тогда вообще нужны, если не
умеют делать даже такой простой вещи, как табличные преобразования? Или это сде-
лано для тех, кто читает Web-страницы не через браузер, а по telnet'у? И почему же
из-за жалкой горстки пользователей должна страдать остальная часть населения
страны?
Ну ладно-ладно, я уже успокоился. Прошу прощения, что влез на стол и кричал. Да-
вайте продолжим.
Глава 2. Интерфейс CGI 37

Что такое формы


и для чего они нужны
Итак, мы знаем, что наиболее распространенными методами передачи данных между
браузером и сценарием являются GET и POST. Однако вручную задавать строки пара-
метров для сценариев и к тому же URL-кодировать их, согласитесь, довольно утоми-
тельно. Давайте посмотрим, что же язык HTML предлагает нам для облегчения жиз-
ни.
Сначала рассмотрим метод GET. Даже программисту довольно утомительно набирать
параметры в URL вручную. Всякие там ?, &, %... Представьте себе пользователя, ко-
торого принуждают это делать. К счастью, существуют удобные возможности языка
HTML, которые, конечно, поддерживаются браузерами. И хотя я уже замечал, что в
этой книге будет лишь немного рассказано о HTML, о формах мы поговорим очень
подробно.
Итак, пусть у нас на сервере в корневом каталоге размещен файл сценария
script.cgi (наверное, вы уже заметили, что расширение cgi принято присваивать
CGI-сценариям, хотя, как уже упоминалось, никто не мешает вам использовать любое
другое слово). Этот сценарий распознает 2 параметра: name и age. Где эти парамет-
ры задаются, мы пока не решили. При переходе по адресу
http://www.somehost.com/script.cgi он должен отработать и вывести сле-
дующую HTML-страницу:
<html><body>
Привет, name! Я знаю, Вам age лет!
</body></html>

Разумеется, при генерации страницы нужно name и age заменить на соответствую-


щие значения, переданные в параметрах.

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


Давайте будем включать параметры прямо в URL, в строку параметров. Таким обра-
зом, если запустить в браузере
http://www.somehost.com/script.cgi?name=Vasya&age=20
мы получим страницу с нужным результатом:
<html><body>
Привет, Vasya! Я знаю, Вам 20 лет!
</body></html>
Заметьте, что мы разделяем параметры символом &, а также используем знак равен-
ства =. Это неспроста. Сейчас мы обсудим, почему.
38 Часть I. Основы Web-программирования

Использование формы
Как теперь нам сделать, чтобы пользователь мог в удобной форме ввести свое имя и
возраст? Очевидно, нам придется создать что-то вроде диалогового окна Windows,
только в браузере. Итак, нам понадобится обычный HTML-документ (например, с
именем form.html и расположенный в корневом каталоге) с элементами этого диало-
га — полями ввода текста и кнопкой, при нажатии на которую запустится наш сцена-
рий. Текст этого документа приведен в листинге 2.1.

Листинг 2.1. Документ /form.html с формой

<html><body>
<form action=script.cgi method=GET>
Введите имя:
<input type=text name="name" value="Неизвестный"><br>
Введите возраст:
<input type=text name="age" value="неопределенный"><br>
<input type=submit value="Нажмите кнопку!">
</body></html>

Вы можете заметить, что некоторые атрибуты тэгов я написал в кавычках (на-


пример, name="age"), а некоторые — нет. Как показывает практика, везде, где
это не конфликтует с синтаксисом HTML (то есть, в текстах, в которых нет про-
белов и букв кириллицы), можно кавычки опускать. Мне лично нравится за-
ключать значения полей name и value в кавычки, а остальные — писать без
них. Правда, стандарт на язык HTML это не допускает (он требует
обязательного наличия кавычек), но большинство браузеров относится к этому
весьма и весьма лояльно.

Загрузим наш документ в браузер. Получим примерно следующее:


Глава 2. Интерфейс CGI 39

Рис. 2.1. HTML-форма

Теперь, если занести в поле name свое имя, а в поле для возраста — возраст и нажать
кнопку Нажмите кнопку!, браузер обратится к сценарию по URL, указанному в ат-
рибуте action тэга <form> формы:
http://www.somehost.com/script.cgi
Он передаст через ? все параметры, которые помещены внутрь тэгов input в форме,
отделяя их амперсандом (&). Имена полей и их значения будут разделены знаком =.
Теперь вы понимаете, почему мы с самого начала использовали эти символы?
Итак, реальный URL, который будет сформирован браузером при старте сценария,
будет таким (учитывая, что на странице был пользователь по имени Vasya и ему 20
лет):
http://www.somehost.com/script.cgi?name=Vasya&age=20
Самое, пожалуй, полезное, что можно вынести из рассмотренного примера, — то, что
все URL-перекодирования и преобразования осуществляются браузером автоматиче-
ски. То есть, пользователю теперь совершенно не нужно об этом задумываться и ло-
мать голову над путаницей шестнадцатеричных кодов и управляющих символов.

Абсолютный и относительный
путь к сценарию
Обратим внимание на поле action тэга <form>. Поскольку он не предваряется слэ-
шем (/), то представляет собой относительный путь к сценарию.
То есть браузер при анализе тэга попытается выдать запрос на запуск сценария,
имеющего имя script.cgi и расположенного в том же самом каталоге, что и форма
(точнее, HTML-документ с формой).
40 Часть I. Основы Web-программирования

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

Однако можно указать и абсолютный путь, как на текущем, так и на другом хосте. В
первом случае параметр action будет выглядеть примерно следующим образом:
<form action="/some/path/script.cgi">

Браузер определит, что это абсолютный путь в пределах текущего хоста (точнее, хос-
та, на котором расположен документ с формой) по наличию символа / впереди пути.
Рекомендуется везде, где только возможно, пользоваться таким определением пути,
всячески избегая указания абсолютного URL с именем хоста — конечно, за исключе-
нием тех ситуаций, когда ресурс размещен сразу на нескольких хостах (такое тоже
иногда встречается).
Во втором случае получится приблизительно следующее:
<form action="http://www.other.com/any/script.cgi">

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

Метод POST и формы


Что же теперь нужно сделать, чтобы послать данные не методом GET, а методом
POST? Нетрудно догадаться: достаточно вместо method=GET указать method=POST.
Больше ничего менять не надо.

Если не задать параметра action в тэге <form> вообще, то по умолчанию


подразумевается метод GET.
Таким образом, мы можем теперь вообще не нагружать пользователя такой инфор-
мацией, как имя сценария, его параметры и т. д. Он всегда будет иметь дело только с
полями ввода текста, переключателями и кнопками формы, а также с гиперссылками.
Однако в рассмотренной схеме не все гладко с точки зрения простоты: сценарий
один, а файла-то два (документ с формой и файл сценария). Есть простое обходное
Глава 2. Интерфейс CGI 41

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

Приведенная схема минимизации количества документов стандартна и весьма


универсальна (ее применют 99% сценариев, которые можно найти в Интерне-
те). Она еще и удобна для пользователя, потому что не создает "мертвых"
ссылок (любой URL сценария, который он наберет, пусть даже и без парамет-
ров, будет корректным). Однако программирование этой схемы на Си (и на не-
которых других языках) вызывает определенные проблемы. Язык PHP таких
проблем лишен.
Глава 3

CGI изнутри

До сих пор мы рассматривали лишь теоретические аспекты CGI. Мы знаем в общих


чертах, как и что передается пользователю сервером и наоборот. Однако как же все-
таки должна быть устроена CGI-программа (CGI-сценарий), чтобы работать с этой
информацией? Откуда она ее вообще получает и куда должна выводить, чтобы пере-
слать текст пользователю?
И это только небольшая часть вопросов, которые пока остаются открытыми. В этой
главе я постараюсь вкратце описать, как же должны на самом деле быть устроены
внутри CGI-сценарии. На мой взгляд, каждый программист обязан хотя бы в общих
чертах знать, как работает то, что он использует — будь то операционная система
(ОС) или удобный язык-интерпретатор для написания CGI-сценариев (каким является
PHP). А значит, речь пойдет о программировании на Си. Я выбрал Си, т. к. это одно
из самых лучших и лаконичных средств; кроме того, именно на Си чаще всего пишут
те сценарии, которым требуется максимально критичное быстродействие (базы дан-
ных, поисковые системы, системы почтовой рассылки с сотнями тысяч пользователей
и др.). В пользу этого языка говорит также и то, что его компиляторы можно встре-
тить практически в любой сколько-нибудь серьезной ОС.
Тем не менее, вы не найдете в этой главе ни одной серьезной законченной программы
на Си (за исключением разве что самой простой, типа "Hello, world!"). Несмотря на
это, я попытаюсь описать практически все, что может понадобиться при программи-
ровании сценариев на Си (кроме работы с сокетами, — это тема для отдельной книги,
да и, пожалуй, лишь косвенно примыкает к Web-программированию). По возможно-
сти я не буду привязываться к специфике конкретной ОС, ведь для CGI существует
стандарт, независимый от операционной системы, на которой будет выполняться сце-
нарий. Вооружившись материалом этой главы, можно написать самые разнообразные
сценарии — от простых до самых сложных (правда, для последних потребуется также
недюжинная сноровка).
И все-таки, моя цель — набросать общими мазками, как неудобно (повторюсь —
именно неудобно!) программировать сценарии на языках, обычных для прикладного
программиста (в том числе на Си и Си++). Как только вы проникнетесь этой идеей,
мы плавно и не торопясь двинемся в мир PHP, где предусмотрены практически все
удобства, так необходимые серьезному языку программирования сценариев.
Если вы не знакомы с языком Си, не отчаивайтесь. Все примеры хорошо комменти-
рованы, а сложные участки не нуждаются в непременном понимании "с первого про-
Глава 3. CGI изнутри 43

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

Передача документа
пользователю
Вначале рассмотрим более простой вопрос: как программа посылает свой ответ (то
есть документ) пользователю.
А сделано это просто и логично (а главное, универсально и переносимо между опера-
ционными системами): сценарий просто помещает документ в стандартный поток
вывода (на Си он называется stdout), который находится под контролем программ-
ного обеспечения сервера. Иными словами, программа работает так, как будто нет
никакого пользователя, а нужно вывести текст прямо на "экран". (Это она так думает,
на самом деле выводимая информация будет перенаправлена сервером в браузер
пользователя. Ясно, что у сценария никакого "экрана" нет и быть не может.)
Ответ программы, как и запрос пользователя, должен состоять из заголовков. Иными
словами, мы не можем просто направить документ в стандартный поток вывода: нам
сначала нужно по крайней мере указать, в каком формате информация должна быть
передана пользователю. Действительно, представьте, что произойдет, если браузер
попытается отобразить GIF-рисунок в текстовом виде? В худшем случае вашим поль-
зователям придется всю жизнь лечиться от заикания — особенно если до этого их
просили ввести номер кредитной карточки.…

Заголовки ответа
Заголовки ответа должны следовать точно в таком же формате, как и заголовки за-
проса, рассмотренные нами в предыдущей главе. А именно, это набор строк (завер-
шающийся пустой строкой), каждая из которых представляет собой имя заголовка и
44 Часть I. Основы Web-программирования

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


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

Заголовок кода ответа


Однако здесь все же имеется одно отличие от формата, который используется в заго-
ловках запроса. Дело в том, что первый заголовок ответа обязан иметь слегка специ-
фичный вид — в нем не должно быть двоеточия. Он задает так называемый код от-
вета сервера и выглядит, например, так:
HTTP/1.1 OK
или так:
HTTP/1.1 404 File Not Found
В первом примере заголовок говорит браузеру, что все в порядке и дальше следует
некоторый документ. Во втором примере сообщается, что затребованный файл не был
найден на сервере. Конечно, существует еще множество других кодов ошибок, но для
нас они не представляют особого интереса, и вот почему.
Чаще всего (за исключением редких случаев) браузеры не обращают особого внима-
ния на заголовок кода ответа, а просто выводят следующий за ним документ. Кроме
того, такой заголовок формируется сервером, а в сценарии мы никак не можем его
изменить (правда, есть специальный заголовок Status, но мы не будем здесь о нем
говорить). Поэтому я и не рассматриваю подробно этот вопрос в данной книге.
Вот другие наиболее распространенные заголовки ответа.

Content-type
r Формат: Content-type: mime_тип; charset=koi8-r
Задает тип документа и его кодировку. Параметр charset задает кодировку доку-
мента (в нашем примере это KOI8-R). Поле mime_тип определяет тип информации,
которую содержит документ:
r text/html — HTML-документ;
r text/plain — простой текстовый файл;
r image/gif — GIF-изображение;
r image/jpeg — JPG-изображение;
r еще несколько десятков других типов.

Pragma
Формат: Pragma: no-cache
Глава 3. CGI изнутри 45

Запрещает кэширование документа браузером, так что при повторном визите на


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

Location
Формат: Location: http://www.otherhost.com/somepage.html
Этот заголовок особенный и определяет, что браузер пользователя должен немедлен-
но перейти по указанному адресу, не дожидаясь тела документа ответа (как будто бы
пользователь сам набрал в адресной строке нужный URL). Так что, очевидно, если вы
собираетесь использовать заголовок Location, то никакого документа выводить не
надо.

Рекомендуется всегда указывать в заголовке Location абсолютный путь вме-


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

В браузере Netscape имеется ошибка, проявляющаяся, когда сценарий выво-


дит заголовок Location с указанием перейти на собственный URL (то есть,
сам на себя, для этого даже придуман специальный термин — self-redirect).
Такое решение не так бесполезно, как кажется, и используется, например, в
гостевых книгах. В этом случае Netscape прекрасно принимает ответ сценария,
но затем почему-то сообщает о том, что "документ не содержит данных". Как
решить указанную проблему, см. в части V книги.

Set-cookie
Формат: Set-cookie: параметры_cookie
Устанавливает Cookie в браузер пользователя. Позже в этой главе мы рассмотрим
подробнее, что такое Cookies и как с ними работать.

Date
Формат: Date: Sat, 08 Jan 2000 11:56:26 GMT
Указывает браузеру дату отправки документа.
46 Часть I. Основы Web-программирования

Server
Формат: Server: Apache/1.3.9 (Unix) PHP/3.0.12
Устанавливается сервером и указывает браузеру тип сервера и другую информацию о
серверном программном обеспечении.

Пример CGI-сценария
Настало время привести небольшой сценарий на Си, который иллюстрирует некото-
рые возможности, которые были описаны выше (листинг 3.1).

Листинг 3.1. Простейший сценарий script.c

#include <time.h> // Нужна для инициализации функции rand()


#include <stdio.h> // Включаем поддержку функций ввода/вывода
#include <stdlib.h> // А это — для поддержки функции rand()

// Главная функция. Именно она и запускается при старте сценария.


void main(void) {
// инициализируем генератор случайных чисел
int Num; time_t t; srand(time(&t));
// в Num записывается случайное число от 0 до 9
Num = rand()%10;
// далее выводим заголовки ответа. Тип — html-документ
printf("Content-type: text/html\n");
// запрет кэширования
printf("Pragma: no-cache\n");
// пустой заголовок
printf("\n");
// выводим текст документа — его мы увидим в браузере
printf("<html><body>");
printf("<h1>Здравствуйте!</h1>");
printf("Случайное число в диапазоне 0-9: %d",Num);
printf("</body></html>");
}
Исходный текст можно откомпилировать и поместить в каталог с CGI-сценариями на
сервере. Обычно стараются все сценарии хранить в одном месте — в каталоге cgi-
bin, у которого имеется разрешение на выполнение всех файлов внутри него. Правда,
это правило не является обязательным — конечно же, можно разместить файлы сце-
нария где душе угодно (не забыв проставить соответствующие права на каталог в на-
Глава 3. CGI изнутри 47

стройках сервера). На мой взгляд, логично хранить файлы сценариев там, где это
наиболее вам удобно, а не пользоваться общепринятыми штампами. Теперь наберем
в адресной строке браузера:
http://www.myhost.com/cgi-bin/script.cgi

Мы получим нашу HTML-страницу. Заметьте, что при нажатии Reload


(а также при повторном посещении страницы) браузер перезагрузит страницу цели-
ком, а не возьмет ее копию из своего кэша (это можно видеть по постоянно изме-
няющемуся случайному числу или по лампочкам модема). Мы добились такого ре-
зультата благодаря заголовку
Pragma: no-cache

Давайте теперь посмотрим, что нужно изменить в нашем сценарии, чтобы его вывод
представлял из себя с точки зрения браузера не HTML-документ, а рисунок. Пусть
нам нужен сценарий, который бы передавал пользователю какой-то GIF-рисунок (на-
пример, выбираемый случайным образом из некоторого списка). Делается это абсо-
лютно аналогично: выводим заголовок
Content-type: image/gif

Затем копируем один-в-один нужный нам GIF-файл в стандартный поток вывода


(лучше всего — функцией fwrite, т. к. иначе могут возникнуть проблемы с "бинар-
ностью" GIF-рисунка). Теперь можно использовать этот сценарий даже в таком кон-
тексте:
... какой-то текст страницы ...
<img src=http://www.myhost.com/cgi-bin/script.cgi>
... продолжение страницы ...

В результате таких действий в нашу страницу будет подставляться каждый раз слу-
чайное изображение, генерируемое сценарием. Разумеется, чтобы избежать неприят-
ностей с кэшированием, которое особенно интенсивно применяется браузерами по
отношению к картинкам, мы должны его запретить выводом соответствующего заго-
ловка. Именно так устроены графические счетчики, столь распространенные в Ин-
тернете.
Еще раз обращаю ваше внимание на такой момент: CGI-сценарии могут использоваться
не только для вывода HTML-информации, но и для любого другого ее типа — начиная с
графики и заканчивая звуковыми MIDI-файлами. Тип документа задается в единственном
месте — заголовке Content-type. Не забывайте добавлять этот заголовок, в против-
ном случае пользователю будет отображена стандартная страница сервера с сообще-
нием о 500-й ошибке (для сервера Apache), из которой он вряд ли что поймет.
48 Часть I. Основы Web-программирования

Передача информации
CGI-сценарию
Проблема приема параметров, заданных пользователем (с точки зрения сценария —
все равно, через форму или вручную), несколько сложнее. Мы уже частично затраги-
вали ее и знаем, что основная информация приходит через заголовки, а также (при
использовании метода POST) после всех заголовков. Рассмотрим эти вопросы под-
робнее.

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

HTTP_ACCEPT
В этой переменной перечислены все (во всяком случае, так говорится в документа-
ции) MIME-типы данных, которые могут быть восприняты браузером. Как мы уже
замечали, современные браузеры частенько ленятся и передают строку */*, что озна-
чает, что они якобы понимают любой тип.

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

HTTP_USER_AGENT
Идентифицирует браузер пользователя. Если в данной переменной окружения при-
сутствует подстрока MSIE, то это — Internet Explorer, в противном случае, если в на-
личии лишь слово Mozilla, — Netscape.

HTTP_HOST
Доменное имя Web-сервера, на котором запустился сценарий. Эту переменную окру-
жения довольно удобно использовать, например, для генерации полного пути, кото-
рый требуется в заголовке Location, чтобы не привязываться к конкретному серверу
Глава 3. CGI изнутри 49

(вообще говоря, чем меньше сценарий задействует "зашитую" в него информацию об


имени сервера, на котором он запущен, тем лучше — в идеале ее не должно быть во-
все).

SERVER_PORT
Порт сервера (обычно 80), к которому обратился браузер пользователя. Также может
привлекаться для генерации параметра заголовка Location.

REMOTE_ADDR
Эта переменная окружения задает IP-адрес (или доменное имя) узла пользователя, на
котором был запущен браузер.

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

SCRIPT_NAME
Виртуальное имя выполняющегося сценария (то есть часть URL после имени сервера, но
до символа ?). Эту переменную окружения, опять же, очень удобно брать на вооружение
при формировании заголовка Location при переадресации на себя (self-redirect), а также
при проставлении значения атрибута action тэга <form> на странице, которую выдает
сценарий при запуске без параметров (для того чтобы не привязываться к конкретному
имени сценария).

REQUEST_METHOD
Метод, который применяет пользователь при передаче данных (мы рассматриваем
только GET и POST, хотя существуют и другие методы). Надо заметить, что грамотно
составленный сценарий должен сам определять на основе этой переменной, какой
метод задействует пользователь, и принимать данные из соответствующего источни-
ка, а не рассчитывать, что передача будет осуществляться, например, только методом
POST. Впрочем, все PHP-сценарии так и устроены.

QUERY_STRING
Параметры, которые в URL указаны после вопросительного знака. Напомню, что они
доступны как при методе GET, так и при методе POST (если в последнем случае они
были определены в атрибуте action тэга <form>).

CONTENT_LENGTH
Количество байтов данных, присланных пользователем. Эту переменную необходимо
анализировать, если вы занимаетесь приемом и обработкой POST-формы.
50 Часть I. Основы Web-программирования

Передача параметров методом 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
Глава 3. CGI изнутри 51

Передача параметров методом 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;
52 Часть I. Основы Web-программирования

// выводим заголовок
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).
Глава 3. CGI изнутри 53

Расшифровка URL-кодированных данных


Если бы в предыдущем примере мы ввели параметры, содержащие, например, буквы
кириллицы, то сценарию они бы поступили не в "нормальном" виде, а в URL-
закодированном. Пожалуй, ни один сценарий не обходится без функции расшифровки
URL-кодированных данных. И это совсем не удивительно. Радует только то, что та-
кую функцию нужно написать один раз, а дальше можно пользоваться ей по мере
необходимости.
Как уже упоминалось, кодирование заключается в том, что некоторые неалфавитно-
цифровые символы (в том числе и "русские" буквы, которые тоже считаются неалфа-
витными) преобразуются в форму %XX, где XX — код символа в шестнадцатеричной
системе счисления. Далее представлена функция на Си, которая умеет декодировать
подобные данные и приводить их к нормальному представлению.

Мы не можем сначала все данные (например, полученные из стандартного по-


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

Итак, приходим к следующему алгоритму: сначала разбиваем строку параметров на


блоки (параметр=значение), затем из каждого блока выделяем имя параметра и его
значение (обособленные символом =), а уж потом для них вызываем функцию пере-
кодировки, приведенную ниже:

Листинг 3.5. Функция URL-декодирования

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


// Результат помещается в ту же строку, что была передана в параметрах.
void UrlDecode(char *st) {
char *p=st; // указывает на текущий символ строки
char hex[3]; // временный буфер для хранения %XX
int code; // преобразованный код
// запускаем цикл, пока не кончится строка (то есть, пока не
// появится символ с кодом 0, см. ниже)
do {
// Если это %-код ...
if(*st == '%') { // тогда копируем его во временный буфер
hex[0]=*(++st); hex[1]=*(++st); hex[2]=0;
54 Часть I. Основы Web-программирования

// переводим его в число


sscanf(hex,"%X",&code);
// и записываем обратно в строку
*p++=(char)code;
// указатель p всегда отмечает то место в строке, в которое
// будет помещен очередной декодированный символ
}
// иначе, если это "+", то заменяем его на " "
else if(*st=='+') *p++=' ';
// а если не то, ни другое — оставляем как есть
else *p++=*st;
} while(*st++!=0); // пока не найдем нулевой код
}

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

Листинг 3.6. Получение POST-данных с URL-декодированием

#include <stdio.h>
#include <stdlib.h>

void main(void) {
// получаем значения переменных окружения
char *RemoteAddr = getenv("REMOTE_ADDR");
char *ContentLength = getenv("CONTENT_LENGTH");
// выделяем память для буфера QUERY_STRING
char *QueryString = malloc(strlen(getenv("QUERY_STRING")) + 1);
// копируем QUERY_STRING в созданный буфер
strcpy(QueryString, getenv("QUERY_STRING"));
// декодируем QUERY_STRING
UrlDecode(QueryString);
// вычисляем количество байтов данных — переводим строку в число
int NumBytes = atoi(ContentLength);
Глава 3. CGI изнутри 55

// выделяем в свободной памяти буфер нужного размера


char *Data = (char*)malloc(NumBytes + 1);
// читаем данные из стандартного потока ввода
fread(Data, 1, NumBytes, stdin);
// добавляем нулевой код в конец строки
// (в Си нулевой код сигнализирует о конце строки)
Data[NumBytes] = 0;
// декодируем данные (хоть это и не совсем осмысленно, но выполняем
// сразу для всех POST-данных, не разбивая их на параметры)
UrlDecode(Data);
// выводим заголовок
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>");
}

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


промежуточный буфер для хранения QUERY_STRING. Зачем? Попробуем поставить
все на место, т. е. не задействовать промежуточный буфер, а работать с переменной
окружения напрямую, как это было в листинге 3.5. Тогда в одних операционных сис-
темах этот код будет работать прекрасно, а в других — генерировать ошибку общей
защиты, что приведет к немедленному завершению работы сценария. В чем же дело?
Очень просто: переменная QueryString ссылается на значение переменной окруже-
ния QUERY_STRING, которая расположена в системной области памяти, а значит, дос-
тупна только для чтения. В то же время функция UrlDecode(), как я уже замечал,
помещает результат своей работы в ту же область памяти, где находится ее параметр,
что и вызывает ошибку.
Чтобы избавиться от указанного недостатка, мы и копируем значение переменной
окружения QUERY_STRING в область памяти, доступной сценарию для записи — на-
пример, в какой-нибудь буфер, а потом уже преобразовываем его. Что и было сделано
в последнем сценарии.
56 Часть I. Основы Web-программирования

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

Формы
До сих пор из всех полей формы мы рассматривали только текстовые поля и кнопки
отправки (типа submit). Давайте теперь поглядим, в каком виде приходят данные и
от других элементов формы (а их существует довольно много).
Все элементы формы по именам соответствующих им тэгов делятся на 3 категории:
r <input...>
r <textarea...>...</textarea>
r <select...><option...>...</option>...</select>
Каждый из этих тэгов, конечно, может иметь имя. Ранее уже упоминалось, что пары
имя=значение перед тем, как отправятся сценарию, будут разделены в строке пара-
метров символом &. Кроме того, следует учитывать, что для тех компонентов формы,
у тэгов которых не задан параметр name, соответствующая строка имя=значение
передана не будет. Это ограничение введено для того, чтобы можно было в форме
определять служебные элементы, которые не будут посылаться сценарию. Например,
в их число входят кнопки (подтверждения отправки или обычные, используемые при
программировании на JavaScript) и т. д. Так, создадим форму:
<form action=script.cgi>
... какие-то поля ...
<input type=submit value="Go!">
</form>

Несмотря на то, что кнопка Go! формально является полем ввода, ее данные не будут
переданы сценарию, поскольку у нее отсутствует параметр name.
Чаще все же бывает удобно давать имена таким кнопкам. Например, для того, чтобы
определить, каким образом был запущен сценарий — путем нажатия на кнопку или
как-то еще (например, просто набором его URL в браузере). Создадим следующую
форму:
<form action=script.cgi>
<input type=submit name="submit" value="Go!">
</form>

После запуска такой формы и нажатия в ней кнопки Go! сценарию среди прочих па-
раметров будет передана строка submit=Go!. Вернувшись к примеру из предыдущей
главы, мы теперь легко сможем определить, был ли сценарий выполнен из формы
Глава 3. CGI изнутри 57

или же простым указанием его URL (для этого достаточно проанализировать команд-
ную строку сценария и определить, присутствует ли в ней атрибут submit).
В принципе, все тэги, за исключением <select>, с точки зрения сценария выглядят
одинаково — как один они генерируют строки вида имя=значение, где имя — то,
что задано в атрибуте name, а значение — либо текст, введенный пользователем,
либо содержимое атрибута value (например, так происходит у независимых и зави-
симых переключателей, которые мы вскоре рассмотрим).

Тэг <input> — различные поля ввода


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

Текстовое поле (text)


<input type=text
name=имя
[value=значение]
[size=размер]
[maxlen=число]
>
Создает поле ввода текста размером примерно в size знакомест и максимально до-
пустимой длиной maxlen символов (то есть пользователь сможет ввести в нем не
больше этого количества символов).

Не советую, тем не менее, в программе на Си полагаться, что придет не боль-


ше maxlen символов и выделять для их получения буфер фиксированного
размера. Дело в том, что злоумышленник вполне может запустить ваш сцена-
рий в обход стандартной формы (содержащей "правильный" тэг <input>) и
задать большой объем данных, чтобы этот буфер переполнить — известный
прием взлома недобросовестно написанных программ.

Если задано значение атрибута value, то в текстовом поле будет изначально отобра-
жена указанная строка.
58 Часть I. Основы Web-программирования

Поле ввода пароля (password)


<input type=password
name=имя
[value=значение]
[size=размер]
[maxlen=число]
>
Полностью аналогичен тэгу <input type=text>, за исключением того, что симво-
лы, набираемые пользователем, не будут отображаться на экране. Это удобно, если
нужно запросить какой-то пароль. Кстати, если в качестве маски задается значение
параметра value, все будет в порядке, однако, посмотрев исходный HTML-текст
страницы в браузере, можно увидеть, что он (браузер) это значение не показывает
(непосредственно на странице). Сделано это, видимо, из соображений безопасности,
хотя, конечно же, злоумышленник легко преодолеет такую защиту, если вы попытае-
тесь скрыть с ее помощью что-то важное.

Скрытое текстовое поле (hidden)


<input type=hidden
name=имя
value=значение
>
Создает неотображаемое (скрытое) поле. Такой объект нужен исключительно для
того, чтобы передать сценарию какую-то служебную информацию, до которой поль-
зователю нет дела, — например, параметры настройки.
Пусть, например, у нас имеется многоцелевой CGI-сценарий, который умеет прини-
мать данные пользователя и отправлять их как почтовое сообщение. Поскольку мы
бы не хотели фиксировать E-mail получателя жестко, но в то же время и не стремим-
ся, чтобы пользователь мог его менять перед отправкой формы, оформим соответст-
вующий тэг в виде скрытого поля:
<form action=/cgi/sendmail.cgi method=post>
<input type=hidden name=email value="admin.microsoft.com.">
<h2>Пошлите сообщение администратору:</h2>
<input type=text name="text">
<input type=submit name=doSend value="Отослать">
</form>
Я подразумеваю, что сценарий анализирует свои входные параметры и посылает
текст из параметра text по адресу email. А вот еще один пример использования
этого сценария, но уже без скрытого поля. Сравните:
<form action=/cgi/sendmail.cgi method=post>
<h2>Пошлите сообщение другу:</h2>
Глава 3. CGI изнутри 59

Его E-mail: <input type=text name=email><br>


Текст: <input type=text name="text"><br>
<input type=submit name=doSend value="Отослать">
</form>
Итак, мы задействовали один и тот же сценарий для нескольких разных целей. Еще
раз напоминаю, что для сценария безразлично, получает он данные из обычного тек-
стового или же из скрытого поля — в любом случае данные выглядят одинаково.
Часто скрытое поле используют для индикации того, что сценарий запущен в резуль-
тате нажатия кнопки в форме, а не простым набором его URL в строке адреса браузе-
ра. Тем не менее, это, как уже говорилось, довольно плохой способ — лучше приме-
нять именованные кнопки submit.

В некоторых случаях именованные кнопки submit не помогают, и приходится


пользоваться скрытым полем для индикации запуска сценария из формы. Про-
исходит это в случае, если форма очень проста и состоит, например, всего из
двух элементов — поля ввода текста и кнопки submit (пусть даже и имено-
ванной). Практически все браузеры в такой ситуации позволяют пользователю
просто нажать <Enter> для отправки формы, а не возиться с нажатием на
submit-кнопку. При этом разумеется, данные кнопки не посылаются на сервер.
Вот тогда-то нас и выручит hidden-поле, например, с именем submit: если его
значение установлено, то сценарий понимает, что пользователь ввел какие-то
данные, в противном случае сценарий был запущен впервые путем набора его
URL или перехода по гиперссылке.

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


<input type=checkbox
name=имя
value=значение
[checked]
>
Этот тэг генерирует независимый переключатель (или флажок), который может быть
либо установлен, либо сброшен (квадратик с галочкой внутри или пустой соответст-
венно). Если пользователь установил этот элемент, прежде чем нажать кнопку дос-
тавки, сценарию поступит строка имя=значение, в противном случае не придет ни-
чего, будто нашего поля и не существует вовсе. Если задан атрибут checked, то
переключатель будет изначально установленным, иначе — изначально сброшенным.

Зависимый переключатель (radio)


<input type=radio
name=имя
value=значение
60 Часть I. Основы Web-программирования

[checked]
>
Включение в форму этого тэга вызывает появление на ней зависимого переключате-
ля (или радиокнопки). Зависимый переключатель — это элемент управления, кото-
рый, подобно независимому переключателю, может находиться в одном из двух со-
стояний. С тем отличием, что если флажки не связаны друг с другом, то только одна
радиокнопка из группы может быть выбрана в текущий момент. Конечно, чаще всего
определяются несколько групп радиокнопок, независимых друг от друга. Наша кноп-
ка будет действовать сообща с другими, имеющими то же значение атрибута
name — иными словами, то же имя. Отсюда вытекает, что, в отличие от всех других
элементов формы, две радиокнопки довольно часто имеют одинаковые имена. Если
пользователь установит какую-то кнопку, сценарию будет передана строка
имя=значение, причем значение будет тем, которое указано в атрибуте value вы-
бранной кнопки (а все остальные переключатели проигнорируются, как будто неуста-
новленные флажки). Если указан параметр checked, кнопка будет изначально вы-
брана, в противном случае — нет.

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

Кнопка отправки формы (submit)


<input type=submit
[name=имя]
value=текст_кнопки
>
Создает кнопку подтверждения с именем name (если этот атрибут указан) и названи-
ем (текстом, выводимым поверх кнопки), присвоенным атрибуту value. Как уже го-
ворилось, если задан параметр name, после нажатия кнопки отправки сценарию вме-
сте с другими парами будет передана и пара имя=текст_кнопки (если нажата не эта
кнопка, а другая, будет передана строка другой, нажатой, кнопки). Это особенно
удобно, когда в форме должно быть несколько кнопок submit, определяющих раз-
личные действия (например, кнопки Сохранить и Удалить в сценарии работы с за-
писью какой-то базы данных) — в таком случае чрезвычайно легко установить, какая
же кнопка была нажата, и предпринять нужные действия.
Глава 3. CGI изнутри 61

Кнопка сброса формы (reset)


<input type=reset
value=текст_кнопки
>
Пожалуй, это самый простой элемент формы. Тэг создает кнопку, при нажатии на
которую все элементы формы в браузере будут сброшены (точнее, установлены в то
состояние, которое было задано в их атрибутах по умолчанию). Причем отправка
формы не производится, т. е. для сценария кнопка reset незаметна.

Рисунок для отправки формы (image)


<input type=image
[name=имя]
src=изображение
>
Создает рисунок, при щелчке на котором кнопкой мыши будет происходить то же,
что и при нажатии на кнопку submit, за тем исключением, что сценарию также бу-
дут пересланы координаты в пикселах того места, где произведен щелчок (отсчиты-
ваемые от левого верхнего угла рисунка). Придут они в форме: имя.x=X&имя.y=Y,
где (X, Y) — координаты точки. Если же атрибут name не задан, то координаты по-
ступят в формате: x=X&y=Y.

Тэг <textarea> — многострочное поле ввода текста


Теперь посмотрим, что же из себя представляет тэг <textarea>. Смысл у него тот
же, что и у <input type=text>, разве что может быть отправлена не одна строка
текста, а сразу несколько. Формат тэга следующий:
<textarea
name=имя
[width=ширина][height=высота]
[wrap=тип]
>Текст, который будет изначально отображен в текстовом поле</textarea>

Как легко видеть, этот тэг имеет закрывающий парный. Параметр width задает ши-
рину поля ввода в символах, а height — его высоту. Параметр wrap определяет, как
будет выглядеть текст в поле ввода. Он может иметь одно из трех значений (по умол-
чанию подразумевается none).
r Virtual — наиболее удобный тип вывода. Справа от текстового поля выводится
полоса прокрутки, и текст, который набирает пользователь, внешне выглядит раз-
битым на строки в соответствии с шириной поля ввода, причем перенос осуществ-
ляется по словам. Однако символ новой строки вставляется в текст только при
нажатии <Enter>.
62 Часть I. Основы Web-программирования

r Physical — зависит от реализации браузера, обычно очень похож на none.


r None — текст отображается в том виде, в котором заносится. Если он не умещает-
ся в текстовое поле, активизируются линейки прокрутки (в том числе, и горизон-
тальная).
После отправки формы текст, который ввел пользователь, будет, как обычно, пред-
ставлен парой имя=текст, аналогично тэгу однострочного поля ввода
<input type=text>.

Тэг <select> — список


У нас остался последний тэг — <select>. Он представляет собой выпадающий (или
раскрытый) список. Одновременно могут быть выбрана одна или несколько строк.
Формат этого тэга следующий:
<select name=имя [size=размер] [multiple]>
<option [value1=значение1][selected]>Строка1</option>
<option [value2=значение2][selected]>Строка2</option>
. . .
<option [valueN=значениеN][selected]>СтрокаN</option>
</select>

Мы видим, что и этот тэг имеет парный закрывающий. Кроме того, его существова-
ние немыслимо без тэгов <option>, которые и определяют содержимое списка.
Параметр size задает, сколько строк будет занимать список. Если size равен 1, то спи-
сок будет выпадающим, в противном случае — занимает size строк и имеет полосы
прокрутки. Если указан атрибут multiple, то будет разрешено выбирать сразу не-
сколько элементов из списка, а иначе — только один. Кроме того, атрибут multiple
не имеет смысла для выпадающего списка.
Каждая строка списка определяется своим тэгом <option>. Если в нем задан атрибут
value, как это часто бывает, то соответствующая строка списка будет идентифици-
роваться его значением, а если не задан, то самим текстом этой строки (считается,
что value равно самой строке). Кроме того, если указан параметр selected, то дан-
ная строка будет изначально выбранной. Кстати, чуть не забыл: закрывающие тэги
</option> можно опускать, если упрощение не создает конфликтов с синтаксисом
HTML (в действительности это можно делать почти всегда).
Давайте теперь посмотрим, в какой форме пересылаются данные списка сценарию.
Ну, со списком одиночного выбора вроде бы ясно — просто передается пара
имя=значение, где имя — имя тэга <select>, а значение — идентификатор вы-
бранного элемента (то есть, либо атрибут value, либо сама строка элемента списка).
Глава 3. CGI изнутри 63

Списки множественного выбора (multiple)


В какой форме приходят данные сценарию, если был создан multiple-список? Очень
просто: все произойдет так, будто есть не один, а несколько не-multiple-списков, все с
одинаковым именем, и в каждом из которых выбрано по одному элементу. Иными
словами, строка параметров, порожденная этим тэгом, будет выглядеть примерно
так:
имя=значение1&имя=значение2&...&имя=значениеN
Кстати говоря, совершенно не уникальный случай — то, что с одним именем связано
сразу несколько значений. Действительно, нам никто не мешает создавать и другие
тэги с идентичными именами. Это часто делается, например, для переключателей-
флажков:
<input type=checkbox name=имя value="Один">Один<br>
<input type=checkbox name=имя value="Два">Два<br>
<input type=checkbox name=имя value="Три">Три<br>
Если теперь пользователь установит сразу все флажки, то сценарию поступит строка
(конечно, в URL-кодированном виде):
имя=Один&имя=Два&имя=Три
Из всего сказанного следует не очень утешительный вывод: при разборе строки пара-
метров в сценарии мы не можем полагаться на то, что каждой переменной соответст-
вует только одно значение. Нам придется учитывать, что их может быть не "один", а
"много". А это очень неприятно с точки зрения программирования — особенно на Си.
Попутно мы обнаружили, что любой multiple-список может быть представлен набо-
ром флажков (независимых переключателей), а любой не-miltiple — в виде несколь-
ких радиокнопок. Так что, вообще говоря, тэг <select> — некоторое функциональ-
ное излишество, и с точки зрения сценария вполне может заменяться флажками и
радиокнопками.

Загрузка файлов

Данный раздел главы предназначен скорее для ознакомления, нежели для


применения в качестве точной инструкции по загрузке файлов. Он прекрасно
демонстрирует, почему нам так удобно использовать PHP для программиро-
вания в Web. Организацию загрузки файлов в PHP мы подробно разберем в
части V.

Иногда бывает просто необходимо позволить пользователю не только заполнить тек-


стовые поля формы и установить соответствующие переключатели, но также и ука-
зать несколько файлов, которые будут впоследствии загружены с компьютера пользо-
64 Часть I. Основы Web-программирования

вателя на сервер. Для этого в языке HTML предусмотрены специальные средства.


Рассмотрим их подробнее.

Формат данных
В свое время я говорил, что все данные из формы при передаче их на сервер упако-
вываются в строку при помощи символов ?, & и =. Легко видеть, что при загрузке
файлов такой способ, хотя и приемлем, но будет существенно увеличивать размер
передаваемой информации. Действительно, ведь большинство файлов — бинарные, а
мы знаем, что при URL-кодировании данные таких файлов сильно "распухают" —
примерно в три раза (например, простой нулевой байт при URL-кодировании превра-
тится в %00). Это сильно замедлит передачу и увеличит нагрузку на канал. И вот, от-
части специально для решения указанной проблемы был изобретен другой формат
передачи данных, отличный от того, который мы до сих пор рассматривали.
В нем уже не используются пресловутые символы ? и &. Кроме того, похоже, в случае
применения такого формата передачи может быть задействован только метод POST,
но не метод GET. Нас это вполне устроит — ведь файлы обычно большие, и достав-
лять их через GET вряд ли разумно...
Если нужно указать браузеру, что в какой-то форме следует применять другой формат
передачи, следует в соответствующем тэге <form> задать атрибут
enctype=multipart/form-data. (Кстати говоря, если этот атрибут не указан, то
форма считается обычной, что эквивалентно enctype=application/x-www-form-
urlencoded — именно так обозначается привычный нам формат передачи.) После
этого данные, поступившие от нашей формы, будут выглядеть как несколько блоков
информации (по одному на элемент формы). Каждый такой блок очень напоминает
HTTP-формат "заголовки-данные", используемый при традиционном формате пере-
дачи. Выглядит блок примерно так (\n, как всегда, обозначает символ перевода стро-
ки):
-----------------Идентификатор_начала\n
Content-Disposition: form-data; name="имя"\n
\n
значение\n
Например, пусть у нас есть форма:

Листинг 3.7. Multipart-форма

<form action=... enctype=multipart/form-data method=post>


Name: <input type=text name="Name" value="Мое имя"><br>
Box: <input type=checkbox name="Box" value=1 checked><br>
Area: <input type=textarea name="Area">Это какой-то текст</textarea><br>
<input type=submit>
Глава 3. CGI изнутри 65

</form>

Данные, поступившие по нажатии кнопки submit на сервер, будут иметь


следующий вид:
----------------127462537625367\n
Content-Disposition: form-data; name="Name"\n
\n
Мое имя\n
----------------127462537625367\n
Content-Disposition: form-data; name="Box"\n
\n
1\n
----------------127462537625367\n
Content-Disposition: form-data; name="Area"\n
\n
Это какой-то текст\n

Заметьте, что несколько дефисов и число (которое мы ранее назвали


Идентификатор_начала) предшествуют каждому блоку. Более того, строка из дефи-
сов и этого числа служит своеобразным маркером, который разделяет блоки. Очевид-
но, эта строка должна быть уникальной во всех данных. Именно так ее и формирует
браузер. Правда, сказанное означает, что сегодня идентификатор будет одним, а зав-
тра, возможно, совсем другим. Так что нам придется, прежде чем анализировать дан-
ные, считать этот идентификатор в буфер (им будет последовательность символов до
первого символа \n).

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

Далее алгоритм разбора должен быть следующим: в цикле мы пропускаем символы иден-
тификатора и перевода строки, извлекаем подстроку имя="что-то" (не обращая внима-
ния на Content-Disposition), дожидаемся двух символов перевода строки и затем
считаем значением соответствующего поля все те данные, которые размещены до
строки \nИдентификатор (или же до конца, если такой строки больше нет). Как ви-
дите, все довольно просто.
66 Часть I. Основы Web-программирования

Стандарт HTTP предписывает, чтобы перевод строки содержал два символа —


\r\n, а не один \n. Как вы уже, наверное, чувствуете, существуют браузеры,
которые об этом и не догадываются и посылают только один \n. Так что, будь-
те готовы к тому, чтобы правильно обрабатывать и эту ситуацию.

Тэг загрузки файла (file)


Теперь вернемся к тому, с чего начали — к загрузке файлов. Сначала выясним, какой
тэг надо вставить в форму, чтобы в ней появился соответствующий элемент управле-
ния — поле ввода текста с кнопкой Browse справа. Таким тэгом является разновид-
ность <input>:
<input type=file
name=имя_элемента
[value=имя_файла]
>
Пусть пользователь выбрал какой-то файл (скажем, с именем каталог\
имя_файла) и нажал кнопку отправки. В этом случае для нашего элемента формы
создается один блок примерно такого вида:
----------------127462537625367\n
Content-Disposition: form-data; name="имя_элемента";
Ä filename="каталог\имя_файла"\n \n
........
Бинарные данные этого файла любой длины.
Здесь могут быть совершенно любые
байты без всякого ограничения.
........
\n
Мы видим, что сценарию вместе с содержимым файла передается и его имя в системе
пользователя (параметр filename).
На этом, пожалуй, и завершим обозрение возможностей загрузки файлов.
Надеюсь, я посеял в вас неприязненное отношение к подобным методам: действи-
тельно, программировать это — не самое приятное занятие на свете (укажу только на
то, что придется использовать приемы программной буферизации, чтобы правильно
найти разделитель). Вот еще один довод в пользу PHP, в котором не нужно выпол-
нять в принципе никакой работы, чтобы создать полноценный сценарий с возможно-
стью загрузки файла.
Глава 3. CGI изнутри 67

Что такое Cookies и с чем их едят


Сначала хотелось бы сказать пару слов насчет самого термина Cookies (это множест-
венное число, произносится как "кукис" или, более "русифицировано", "куки"). В бук-
вальном переводе слово звучит как "печенье", и почему компания Netscape так назва-
ла свое изобретение, не совсем ясно. А поскольку писать "печенье" несколько
неудобно, чтобы не вызывать несвоевременных гастрономических ассоциаций, везде,
где можно, я буду применять именно слово Cookies, с большой буквы, во множест-
венном числе и мужского рода. Кстати, в единственном числе это понятие записыва-
ется Cookie и произносится на русский манер — "кука".
Начну с примера. Скажем, мы хотим завести гостевую книгу: пользователь вводит
свое имя, E-mail, адрес домашней странички (и другую информацию о себе), наконец,
текст сообщения, и после нажатия на кнопку его мысль отправляется в путешествие
по проводам и серверам, чтобы в конце концов попасть в некую базу данных на на-
шем сервере и остаться там на веки вечные. М-да….
Теперь предположим, что эта наша гостевая книга — довольно часто посещаемое
место, у нее есть постоянные пользователи, которые несколько раз на дню оставляют
там свои сообщения. Что же — им придется каждый раз вводить свое имя, адрес
электронной почты и другую информацию в пустые поля? Как бы сделать так, чтобы
это все запоминалось где-то, чтобы даже при следующем запуске браузера нужные
поля формы инициализировались автоматически, разумеется — у каждого пользова-
теля индивидуально, тем, чем он заполнил их ранее?
Чтобы этого добиться, в принципе существуют два метода. Оба они имеют как досто-
инства, так и недостатки, и вскоре мы увидим, в чем же они заключаются.
Первый способ: хранить на сервере отдельную базу данных, в которой для каждого
пользователя по его IP-адресу можно было бы получить последние им же введенные
данные. В принципе, это решение довольно универсально, однако у него есть два су-
щественных недостатка, которые сводят на нет все преимущества. Главный из них —
то, что большинство пользователей не имеют фиксированного (как говорят, стати-
ческого) IP-адреса — каждый раз при входе в Интернет он назначается им (провай-
дером) автоматически (сервер провайдера обычно имеет контроль над несколькими
десятками зарезервированных IP-адресов, доступных для пользователя, и выбирает
для него тот, который еще не занят кем-то еще). Таким образом, мы вряд ли сможем
определить, кто на самом деле зашел в нашу гостевую книгу. Второй недостаток мало
связан с первым — дело в том, что если ваших пользователей очень много, то до-
вольно проблематично в принципе иметь такую базу данных, ведь она занимает ме-
сто на диске, не говоря уж о издержках на поиск в ней.
Второй способ подразумевает использование Cookies. Cookie — это небольшая име-
нованная порция информации, которая хранится в каталоге браузера пользователя (а
не на сервере, заметьте!), но которую сервер (а точнее, сценарий) волен в любой мо-
мент изменить. Кстати, сценарий также получает все Cookies, которые сохранены на
удаленном компьютере, при каждом своем запуске, так что он может в любой момент
68 Часть I. Основы Web-программирования

времени узнать, что же там у пользователя установлено. Самым удобным в Cookies


является то, что они могут храниться недели и годы до тех пор, пока их не обновит
сервер или же пока не истечет срок их жизни (который тоже назначается сценарием
при создании Cookie). Таким образом, мы можем иметь Cookies, которые "живут"
всего несколько минут (или до того момента, пока не закроют браузер), а можем —
"долгожителей".
Не правда ли, последний способ представляет собой идеальное решение для нашей
проблемы? Действительно, теперь сцеанарию гостевой книги достаточно получить у
пользователя его данные, запомнить их в Cookies (как это сделать — см. ниже), а
затем работать, будто ничего и не произошло. Конечно, перед выводом HTML-
документа формы обязательно придется проставить значения value для некоторых
элементов (которые, ясно, извлечены из соответствующих Cookies).
Но не все так гладко. Конечно, и у этой схемы есть недостатки. Первый из них — не все
браузеры поддерживают Cookies, а пользователи тех, которые поддерживают, иногда
имеют обыкновение отключать Cookies — якобы для большей безопасности (хотя безо-
пасность тут совсем ни при чем, дело в самих этих пользователях). Второй недостаток
заключается в том, что каждый браузер хранит свои Cookies отдельно. То есть Cookies,
установленные при пользовании Internet Explorer, не будут "видны" при работе в Netscape,
и наоборот.
Но, согласитесь, все же это почти не умаляет достоинств Cookies — в конце концов,
обычно пользователи работают только в одном из перечисленных браузеров. Кстати,
все чаще в Internet Explorer. На момент написания этих строк указанный браузер име-
ет в несколько раз большие возможности, чем Netscape (работая при этом, правда,
несколько медленнее). Что ж... Время покажет, кто из них выживет.
Но я несколько отклонился от темы. Как уже упоминалось, каждому Cookie сопостав-
лено время его жизни, которое хранится вместе с ним. Кроме этого, имеется также
информация об имени сервера, установившего этот Cookie, и URL каталога, в кото-
ром находился сценарий-хозяин в момент инициализации (за некоторыми исключе-
ниями).
Зачем нужны имя сервера и каталог? Очень просто: дело в том, что сценарию пере-
даются только те Cookies, у которых параметры с именем сервера и каталога совпа-
дают соответственно с хостом и каталогом сценария (ну, на самом деле каталог не
должен совпадать полностью, он может являться подкаталогом того, который создан
для хранения Cookies). Так что совершенно невозможно получить доступ к "чужим"
Cookies — браузер просто не будет посылать их серверу. Это и понятно: представьте
себе, сколько ненужной информации передавалось бы сценарию, если бы все было не
так (особенно если пользователь довольно активно посещает различные серверы, ко-
торые не прочь поставить ему свой набор Cookies). Кроме того, дополнительные све-
дения предоставляются в целях защиты информации от несанкционированного дос-
тупа — ведь в каком-то Cookie может храниться, скажем, важный пароль (как часто
делается при авторизации), а он должен быть доступен только одному определенному
хосту.
Глава 3. CGI изнутри 69

Установка Cookie
Мы подошли к вопросу: как же сценарий может установить Cookie в браузере пользо-
вателя? Ведь он работает "на одном конце провода", а пользователь — на другом.
Решение довольно логично: команда установки Cookie — это просто один из заголов-
ков ответа, передаваемых сервером браузеру. То есть, перед тем как выводить
Content-type, мы можем указать некоторые команды для установки Cookie. Вы-
глядит такая команда следующим образом (разумеется, как и всякий заголовок, запи-
сывается она в одну строку):
Set-Cookie: name=value; expires=дата; domain=имя_хоста; path=путь; secure

Существует и другой подход активизировать Cookie — при помощи HTML-тэга


<meta>. Соответственно, как только браузер увидит такой тэг, он займется обработ-
кой Cookie. Формат тэга такой:
<meta http-equiv="Set-Cookie"
content="name=value; expires=дата; domain=имя_хоста; path=путь; secure"
>

Мы можем видеть, что даже названия параметров в этих двух способах одинаковы.
Какой из них выбрать — решать вам: если все заголовки уже выведены к тому мо-
менту, когда вам потребовалось установить Cookie, используйте тэг <meta>. В про-
тивном случае лучше взять на вооружение заголовки, т. к. они не видны пользовате-
лю, а чем пользователь меньше видит при просмотре исходного текста страницы в
браузере — тем лучше нам, программистам.

Возможно, вы спросите, нахмурив брови: "Что же, с точки зрения программиста


хороший пользователь — слепой пользователь?" Тогда я отвечу: "Что вы, нет
и еще раз нет! Такой пользователь хорош лишь для дизайнера, для програм-
миста же желателен пользователь безрукий (или, по крайней мере, лишенный
клавиатуры и мыши)".

Вот что означают параметры Cookie:

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

value
Текст, который будет рассматриваться как значение Cookie. Важно отметить, что этот
текст (ровно как и строка названия Cookie) должен быть URL-кодирован. Таким об-
70 Часть I. Основы Web-программирования

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

expires
Необязательная пара expires=дата задает время жизни нашего Cookie. Точнее,
Cookie самоуничтожится, как только наступит указанная дата. Например, если задать
expires=Friday,31-Dec-99 23:59:59 GMT, то "печенье" будет "жить" только до
31 декабря 1999 года. Кстати, вот вам и вторая неприятность: хорошо, если мы знаем
наверняка время "смерти" Cookie. А если нам нужно его вычислять на основе текуще-
го времени (например, если мы хотим, чтобы Cookie существовал 10 дней после его
установки, как в подавляющем большинстве случаев и происходит)? Придется ис-
пользовать функцию, которая формировала бы календарную дату в указанном выше
формате. Кстати, если этот параметр не указан, то временем жизни будет считаться
вся текущая сессия работы браузера, до того момента, как пользователь его закроет.

domain
Параметр domain=имя_хоста задает имя хоста, с которого установили Cookie. Ранее
я уже говорил про этот параметр. Так вот, оказывается, его можно менять вручную,
прописав здесь нужный адрес, и таким образом "подарить" Cookie другому хосту.
Только в том случае, если параметр не задан, имя хоста определяется браузером ав-
томатически.

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

secure
Этот параметр связан с защищенным протоколом передачи HTTPS, который в книге
не рассматривается. Если вы не собираетесь писать сценарии для проведения банков-
ских операций с кредитными карточками (или иные, требующие повышенной безо-
пасности), вряд ли стоит обращать на него внимание.
После запуска сценария, выводящего соответствующий заголовок (или тэг <meta>), у
пользователя появится Cookie с именем name и значением value. Еще раз напоми-
наю: значения всех параметров Cookie должны быть URL-кодированы, в противном
случае возможны неожиданности.
Глава 3. CGI изнутри 71

Получение Cookies из браузера


Получить Cookies для сценария несколько проще: все они хранятся в переменной ок-
ружения HTTP_COOKIE в таком же формате, как и QUERY_STRING, только вместо &
используется ;. Например, если мы установили два Cookies: cookie1=value1 и
cookie2=value2, то в переменной окружения HTTP_COOKIE будет следующее:
cookie1=value1;cookie2=value2.
Сценарий должен разобрать эту строку, распаковать ее и затем работать по своему
усмотрению.

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

Листинг 3.8. Простой сценарий, использующий Cookies

#include <stdio.h>
#include <stdlib.h>

// начало программы
void main() {
// Временный буфер
char Buf[1000];
// получаем в переменную Cook значение Cookies
char *Cook = getenv("HTTP_COOKIE");
// пропускаем в ней 5 первых символов ("cook="), если она не пустая –
// получим как раз значение Cookie, которое мы установили ранее
// (см. ниже).
Cook += 5; // сдвинули указатель на 5 символов вперед по строке
// получаем переменную QUERY_STRING
char *Query = getenv("QUERY_STRING");
// проверяем, заданы ли параметры у сценария — если да, то
// пользователь, очевидно, ввел свое имя или нажал кнопку,
// в противном случае он просто запустил сценарий без параметров
if(strcmp(Query, "")) { // строка не пустая?
// копируем в буфер значение QUERY_STRING,
72 Часть I. Основы Web-программирования

// пропуская первые 5 символов (часть "name=") -


// получим как раз текст пользователя
strcpy(Buf, Query + 5);
// Пользователь ввел имя — значит, нужно установить Cookie
printf("Set-cookie: cook=%s; "
"expires=Friday,31-Dec-01 23:59:59 GMT", Buf);
// Теперь это — новое значение Cookie
Cook=Buf;
}
// выводим страницу с формой
printf("Content-type: text/html\n\n");
printf("<html><body>\n");
// если имя задано (не пустая строка), приветствие
if(strcmp(Cook, ""))
printf("<h1>Привет, %s!</h1>\n",Cook);
// продолжаем
printf("<form action=/cgi-bin/script.cgi method=get>\n");
printf("Ваше имя: ");
printf("<input type=text name=name value=’%s’>\n",Cook);
printf("<input type=submit value=’Отправить’>\n");
printf("</form>\n");
printf("</body></html>");
}

Теперь при первом заходе на этот URL пользователь получит форму с пустым полем
для ввода имени. Если он что-то туда напечатает и нажмет кнопку отправки, его ин-
формация запомнится браузером. Итак, посетив в любое время до 31 декабря 2001
года этот же URL, он увидит то, что напечатал давным-давно в текстовом поле. И,
что самое важное, — его информацию "увидит" также и сценарий. Кстати, у зло-
умышленника нет никаких шансов получить значение Cookie посетителя, потому что
оно хранится у него на компьютере, а не на сервере.
И опять я намекаю на то, что использование Си и на этот раз довольно затруднитель-
но. Неудобно URL-декодировать и кодировать при установке Cookies, накладно раз-
бирать их на части, да и вообще наша простая программа получилась слишком длин-
ной. Не правда ли, приятно будет обнаружить, что в PHP все это реализовано
автоматически: для работы с Cookies существует всего одна универсальная функция
SetCookie(), а получение Cookies от браузера вообще не вызовет никаких проблем,
потому что оно ничем не отличается от получения данных формы. Это логично. В
Глава 3. CGI изнутри 73

самом деле, какая нам разница, какие данные пришли из формы, а какие — из
Cookies? С точки зрения сценария — все равно...
Но не буду забегать вперед. Займемся пока теорией авторизации.

Авторизация
Часто бывает нужно, чтобы на какой-то URL могли попасть только определенные
пользователи. А именно, только те, у которых есть зарегистрированное имя (login) и
пароль (password). Механизм авторизации как раз и призван упростить проверку
данных таких пользователей.
Я не буду здесь рассматривать все возможности этого механизма по трем причинам.
Во-первых, существует довольно много типов авторизации, различающихся степенью
защищенности передаваемых данных. Во-вторых, при написании обычных CGI-
сценариев для того, чтобы включить механизм авторизации, необходимо провести
некоторые манипуляции с настройками (файлами конфигурации) сервера, что, скорее
всего, будет затруднительно (ведь обычно компания, которая предоставляет услуги по
обслуживанию виртуального хоста, не позволяет вмешиваться в настройки сервера).
И наконец, в-третьих, весь механизм авторизации значительно упрощается и унифи-
цируется при использовании PHP, и вам не придется ничего исправлять в этих злопо-
лучных настройках сервера. Так что давайте отложим практическое знакомство с ав-
торизацией и займемся ее теорией.
Расскажу вкратце о том, как все происходит на нижнем уровне при одном из самых
простых типов авторизации — basic-авторизации. Итак, предположим, что сценарий
посылает браузеру пользователя следующий заголовок:
WWW-Authenticate: Basic realm="имя_зоны"
HTTP/1.0 401 Unauthorized"

Обратите внимание на то, что последний заголовок несколько отличается по форме от


обычных заголовков. Так и должно быть. Строка имя_зоны в первом из них задает
некоторый идентификатор, который будет определять, к каким ресурсам будет раз-
решен доступ зарегистрированным пользователям. При программировании CGI-
сценариев этот параметр используется в основном исключительно для формирования
приветствия (подсказки) в диалоговом окне, появляющемся в браузере пользователя
(там отображается имя зоны), так что мы не будем вдаваться в детали относительно
него.
Затем, как обычно, посылается тело документа (сразу отмечу, что именно это тело
ответа будет выдано пользователю, если он нажмет в диалоговом окне (см. ниже)
кнопку Cancel, т. е. отменит вход). В этом случае происходит нечто удивительное: в
браузере пользователя появляется небольшое диалоговое окно, в котором предлагает-
ся вести login и password. После того как пользователь это сделает, управление пере-
74 Часть I. Основы Web-программирования

дается обратно серверу, который среди обычных заголовков запроса (которые посы-
лает браузер) получает примерно такой:
Authorization: Basic TG9naW46UGFzcw==

Это — ни что иное, как закодированные данные, введенные пользователем. Теорети-


чески, далее этот заголовок должен каким-то образом передаться сценарию (для этого
как раз и необходимо добавление команд в файлы конфигурации сервера). Сценарий,
декодировав его, может решить: то ли повторить всю процедуру сначала (если имя
или пароль неправильные), или же начать работать с сообщением "OK, все в порядке,
вы — зарегистрированный пользователь".
Предположим, что сценарий подтвердил верность данных и "пропустил" пользовате-
ля. В этом случае происходит еще одна вещь: login и password пользователя запоми-
наются в скрытом Cookie, "живущем" в течение одной сессии работы с браузером.
Затем, что бы мы ни делали, заголовок
Authorization: Basic значение_Cookie

будет присылаться для любого сценария (и даже для любого документа) на нашем
сервере. Таким образом, посетителю, зарегистрировавшемуся однажды, нет необхо-
димости каждый раз заново набирать свое имя и пароль в течение текущего сеанса
работы с браузером, т. е., пока пользователь его не закроет.
И еще: после верной авторизации при вызове любого сценария будет установлена
переменная окружения REMOTE_USER, содержащая имя пользователя. Так что в даль-
нейшем можно ее задействовать для определения того, какой же посетитель зарегист-
рировался.
ЧАСТЬ II.
ВЫБОР И НАСТРОЙКА
ИНСТРУМЕНТАРИЯ.
WEB-СЕРВЕР APACHE
Глава 4

Установка Apache
Введение: зачем нужен
домашний сервер?
Эта часть книги поможет вам "скачать" и установить один из лучших серверов —
Apache, а также те приложения, из-за которых большинство программистов и любят
Apache для Windows 95/98. Имеются в виду, конечно, интерпретатор PHP и популяр-
ная СУБД MySQL, также работающие под Windows. Прочитав эту часть книги и ска-
чав дистрибутивы (заметьте, совершенно бесплатно!), вы будете вооружены всеми
инструментами, которые так необходимы для профессиональной работы в Web!

Бытует мнение, что MySQL (а тем более для Windows 95/98) нельзя получить
бесплатно, а можно только купить. Так вот, можете вздохнуть с облегчением:
недавно разработчики MySQL выпустили бесплатную версию сервера для
Windows 95/98, вы можете загрузить самую последнюю ее версию на офици-
альном сайте MySQL: http://www.mysql.com.

Даже если вы и не планируете в будущем использовать PHP, а предпочитаете другой


язык (например, Perl), то после внимательного ознакомления с этой частью книги вы
сможете на порядок упростить себе жизнь — точнее, ее часть, касающуюся написа-
ния и отладки сценариев. И это благодаря тому, что все описанное здесь почти на
100% совместимо с тем программным обеспечением, которое скорее всего установле-
но у вашего хостинг-провайдера
(а больше половины современных хостинг-провайдеров работают с Unix, но не с
Windows). Однако, если вы собираетесь всерьез заняться хостингом на платформе
Win32, то лучше, наверное, будет использовать не Apache и PHP, а MIIS (Microsoft
Internet Information Server — Информационный сервер Интернета Microsoft) и ASP
(Active Server Pages — Активные серверные страницы), про которые, я уверен, напи-
сано множество других книг.
Эта часть книги, как уже говорилось, будет полезна не только программистам на
PHP. Ведь часто возникает ситуация, когда необходимо проверить полный вид
HTML-страницы. Однако чаще всего это невозможно при работе дома — технологии
SSI (Server-Side Includes — Включения на стороне сервера), CGI (Common Gateway
Interface — Общий шлюзовой интерфейс) и, конечно, PHP требуют использования
сервера. Как же быть? Не стоит впадать в апатию — нужно просто установить на ваш
80 Часть II. Выбор и настройка инструментария. Web-сервер Apache

домашний компьютер (пусть даже и не подключенный к Интернету) специальную


программу — Web-сервер. Вообще-то серверов существует множество — плохие и
хорошие, медленные и быстрые... Я предлагаю вам установить сервер, подпадающий
под категории, следующие за "и". А именно — Apache. Самое главное то, что это
чуть ли не единственный сервер, который позволяет работать в Windows 95/98 с тех-
нологиями PHP, CGI и Perl-сценариями одновременно так же просто и непринужден-
но, как будто у вас инсталлирована Unix.

Дистрибутивы и ссылки
Я привожу список ссылок на сайты, на которых всегда можно найти самые свежие
версии программных продуктов. Все описываемые здесь программы были загружены
и установлены мной именно с этих сайтов. Итак:
r официальный сайт Apache: http://www.apache.org;
r официальный сайт PHP: http://www.php.net;
r официальный сайт MySQL: http://www.mysql.com;
r официальный сайт Active Perl: www.activestate.com;
И еще несколько ссылок, полезных Web-программисту.
r Всероссийский Клуб Веб-мастеров: http://www.webclub.ru.
r Клуб разработчиков PHP: http://www.phpclub.net.
r Лаборатория dk: http://www.dklab.ru.

От слов к делу:
установка Apache
Итак, вы решились установить на свой компьютер Apache для Windows 95/98. В та-
ком случае вам следует запастись терпением и для начала "скачать" дистрибутив сер-
вера с официального сайта Apache: http://www.apache.org. Советую вам выбрать
самую последнюю версию сервера для платформы Windows. Теперь нам предстоит
настройка Apache для вашей системы.

Мы попросим вас в точности выполнять перечисленные ниже шаги, не пропус-


кая и не откладывая ни одного. Дело в том, что конфигурирование и настройка
Apache — довольно непростая работа, которая обычно поручается профес-
сионалам. Далее приводятся инструкции с довольно скупыми объяснениями,
почему нужно сделать то или иное действие, в расчете на то, что вы будете
соблюдать их буквально. В противном случае вам, скорее всего, придется до-
полнительно провести пару неприятных часов (или дней) за изучением доку-
Глава 4. Установка Apache 81

ментации Apache, в частности, той ее части, которая касается конфигурирова-


ния.

Этап первый: установка


1. Запустите только что полученный файл дистрибутива Apache. В появившемся
диалоговом окне нажмите кнопку Next (рис. 4.1), а затем — кнопку Yes, чтобы
согласиться с условиями лицензии.

Рис. 4.1. Установка Apache

Рис. 4.2. Каталог для установки сервера


82 Часть II. Выбор и настройка инструментария. Web-сервер Apache

2. Нажимайте кнопку Next в открывающихся окнах до тех пор, пока не появится за-
прос о выборе каталога для установки Apache (рис. 4.2). Рекомендую вам оставить
тот каталог, который предлагается по умолчанию (пусть это, например,
C:\Program Files\Apache Group\Apache). Запомните его на будущее.
3. В появившемся окне установите флажок Typical (Обычная) и нажмите кнопку
Next (рис. 4.3).
4. Программа инсталляции Apache предложит создать папку в меню Пуск в папке
Программы. Позвольте ей это сделать, нажав кнопку Next. Начнется процесс ко-
пирования программного обеспечения.
5. После окончания копирования нажмите кнопку Finish. Процесс установки сервера
завершен, впереди — его настройка.

Рис. 4.3. Тип установки

Этап второй: настройка файла


конфигурации Apache
На этом этапе вам нужно определиться с каталогом, в котором будут храниться ваши сай-
ты. По умолчанию Apache использует для этого C:\Program Files\
Apache Group\Apache\htdocs, где сразу после установки можно найти докумен-
тацию по серверу. Думаю, для серьезных целей такая дислокация не очень подхо-
дит — слишком уж длинное имя, поэтому я рекомендую создать для всех сайтов от-
дельный виртуальный диск (например, с именем Z:) при помощи утилиты subst,
входящей в Windows. Итак, вам нужно проделать ряд действий.
Глава 4. Установка Apache 83

1. Выберите каталог, в котором будут храниться ваши сайты (их может быть не-
сколько). Пусть, например, это будет C:\INTERNET. Ваш каталог будет содержать
корневой каталог нового диска Z:.
2. В начале файла autoexec.bat (но после команды @echo off, если она у вас там есть)
напишите такую строку:
subst Z: C:\INTERNET
3. Перезагрузите компьютер, чтобы новый логический диск Z: создался. Теперь все,
что сохранено в каталоге C:\INTERNET, будет отображаться на панели диска Z:,
как будто это — обычный жесткий диск.

Имеются сведения, что в Windows 95/98 есть ошибка. В результате при ис-
пользовании subst пути иногда "сами по себе" преобразуются в абсолютные
(то есть, например, в нашем случае Z: преобразуется в C:\INTERNET), при-
чем в процессе работы какой-нибудь программы и совершенно неожиданно
для нее. Указанная ошибка чаще всего проявляется в неработоспособности
Perl-транслятора (если его не совсем корректно настроить). При работе с PHP
никаких побочных эффектов не наблюдалось.

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


альных разделов (например, с помощью встроенной в Windows 95/98 программы
DriveSpace). Это решение, пожалуй, даже лучше, чем использование subst, как с точ-
ки зрения экономии памяти, и с точки зрения быстродействия. Ведь что такое Web-
сайт, как не набор очень небольших файлов? А DriveSpace как раз и оптимизирует
работу с такими файлами. Как использовать DriveSpace, смотрите во встроенной в
Windows документации.
r Создайте на диске Z: каталог home, а в нем — каталог localhost. В нем будет
храниться содержимое главного хоста Apache — того, который доступен по адресу
http://localhost. Перейдите в последний созданный каталог. Создайте в нем ката-
логи cgi и www. В первом будут храниться CGI-сценарии, а во втором — ваши
документы и программы на PHP. Замечу, что подобную операцию вам нужно бу-
дет проделывать каждый раз при создании нового виртуального хоста (о них мы
поговорим чуть позже). Полученная структура каталогов показана на рис. 4.4.
Откройте в Блокноте файл конфигурации httpd.conf, который расположен в
подкаталоге conf каталога Apache (в нашем примере это
C:\Program Files\Apache Group\Apache). Впрочем, вы можете и не искать
этот файл вручную, а воспользоваться командой Edit configuration, пройдя по це-
почке меню Пуск ú Программы ú Apache Web Server ú Management.
Httpd.conf — единственный файл, который вам нужно настроить. Вам пред-
стоит найти и изменить в нем некоторые строки, а именно те, о которых упомина-
ется далее. Во избежание недоразумений не трогайте все остальное. Следует заме-
тить, что в файле каждый параметр сопровождается несколькими строками
84 Часть II. Выбор и настройка инструментария. Web-сервер Apache

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


вы можете обратиться к Приложению Б, в котором приведен полный перевод этих
комментариев на русский язык). Поэтому не обращайте на них особого внимания.
Для начала мы настроим параметры для главного хоста Apache — localhost, а так-
же параметры по умолчанию, которые будут унаследованы всем остальными вирту-
альными хостами, если мы когда-либо захотим их создать.

Рис. 4.4. Структура каталогов главного хоста

r Задайте значение параметра ServerName следующим образом:


ServerName localhost
Только не забудьте раскрыть комментарий для поля ServerName, т. е. убрать
символ # перед этим параметром (установленный по умолчанию), поскольку все,
что идет после этого символа и до конца строки, Apache игнорирует.
r В поле DocumentRoot укажите тот каталог, в котором будут размещены ваши
HTML-файлы. Мы ранее договорились, что это будет z:\home\localhost\www)
DocumentRoot z:/home/localhost/www
r Найдите секцию, начинающуюся строкой <Directory диск:/> и заканчиваю-
щийся строкой </Directory> (такие блоки содержат установки для заданного
каталога и всех его подкаталогов). Этот блок может содержать множество ком-
ментариев — не обращайте на них внимания. Его нужно заменить на секцию сле-
дующего вида:
<Directory z:/>
Options Indexes Includes
AllowOverride All
Allow from all
</Directory>
Этим вы обеспечите, что в данном блоке будут храниться настройки для всех ка-
талогов по умолчанию (так как z: — корневой каталог). А именно, для всех ката-
логов по умолчанию предоставляется возможность автоматической генерации ин-
декса — списка содержимого каталога при просмотре его в браузере, а также
Глава 4. Установка Apache 85

поддержка SSI и разрешение использовать файлы .htaccess для индивидуаль-


ных настроек каталогов.
r Найдите аналогичный блок, начинающийся строкой
<Directory "C:/Program Files/Apache Group/Apache/htdocs"> и закан-
чивающийся ограничителем </Directory>. Там будет много комментариев, не
обращайте на них внимание. Эту секцию вам нужно удалить, т. к. все настройки
для каталога со страничками должны наследоваться от настроек по умолчанию,
которые мы только что установили.
r Инициализируйте параметр DirectoryIndex так:
DirectoryIndex index.htm index.html
Это — так называемые файлы индекса, которые автоматически возвращаются
сервером при обращении к какому-либо каталогу, если не указано имя HTML-
документа. В принципе, можно добавить сюда и другие имена, например,
index.php, и т. д. Тем не менее, дополнительные настройки все же лучше де-
лать в файлах .htaccess для каждого сайта в отдельности.
r Найдите и исправьте следующий параметр:
ScriptAlias /cgi-bin/ "z:/home/localhost/cgi/"
Добавьте после него еще такую строчку:
ScriptAlias /cgi/ "z:/home/localhost/cgi/"
Да, именно так, с двумя слэшами — в начале и в конце. Это будет тот каталог, в
котором должны располагаться ваши CGI-сценарии. Подобный параметр говорит
Apache о том, что, если будет указан путь вида http://localhost/cgi-bin, то
на самом деле следует обратиться к каталогу z:/home/localhost/cgi. Мы ис-
пользуем два псевдонима для CGI-каталога потому, что /cgi-bin/ будет досту-
пен не только главному хосту localhost, но и всем остальным виртуальным хостам.
В то же время у каждого из них будет дополнительно свой CGI-каталог /cgi/.
r Теперь следует найти блок параметров, начинающийся с <Directory "C:/
Program Files/Apache Group/Apache/cgi-bin"> и заканчивающийся
</Directory>. Это — настройки для CGI-каталога. Так как мы не собираемся
указывать никаких дополнительных параметров взамен тех, которые уже установ-
лены по умолчанию, этот блок нужно удалить.
r Найдите и настройте (не забудьте раскрыть комментарий!) следующий параметр:
AddHandler cgi-script .bat .exe .cgi

Он говорит Apache о том, что файлы с расширениями exe, bat и cgi надо рас-
сматривать как CGI-модули.
r И последнее — установите следующие параметры:
AddType text/html .shtml
AddHandler server-parsed .shtml .html .htm
86 Часть II. Выбор и настройка инструментария. Web-сервер Apache

Этим вы заставляете Apache обрабатывать файлы с указанными расширениями


процессором SSI.
r Теперь не забудьте сохранить изменения и закройте Блокнот.

Этап третий: тестирование Apache


Поздравляем — вы настроили свой Apache, и он должен уже работать! Для запуска
сервера нажмите кнопку Пуск, затем выберите Программы, Apache Web Server,
Management и Start Apache, при этом всплывет окно, очень похожее на Сеанс MS-
DOS, и ничего больше не произойдет. Не закрывайте его и не трогайте до конца ра-
боты с Apache.
Если окно открывается и тут же закрывается, это означает, что вы допустили какую-
то ошибку в файле httpd.conf. В этом случае придется искать неточность. Проще
всего это сделать, как указано ниже.
1. Запустите Сеанс MS-DOS. Для этого нажмите кнопку Пуск, затем выберите Вы-
полнить. Наберите в появившемся диалоговом окне строку command и нажмите
клавишу <Enter>. Появится подсказка командной строки.
2. Наберите следующие команды DOS:
c:
cd "\Program Files\Apache Group\Apache"
apache.exe
3. Если до этого Apache не выполнялся, то вы получите сообщение об ошибке и но-
мер строки в httpd.conf, где она произошла. Исправьте httpd.conf и повторите опи-
санный процесс сначала, до тех пор, пока в окне не отобразится что-то вроде
"Apache/1.3.14 (Win32) running..."
Несколько слов о том, как можно упростить запуск и завершение сервера.
В Windows можно назначить любому ярлыку функциональную комбинацию клавиш,
нажав которые, вы запустите связанное с ним приложение. Так что щелкните правой
кнопкой мыши на панели задач, в контекстном меню выберите Свойства, затем На-
стройка меню и кнопку Дополнительно. В открывшемся Проводнике присвойте
ярлыку Start Apache комбинацию клавиш <Ctrl>+<Alt>+<A>, а ярлыку Stop
Apache — <Ctrl>+<Alt>+<S>. Теперь вы сможете запускать сервер нажатием
<Ctrl>+<Alt>+<A> и останавливать его, нажав <Ctrl>+<Alt>+<S>.
Теперь проверим, правильно ли мы настроили сервер.

Проверка html
В каталоге z:/home/localhost/www, содержащем HTML-документы Apache, соз-
дайте файл index.html с любым текстовым наполнением. Теперь запустите браузер
и наберите:
http://localhost/index.html
Глава 4. Установка Apache 87

или просто
http://localhost/

Должен загрузиться ваш файл.

Проверка SSI
В каталоге z:/home/localhost/www с HTML-документами Apache создайте файл
test.shtml со следующим содержанием (внимательно следите за соблюдением
пробелов в директиве include!):

Листинг 4.1. Файл test.shtml

SSI Test!<hr>
<!--#include virtual="/index.html" -->
<hr>

Затем наберите в браузере:


http://localhost/test.shtml

Должен открыться файл, который состоит из текста "SSI Test!", за которым следует со-
держимое файла index.html между двумя горизонтальными чертами. Если этого не про-
изошло, значит, вы неправильно сконфигурировали SSI.

Проверка CGI
В каталоге z:/home/localhost/cgi, предназначенном для хранения CGI-
сценариев, создайте файл test.bat с таким содержанием:

Листинг 4.2. Файл test.bat

@echo off
echo Content-type: text/html
echo.
echo.
Dir
Далее в браузере наберите:
http://localhost/cgi-bin/test.bat
В окне отобразится результат команды DOS dir.
88 Часть II. Выбор и настройка инструментария. Web-сервер Apache

Нужно отметить, что последний пример работает не под всеми версиями


Windows: иногда вместо того, чтобы выполнить файл test.bat, Apache выво-
дит в браузер его содержимое. С чем это связано — не совсем ясно, однако,
кажется, можно избавиться от указанной ошибки путем манипулирования с
реестром Windows. Если у вас test.bat не запускается, не расстраивайтесь:
вряд ли вы когда-нибудь будете писать сценарии в виде bat-файлов, тем бо-
лее, что этот способ несовместим с Unix.

Если что-то пошло не так, либо окно Apache открывается и тут же закрывается, зна-
чит, где-то произошла ошибка — скорее всего, причины ее возникновения можно
найти в httpd.conf. За детальным разъяснением этих причин можно обратиться к
log-файлам, расположенным в каталоге C:\Program Files\Apache
Group\Apache\logs.

Виртуальные хосты Apache


Итак, вы установили Apache и получили, таким образом, каталог
z:/home/localhost/www для хранения документов и z:/home/localhost/cgi
для CGI. Однако в Интернете вы поддерживаете (или, скорее всего, будете поддержи-
вать) несколько серверов, а Apache создал для вас только один. Конечно, можно
структуру этих нескольких серверов хранить на одном сервере, однако проще и удоб-
нее было бы создать несколько виртуальных хостов с помощью Apache. В нашем
распоряжении есть два вида виртуальных хостов: отдельные для каждого IP-адреса
или же использующие один общий IP-адрес (так называемые name-based хосты —
хосты, определяемые по имени). В тренировочных целях мы рассмотрим оба вариан-
та, а именно, создадим хост hacker, задействующий тот же адрес, что и localhost,
а также хост cracker с адресом 127.0.0.2.

Конечно, вместо "hacker" и "cracker" вам нужно будет указать желаемые


имена ваших виртуальных хостов. Советуем назвать их так же, как и на вашем
настоящем Web-сервере, но только без "суффикса" .ru или .com — это может
многое упростить при программировании сценариев.

Как это принято в Unix, каждый сервер будет представлен своим каталогом в z:/home с
именем, совпадающим с именем сервера (мы уже проделывали нечто подобное с хостом
localhost). Например, сервер hacker будет храниться в каталоге z:/home/hacker,
который вам необходимо создать прямо сейчас (конечно, вместе с его подкаталогами cgi
и www, как мы делали это ранее), а хост cracker — в каталоге z:/home/cracker. В
этих каталогах будут находиться:
r файлы access.log с журналом доступа к виртуальному серверу;
r файлы errors.log с журналом ошибок сервера;
Глава 4. Установка Apache 89

r каталог www, где, как обычно, будут размещаться HTML-документы;


r каталог cgi для хранения CGI-программ.
На рис. 4.5 представлена структура каталогов, которая должна у нас получиться.

Рис. 4.5. Структура каталогов виртуального хоста


с объявлением главного хоста

Для установки виртуальных хостов необходимо внести некоторые изменения в файл


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

Листинг 4.3. Настройка виртуальных хостов

NameVirtualHost 127.0.0.1
#----localhost
<VirtualHost localhost>
ServerAdmin webmaster@localhost
ServerName localhost
DocumentRoot "z:/home/localhost/www"
ScriptAlias /cgi/ "z:/home/localhost/cgi/"
ErrorLog z:/home/localhost/error.log
CustomLog z:/home/localhost/access.log common
</VirtualHost>
#----hacker
<VirtualHost hacker>
90 Часть II. Выбор и настройка инструментария. Web-сервер Apache

ServerAdmin webmaster@hacker.ru
ServerName hacker
DocumentRoot "z:/home/hacker/www"
ScriptAlias /cgi/ "z:/home/hacker/cgi/"
ErrorLog z:/home/hacker/error.log
CustomLog z:/home/hacker/access.log common
</VirtualHost>
#----cracker
<VirtualHost cracker>
ServerAdmin webmaster@cracker.ru
ServerName cracker
DocumentRoot "z:/home/cracker/www"
ScriptAlias /cgi/ "z:/home/cracker/cgi/"
ErrorLog z:/home/cracker/error.log
CustomLog z:/home/cracker/access.log common
</VirtualHost>

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


<VirtualHost> для хоста localhost. Если этого не сделать, то все запросы
к нему (то есть, по адресу 127.0.0.1) будут обработаны name-based хостом
hacker. Происходит это, видимо, из-за того, что хосты в секции
<VirtualHost> имеют больший приоритет при обработке, чем главный хост,
который мы создали ранее.

Директива NameVirtualHost говорит серверу, что указанный IP-адрес может ис-


пользоваться несколькими виртуальными хостами, поэтому для обработки запросов,
поступающих на этот адрес, нужно привлекать протокол HTTP 1.1 (который, собст-
венно, и поддерживает технику работы с name-based хостами).
При желании можно добавить и другие параметры в блоки <VirtualHost> (напри-
мер, DirectoryIndex и т. д.) Не переопределенные параметры наследуются вирту-
альным хостом от главного. Однако не советую злоупотреблять настройками в этих
секциях — лучше сделать их в файле htaccess в директории нужного хоста, потому
что компания, которая предоставляет (будет предоставлять) вам "настоящие" вирту-
альные хосты в Интернете, вряд ли позволит менять данные блоки.
Но как же система узнает, что хост cracker сопоставлен с адресом 127.0.0.2, а
hacker — name-based хост? Для решения проблемы надо немного подправить сис-
темный файл hosts, который находится в каталоге C:\WINDOWS для операционных
систем Windows 95/98/Millenium и C:\WINNT\SYSTEM32\DRIVERS\etc для
Windows NT и Windows 2000.
Глава 4. Установка Apache 91

Не путайте файл hosts (без расширения) с файлом hosts.sam, который, ско-


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

Файл hosts — обычный текстовый файл, и в него может быть заранее включена
только одна строка:
127.0.0.1 localhost
Именно эта строка и задает соответствие имени localhost адресу 127.0.0.1.

Ради справедливости следует сказать, что имя localhost работает и без ука-
занной выше строки. Ну и выдумщики же эти парни из компании Microsoft!

Для нашего виртуального хоста надо добавить соответствующую строчку, чтобы


файл выглядел так:

Листинг 4.4. Файл hosts

127.0.0.1 localhost hacker


127.0.0.2 cracker

Обратите внимание на то, что хост hacker описан на той же строке, что и
localhost. Дело в том, что в файле hosts должны указываться только уникальные
IP-адреса. Если же одному адресу сопоставляется сразу несколько хостов, то один из
них (тот, который идет первым) объявляется главным, а остальные — его псевдони-
мами. В нашем случае localhost — главный, а hacker — его псевдоним. Apache
при получении запроса на адрес 127.0.0.1 узнает, что он пришел хосту с именем
hacker, и активизирует соответствующий блок <VirtualHost>.
Итак, мы создали виртуальные хосты со следующими свойствами:
Хост hacker:
r имя — hacker;
r доступен по адресу http://hacker;
r расположен в каталоге z:/home/hacker;
r каталог для хранения документов — z:/home/hacker/www, доступный по адре-
су http://hacker/;
r каталог для CGI — z:/home/hacker/cgi, доступный по адресу
http://hacker/cgi/;
r файлы журналов хранятся в z:/home/hacker.
92 Часть II. Выбор и настройка инструментария. Web-сервер Apache

Хост cracker:
r имя — cracker;
r доступен по адресу http://cracker или http://127.0.0.2;
r размещен в каталоге z:/home/cracker;
r каталог для хранения документов — z:/home/cracker/www, доступен по адресу
http://cracker/;
r каталог для CGI — z:/home/cracker/cgi, доступен по адресу
http://cracker/cgi/;
r файлы журналов содержатся в z:/home/cracker.

Необходимо заметить, что главный хост (невиртуальный, тот, который мы соз-


дали ранее) по-прежнему доступен по адресу http://127.0.0.1 или
http://localhost. Более того, его директория cgi-bin "видна" всем суще-
ствующим виртуальным хостам, так что вы можете ее использовать.

После всех изменений не забывайте перезапускать Apache.

Просто завершить сервер, нажав на кнопку Закрыть в правом верхнем углу его
окна, недостаточно — нужно воспользоваться пунктом Stop Apache в меню
Пуск ú Программы ú Apache Web Server ú Management. В противном случае
закроется только окно Apache, а сам сервер останется работать в фоновом
режиме, так что изменения, внесенные в httpd.conf, не будут активизирова-
ны.
Глава 5

Установка PHP и MySQL


Давайте теперь перейдем к установке языка PHP версии 4, ради которого, собственно,
мы и устанавливали сервер Apache. К сожалению, на момент написания этих строк у
PHP не было нормальной setup-программы, которая могла бы установить PHP со все-
ми необходимыми нам модулями за один прием, как мы проделали это с Apache. Так
что, возможно, его инсталляция покажется вам чуть сложнее.
Прежде всего, вам нужно запастись терпением и загрузить с официального сайта PHP
http://www.php.net из секции Downloads два файла: один с расширением zip, а дру-
гой — exe. Ссылки на эти файлы находятся почти на самом верху страницы, после
заголовка Win32 Binaries. Первый файл представляет собой полную версию PHP 4,
но не имеет удобной программы установки, а второй, наоборот, является автоматиче-
ской программой установки, но не содержит в себе наиболее часто используемых мо-
дулей.

Так было на момент написания данной книги. Возможно, в будущем разработ-


чики PHP будут поставлять дистрибутив в виде одного большого exe-файла,
но пока это не так.

Советую вам также скопировать полную документацию по PHP, ссылка на которую


есть на странице чуть ниже. Уверен, в будущем она еще не раз вас выручит.
Стоит сказать еще пару слов насчет версии PHP. Язык постоянно совершенствуется, и
на момент создания книги последней версией была 4.0.3. Скорее всего, когда вы бу-
дете читать эти строки, выйдет более новая версия — например, 4.0.10. Думаю, наи-
лучшим решением будет загрузить ту, что поновее, потому что в ней, возможно, ис-
правлены некоторые ошибки из предыдущих версий языка. Главное, чтобы первая
цифра была 4, потому что "третий" PHP сильно проигрывает "четвертому" по количе-
ству поддерживаемых функций.

Установка PHP
1. Запустите только что загруженный exe-файл. В открывшемся диалоговом окне
нажмите кнопку Next (рис. 5.1).
94 Часть II. Выбор и настройка инструментария. Web-сервер Apache

Рис. 5.1. Установка PHP

2. Согласитесь с условиями лицензии, нажав кнопку I Agree. В появившемся диало-


говом окне выберите тип установки Standard.
3. Теперь укажите директорию, в которую будет установлен PHP. По умолчанию
предлагается C:\PHP, но, думаю, логичнее было бы выбрать
C:\Program Files\PHP4, "поближе" к Apache (рис. 5.2). Для указания этого ка-
талога нажмите кнопку Browse... и введите его имя, затем нажмите, как обычно,
кпопку OK и потом — Next, чтобы перейти к следующему диалоговому окну.

Рис. 5.2. Выбор каталога для установки PHP


Глава 5. Установка PHP и MySQL 95

4. Задайте адрес вашего SMTP-сервера (Send Mail Transfer Protocol — Протокол пе-
ресылки почтовой корреспонденции), а также ваш адрес электронной почты.
Именно этот сервер и обратный адрес будут использованы для исходящих почто-
вых запросов, когда вызывается функция Mail() языка PHP. В общем, это тот
самый сервер, через которого отсылает почту ваш обычный почтовый клиент —
например, Outlook Express. Впрочем, можете и оставить в текстовых полях значе-
ния по умолчанию — в этом случае функция Mail() просто не будет работать на
локальной машине.
5. Выберите сервер, на который будет настроен PHP. В нашем случае это — Apache
(рис. 5.3).
Начнется процесс копирования файлов. После его окончания, возможно, появятся
еще некоторые диалоговые окна с различными извещениями. Не обращайте на
них внимания.
На этом этапе язык PHP можно считать уже почти установленным — нам осталось
только настроить Apache, чтобы он мог распознать PHP-сценарии, а также подклю-
чить дополнительные модули, которые содержатся в загруженном нами zip-архиве.

Рис. 5.3. Выбор сервера

Настройка Apache для работы с PHP


1. Откройте в Блокноте файл конфигурации Apache httpd.conf, находящийся в ката-
логе C:\Program Files\Apache Group\Apache\conf. Впрочем, вы можете и не
искать этот файл вручную, а воспользоваться пунктом Edit configuration в меню
Пуск ú Программы ú Apache Web Server ú Management.
2. Найдите в тексте файла такую закомментированную строку:
96 Часть II. Выбор и настройка инструментария. Web-сервер Apache

#AddType application/x-httpd-php php

3. Раскройте комментарий:
AddType application/x-httpd-php php

Таким образом, мы присвоили всем файлам с расширением php тип


application/x-httpd-php.
4. Сразу же после этой строки добавьте такие настройки:
ScriptAlias /_php/ "C:/Program Files/PHP4/"
Action application/x-httpd-php "/_php/php.exe"

Этим мы, во-первых, создаем синоним _php для каталога с процессором PHP,
чтобы Apache мог получить к нему доступ, а во-вторых, связываем все файлы ти-
па application/x-httpd-php с обработчиком php.exe.

Префикс к строке "_php" выбран из такого расчета, чтобы она в будущем не кон-
фликтовала с именами каталогов, которые вы можете объявить на вашем хосте.

5. Сохраните изменения в файле конфигурации, остановите Apache, если он был до


этого запущен (пункт Пуск ú Программы ú Apache Web Server ú Management ú
Stop Apache), и стартуйте сервер снова. Если Apache не запускается (его окно от-
крывается и тут же закрывается), значит, вы где-то допустили синтаксическую
ошибку. В этом случае можете воспользоваться рекомендациями по устранению
ошибок, описанными в главе 4.

Тестирование PHP
Давайте теперь убедимся, что PHP-сценарии работают. Для этого создадим в каталоге
z:/home/localhost/www файл test.php со следующим содержанием:

Листинг 5.1. Тестовый сценарий

<?
echo "It works!<br>\n";
phpinfo();
?>

Теперь наберите в браузере: http://localhost/test.php. Должна отобразиться


страница с разнообразной информацией о PHP, которая генерируется функцией
phpinfo().
Глава 5. Установка PHP и MySQL 97

Напоминаем, что PHP-сценарии — не то же самое, что CGI-сценарии. В част-


ности, если CGI-сценарий обычно располагают в /cgi-bin/ или /cgi/, то
php-сценарий должен находиться в каталоге с документами.

Если страница не отображается, значит, вы допустили ошибку в файле httpd.conf.


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

Напоминаю еще раз, что просто остановить Apache, так сказать, принудитель-
ным образом нельзя — необходимо воспользоваться ярлыком Stop Apache,
как это было описано выше. В противном случае при использовании некоторых
версий сервера закроется только окно Apache, а сам сервер останется рабо-
тать.

Установка дополнительных модулей


После того как мы убедились в работоспособности PHP, нужно подключить к нему
дополнительные модули, которые находятся в загруженном zip-файле. Среди них —
средства для работы с рисунками, календарем, FTP (File Transfer Protocol — Прото-
кол передачи файлов) и т. д. Нужно заметить, что архив содержит полную версию
PHP, а не только модули для него. Единственная причина, почему мы не обратились к
нему сразу — отсутствие удобной программы установки. Итак, для этого нужно про-
делать ряд действий.
1. Разверните zip-архив прямо в тот же самый каталог, где уже установлен PHP (в
нашем примере это C:\Program Files\PHP4). Некоторые файлы перекроются,
некоторые — добавятся. В частности, появится каталог extensions, как раз и
содержащий практически все необходимые файлы.
2. Теперь нужно дать знать PHP, какие модули он может использовать, а также осу-
ществить еще некоторые настройки. Для этого откройте в Блокноте файл
php.ini из каталога с файлами Windows (обычно C:\WINDOWS). Этот файл был
помещен туда программой установки PHP. Файл представляет собой набор строк,
каждая из которых соответствует значению одного параметра. Части строк, распо-
ложенные после символа ;, рассматриваются как комментарии и игнорируются.
3. Найдите параметр magic_quotes_gpc и отключите его:
magic_quotes_gpc=Off

Этим мы запрещаем PHP принудительно вставлять обратные слэши перед некото-


рыми символами, поступающими из формы. Мы еще обязательно поговорим об
этом и других параметрах ближе к концу книги.
4. Теперь найдите и настройте следующий параметр:
98 Часть II. Выбор и настройка инструментария. Web-сервер Apache

extension_dir=C:\Program Files\PHP4\extensions

Здесь мы уведомляем PHP, что модули он должен искать в каталоге


C:\Program Files\PHP4\extensions, т. е. как раз там, где нужно. Обратите
внимание на то, что по умолчанию в этом параметре стоит значение ./, т. е. по-
иск будет производиться в том же самом каталоге, где установлен PHP. Это, ко-
нечно же, неудобно.
5. Найдите "закомментированные" строки, которые начинаются с ;extension=. Вам
предстоит раскрыть те из них, которые соответствуют нужным нам модулям. В
этой книге описывается библиотека GD для работы с изображениями, поэтому
нам обязательно понадобится модуль php_gd.dll. Поддержка MySQL и кален-
дарных функций уже встроена в PHP.
6. Не забудьте сохранить изменения в файле php.ini. Чтобы изменения вступили в
силу, перезапускать Apache не нужно, ведь мы установили PHP не как модуль сер-
вера, а как отдельную программу.

Установка MySQL
Сначала определимся: зачем же вообще нужны базы данных Web-программисту?
Неужели не проще использовать обычный обмен с файлами? Ведь обычно объем
данных не очень велик (если вы только не пишите поисковую систему). Наш личный
опыт таков: оказывается, стоит затратить какое-то время на изучение MySQL — это
удивительно мощный инструмент, который сэкономит в будущем немало часов, по-
траченных на отладку "вышедшего из-под контроля" сценария.
Итак, вы решили установить у себя на локальном хосте поддержку MySQL. Это до-
вольно несложно. Что ж, приступим.
1. Для начала загрузите с официального сайта MySQL (http://www.mysql.com, раз-
дел Downloads) дистрибутив MySQL. Рекомендую выбрать самую последнюю
версию для Windows. Дистрибутив представляет собой zip-архив, который нужно
развернуть в любой удобный вам каталог.
2. Запустите setup.exe из только что разархивированного дистрибутива. Нажмите
кнопку Next (рис. 5.4).
3. В появившемся информационном окне снова нажмите Next. Откроется диалог с
запросом о выборе каталога для MySQL. По умолчанию предлагается C:\mysql,
но, мне кажется, будет удобнее использовать C:\Program Files\MySQL (рис.
5.5). Задайте этот каталог и нажмите Next.
Глава 5. Установка PHP и MySQL 99

Рис. 5.4. Установка MySQL

4. Выберите тип установки Typical. Начнется копирование файлов MySQL. Дожди-


тесь его окончания. MySQL установлена.

Рис. 5.5. Выбор каталога для MySQL

5. Для того чтобы активизировать MySQL-сервер, запустите исполняемый файл


C:\Program Files\MySQL\bin\mysqld.exe. Можете создать для него ярлык,
однако, поскольку обычно MySQL работает "в связке" с Apache, будет логично
создать командный файл, который будет стартовать и Apache, и MySQL. Назовем
его server.bat и расположим в корневом каталоге диска z:. Вот содержание
этого файла:
100 Часть II. Выбор и настройка инструментария. Web-сервер Apache

Листинг 5.2. Файл server.bat

@echo off
"C:\Program Files\MySQL\bin\mysqld"
start /m "C:\Program Files\Apache Group\Apache\Apache"
Для операционных систем Windows NT и Widows 2000, однако, будет удобнее исполь-
зовать несколько другие команды (иначе в этих системах окно процесса MySQL будет
постоянно видно на экране, что нежелательно):
@echo off
start C:\Progra~1\MySQL\bin\mysqld-nt --standalone
C:\Progra~1\Apache~1\Apache\Apache -k start
Именно для приведенного командного файла лучше всего и создать ярлык, назна-
чив ему "горячую" клавишу <Ctrl>+<Alt>+<A> (только если вы до этого связали
ту же комбинацию с ярлыком Apache, не забудьте ее там отключить).
6. Перед выключением или перезагрузкой компьютера нужно завершать работу
Apache и MySQL. Для этого удобнее всего создать следующий bat-файл с именем,
например, shutdown.bat, расположив его в корневом каталоге диска z:.

Листинг 5.3. Файл shutdown.bat

@echo off
"C:\Program Files\Apache Group\Apache\Apache" -k shutdown
"C:\Program Files\MySQL\bin\mysqladmin" -u root shutdown
Удобно также определить для этого файла ярлык и назначить ему комбинацию
клавиш <Ctrl>+<Alt>+<S>.

Тестирование MySQL
Давайте теперь проверим, все ли работает. Для начала запустите наш файл
server.bat, чтобы активизировать сервер. Создайте следующий PHP-сценарий с
именем mysql.php в каталоге z:\home\localhost\www.

Листинг 5.4. Файл mysql.php

<?
define("DBName","test");
define("HostName","localhost");
define("UserName","root");
define("Password","");
if(!mysql_connect(HostName,UserName,Password))
Глава 5. Установка PHP и MySQL 101

{ echo "Не могу соединиться с базой ".DBName."!<br>";


echo mysql_error();
exit;
}
mysql_select_db(DBName);

// Создаем таблицу t. Если такая таблица уже есть,


// сообщение об ошибке будет подавлено, т. к.
// èñïîëüçóåòñÿ "@"
@mysql_query("create table t(id int, a text)");

// Вставляем в таблицу 10 записей


for($i=0; $i<10; $i++)
{ $id=time();
mysql_query("insert into t(id, a) values($id, 'Ñòð$i!')");
}

// Âûâîäèì âñå çàïèñè


$r=mysql_query("select * from t");
for($i=0; $i<mysql_num_rows($r); $i++)
{ $f=mysql_fetch_array($r);
echo "$f[id] -> $f[a]<br>\n";
}
?>

Теперь наберите в браузере:


http://localhost/mysql.php
Если все сконфигурировано правильно, вы должны получить несколько строк вывода
в браузере без сообщений об ошибках. При каждом запуске в таблицу t добавляются
новые строки, так что с каждым нажатием кнопки Обновить в браузере объем таб-
лицы будет все увеличиваться.
Обращаю ваше внимание на константы DBName, HostName, UserName и Password.
DBName должен содержать имя базы данных (в нашем случае это test — база дан-
ных, которая создается MySQL по умолчанию). HostName — всегда localhost, ведь
мы работаем на локальном компьютере. В макросе UserName проще всего подстав-
лять root, который является владельцем всех таблиц. При установке MySQL пользо-
вателю root не назначается пароль, так что константа Password равна пустой стро-
ке.
ЧАСТЬ III.
ОСНОВЫ ЯЗЫКА PHP
Глава 6

Характеристика
языка PHP

Дочитав до этого места, вы уже должны проникнуться мыслью, что писать сценарии
на Си, мягко говоря, неудобно. (Если подобного ощущения у вас нет, значит, я плохо
написал первую часть и ее придется переделывать…).
Так на чем же писать? Многие тут же ответят: "Конечно, на том, на чем обычно пи-
шут сценарии — на Perl!". Да, это распространенная точка зрения. Однако у Perl, на-
ряду с его неоспоримыми достоинствами, существуют и недостатки. Причем недос-
татки весьма серьезные. Вот один из них: Perl не приспособлен непосредственно для
программирования сценариев. Это в некотором роде универсальный язык, поэтому он
не поддерживает напрямую того, чего бы нам хотелось. А вот и второй: у Perl синтак-
сис не способствует читабельности программы. Он не похож ни на Си, ни на Паскаль
(а эти языки замечательно зарекомендовали себя как самодокументирующиеся). Во-
обще, я сам принадлежу к той категории людей, которые очень болезненно воспри-
нимают непродуманный синтаксис языка программирования, отсюда и мое отноше-
ние к Perl...
PHP — язык, специально нацеленный на работу в Интернете, язык с универсальным
(правда, за некоторыми оговорками) и ясным синтаксисом, удивительно похожим на
Си, сочетающий достоинства Perl и Си. И хотя этот язык еще довольно молодой, он
(точнее, его интерпретатор) установлен уже на порядка миллиона серверов по всему
миру, и цифра продолжает расти. Новое поколение PHP — четвертое — должно во-
обще стереть все преимущества Perl перед PHP, как с точки зрения быстродействия
обработки программ (а третья версия PHP сильно отставала от Perl при обработке
больших циклов), так и с точки зрения синтаксиса. Наконец, большинство PHP-
сценариев (особенно не очень больших размеров) работают быстрее аналогичных им
программ, написанных на Perl (конечно, если сравнивать с обычными Perl-
сценариями, а не программами, запускаемыми под управлением mod_perl).
Думаю, у PHP есть лишь один серьезный недостаток, который менее выражен у Perl:
это — его медлительность при работе с большими и сложными сценариями. Однако
работы по преодолению этой трудности давно ведутся и, если верить разработчикам
PHP, версия 4 является уже компилятором, построенным примерно на том же прин-
ципе, что и компилятор Perl. Давайте поговорим на последнюю тему чуть подробнее.
106 Часть III. Основы языка PHP

Интерпретатор или компилятор?


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

Транслятор — программа, которая переводит код с одного "языка" на другой.


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

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

Язык Perl, который практически всегда называют компилятором, работает точ-


но по такой же схеме — он транслирует текст программы во внутреннее пред-
ставление, а затем использует результирующий код при исполнении. Так что,
Глава 6. Характеристика языка PHP 107

можно сказать, PHP версии 4 представляет собой компилятор ровно настоль-


ко, насколько им является Perl.

Впрочем, описанная только что схема работы PHP не совсем соответствует действи-
тельности. Дело в том, что в языке можно создавать конструкции, которые просто
физически невозможно перевести во внутреннее представление во время фазы транс-
ляции (к таковым, например, относится инструкция включения в программу кода
внешнего файла, имя которого выясняется только на этапе исполнения программы —
к примеру, вводится пользователем). В этом случае PHP просто пропускает их, "от-
кладывая на потом", и транслирует, как только до них дойдет управление. Конечно,
это несколько замедляет выполнение программы, но если подобных конструкций в
ней немного (и они не вставлены в цикл с большим количеством итераций), замедле-
ние не так уж и существенно.
Как вы видите, PHP версии 4 коренным образом отличается от своего предшествен-
ника — PHP версии 3. Фактически, весь код программы в очередной раз был перепи-
сан заново. При этом возникла серьезная проблема с переносимостью программ: не
так-то легко обеспечить совместимость классического интерпретатора с новым транс-
лирующим блоком (вообще, трансляторы по своей природе ограничивают свободу
действий, зато привносят быстродействие). Тем не менее, разработчики PHP блестя-
ще справились с проблемой: практически любая программа, работающая на PHP вер-
сии 3 и не использующая недокументированных возможностей языка, будет работать
и на четвертой версии.
Что же такое PHP? Как мы выяснили, уж точно не компилятор, т. к. не имеет ни ма-
лейшего отношения к машинному коду. И, конечно же, не транслятор в чистом ви-
де — ведь оттранслированный байт-код нельзя ни сохранить в файле, ни использо-
вать повторно. В то же время, главной фазой работы PHP является интерпретация
внутреннего представления программы и ее исполнение. Именно эта фаза и занимает
больше всего времени в серьезных сценариях. Итак, мы вынуждены заключить, что
PHP является интерпретатором с встроенным блоком трансляции, оптимизирующим
ход интерпретации.

Я уже предвижу, что множество читателей не согласятся с такой формулиров-


кой. Конечно, слово "компилятор" звучит солиднее, чем какой-то там "интер-
претирующий транслятор". Но все дело в том, что английское слово compiler
переводится не только как "компилятор", но также и как "транслятор". Заду-
майтесь над этим, если окончательно решили для себя считать PHP и Perl
компиляторами.
108 Часть III. Основы языка PHP

Достоинства и недостатки
интерпретатора
Если вы — бывший системщик или прикладной программист и не знакомы с языком
Perl, довольно непросто будет привыкнуть к тому, что PHP, как и большинство язы-
ков для Web, является интерпретатором (правда, как мы уже говорили, с трансли-
рующим оптимизатором).
Что ж, это так. Да, сценарии, написанные на PHP, работают в тысячи раз медленнее,
чем Си-программы (но почти с такой же скоростью, как созданные на Perl — может
быть, отстают максимум в несколько раз на особо критических участках), и к этому
придется привыкнуть. Например, если мы напишем на Си пустой цикл с миллионом
итераций примерно такого вида:
for(long i=0; i<1000000; i++);
то он будет работать всего долю секунды, в то время как аналогичный цикл на PHP:
for($i=0; $i<1000000; $i++);
проработает на процессоре Pentium 100 несколько секунд.

Приведенные оценки, особенно сравнения с Perl, касаются только PHP версии


4, но не версии 3. Последний отстает даже от Perl по быстродействию почти в
100 раз. Так что стоит задуматься, допустимо ли вообще применять PHP вер-
сии 3 при написании нетривиальных программ.

Однако для сценариев, не содержащих в себе таких громадных циклов (а таких про-
грамм, как мы вскоре увидим, большинство), время работы будет отличаться очень
несущественно. Ну, в самом деле, какая разница, работает ли сценарий 0,01 секунды
или 0,1 секунды, если передача данных по каналам Интернета через модем будет
длиться, например, 5 секунд?

Впрочем, тут все-таки есть стимул стараться по возможности ускорить сцена-


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

"А как же быть, — спросят некоторые, — если нам нужно написать сценарий для
работы, скажем, с тысячами и десятками тысяч пользователей, адреса и телефоны
которых хранятся в файле? Ведь, чтобы найти какого-то пользователя (особенно, если
его имя задано не точно), придется просматривать их всех, а это как раз и будет цикл
с огромным количеством итераций?" Да, это действительно так. Если нужно обраба-
Глава 6. Характеристика языка PHP 109

тывать очень большие массивы данных, лучше использовать Си или... базу данных.
База данных — это набор очень большого числа записей с одинаковой структурой
плюс программное обеспечение для быстрого поиска, добавления и удаления записей
(чаще всего написанного как раз на Си). PHP поддерживает работу с очень большим
числом разнообразных баз данных, поэтому написание сценариев с применением баз
данных не должно вызвать особых проблем. Кстати, и выполняться такие скрипты
будут быстрее, чем аналогичные им "самодельные", написанные на Си — ведь разра-
боткой баз данных и эффективных алгоритмов работы с ними занималось множество
людей. А в PHP останется лишь вызвать нужную функцию (например, поиск в базе
данных) и сразу получить результат — многие базы данных даже умеют нужным об-
разом его отсортировать и вообще выполнить всю "грязную работу"...
У интерпретатора есть и другие преимущества перед классическим компилятором,
например, перед Си. Вот некоторые из них.
r Упрощается обнаружение ошибок во время выполнения программы.
В случае сбоя интерпретатор сразу же выведет сообщение, что что-то не так.
r Можно не заботиться об освобождении выделенной памяти. Интерпретатор сам
определит, когда та или иная переменная в программе уже не используется, и ос-
вободит память, выделенную для нее.
r Существует возможность написать программу, которая, грубо говоря, будет фор-
мировать и тут же исполнять другую программу, что очень часто практикуется
при шаблонной системе организации скриптов. В частности, мы можем формиро-
вать идентификаторы во время исполнения программы, создавать массивы ано-
нимных функций и т. д.
r Не нужно думать о типах переменных (как это, кстати, было сделано в приведен-
ном цикле for). Мы еще вернемся к данному вопросу в дальнейшем.
Есть и другие достоинства. Вообще, использование интерпретатора способно дать
сценариям ту мощь, которую пользователи Web от них и ожидают.
Но за все нужно платить: эта пресловутая медлительность интерпретаторов, даже с
блоком трансляции, способна вывести из себя самого закаленного программиста.
Проигрыш особенно заметен в случае больших и сложных циклов, при обработке
большого количества строк и т. д. Однако, заметьте, это единственный недостаток
PHP, который будет все меньше и меньше проявляться по мере выхода более мощных
процессоров, чтобы в конце концов вообще сойти на нет.

Пример PHP-программы
Традиционно, любая книга начинается с программы "Hello world!". Что ж, не буду
отходить от этих канонов и приведу сразу два примера такой программы. Вот первый
из них:
<?
110 Часть III. Основы языка PHP

echo "Hello world!";


?>
Запустим сценарий в браузере. Легко убедиться, что он действительно работает, да к
тому же еще и безотказно.

Это замечание предназначено для тех, кто еще совершенно не знаком с син-
таксисом языка PHP. Итак, возможно, вы немного смущены словами "запустим
сценарий в браузере". Дело в том, что PHP-сценарий по своей природе не-
сколько отличается от обычных CGI-сценариев, которые мы рассматривали в
первой части этой книги. Но не торопитесь. Следующий пример поставит все
точки над "i".

Для тех, кто еще не сталкивался с синтаксисом PHP, более интересен пример второй
программы. Вот как он выглядит:
<body>
Hello world!
</body>
Что — думаете, произошла ошибка и редактор вместо примера кода на PHP случайно
вставил в текст пример HTML-страницы? А вот и нет. Да-да, вы не ошиблись — тут
действительно нет вообще никаких операторов PHP, и содержимое файла с "про-
граммой" состоит целиком из статического текста.
Что же происходит? Выходит, обычный HTML-текст также правильно обрабатывает-
ся PHP? Да, это так. Но рассмотрим чуть более сложный пример (листинг 6.1).

Листинг 6.1. Простой сценарий на PHP

<html><body>
<h1>Здравствуйте!</h1>
<?
// Вычисляем текущую дату в формате "день.месяц год"
$dat=date("d.m y");
// Вычисляем текущее время
$tm=date("h:i:s");
# Выводим их
echo "Текущая дата: $dat года<br>\n";
echo "Текущее время: $tm<br>\n";
# Выводим цифры
echo "А вот квадраты и кубы первых 5 натуральных чисел:<br>\n";
for($i=1; $i<=5; $i++)
{ echo "<li>$i в квадрате = ".($i*$i);
echo ", $i в кубе = ".($i*$i*$i)."\n";
Глава 6. Характеристика языка PHP 111

}
?>
</body></html>

Я убежден, что синтаксис любого языка программирования гораздо легче "почувст-


вовать" на примерах, нежели используя какие-то диаграммы и схемы. Я буду при-
держиваться этого принципа на протяжении всей книги. Что ж, приступим к разбору
программы.
Начало сценария, если бы не был уже затронут второй пример, может озадачить: раз-
ве это сценарий? Откуда HTML-тэги <html> и <body>? Вот тут-то и кроется главная
особенность (кстати, чрезвычайно удобная) языка PHP: PHP-скрипт может вообще не
отличаться от обычного HTML-документа, как мы это уже заметили ранее.
А помните, как мы раньше в примерах на Си писали кучу одинаковых printf 'ов для
того, чтобы выводить HTML-код страницы? На PHP это можно делать естественным
образом, без всяких операторов. Иными словами, все, что расположено в нашем при-
мере до начала PHP-кода, отображается непосредственно, как будто при помощи не-
скольких вызовов printf() в Си.
Идем дальше. Вы, наверное, догадались, что сам код сценария начинается после от-
крывающего тэга <? и заканчивается закрывающим ?>. Итак, между этими двумя
тэгами текст интерпретируется как программа, и в HTML-документ не попадает. Если
же программе нужно что-то вывести, она должна воспользоваться оператором echo
(это не функция, а конструкция языка: ведь, в конце концов, если это функция, то где
же скобки?). Мы подробно рассмотрим ее работу в дальнейшем. Итак, PHP устроен так,
что любой текст, который расположен вне программных блоков, ограниченных <? и ?>,
выводится в браузер непосредственно, т. е. воспринимается, как вызов оператора echo
(последняя аналогия очень точна, и мы остановимся на ней чуть позже).
Нетрудно догадаться, что часть строки после // является комментарием и на про-
грамму никак не влияет. Однострочные комментарии также можно предварять и сим-
волом # вместо //, как мы можем это увидеть в примере. Комментарии еще бывают
и такие:
/*
это комментарий
...и еще одна строка
*/
То есть, комментарии могут, как и в Си, быть однострочными и многострочными.
Однако в некоторых реализациях PHP многострочные комментарии почему-то всту-
пают в конфликт с "русскими" буквами, которые могут находиться между ними. А
именно, появляются бессмысленные сообщения о синтаксических ошибках, причем
совершенно не в том месте. Почему так происходит, неясно: видимо, ошибка в PHP.
Насчет комментариев и контроля ошибок мы еще поговорим, а пока вот вам совет:
никогда не пользуйтесь многострочными комментариями в PHP, если хотите жить
112 Часть III. Основы языка PHP

долго и счастливо (тем более, что не допускаются вложенные многострочные ком-


ментарии).
А пока давайте лучше посмотрим, что происходит дальше. Вот строка:
$dat=date("d.m y");
Делает она следующее: переменной с именем $dat (заметьте, что абсолютно все
переменные в PHP должны начинаться со знака $, потому что "так проще для интер-
претации") присваивается значение, которое вернула функция date(). Итак, мы ви-
дим, что в PHP, во-первых, нет необходимости явно описывать переменные (как это
делается, например, в Паскале или Си), а во-вторых, нигде не указывается их тип
(про типы мы еще поговорим чуть позже). Интерпретатор сам решает, что, где и ка-
кого типа. А насчет функции date()... Можно заметить, что у нее задается один па-
раметр, который определяет формат результата. Например, в нашем случае это будет
строка вида "11.12 01".
В конце каждого оператора должна стоять точка с запятой, как в Си. Заметьте —
именно как в Си, а не как в Паскале. Иными словами, вы обязаны ставить точку с
запятой перед else в конструкции if-else, но не должны после заголовка функции.
На следующей строке мы опять видим комментарии, а дальше — еще один оператор,
похожий на ранее описанный. Он присваивает переменной $tm текущее время в фор-
мате "часы:минуты:секунды", опять же при помощи вызова date(). Все возможно-
сти этой полезной функции будут подробно описаны в четвертой части книги.
Далее следуют операторы echo, выводящие текстовые строки и нашу дату и время.
Рассмотрим один из них:
echo "Текущая дата: $dat года<br>\n";
Заметьте: то, что любая переменная должна начинаться с символа $, позволяет ин-
терпретатору вставить ее прямо в строку символов на место $dat (конечно, в любую
строку, а не только в параметры echo). Разумеется, можно было бы написать и так
(поскольку конструкция echo не ограничена по числу параметров):
echo "Текущая дата: ",$dat," года<br>\n";
или даже так:
echo "Текущая дата: ".$dat." года<br>\n";

так как для слияния строк используется операция "." (к этому придется пока привык-
нуть).
Кстати говоря, на вопрос, почему для конкатенации строк применяется точка а не,
скажем, плюс "+", довольно легко ответить примером:
$a="100";
$b="200";
echo $a+$b; // выведет "300"
echo $a.$b; // выведет "100200"
Глава 6. Характеристика языка PHP 113

Итак, мы видим, что плюс используется именно как числовой оператор, а точка —
как строковой. Все нюансы применения операторов мы рассмотрим в следующей
главе.
Еще один пример "внедрения" переменных непосредственно в строку:
$path="c:/windows"; $name="win"; $ext="com";
FullPath="$path\$name.$ext";

Последнее выглядит явно изящнее, чем:


$path="c:/windows"; $name="win"; $ext="com";
$FullPath=$path."\".$name.".".$ext;

В терминах языка Perl можно сказать, что переменные в строках, заключенных


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

Ну вот, мы почти подобрались к сердцу нашего сценария — "уникальному" алгорит-


му поиска квадратов и кубов первых 5 натуральных чисел. Выглядит он так:
for($i=1; $i<=5; $i++)
{ echo "<li>$i в квадрате = ".($i*$i);
echo ", $i в кубе = ".($i*$i*$i)."\n";
}

В первой строке находится определение цикла for (счетчик $i, которому присваива-
ется начальное значение 1, инкрементируется на единицу на каждом шаге, пока не
достигнет пяти). Затем следует блок, выполняющий вывод одной пары "квадрат-куб".
Я намеренно сделал вывод в две строки, а не в одну, чтобы показать, что в PHP при-
меняются те же самые правила группировки операторов, что и в Си. А именно: не-
сколько операторов можно сделать одним сложным оператором, заключив их в фи-
гурные скобки, как это сделано выше.
Наконец, после всего этого расположен закрывающий тэг PHP ?>, а дальше — опять
обычные HTML-тэги, завершающие нашу страничку.
Уф! Вот какой код получился в результате работы нашего сценария (листинг 6.2):

Листинг 6.2. Результат работы сценария

<html><body>
<h1>Здравствуйте!</h1>
Текущая дата: 29.01 01 года<br>
114 Часть III. Основы языка PHP

Текущее время: 04:34:16<br>


А вот квадраты и кубы первых 5 натуральных чисел:<br>
<li>1 в квадрате = 1, 1 в кубе = 1
<li>2 в квадрате = 4, 2 в кубе = 8
<li>3 в квадрате = 9, 3 в кубе = 27
<li>4 в квадрате = 16, 4 в кубе = 64
<li>5 в квадрате = 25, 5 в кубе = 125
</body></html>

Как видите, выходные данные сценария скомбинировались с текстом, расположен-


ным вне скобок <? и ?>. В этом-то и заключена основная сила PHP: в легком встраи-
вании кода в тело документа.

Использование PHP в Web


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

Если вы думаете, что все это лишь игра слов, и "хоть горшком назови, только в
печь не ставь", то ошибаетесь. Дело в том, что затронутая тема почти вплот-
ную стыкуется с идеологией отделения кода сценария от дизайна страницы —
идее очень важной, особенно при работе нескольких человек над одним проек-
том, и довольно нетривиальной самой по себе. Мы очень подробно рассмот-
рим ее в пятой части книги, которая посвящена методам программирования на
PHP.
Глава 6. Характеристика языка PHP 115

Ну что, стало понятнее? Пожалуй, нет. Ну что ж, давайте пока будем рассматривать
все наши примеры так, как будто они подходят под второе утверждение (хотя в по-
следнем примере — положа руку на сердце — больше программного кода, чем
HTML-тэгов). Итак, программа, показанная в листинге 6.1, представляет собой
HTML-страницу с "вкрапленным" кодом на PHP. А раз так, то назовем ее, например,
list1.1.php и расположим в каталоге для документов на Web-сервере. Теперь с
точки зрения Web-пользователя она — просто страница.

Для иллюстрации примеров здесь и далее я буду использовать локальный


сервер Apache для платформы Win32, установка которого подробно описана в
главе 3. Примеры я располагал на хосте localhost в его корневом каталоге.
Конечно, это ни в коей мере не означает, что примеры будут работать только
под Windows-версией PHP. Язык PHP задумывался как платформенно-неза-
висимый, поэтому, если вы не задействуете в сценарии особенностей той или
иной операционной системы, он будет одинаково хорошо (или одинаково пло-
хо) работать в любой системе — будь то Unix у хостинг-провайдера или
Windows дома.

Рис. 6.1 — это то, что я увидел, когда открыл в браузере рассмотренный выше при-
мер (файл со сценарием я разместил по адресу:
z:/home/localhost/www/list1.1.php).
Обратите внимание на URL в строке браузера (http://localhost/list1.1.php). Все вы-
глядит так, как будто мы просто открыли обычную Web-страничку. Пока что мы при-
своили расширение php для этой страницы для того, чтобы сервер смог понять, что
ему нужно на самом деле использовать PHP-интерпретатор для обработки документа.
В пятой части этой книги мы рассмотрим, как можно связать PHP с любым расшире-
нием и любым документом на сервере, а пока давайте договоримся давать PHP-
сценариям расширение php.
116 Часть III. Основы языка PHP

Рис. 6.1. Результат работы сценария, приведенного в листинге 6.1


Глава 7

Переменные, константы,
выражения

Как вы, наверное, уже заметили, структура PHP-программы довольно сильно напо-
минает смесь Бейсика и Си, да еще со включениями на HTML. Что ж, так оно, в об-
щем, и есть. Однако мы рассмотрели лишь очень простой пример программы на PHP,
поэтому вряд ли сможем сейчас увидеть общую картину языка. А теперь настало
время заняться конструкциями PHP вплотную.
Начнем мы с основ языка. Итак...

Переменные
Как и в любом другом языке программирования (за исключением, может быть, языка
Forth), в PHP существует такое понятие, как переменная. Даже в простом примере,
какой был описан выше, мы использовали целых 3 переменных!
При программировании на PHP принято не скупиться на объявление новых перемен-
ных, даже если можно обойтись и без них. Например, в том простом сценарии мы
вполне могли бы использовать всего одну переменную — счетчик цикла. Однако зна-
чительно читабельнее будет определить их несколько штук. Отчасти это связано с
тем, что создание нового идентификатора интерпретатору обходится довольно деше-
во, частично из-за того, что все переменные в функциях по умолчанию локальны (о
локальных переменных разговор пойдет чуть позже).
Имена переменных чувствительны к регистру букв: например, $my_variable — не
то же самое, что $My_Variable или $MY_VARIABLE. Кроме того, имена всех пере-
менных должны начинаться со знака $ — так интерпретатору значительно легче "по-
нять" и отличить их, например, в строках. Поначалу это довольно сильно раздражает,
но потом привыкаешь (и даже автоматически начинаешь писать "доллары" перед
именами переменных в программах на Си, Паскале...)

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


только из "английских" букв и цифр, но также и из любых символов, код кото-
рых старше 127, — в частности, и из "русских" букв! Однако я категорически не
советую вам применять кириллицу в именах переменных — хотя бы из-за того,
что в различных кодировках ее буквы имеют различные коды.
118 Часть III. Основы языка PHP

Переменные в PHP — особые объекты, которые могут содержать в буквальном смыс-


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

Типы переменных
PHP непосредственно поддерживает 5 типов переменных, которые я здесь перечислю
и коротко опишу.

integer
Целое число со знаком, обычно длиной 32 бита (от –2 147 483 648 до 2 147 483 647,
если это еще кому-то может быть интересно).

double
Вещественное число довольно большой точности (ее должно хватить для подавляю-
щего большинства математических вычислений).

string
Строка любой длины. В отличие от Си, строки могут содержать в себе также и нуле-
вые символы, что никак не повлияет на программу. Иными словами, строки можно
использовать для хранения бинарных данных. Длина строки ограничена только раз-
мером свободой памяти, так что вполне реально прочитать в одну строку целый "объ-
емистый" файл размером так килобайтов 200—300 (что часто и делается). Строка
легко может быть обработана при помощи стандартных функций, можно также непо-
средственно обратиться к любому ее символу.

array
Ассоциативный массив (или, как его часто называют, хэш, хотя для PHP такое поня-
тие совсем не подходит). Это набор из нескольких элементов, каждый из которых
представляет собой пару вида ключ=>значение (символом => я обозначаю соответ-
Глава 7. Переменные, константы, выражения 119

ствие определенному ключу какого-то значения). Доступ к отдельным элементам


осуществляется указанием их ключа. В отличие от массивов Си, ключами здесь могут
служить не только целые числа, начиная с нуля, но и любые строки. Например, впол-
не возможно существование таких команд:
// создаст массив с ключами "0", "a", "b" и "c"
$a=array(0=>"zzzz", "a"=>"aaa", "b"=>"bbb", "c"="ccc");
echo $a["b"]; // âûâåäåò "bbb"
$a["1"]="qq"; // создаст новый элемент в массиве и присвоит ему "qq"
$a["a"]="new_aaa"; // присвоит существующему элементу "new_aaa";
Забегая вперед, скажу, что оператор array() создает массив, элементы которого пе-
речислена в его скобках.

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

Логические переменные
Существует и еще один гипотетический тип переменных — логический. Логическая
переменная может содержать одно из двух значений: false (ложь) или true (исти-
на). Любое ненулевое число (и непустая строка), а также ключевое слово true сим-
волизирует истину, тогда как 0, пустая строка и слово false — ложь. Таким обра-
зом, любое ненулевое выражение (в частности, значение переменной)
рассматривается в логическом контексте как истина. Вы можете пользоваться кон-
стантами false и true в зависимости от логики программы.
Ключевые слова false и true — не совсем обычные константы. Раньше я говорил,
что false является просто синонимом для пустой строки, а true — для единицы.
Именно так они выглядят, если написать следующие операторы:
echo false; // выводит пустую строку, т. е. ничего не выводит
echo true; // выводит 1

Теперь давайте рассмотрим такую программу (листинг 7.1).

Листинг 7.1. Логические величины

<?
$a=100;
if($a==1) echo "переменная равна 1!<br>"
if($a==true) echo "переменная истинна!<br>"
120 Часть III. Основы языка PHP

?>

Если бы true была в точности равна константе 1, то вывелись бы обе строки, не


правда ли? А отображается только последняя. Это говорит о том, что не все так про-
сто. Мы видим, что в операторах сравнения (например, в операторе сравнения на ра-
венство ==, а также в операторах >, < и т. д.) PHP интерпретирует один из операндов
как логический, если другой также логический. Следующий пример (листинг 7.2) по-
казывает, что, вообще говоря, PHP хранит для каждой переменной признак, является
ли она логической.

Листинг 7.2. Логические переменные

<?
$a=100;
$b=true;
echo "a = $a<br>";
echo "b = $b<br>";
if($a==$b) echo 'а "равно" b!';
?>

Как ни странно, но программа печатает, что "а=100 и b=1", а затем с гордостью заяв-
ляет, что "a равно b". Хотя в данном примере мы прекрасно понимаем, что так и
должно быть (потому что на самом-то деле переменные сравниваются как логиче-
ские), поэтому будьте осторожны, когда вместо $a используется, например, число,
возвращенное функцией. Иначе это может породить ошибку, которая "убьет" не-
сколько часов на ее поиски.
Конечно, при выполнении арифметических операций над логической переменной она
превращается в обычную, числовую переменную. Однако при написании этой книги я
наткнулся на интересное исключение: по-видимому, операторы ++ и -- для увеличе-
ния и уменьшения переменной на 1 не работают с логическими переменными (лис-
тинг 7.3):

Листинг 7.3. Особенности операторов ++ и --

<?
$b=true;
echo "b: $b<br>";
$b++;
echo "b: $b<br>";
?>
Глава 7. Переменные, константы, выражения 121

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

Некоторые особенности работы с логическими переменными вполне могут из-


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

Действия с переменными
Вне зависимости от типа переменной, с ней можно делать три основных действия.

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

Проверка существования
Можно проверить, существует ли (то есть, инициализирована ли) указанная перемен-
ная. Осуществляется это при помощи оператора IsSet(). Например:
if(IsSet($MyVar))
echo "Такая переменная есть. Ее значение $MyVar";

Если переменной в данный момент не существует (то есть нигде ранее ей не присваи-
валось значение, либо же она была вручную удалена при помощи Unset()), то
IsSet() возвращает ложь, в противном случае — истину.
Важно помнить, что мы не можем использовать неинициализированную переменную
в программе — иначе это породит предупреждение со стороны интерпретатора (что,
скорее всего, свидетельствует о наличии логической ошибки в сценарии). Конечно,
предупреждения можно выключить, тогда все неинициализированные переменные
будут полагаться равными пустой строке. Однако я категорически не советую вам
этого делать — уж лучше лишняя проверка присутствия в коде, чем дополнительная
возня с "отлавливанием" возможной ошибки в будущем. Если вы все же захотите
отключить это злополучное предупреждение (а заодно и все остальные), лучше ис-
122 Часть III. Основы языка PHP

пользовать оператор отключения ошибок @, который действует локально (о нем мы


тоже вскоре поговорим).

Уничтожение
Уничтожение переменной реализуется оператором Unset(). После этого действия
переменная удаляется из внутренних таблиц интерпретатора, т. е. программа начина-
ет выполняться так, как будто переменная еще не была инициализирована. Например:
// Переменной $a еще не существует
$a="Hello there!";
// Теперь $a инициализирована
// ... какие-то команды, использующие $a
echo $a;
// А теперь удалим переменную $a
Unset($a);
// Теперь переменной $a опять не существует
echo $a; // Ошибка: нет такой переменной $a

Впрочем, применение Unset() для работы с обычными переменными редко бывает


целесообразно. Куда как полезнее использовать его для удаления элемента в ассоциа-
тивном массиве. Например, если в массиве $A нужно удалить элемент с ключом
for_del, это можно сделать так:
Unset($A["for_del"]);

Теперь элемент for_del не просто стал пустым, а именно удалился, и последующий


перебор элементов массива его не обнаружит.

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


Кроме этих трех действий существуют еще несколько стандартных функций, которые
занимаются определением типа переменных и часто включаются в условные опера-
торы. Вот они.
r is_integer($a)
Возвращает true, если $a — целое число.
r is_double($a)
Возвращает true, если $a — действительное число.
r is_string($a)
Возвращает true, если $a является строкой.
r is_array($a)
Возвращает true, если $a является массивом.
Глава 7. Переменные, константы, выражения 123

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.

Установка типа переменной


Существует функция, которая пытается привести тип указанной переменной к одному
из стандартных (например, вам может понадобиться перевести строку в целое число).
Вот она.
settype($a,$type)
Функция пытается привести тип переменной $a к типу $type ($type — одна из
строк, возвращаемых gettype(), кроме boolean). Если это сделать не удалось (на-
пример, в $a "нечисловая" строка, а мы вызываем settype
($a,"integer")), возвращает false.

Оператор присваивания
Сильно не ошибусь, если скажу, что нет на свете такой программы, в которой не было
бы ни одного оператора присваивания. И в PHP-программе этот оператор, конечно
же, тоже есть. Мы уже с ним встречались — это —знак равенства =:
$имя_переменной=значение;
Как видите, разработчики PHP пошли по линии Си в вопросе операторов присваива-
ния (и проверки равенства, которая обозначается ==), чем, я уверен, привнесли свой
вклад в размножение многочисленных ошибок. Например, если в Си мы пишем
if(a=b) { ... }
вместо
if(a==b) { ... }
124 Часть III. Основы языка PHP

(пропуская ненароком один символ равенства), то компилятор выдаст нам по крайней


мере предупреждение. Иначе обстоит дело в PHP: попробуйте как-нибудь на досуге
написать:
$a=0; $b=1;
if($a=$b) echo "a è b îäèíàêîâû"; else echo "a è b ðàçëè÷íû";
Интерпретатор даже не "пикнет", а программа восторженно заявит, что "a и b одина-
ковы", хотя это, очевидно, совсем не так (дело в том, что $a=$b так же, как и $a+$b,
является выражением, значение которого есть правая часть оператора присваивания,
равная в нашем примере 1).

Почему разработчики PHP пошли таким путем, хотя, я уверен, отлично пони-
мали его недостатки (двух мнений тут быть просто не может)? Что бы им стои-
ло вместо = использовать (например, как в Паскале) :=, а вместо == — =? Я
не знаю. Зато знаю, что в PHP есть еще несколько "ляпов" (только давайте не
будем разжигать религиозных войн по поводу оператора == — каждый про-
граммист волен иметь свое мнение), перенятых из Си. Так что призываю вас
быть предельно внимательными — тут могут поджидать очень даже неприят-
ные сюрпризы.

Ссылочные переменные
Хотя в PHP нет такого понятия, как указатель (что, возможно, к лучшему, а скорее
всего — нет), все же можно создавать ссылки на другие переменные. Существует две
разновидности ссылок: жесткие и символические (первые часто называют просто
ссылками). Жесткие ссылки появились лишь в PHP версии 4 (в третьей версии суще-
ствовали лишь символические ссылки).

Жесткие ссылки
Жесткая ссылка представляет собой просто переменную, которая является синонимом
другой переменной. Многоуровневые ссылки (то есть, ссылка на ссылку на перемен-
ную, как это можно делать, например, в Perl) не поддерживаются. Так что, думаю, не
стоит воспринимать жесткие ссылки серьезнее, чем синонимы.
Чтобы создать жесткую ссылку, нужно использовать оператор & (амперсанд). Напри-
мер:
$a=10;
$b = &$a; // теперь $b — то же самое, что и $a
$b=0; // на самом деле $a=0
echo "b=$b, a=$a"; // âûâîäèò "b=0, a=0"
Глава 7. Переменные, константы, выражения 125

Ссылаться можно не только на переменные, но и на элементы массива (этим жесткие


ссылки выгодно отличаются от символических). Например:
$A=array('a' => 'aaa', 'b' => 'bbb');
$b=&$A['b']; // теперь $b — то же, что и элемент с индексом 'b' массива
$b=0; // на самом деле $A['b']=0;
echo $A['b']; // âûâîäèò 0
Впрочем, элемент массива, для которого планируется создать символическую ссылку,
может и не существовать. Как в следующем случае:
$A=array('a' => 'aaa', 'b' => 'bbb');
$b=&$A['c']; // теперь $b — то же, что и элемент с индексом 'c' массива
echo "Элемент с индексом 'c': (".$A['c'].")";
В результате выполнения этой программы, хотя ссылке $b и не было ничего присвое-
но, в массиве $A создастся новый элемент с ключом c и значением — пустой строкой
(мы можем это определить по результату работы echo). То есть, жесткая ссылка на
самом деле не может ссылаться на несуществующий объект, а если делается такая
попытка, то объект создается.

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


получите сообщение о том, что элемент с ключом c не определен в массиве
$A.

И все же, жесткая ссылка — не абсолютно точный синоним объекта, на который она
ссылается. Дело в том, что оператор Unset(), выполненный для жесткой ссылки, не
удаляет объект, на который она ссылается, а всего лишь разрывает связь между ссыл-
кой и объектом.

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

Итак, жесткая ссылка и переменная (объект), на которую она ссылается, совершенно


равноправны, но изменение одной влечет изменение другой. Оператор Unset() раз-
рывает связь между объектом и ссылкой, но объект удаляется только тогда, когда на
него никто уже не ссылается.
Жесткие ссылки удобно применять при передаче параметров функции и возврате зна-
чения из нее. Как это делается, мы рассмотрим в главе, описывающей возможности
создания функций на PHP.
126 Часть III. Основы языка PHP

Символические ссылки
Символическая ссылка — это всего лишь строковая переменная, хранящая имя дру-
гой переменной. Чтобы добраться до значения переменной, на которую ссылается
символическая ссылка, необходимо применить оператор разыменования — дополни-
тельный знак $ перед именем ссылки. Давайте разберем пример:
$a=10;
$b=20;
$c=30;
$p="a"; // или $p="b" или $p="c" (присваиваем $p имя другой переменной)
echo $$p; // выводит переменную, на которую ссылается $p, т. е. $a
$$p=100; // присваивает $a значение 100
Мы видим, что для того, чтобы использовать обычную строковую переменную как
ссылку, нужно перед ней поставить еще один символ $.Это говорит интерпретатору,
что надо взять не значение самой $p, а значение переменной, имя которой хранится в
переменной $p.
Все это настолько редко востребуется, что вряд ли стоит посвящать теме символиче-
ских ссылок больше внимания, чем это уже сделано. Думаю, использование символи-
ческих ссылок — лучший способ запутать и без того запутанную программу, поэтому
старайтесь их избегать, как огня.

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

Некоторые условные обозначения


Как мы уже знаем, в PHP нет необходимости указывать тип какой-либо переменной
или выражения явно. Однако, как мы видели, с каждой величиной в программе все
же ассоциирован конкретный тип, который, впрочем, можно поменять в процессе
выполнения программы. Такие "подмены" будут вполне осмысленными, если, на-
пример, мы к строке "20" прибавим число 10 и получим результат 30 (а не "2010") —
это хороший пример того, как PHP выполняет преобразования из числа в строку и
наоборот.
Но представьте себе, что мы хотим привести тип переменной $a к числу, а она на
самом деле — массив. Ясно, что такое преобразование лишено всякого смысла — о
чем вам и сообщит (в лучшем случае) PHP, если вы попытаетесь, например, приба-
вить $a к 10. А может и не сообщить (скажем, если перевести массив в строку, то
всегда получится строка "Array"). В то же время, дальше, когда мы будем рассмат-
Глава 7. Переменные, константы, выражения 127

ривать стандартные функции и операторы PHP (которых, кстати, очень много), мне в
большинстве мест придется разъяснять, какой тип имеет тот или иной параметр
функции или оператора, причем все другие несовместимые с ним типы должны быть
исключены. Также было бы полезным обозначить явно тип возвращаемого значения
функций. В этой связи я, подражая оригинальной документации по PHP, буду указы-
вать типы переменных и функций там, где это необходимо, а также некоторые другие
метасимволы. Вот пример описания функции по имени FuncName:
<return_type> FuncName(<type1> $param1 [,<type1> $param2])
Функция делает то-то и то-то. Возвращает то-то.
Здесь должно быть приведено описание функции, возвращающей значение типа
<return_type>, и принимающей один или два аргумента (второй аргумент необяза-
тельный, на что указывают квадратные скобки). Тип первого параметра <type1>, а
второго — <type2>. Описание возможных типов, которые я здесь выделил угловыми
скобками, приводится в следующих подразделах.

string
Обычная строка, или тип, который можно перевести в строку.

int, long
Целое число, либо вещественное число (в последнем случае дробная часть отсекает-
ся), либо строка, содержащая число в одном из перечисленных форматов. Если стро-
ку не удается перевести в int, то вместо нее подставляется 0, и никаких предупреж-
дений не генерируется!

double, float
Вещественное число, или целое число, или строка, содержащая одно из таких чисел.

bool
Логический тип, который будет восприниматься либо как ложь (нулевое число, пустая
строка или константа false), либо как истина (все остальное). Обычно редко указы-
вается этот тип (вместо него пишут int, хотя это и неверно), но я все же постараюсь
применять его там, где это возможно.

array
Массив, в общем случае ассоциативный (см. ниже). То есть набор пар
ключ=>значение. Впрочем, здесь может быть передан и список list.
128 Часть III. Основы языка PHP

list
Обычно это массив с целыми ключами, пронумерованными от 0 и следующими под-
ряд. Так как список является разновидностью ассоциативного массива, то обычно
вместо параметров функций типа list можно подставлять и параметры типа array.
При этом, скорее всего, функция "ничего не заметит" и будет работать с этим масси-
вом как со списком, "мысленно" пронумеровав его элементы. Можно также сказать,
что список представляет собой упорядоченный набор значений (который можно, на-
пример, отсортировать в порядке возрастания), тогда как ассоциативный массив —
упорядоченный набор пар значений, каждую из которых логически бессмысленно
разъединять.

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

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

mixed
Все, что угодно. Это может быть целое или дробное число, строка, массив или объ-
ект... Например, параметр типа mixed имеет стандартная функция gettype() или
функция settype(). Если написано, что функция возвращает mixed, это значит, что
тип результата зависит от операндов и уточняется при описании функции.

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

Константы
Встречаются случаи, когда переменные довольно неудобно использовать для посто-
янного хранения каких-либо определенных величин, которые не меняются в течение
работы программы. Такими величинами могут быть математические константы, пути
к файлам, разнообразные пароли и т. д. Как раз для этих целей в PHP предусмотрена
такая конструкция, как константа.
Глава 7. Переменные, константы, выражения 129

Константа отличается от переменной тем, что, во-первых, ей нигде в программе нель-


зя присвоить значение больше одного раза, а во-вторых, ее имя не предваряется зна-
ком $, как это делается для переменных. Например:
// Предположим, определена константа PI, равная 3.146
$a=2.34*sin(3*PI/8)+5; // использование константы
echo "Это число PI"; // выведет "Это число PI"
echo "Это число ".PI; // выведет "Это число 3.14"

То, что не надо писать "доллар" перед именем константы — это, конечно хорошо.
Однако, как мы можем видеть из примера, есть и минусы: мы уже не можем исполь-
зовать имя константы непосредственно в текстовой строке.

Предопределенные константы
Константы бывают двух типов: одни — предопределенные (то есть устанавливаемые
самим интерпретатором), а другие определяются программистом. Существуют не-
сколько предопределенных констант.
r __FILE__
Хранит имя файла программы, которая выполняется в данный момент.
r __LINE__
Содержит текущий номер строки, которую обрабатывает в текущий момент ин-
терпретатор. Эта своеобразная "константа" каждый раз меняется по ходу исполне-
ния программы.
r PHP_VERSION
Версия интерпретатора PHP.
r PHP_OS
Имя операционной системы, под которой работает PHP.
r TRUE или true
Эта константа нам уже знакома и содержит значение "истина".
r FALSE или false
Содержит значение "ложь".

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

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;

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

Проверка существования константы


В PHP существует также функция, которая проверяет, существует ли (была ли опре-
делена ранее) константа с указанным именем. Вот она.
bool defined(string $name)

Возвращает true, если константа с именем $name была ранее определена.


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

Выражения
Выражения — это один из "кирпичей", на которых держится здание PHP. Действи-
тельно, практически все, что вы пишете в программе — это выражение. Мне нравит-
ся следующее определение понятия "выражение": "нечто, имеющее определенное зна-
чение". И обратно: если что-то имеет значение, то это "что-то" есть выражение.
Самый простой пример выражения — переменная или константа, стоящая, скажем, в
правой части оператора присваивания. Например, цифра 5 в операторе
$a=5;

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


ожидать, что в $a окажется 5. Теперь, если мы напишем
$b=$a;

то, очевидно, в $b окажется также 5, ведь выражение $a в правой части оператора


имеет значение 5.
Глава 7. Переменные, константы, выражения 131

Посмотрим еще раз на этот пример. Помните, я говорил, что практически все, из чего
мы составляем программу — это выражения? Так вот, $b=$a —тоже выражение!
(Впрочем, это не будет сюрпризом для знатоков Си или Perl). Нетрудно догадаться,
какое оно имеет значение: 5 (тут просто не может быть никаких других вариантов, не
правда ли?). А это значит, что мы можем написать что-то типа следующих команд:
$a=($b=10); // или просто $a=$b=10

При этом переменным $a и $b присвоится значение 10. А вот еще пример, уже менее
тривиальный:
$a=3*sin($b=$c+10)+$d;

Что окажется в переменных после выполнения этих команд? Очевидно, то же, что и в
результате работы следующих операторов:
$b=$c+10;
$a=3*sin($c+10)+$d;

Мы видим, что в PHP при вычислении сложного выражения можно (если какая-то его
часть понадобится нам впоследствии) задавать переменным значения этой части пря-
мо внутри оператора присваивания. Этот прием может действительно сильно упро-
стить жизнь и сократить код программы, "читабельность" которой сохранится на
прежнем уровне, так что советую им иногда пользоваться.
Совершенно точно можно сказать, что у любого выражения есть тип его значения.
Например:
$a=10*20;
$b="".(123*3);
echo "$a:",gettype($a)," $b:",gettype($b);
// выведет "200:integer 200:string"

Чтобы преобразовать одно значение в другое (например, нам может не понравиться,


что $b — типа string, хотя содержит целое число), используются операторы преоб-
разования типов. Эти операторы доступны как в функциональной, так и в префиксной
операторной форме. Например, следующие две инструкции эквивалентны:
$a = intval($b);
$a = (int)$b;

Итак, вот эти операторы:


r $b=intval(выражение) или $b=(int)(выражение)
Переводит значение выражения в целое число и присваивает его $b.
r $b=doubleval(выражение) или $b=(double)(выражение)
Переводит значение в действительное число и присваивает его $b.
r $b=strval(выражение) или $b=(string)(выражение)
132 Часть III. Основы языка PHP

Переводит значение выражения в строку.


r $b=(bool)(выражение)
Преобразует значение выражения в логический тип. То есть, после выполнения
этого оператора в $b окажется либо true, либо false.
Вообще-то, есть еще два хитроумных оператора (array) и (object), но эти опера-
торы мы рассматривать не будем в силу их крайне слабой распространенности.

Логические выражения
Логические выражения — это выражения, у которых могут быть только два значения:
ложь и истина (или, что почти то же самое, 0 и 1). Что, поверили? Напрасно — на
самом деле абсолютно любое выражение может рассматриваться как логическое в
"логическом" же контексте (например, как условие для конструкции if-else). Ведь,
как уже говорилось, в качестве истины может выступать любое ненулевое число, не-
пустая строка и т. д., а под ложью подразумевается все остальное.
Для логических выражений справедливы все те выводы, которые мы сделали насчет
логических переменных. Эти выражения чаще всего возникают при применении опе-
раторов >, < и == (равно), || (логическое ИЛИ), && (логическое И), ! (логическое
НЕ) и других. Например:
$a = 10<5; // $a=false
$a = $b==1; // $a=true, åñëè $b=5
$a = $b>=1&&$b<=10 // $a=true, если $b в пределах от 1 до 10
$a = !($b||$c)&&$d; // $a=true, если $b и $c ложны, а $d — истинно

Как осуществляется проверка истинности той или иной логической переменной? Да


точно так же, как и любого логического выражения:
$b = $a>=1&&$a<=10; // присваиваем $b значение логического выражения
if($b) echo "a в нужном диапазоне значений";

Строковые выражения
Строки в PHP — одни из самых основных объектов. Как мы уже говорили, они могут
содержать текст вместе с символами форматирования или даже бинарные данные.
Определение строки в кавычках или апострофах может начинаться на одной строке, а
завершаться — на другой. Вот пример, который синтаксически совершенно коррек-
тен:
$a="Это текст, начинающийся на одной строке
и продолжающийся на другой,
третьей и т. д.";
Глава 7. Переменные, константы, выражения 133

Я уже много раз использовал в примерах строковые константы, заключенные как в


кавычки, так и в апострофы. Настало время поговорить о том, чем эти представления
отличаются.

Строка в апострофах
Начнем с самого простого. Если строка заключена в апострофы (например,
'строка'), то она трактуется почти в точности так же, как записана, за исключением
двух специальных последовательностей символов:
r последовательность \' трактуется PHP как апостроф и предназначена для вставки
апострофа в строку, заключенную в апострофы;
r последовательность \\ трактуется как один обратный слэш и позволяет вставлять
в строку этот символ.
Все остальные символы обозначают сами себя, в частности, символ $ не имеет ника-
кого специального значения (отсюда вытекает, что переменные внутри строки, за-
ключенной в апострофы, не интерполируются, т. е. их значение не подставляется).

Строка в кавычках
По сравнению с апострофами, кавычки более "либеральны". То есть, набор специальных
метасимволов, которые, будучи помещены в кавычки, определяют тот или иной специаль-
ный символ, гораздо богаче. Вот некоторые из них:
r \n обозначает символ новой строки;
r \r обозначает символ возврата каретки;
r \t обозначает символ табуляции;
r \$ обозначает символ $, чтобы следующий за ним текст случайно не был интер-
полирован, как переменная;
r \" обозначает кавычку;
r \\ обозначает обратный слэш;
r \xNN обозначает символ с шестнадцатеричным кодом NN.
Переменные в строках интерполируются. Например:
$a="Hello";
echo "$a world!"
Этот фрагмент выведет Hello world!, т. е. $a в строке была заменена на значение
переменной $a (этому поспособствовал знак доллара, предваряющий любую пере-
менную).
Давайте рассмотрим еще один пример.
$a="Hell"; // ñëîâî Hello áåç áóêâû "o"
echo "$ao world!";
134 Часть III. Основы языка PHP

Мы ожидаем, что выведется опять та же самая строка. Но задумаемся: как PHP узна-
ет, имели ли мы в виду переменную $a или же переменную $ao? Очевидно, никак.
Запустив фрагмент, убеждаемся, что он генерирует сообщение о том, что переменная
$ao не определена. Как же быть? А вот как:
$a="Hell"; // слово Hello без буквы "o"
echo $a."o world!"; // один способ
echo "{$a}o world!"; // другой способ
echo "${a}o world!"; // третий способ!
Мы видим, что существует целых три способа преодолеть проблему. Каким из них
воспользоваться — дело ваше. Мне больше нравится вариант с {$a}, хотя он и вве-
ден в PHP лишь недавно.

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


могут трактоваться как спецсимволы.

Here-документ
В четвертой версии PHP появился и еще один способ записи строковых констант, ко-
торый исторически называется here-документом (встроенный документ). Фактически
он представляет собой альтернативу для записи многострочных констант. Выглядит
это примерно так:
$a=<<<MARKER
Далее идет какой-то текст,
возможно, с переменными, которые интерполируются:
например, $name будет интерполирована здесь.
MARKER;
Строка MARKER может быть любым алфавитно-цифровым идентификатором, не
встречающимся в тексте here-документа в виде отдельной строки. Синтаксис накла-
дывает 2 ограничения на here-документы:
r после <<<MARKER и до конца строки не должны идти никакие непробельные сим-
волы;
r завершающая строка MARKER; должна оканчиваться точкой с запятой, после кото-
рой до конца строки не должно быть никаких инструкций.
Эти ограничения настолько стесняют свободу при использовании here-документов,
так что, думаю, вам стоит совсем от них отказаться. Например, следующий код рабо-
тать не будет, как бы нам этого ни хотелось (функция strip_tags() удаляет тэги из
строки):
echo strip_tags(<<<EOD);
Глава 7. Переменные, константы, выражения 135

Какой-то текст с <b>тэгами </b> — этот пример НЕ работает!


EOD;
Надеюсь, в будущем разработчики PHP изменят ситуацию к лучшему, но пока они
этого не сделали.

Вызов внешней программы


Последняя строковая "константа" — строка в обратных апострофах (например, `ко-
манда`), заставляет PHP выполнить команду операционной системы и то, что она вы-
вела, подставить на место строки в обратных апострофах. Вот так, например, мы мо-
жем в системе Windows узнать содержимое текущего каталога, которое выдает
команда dir:
$st=`dir`;
echo "<pre>$st</pre>";
Впрочем, если в настройках PHP установлен так называемый безопасный режим,
который ограничивает возможность запуска внешних программ лишь некоторыми,
указанная команда может и не сработать. Мы еще вернемся к запуску программ в
следующей части этой книги.

Операции
На самом деле, к этому моменту вы уже знакомы практически со всеми операциями
над переменными и выражениями в PHP. И все же я приведу здесь их полный список
с краткими комментариями, заменяя выражения-операнды буквами a и b.

В большинстве публикаций, как только разговор заходит о выражениях и опе-


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

Арифметические операции
r a + b — сложение
r a — b — вычитание
r a * b — умножение
r a / b — деление
r a % b — остаток от деления a на b
136 Часть III. Основы языка PHP

Операция деления / возвращает целое число (то есть, результат деления нацело), если оба
выражения a и b — целого типа (или же строки, выглядящие как целые числа), в против-
ном случае результат будет дробным. Операция вычисления остатка от деления % работает
только с целыми числами, так что применение ее к дробным может привести к, мягко го-
воря, нежелательному результату.

Строковые операции
r a.b — слияние строк a и b
r a[n] — символ строки в позиции n
Собственно, других строковых операций и нет — все остальное, что можно сделать со
строками в PHP, выполняют стандартные функции.

Операции присваивания
Основным из этой группы операций является оператор присваивания =. Еще раз на-
помню, что он не обозначает "равенство", а говорит интерпретатору, что значение
правого выражения должно быть присвоено переменной слева. Например:
$a = ($b = 4) + 5;
После этого $a равно 9, а $b равно 4.

Обратите внимание на то, что в левой части всех присваивающих операторов


должна стоять переменная или ячейка массива.
Помимо этого основного оператора, существует еще множество комбинированных —
по одному на каждую арифметическую, строковую и другую операцию. Например:
$a = 10;
$a += 4; // ïðèáàâèòü ê $a 4
$s = "Hello";
$s .= " world!"; // òåïåðü â $s "Hello world!"
Думаю, не стоит особо на них задерживаться.

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


Для операций $a+=1 и $b-=1 в связи с их чрезвычайной распространенностью в PHP
ввели, как и в Си, специальные операторы. Итак:
r $a++ — увеличение переменной $a на 1;
r $a-- — уменьшение переменной $a на 1.
Как и в языке Си, эти операторы увеличивают или уменьшают значение переменной,
а в выражении возвращают значение переменной $a до изменения. Например:
Глава 7. Переменные, константы, выражения 137

$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.

Битовые операции
Эти операции предназначены для работы (установки/снятия/проверки) групп битов в
целой переменной. Биты целого числа — это не что иное, как отдельные разряды того
же самого числа, записанного в двоичной системе счисления. Например, в двоичной
системе число 12 будет выглядеть как 1100, а 2 — как 10, так что выражение 12|2
вернет нам число 14 (1110 в двоичной записи). Если переменная не целая, то она вна-
чале округляется, а уж затем к ней применяются перечисленные ниже операторы.
r a & b — результат — число, у которого установлены только те биты, которые
установлены и у a, и у b одновременно.
r a | b — результат — число, у которого установлены только те биты, которые
установлены либо в a, либо в b (либо одновременно).
r ~ a — результат, у которого на месте единиц в a стоят нули, и наоборот.
r a << b — результат — число, полученное поразрядным сдвигом a на b битов
влево.
r a >> b — аналогично, только вправо.

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

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


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

Операции эквивалентности
В 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 ðàâíû";
Глава 7. Переменные, константы, выражения 139

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


идут далеко за пределы сравнения "обычных" переменных. С его помощью можно
сравнивать также и массивы, объекты и т. д. Это бывает иногда очень удобно. Вот
пример:
$a=array(’a’=>’aaa’);
$b=array(’b’=>’bbb’);
if($a==$b) echo "С использованием == a=b<br>";
if($a===$b) echo "С использованием === a=b<br>";
Если запустить представленный код, то выведется первое сообщение, но не второе.
Произойдет это по той причине, что, как мы уже говорили, операнды-массивы преоб-
разуются в строки array, которые затем и будут сравниваться. Оператор === лишен
этого недостатка, поэтому работает верно.
Разумеется, для оператора === существует и его антипод — оператор !=== (он состо-
ит из целых четырех символов!). Думаю, что не нужно объяснять, как он работает.

Логические операции
Эти операции предназначены исключительно для работы с логическими выражения-
ми и также возвращают false или true.
r ! a — истина, если a ложно, и наоборот.
r a && b — истина, если истинны и a, и b.
r a || b — истина, если истинны или a, или b, или они оба.
Следует заметить, что вычисление логических выражений, содержащих такие опера-
ции, идет всегда слева направо, при этом, если результат уже очевиден (например,
false&&что-то всегда дает false), то вычисления обрываются, даже если в выра-
жении присутствуют вызовы функций. Например, в операторе
$logic = 0&&(time()>100);
стандартная функция time() никогда не будет вызвана.
Будьте осторожны с логическими операциями — не забывайте про удваивание сим-
вола. Обратите внимание, что, например, | и || — два совершенно разных операто-
ра, один из которых может потенциально возвращать любое число, а второй — толь-
ко false и true.

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

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

К сожалению, PHP — чуть ли не первый язык, который выводит предупрежде-


ния в браузер, а не в файлы журналов. Если вы работали некоторое время с
таким языком, как Perl, то, наверное, уже успели устать от бесконечных вере-
ниц "500-х ошибок", которые Perl выдает при малейшей оплошности в сцена-
рии. Теперь можете вздохнуть с облегчением: PHP никогда не выдаст сообще-
ние о 500-й ошибке, что бы ни произошло.

PHP устроен так, что ранжирует ошибки и предупреждения по четырем основным


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

Листинг 7.4. Навязчивые предупреждения

<form action=test.php>
<input type=submit name="doGo" value="Click!">
</form>
<?
if($doGo) echo "Вы нажали кнопку!";
?>

Мы хотели сделать так, чтобы при нажатии на кнопку выдавалось соответствующее


сообщение, но вот беда: теперь при первом запуске сценария PHP выдаст предупреж-
дение о том, что "переменная $doCliсk не инициализирована". Ну не отключать же
из-за такой мелочи контроль ошибок во всем сценарии, не правда ли? Как бы нам
временно блокировать проверку ошибок, чтобы она не действовала только в одном
месте, не влияя на остальной код?
Вот для этого и существует оператор @ (отключение ошибок). Если разместить дан-
ный оператор перед любым выражением (возможно, включающим вызовы функций,
генерирующих предупреждения), то сообщения об ошибках в этом выражении будут
подавлены и в окне браузера не отображены.
Глава 7. Переменные, константы, выражения 141

На самом деле текст предупреждения сохраняется в переменной PHP


$php_errormsg, которая может быть в будущем проанализирована. Эта воз-
можность доступна, если в настройках PHP включен параметр track_errors
(по умолчанию он как раз и установлен в yes).

Вот теперь мы можем переписать наш пример, грамотно отключив надоедливое пре-
дупреждение (листинг 7.5).

Листинг 7.5. Отключение навязчивого предупреждения

<form action=test.php>
<input type=submit name="doGo" value="Click!">
</form>
<?
if(@$doGo) echo "Вы нажали кнопку!";
?>

Как можно заметить, листинг 7.5 отличается от листинга 7.4 всего лишь наличием
оператора @ внутри скобок инструкции if.

Еще раз хочу посоветовать вам включать максимальный контроль ошибок в


настройках PHP, а в спорных местах применять оператор @. Это просто, кра-
сиво, удобно. К тому же, как я уже говорил, способно в несколько раз облег-
чить отладку сценариев, не работающих по загадочным причинам.
Глава 8

Работа с данными
формы

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


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

Передача данных командной строки


Вначале хочу вас поздравить: сейчас мы уже знаем достаточно, чтобы начать писать
простейшие сценарии на PHP типа "Hello world, сейчас 10 часов утра". Однако нашим
сценариям будет недоставать одного — интерактивного взаимодействия с пользова-
телем.
Зададимся задачей написать сценарий, который принимает в параметрах имя и воз-
раст пользователя и выводит: "Привет, <имя>! Я знаю, вам <возраст> лет!".
Сначала рассмотрим наиболее простой способ передачи имени и возраста сцена-
рию — непосредственный набор их в URL после знака ? — например, в формате
name=имя&age=возраст (мы рассматривали этот прием в первой части книги).
Правда, даже программисту довольно утомительно набирать эту строку вручную.
144 Часть III. Основы языка PHP

Всякие там ?, &, %... К счастью, существуют удобные возможности языка HTML, ко-
торые, конечно, поддерживаются всеми браузерами.
Итак, пусть у нас на сервере в корневом каталоге есть сценарий на PHP под названи-
ем hello.php. Наш сценарий распознает 2 параметра: name и age. Он должен отра-
ботать и вывести следующую HTML-страницу:
<html><body>
Привет, name! Я знаю, Вам age лет!
</body></html>
Разумеется, нужно name и age заменить на соответствующие значения. Таким обра-
зом, если задать в адресной строке браузера
http://www.somehost.com/script.cgi?name=Vasya&age=20
мы должны получить страницу с требуемым результатом.
Как только задача осознана, можно приступать к ее решению. Но прежде бывает по-
лезно решить аналогичную, но более простую задачу. Итак, как же нам в сценарии
получить строку параметров, переданную после знака вопроса в URL при обращении
к сценарию? Как было указано в первой части книги, для этого можно проанализиро-
вать переменную окружения QUERY_STRING, которая в PHP доступна под именем
$QUERY_STRING. Напишем небольшой пример, чтобы это проиллюстрировать (лис-
тинг 8.1).

Листинг 8.1. Вывод параметров командной строки

<html><body>
<?
echo "Данные из командной строки: $QUERY_STRING";
?>
</body></html>

Если теперь мы запустим этот сценарий из браузера (перед этим сохранив его в фай-
ле test.php в корневом каталоге сервера) примерно вот таким образом:
http://www.myhost.com/test.php?aaa+bbb+ccc+ddd
то получим документ следующего содержания:
Данные из командной строки: aaa+bbb+ccc+ddd
Обратите внимание на то, что URL-декодирование символов не произошло: строка
$QUERY_STRING, как и одноименная переменная окружения, всегда приходит в той
же самой форме, в какой она была послана браузером. Давайте запомним этот не-
большой пример — он еще послужит нам в будущем.
Так как PHP изначально создавался именно как язык для Web-программирования, то
он дополнительно проводит некоторую работу с переменной $QUERY_STRING перед
Глава 8. Работа с данными формы 145

тем, как управление будет передано сценарию.


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

Формы
Вернемся к поставленной задаче. Как нам сделать, чтобы пользователь мог в удобной
форме ввести свое имя и возраст? Очевидно, нам придется создать что-нибудь типа
диалогового окна Windows, только в браузере. Итак, нам понадобится обычный
HTML-документ (например, по имени form.html в корневом каталоге) с элементами
этого диалога — текстовыми полями — и кнопкой. Давайте возьмем ту же самую
форму, которую я уже приводил в примере в первой части книги, только теперь мы
уже будем не просто разбирать, как и куда поступают данные, а напишем сценарий,
который эти данные будет обрабатывать (листинг 8.2).

Листинг 8.2. form.html: страница с формой

<html><body>
<form action=hello.php>
Введите имя: <input type=text name="name" value="Неизвестный"><br>
Введите возраст: <input type=text name="age" value="неопределенный"><br>
<input type=submit value="Нажмите кнопку, чтобы запустить сценарий!">
</form>
</body></html>

Загрузим наш документ в браузер. Теперь, если ввести в поле с именем свое имя, а в
поле для возраста — свой возраст и нажать кнопку, браузер автоматически обратится
к сценарию hello.php и передаст через ? все атрибуты, расположенные внутри тэ-
гов <input> в форме и разделенные символом & в строке параметров. Заметьте, что
в атрибуте action тэга <form> мы задали относительный путь, т. е. сценарий
hello.php будет искаться браузером в том же самом каталоге, что и файл
form.html.
Как мы знаем, все перекодирования и преобразования, которые нужны для URL-
кодирования данных, осуществляются браузером автоматически. В частности, буквы
146 Часть III. Основы языка PHP

кириллицы превратятся в %XX, где XX — некоторое шестнадцатеричное число, обо-


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

Листинг 8.3. hello.php — модель простого PHP-сценария

<html><body>
<?
получаем в $name имя из параметров, а в $age — возраст
echo "Привет, $name!<br> Я знаю, Вам $age лет!";
?>
</html></body>

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

Трансляция полей формы


в переменные
Итак, мы не хотим заниматься прямым разбором переменной окружения
QUERY_STRING, в которой хранятся параметры сценария. И правильно не хотим —
интерпретатор перед запуском сценария делает все сам. Причем независимо от того,
каким методом — GET или POST — воспользовался "браузер". То есть, PHP сам оп-
ределяет, какой метод был задействован (благо, информация об этом доступна через
переменную окружения REQUEST_METHOD), и получает данные либо из
QUERY_STRING, либо из стандартного входного потока. Это крайне удобно и достой-
но подражания, вообще говоря, в любых CGI-сценариях.
А именно, интерпретатор все данные из полей формы преобразует в глобальные од-
ноименные переменные. В нашем случае значение поля name после начала работы
программы будет храниться в переменной $name, а значение поля age — в перемен-
ной $age. То есть, не надо ничего ниоткуда "получать" — все уже установлено и рас-
паковано из URL-кодировки. Максимум удобств, минимум затрат, не правда ли? К
тому же, еще и работает быстрее, чем аналогичный кустарный код, написанный на
PHP, потому что разработчики PHP предусмотрели функцию разбора командной
строки на Си.
Глава 8. Работа с данными формы 147

Вот наш окончательный сценарий hello.php (листинг 8.4). Как видите, он сжался
до неприличных размеров:

Листинг 8.4. hello.php: окончательная версия

<html><body>
<? echo "Привет, $name!<br> Я знаю, Вам $age лет!" ?>
</html></body>

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

Листинг 8.5. hello.php: усовершенствованная версия

<html><body>
<?if($doGo) {?>
<form action="<?=$SCRIPT_NAME?>">
Введите имя: <input type=text name="name"><br>
Введите возраст: <input type=text name="age"><br>
<input type=submit name="doGo" value="Нажмите кнопку!">
</form>
<?} else {?>
Привет, <?=$name?>!<br>
Я знаю, Вам <?=$age?> лет!"
<?}?>
</html></body>

Из этого примера мы можем почерпнуть еще один удобный прием, который нами
пока не рассматривался. Это конструкция <?=выражение?>. Она является ничем
иным, как просто более коротким обозначением для <?echo(выражение)?>, и пред-
назначена для того, чтобы вставлять величины прямо в HTML-страницу.

Помните наши рассуждения о том, что же первично в PHP: текст или програм-
ма? Конструкция <?= применяется обычно в тот момент, когда выгодно счи-
148 Часть III. Основы языка PHP

тать, что первичен текст. В нашем примере именно так и происходит — ведь
кода на PHP тут очень мало, в основном страница состоит из HTML-тэгов.

Обратите внимание на полезный прием: в параметре action тэга <form> мы не за-


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

Если PHP установлен не как модуль Apache, а как отдельный обработчик, то


переменная $SCRIPT_NAME будет содержать не то значение, на которое мы
рассчитываем. Например, если воспользоваться способом инсталляции PHP,
который предлагается во второй части этой книги (когда мы устанавливаем
PHP именно как внешнюю программу, а не модуль Apache), после запуска сце-
нария переменная $SCRIPT_NAME будет содержать строку /_php/php.exe,
что, конечно же, нам не подходит. "Правильное" значение в этом случае можно
найти в переменной окружения REDIRECT_URL, или в переменной PHP
$REDIRECT_URL.

К тому же, теперь исчезла необходимость и в промежуточном файле form.html: его


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

Трансляция переменных
окружения и Cookies
Однако "интеллектуальные" возможности PHP на этом далеко не исчерпываются.
Дело в том, что в переменные преобразуются не только все данные формы, но и пе-
ременные окружения (включая QUERY_STRING, CONTENT_LENGTH и многие другие), а
также все Cookies.
Например, вот сценарий (листинг 8.6), который печатает IP-адрес пользователя, ко-
торый его запустил, а также тип его браузера (эти данные хранятся в переменных
окружения REMOTE_USER и HTTP_USER_AGENT):

Листинг 8.6. Вывод IP-адреса и браузера пользователя

<html><body>
Ваш IP-адрес: <?=$REMOTE_USER?><br>
Ваш браузер: <?= HTTP_USER_AGENT?>
</body></html>
Глава 8. Работа с данными формы 149

По умолчанию трансляция выполняется в порядке ENVIRONMENT-GET-POST-


COOKIE, причем каждая следующая переменная как бы перекрывает предыдущее
свое значение. Например, пусть у нас есть переменная окружения A=10, параметр,
поступивший из GET-формы A=20 и Cookie A=30.
В этом случае в переменную $A сценария будет записано 30, поскольку Cookie пере-
крывает GET, а GET перекрывает переменные окружения. Так что, проверяя какую-
либо переменную окружения VAR в сценарии (особенно если она касается вопросов,
связанных с разграничением прав доступа — например, переменная содержит па-
роль), задумайтесь на минутку: а что, если злоумышленник запустит ваш сценарий
вот так:
http://www.somehost.com/foo.php?VAR=что_то_очень_нехорошее
и старое значение переменной окружения VAR окажется стертым? К счастью, в таких
ситуациях есть выход — достаточно воспользоваться функцией getenv(), чтобы
прочитать значение переменной окружения с указанным именем, и только его — не-
взирая ни на какие другие данные. Подробнее об этой функции мы поговорим чуть
позже.

Трансляция списков
Механизм трансляции полей формы в PHP работает приемлемо, когда среди них нет
полей с одинаковыми именами. Если же таковые встречаются, то в переменную, яс-
ное дело, записываются только данные последнего встретившегося поля. Это доволь-
но-таки неудобно при работе, например, со списком множественного выбора
<select multiple>:
<select name=Sel multiple>
<option>First
<option>Second
<option>Third
</select>
В таком списке вы можете выбрать (подсветить) не одну, а сразу несколько строчек,
используя клавишу <Ctrl> и щелкая по ним кнопкой мыши. Пусть мы выбрали First
и Third. Тогда после отправки формы сценарию придет строка параметров
Sel=First&Sel=Third, и в переменной $Sel окажется, конечно, только Third.
Значит ли это, что первый пункт потерялся и механизм трансляции в PHP работает
некорректно? Оказывается, нет, и для решения подобных проблем в PHP предусмот-
рена возможность давать имена полям формы в виде имени массива с индексами:
<select name="Sel[]" multiple>
<option>First
<option>Second
<option>Third
150 Часть III. Основы языка PHP

</select>
Теперь сценарию придет строка Sel[]=First&Sel[]=Third, интерпретатор обна-
ружит, что мы хотим создать "автомассив" (то есть массив, который не содержит
пропусков, и у которого индексация начинается с нуля), и, действительно, создаст
переменную $Sel типа массив, содержимое которого следующее:
array(0=>"First", 1=>"Third"). Как мы видим, в результате ничего не пропа-
ло — данные только слегка видоизменились.

Подробнее про ассоциативные массивы и автомассивы читайте в главе 10.

Все же, забегая вперед, еще несколько слов об автомассивах. Рассмотрим такой не-
сложный пример программы:
$A[]=10;
$A[]=20;
$A[]=30;
После отработки этих строк будет создан массив $A, заполненный последовательно
числами 10, 20 и 30, с индексами, отсчитываемыми с нуля. То есть, если внутри квад-
ратных скобок при присваивании элементу массива не указано ничего, то подразуме-
вается элемент массива, следующий за последним. В общем-то это должно быть ин-
туитивно понятным — именно на легкость в использовании и ориентировались
разработчики PHP.
Прием с автомассивом в поле <select multiple>, действительно, выглядит до-
вольно элегантно. Однако не стоит думать, что он применим только к этому элементу
формы: автомассивы мы можем применять и в любых других полях. Вот пример,
создающий 2 переключателя (кнопки со значениями вкл/выкл), один редактор строки
и одно текстовое (многострочное) поле, причем все данные после запуска сценария,
обрабатывающего эту форму, будут представлены в виде одного-единственного авто-
массива:
<input type=checkbox name=Arr[] value=ch1>
<input type=checkbox name=Arr[] value=ch2>
<input type=text name=Arr[] value="Some string">
<textarea name=Arr[]>Some text</textarea>
То есть, мы видим, что PHP совершенно нет никакого дела до того, в каких элементах
формы мы используем автомассивы — он в любом случае обрабатывает все одинако-
во. И это, пожалуй, правильно.
Глава 8. Работа с данными формы 151

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

Листинг 8.7. Трансляция массивов

Имя: <input type=text name=Data[name]><br>


Адрес: <input type=text name=Data[address]><br>
Город:<br>
<input type=radio name=Data[city] value=Moscow>Москва<br>
<input type=radio name=Data[city] value=Peter>Санкт-Петербург<br>
<input type=radio name=Data[city] value=Kiev>Киев<br>

Можно догадаться, что после передачи подобных данных сценарию на PHP в нем
будет инициализирован ассоциативный массив $Data с ключами name, address и
city (асcоциативные массивы мы также затрагивали пока только вскользь, но очень
скоро этот пробел будет достойно восполнен). То есть, имена полям формы можно
давать не только простые, но и представленные в виде одномерных ассоциативных
массивов.
Забегая вперед, скажу, что в сценарии к отдельным элементам формы можно будет
обратиться при помощи указания ключа массива: например, $Data['city'] обо-
значает значение той радиокнопки, которая была выбрана пользователем, а
$Data["name"] — ее имя. Заметьте, что в сценарии мы обязательно должны заклю-
чать ключи в кавычки или апострофы — в противном случае интерпретатором будет
выведено предупреждение. В то же время, в параметрах name полей формы мы, на-
оборот, должны их избегать — уж так устроен PHP.

Если верить официальной документации, то многомерные массивы (то есть,


массивы массивов) указывать нельзя. Например, при передаче данных поля,
определенного как <input type=text name=Silly[one][two][three]> в
программе, действительно, создастся массив $Silly, но он будет одномер-
ный и с ключом one][two][three — совсем не то, что мы ожидали, не прав-
да ли? В принципе, при большом желании можно написать функцию, которая
конвертирует такие "испорченные" массивы в нормальное многомерное пред-
ставление, но это выходит за рамки нашего обзора.
К счастью, похоже, разработчики PHP поняли, что неработоспособность многомер-
ных массивов при передаче их из формы серьезно снижает популярность PHP. По-
этому они наконец-то включили в PHP поддержку последних. Ура! Например, в моей
версии PHP 4.0.3 (самой свежей на момент написания этих строк) они уже работают.
152 Часть III. Основы языка PHP

Впрочем, в документации по-прежнему заявлено, что "многомерные массивы исполь-


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

Листинг 8.8. testarr.php: работают ли многомерные массивы?

<?
// оператор @ нужен, для того чтобы подавить предупреждение, если
// переменная еще не была инициализирована.
if(@$go) {
if(@$A[10][20]=="Yes") {
echo "<h1>Многомерные массивы работают!";
} else {
echo "Многомерные массивы НЕ работают!";
}
} else {
echo "<h1>Testing, wait...</h1>";
echo "<meta http-equiv=Refresh ";
echo "content=’0; URL=$REQUEST_URI?go=1&A[1][2]=Yes’>";
}
?>

Вот вкратце, как он работает. При первом запуске переменная $go не инициализиро-
вана, поэтому управление получает блок, выводящий тэг <meta>. Он заставляет
браузер перезагрузить страницу, но уже с параметрами в командной строке
go=1&A[1][2]=Yes. Сценарий запускается снова, но уже на этот раз переменная $go
равна 1 (потому что именно такое значение было передано в командной строке). Если
многомерные массивы поддержаны, то, очевидно, что элемент массива $A[1][2],
которому мы присвоили значение Yes в командной строке, будет существовать и рав-
няться Yes. В этом случае мы получим сообщение, что массивами пользоваться мож-
но, а иначе — что они не работают.
Глава 9

Конструкции
языка

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


применялись, и не раз — например, инструкция if. В этой главе приводится полное
описание всех языковых конструкций PHP. Их не так много, и это достоинство PHP.
Как показывает практика, чем более лаконичен синтаксис языка, тем проще его ис-
пользовать в повседневной практике. PHP — отличный пример этому.

О тер ми но ло ги и
Иногда я применяю слово "конструкция", а иногда — "инструкция". В данной
книге эти два термина совершенно эквивалентны. Наоборот, термины "опе-
ратор" и "операция" несут разную смысловую нагрузку: любая операция есть
оператор, но не наоборот. Например, echo — оператор, но не операция, а ++
— операция.

Инструкция if-else
Начнем с самой простой инструкции — условного оператора. Его формат таков:
if(логическое_выражение)
инструкция_1;
else
инструкция_2;

Действие его следующее: если логическое_выражение истинно, то выполняется


инструкция_1, а иначе — инструкция_2. Как и в любом другом языке, конструк-
ция else может опускаться, в этом случае при получении должного значения просто
ничего не делается.
Пример:
if($a>=1&&$b<=10) echo "Все OK";
else echo "Неверное значение в переменной!";

Если инструкция_1 или инструкция_2 должны состоять из нескольких команд, то


они, как всегда, заключаются в фигурные скобки. Например:
154 Часть III. Основы языка PHP

if($a>$b) { print "a больше b"; c=$b; }


elseif($a==$b) { print "a равно b"; $c=$a; }
else { print "a меньше b"; $c=$a; }

Это не опечатка: elseif слитно, вместо else if. Так тоже можно писать, хотя это,
по-моему, и не удобочитаемо.
Конструкция if-else имеет еще один альтернативный синтаксис:
if(логическое_выражение):
команды;
elseif(другое_логическое_выражение):
другие_команды;
else:
иначе_команды;
endif

Обратите внимание на расположение двоеточия (:)! Если его пропустить, будет сге-
нерировано сообщение об ошибке. И еще: как обычно, блоки elseif и else можно
опускать.

Использование альтернативного
синтаксиса
В предыдущих главах нами уже неоднократно рассматривался пример вставки
HTML-кода в тело сценария. Для этого достаточно было просто закрыть скобку ?>,
написать этот код, а затем снова открыть ее при помощи <?, и продолжать програм-
му.
Возможно, вы обратили внимание на то, как это некрасиво выглядит. Тем не менее,
если приложить немного усилий для оформления, все окажется не так уж и плохо.
Особенно, если использовать альтернативный синтаксис
if-else и других конструкций языка.
Чаще всего, однако, нужно бывает делать не вставки HTML внутрь программы, а
вставки кода внутрь HTML. Это гораздо проще для дизайнера, который, возможно, в
будущем захочет переоформить ваш сценарий, но не сможет разобраться, что ему
изменять, а что не трогать. Поэтому целесообразно бывает отделять HTML-код от
программы, например, поместить его в отдельный файл, который затем подключает-
ся к программе при помощи инструкции include (см. ниже). Сейчас мы не будем
подробно останавливаться на этом вопросе, но потом обязательно к нему вернемся.
Вот, например, как будет выглядеть наш старый знакомый сценарий, который при-
ветствует пользователя по имени, с использованием альтернативного синтаксиса if-
else (листинг 9.1):
Глава 9. Конструкции языка 155

Листинг 9.1. Альтернативный синтаксис if-else

<?if(@$go):?>
Привет, <?=$name?>!
<?else:?>
<form action=<?=$REQUEST_URI?> method=post>
Ваше имя: <input type=text name=name><br>
<input type=submit name=go value="Отослать!">
<?endif?>

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

Цикл с предусловием while


Эта конструкция также унаследована непосредственно от Си. Ее предназначение —
цикличное выполнение команд в теле цикла, включающее предварительную провер-
ку, нужно ли это делать (истинно ли логическое выражение в заголовке). Если не
нужно (выражение ложно), то конструкция заканчивает свою работу, иначе выполня-
ет очередную итерацию и начинает все сначала. Выглядит цикл так:
while(логическое_выражение)
инструкция;
где, как обычно, логическое_выражение — логическое выражение, а
инструкция — простая или составная инструкция тела цикла. (Очевидно, что внутри
последнего должны производиться какие-то манипуляции, которые будут иногда из-
менять значение нашего выражения, иначе оператор зациклится. Это может быть,
например, простое увеличение некоего счетчика, участвующего в выражении, на еди-
ницу.) Если выражение с самого начала ложно, то цикл не выполнится ни разу. На-
пример:
$i=1; $p=1;
while($i<32) {
echo $p," ";
$p=$p*2; // можно было бы написать $p*=2
$i=$i+1; // можно было бы написать $i+=1 или даже $i++
}
Данный пример выводит все степени двойки до 31-й включительно.
Как и инструкция if, цикл while имеет альтернативный синтаксис, что упрощает его
применение вперемешку с HTML-кодом:
while(логическое_выражение):
команды;
endwhile;
156 Часть III. Основы языка PHP

Цикл с постусловием do-while


В отличие от цикла while, этот цикл проверяет значение выражения не до, а после
каждого прохода. Таким образом, тело цикла выполняется хотя бы один раз. Выгля-
дит оператор так:
do {
команды;
} while(логическое_выражение);
После очередной итерации проверяется, истинно ли логическое_выражение, и, ес-
ли это так, управление передается вновь на начало цикла, в противном случае цикл
обрывается.
Альтернативного синтаксиса для do-while разработчики PHP не предусмотрели (ви-
димо, из-за того, что, в отличие от прикладного программирования, этот цикл до-
вольно редко используется при программировании сценариев).

Универсальный цикл for


Я не зря назвал его универсальным — ведь с его помощью можно (и нужно) созда-
вать конструкции, которые будут выполнять действия совсем не такие тривиальные,
как простая переборка значения счетчика (а именно для этого используется for в
Паскале и чаще всего в Си). Формат конструкции такой:
for(инициализирующие_команды; условие_цикла; команды_после_прохода)
тело_цикла;
Работает он следующим образом. Как только управление доходит до цикла, первым
делом выполняются операторы, включенные в инициализирующие_команды (слева
направо). Эти команды перечисляются там через запятую, например:
for($i=0,$j=10,$k="Test!; ......)
Затем начинается итерация. Первым делом проверяется, выполняется ли усло-
вие_цикла (как в конструкции while). Если да, то все в порядке, и цикл продолжа-
ется. Иначе осуществляется выход из конструкции. Например:
// прибавляем по одной точке
for($i=0,$j=0,$k="Test"; $i<10; .....) $k.=".";
Предположим, что тело цикла проработало одну итерацию. После этого вступают в
действие команды_после_прохода (их формат тот же, что и у инициализирующих
операторов). Например:
for($i=0,$j=0,$k="Points"; $i<100; $j++,$i+=$j) $k=$k.".";
Хочется добавить, что приведенный пример (да и вообще любой цикл for) можно
реализовать и через while, только это будет выглядеть не так изящно и лаконично.
Например:
Глава 9. Конструкции языка 157

$i=0; $j=0; $k="Points";


while($i<100) {
$k.=".";
$j++; $i+=$j;
}
Вот, собственно говоря, и все... Хотя нет. Попробуйте угадать: сколько точек доба-
вится в конец переменной $k после выполнения цикла?
Как обычно, имеется и альтернативный синтаксис конструкции:
for(инициализирующие_команды; условие_цикла; команды_после_прохода):
операторы;
endfor;

Инструкции break и continue


Продолжим разговор про циклические конструкции. Очень часто для того, чтобы уп-
ростить логику какого-нибудь сложного цикла, удобно иметь возможность его пре-
рвать в ходе очередной итерации (к примеру, при выполнении какого-нибудь особен-
ного условия). Для этого и существует инструкция break, которая осуществляет
немедленный выход из цикла. Она может задаваться с одним необязательным пара-
метром — числом, которое указывает, из какого вложенного цикла должен быть про-
изведен выход. По умолчанию используется 1, т. е. выход из текущего цикла, но ино-
гда применяются и другие значения:
for($i=0; $i<10; $i++) {
for($j=0; $j<10; $j++) {
If($A[$i]==$A[$j]) break(2);
}
}
if($i<10) echo 'Найдены совпадающие элементы в матрице \$A!';
В этом примере инструкция break осуществляет выход не только из второго, но и из
первого цикла, поскольку указана с параметром 2.

Применение такой формы записи break — новинка PHP версии 4. Честно го-
воря, я не встречал ни одного другого языка, который бы использовал подоб-
ный (на мой взгляд, крайне удачный) синтаксис. Спасибо вам, разработчики
PHP!

Инструкцию break удобно использовать для циклов поисков: как только очередная
итерация цикла удовлетворяет поисковому условию, поиск обрывается. Например,
вот цикл, который ищет в массиве $A первый нулевой элемент:
158 Часть III. Основы языка PHP

for($i=0; $i<count($A); $i++)


if($A[$i]==0) break;
if($i<count($A)) echo "Нулевой элемент найден: i=$i";

Стандартная функция count(), которую мы еще не рассматривали, просто возвра-


щает число элементов в массиве $A.
Инструкция continue так же, как и break, работает только "в паре" с циклическими
конструкциями. Она немедленно завершает текущую итерацию цикла и переходит к
новой (конечно, если выполняется условие цикла для цикла с предусловием). Точно
так же, как и для break, для continue можно указать уровень вложенности цикла,
который будет продолжен по возврату управления.
В основном continue позволяет вам сэкономить количество фигурных скобок в коде
и увеличить его удобочитаемость. Это чаще всего бывает нужно в циклах-фильтрах,
когда требуется перебрать некоторое количество объектов и выбрать из них только те,
которые удовлетворяют определенным условиям. Например, вот цикл, который обну-
ляет те элементы массива $A, которые удовлетворяют нескольким условиям:
for($i=0; $i<count($A); $i++) {
if(!условие1($A[$i])) continue;
. . .
if(!условиеN($A[$i])) continue;
$A[$i]=0;
}

Грамотное использование break и continue — искусство, позволяющее за-


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

Нетрадиционное использование
do-while и break
Есть один интересный побочный эффект, который дает нам инструкция break, и ко-
торый довольно удобно использовать для обхода "лишних" операторов (кстати, его
можно применять и в Си). Необходимость такого обхода возникает довольно часто,
причем именно при программировании сценариев. Рассмотрим соответствующий
пример (листинг 9.2):

Листинг 9.2. Модель сценария для обработки формы


Глава 9. Конструкции языка 159

. . .
$WasError=0; // индикатор ошибки — если не 0, то была ошибка
// Если нажали кнопку Submit (с именем $doSubmit)...
if(@$doSubmit) do {
// Проверка входных данных
if(неправильное имя пользователя) { $WasError=1; break; }
. . . и т. д.
if(неправильные данные) { $WasError=1; break; }
. . . и т. д.
// Данные в порядке. Обрабатываем их.
выполняем действия;
выводим результат;
завершаем сценарий;
} while(0);
. . .
Выводим форму, через которую пользователь будет запускать этот сценарий,
и, возможно, отображаем сообщение об ошибке в случае, если $WasError!=0.

Здесь представлен наиболее обычный способ для организации сценариев-диалогов.


Запустив сценарий без параметров, пользователь видит форму с приглашением вве-
сти свое имя, пароль и некоторые другие данные. При нажатии кнопки запускается
тот же самый сценарий, который определяет, что была нажата кнопка doSubmit, и
первым делом проверяет имя и пароль. Если они заданы неверно, то отображается
опять наша форма (и где-нибудь красным цветом сообщение об ошибке), в против-
ном случае сценарий завершается и выдает страницу с результатом.
Мы видим, что указанный алгоритм можно реализовать наиболее удобно, имея ка-
кой-то способ обрывания блока "проверки-и-завершения" и возврата к выводу формы
заново. Как раз это и делает конструкция
if(что_то) do { ... } while(0);
Очевидно, что тело цикла do-while выполняется в любом случае только один раз
(так как выражение в while всегда ложно). Тем не менее, такой "вырожденный"
цикл мы можем использовать для быстрого выхода из него посредством break.
Многие сразу возразят, что в таких случаях удачнее будет задействовать функции и опера-
тор return. Однако в PHP как раз это довольно неудобно, поскольку для того, чтобы из
функции добраться до глобальной переменной (коей является любой элемент формы),
нужно проделать несколько дополнительных шагов. Это, конечно, недостаток PHP, и о
нем мы поговорим чуть позже.
160 Часть III. Основы языка PHP

Цикл foreach
Данный тип цикла предназначен специально для перебора всех элементов массива и
был добавлен только в четвертой версии языка PHP. Выглядит он следующим обра-
зом:
foreach(массив as $key=>$value)
команды;
Здесь команды циклически выполняются для каждого элемента массива, при этом оче-
редная пара ключ=>значение оказывается в переменных $key и $value. Давайте
рассмотрим пример (листинг 9.3), где покажем, как мы можем отобразить содержимое
всех глобальных переменных при помощи foreach:

Листинг 9.3. Вывод всех глобальных переменных

<?
foreach($GLOBALS as $k=>$v)
echo "<b>$k</b> => <tt>$v</tt><br>\n";
?>

У цикла foreach имеется и другая форма записи, которую следует применять, когда
нас не интересует значение ключа очередного элемента. Выглядит она так:
foreach(массив as $value)
команды;
В этом случае доступно лишь значение очередного элемента массива, но не его ключ.
Это может быть полезно, например, для работы с массивами-списками.

Цикл foreach оперирует не исходным массивом, а его копией. Это означает,


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

В следующей главе мы рассмотрим ассоциативные массивы и все, что к ним относит-


ся, гораздо более подробно.
Глава 9. Конструкции языка 161

Конструкция switch-case
Часто вместо нескольких расположенных подряд инструкций if-else целесообразно
воспользоваться специальной конструкцией switch-case:
switch(выражение) {
case значение1: команды1; [break;]
case значение2: команды2; [break;]
. . .
case значениеN: командыN; [break;]
[default: команды_по_умолчанию; [break]]
}

Делает она следующее: вычисляет значение выражения (пусть оно равно, например,
V), а затем пытается найти строку, начинающуюся с case V:. Если такая строка об-
наружена, выполняются команды, расположенные сразу после нее (причем на все
последующие операторы case что_то внимание не обращается, как будто их нет, а
код после них остается без изменения). Если же найти такую строку не удалось, вы-
полняются команды после default (когда они заданы).
Обратите внимание на операторы break (которые условно заключены в квадратные
скобки, чтобы подчеркнуть их необязательность), добавленные после каждой строки
команд, кроме последней (для которой можно было бы тоже указать break, что не
имело бы смысла). Если бы не они, то при равенстве V=значение1 сработали бы не
только команды1, но и все нижележащие.
Вот альтернативный синтаксис для конструкции switch-case:
switch(выражение):
case значение1: команды1; [break;]
. . .
case значениеN: командыN; [break;]
[default: команды_по_умолчанию; [break]]
endswitch;

Инструкция require
Эта инструкция позволяет нам разбить текст программы на несколько файлов. Ее
формат такой:
require имя_файла;
При запуске (именно при запуске, а не при исполнении!) программы интерпретатор
просто заменит инструкцию на содержимое файла имя_файла (этот файл может так-
же содержать сценарий на PHP, обрамленный, как обычно, тэгами <? и ?>). Причем
сделает он это только один раз (в отличие от include, который рассматривается
162 Часть III. Основы языка PHP

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


удобно для включения в вывод сценария всяких "шапок" с HTML-кодом. Например
(листинги 9.4, 9.5 и 9.6):

Листинг 9.4. Файл header.htm

<html>
<head><title>Title!</title></head>
<body bgcolor=yellow>

Листинг 9.5. Файл footer.htm

&copy;My company, 1999.


</body></html>

Листинг 9.6. Файл script.php

<?
require "header.htm";
. . . работает сценарий и выводит само тело документа
require "footer.htm";
?>

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

Инструкция include
Эта инструкция практически идентична require, за исключением того, что вклю-
чаемый файл вставляется "в сердце" нашего сценария не перед его выполнением, а
прямо во время.
Какая разница? Поясню. Пусть у нас есть 10 текстовых файлов с именами
file0.php, file1.php и так далее до file9.php, содержимое которых просто де-
сятичные цифры 0, 1 ...… 9 (по одной цифре в каждом файле). Запустим такую про-
грамму:
Глава 9. Конструкции языка 163

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


include "file$i.php";
}
В результате мы получим вывод, состоящий из 10 цифр: "0123456789". Из этого мы
можем заключить, что каждый из наших файлов был включен по одному разу прямо
во время выполнения цикла! (Попробуйте теперь вместо include подставить
require. Сравните результат.)
Вы, должно быть, обратили внимание на, казалось бы, лишние фигурные скобки во-
круг include. Попробуйте их убрать. Вы тут же можете получить совершенно бес-
толковое сообщение об ошибке (или, еще хуже, программа начнет неправильно рабо-
тать, а причину разыскать будет нелегко). Почему так происходит? Да потому, что
include не является на самом деле оператором в привычном нам смысле этого сло-
ва. Чтобы это понять, представьте, что каждый раз, когда интерпретатор встречает
инструкцию include, он просто "в лоб" заменяет ее на содержимое файла, указанно-
го в параметре.
А вдруг в этом файле несколько команд? Тогда в цикле выполнится только первая из
них, а остальные будут запущены уже после окончания цикла. Так что общее правило
гласит: всегда обрамляйте инструкцию include фигурными скобками, если разме-
щаете ее внутри какой-либо конструкции.

В будущих версиях разработчики PHP, возможно, и исправят положение к луч-


шему, однако не советую вам рассчитывать на это.

Трансляция и проблемы с include


Как мы знаем, перед исполнением PHP транслирует программу во внутреннее пред-
ставление. Это означает, что в памяти создается как бы "полуфабрикат", из которого
исключены все комментарии, лишние пробелы, некоторые имена переменных и т. д.
Впоследствии это внутреннее представление интерпретируется (выполняется). Однако
мы знаем также, что в программе могут встретиться такие места, "подводные камни"
для интерпретатора, которые PHP не сможет оттранслировать заранее. В этом случае
он их пропускает, "откладывает на потом", чтобы в момент, когда управление дойдет
до определенной точки, опять запустить транслятор.
Одним из таких "камней" как раз и является инструкция include. Как только управ-
ление программы доходит до нее, PHP вынужден приостановиться и ждать, пока
транслятор не оттранслирует код включаемого файла. А это достаточно отрицательно
сказывается на быстродействии программы, особенно большой. Поэтому, если вы
пишете большой и сложный сценарий, применяйте инструкцию require вместо
include, где только можно.
В пользу последнего говорит также и перспектива появления в будущем компилятора
для PHP, который будет уметь сохранять оттранслированный код в исполняемые
164 Часть III. Основы языка PHP

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

Инструкции однократного включения


В больших и непростых сценариях инструкции include и require применяются
очень и очень часто. Поэтому становится довольно сложно контролировать, как бы
случайно не включить один и тот же файл несколько раз (что чаще всего приводит к
ошибке).
Чтобы стало яснее, я расскажу вам притчу. Как-то раз разработчик Билл написал не-
сколько очень полезных функций для работы с файлами Excel и решил объединить их
в библиотеку — файл xllib.php (листинг 9.7):

Листинг 9.7. Библиотека xllib.php

<?
Function LoadXlDocument($filename) { . . . }
Function SaveXlDocument($filename,$doc) { . . . }
?>

Разработчик Вася захотел сделать то же самое для работы с документами Microsoft


Word, в результате чего на свет явилась библиотека wlib.php. Так как Word и Excel
связаны между собой, Вася использует в своей библиотеке (листинг 9.8) возможно-
сти, предоставляемые библиотекой xllib.php — подключает ее командой require:

Листинг 9.8. Библиотека wlib.php

<?
require "xllib.php";
Function LoadWDocument($filename) { . . . }
Function SaveWDocument($filename,$doc) { . . . }
?>

Эти две библиотеки стали настолько популярны в среде Web-программистов, что


скоро все стали их внедрять в свои программы. При этом, конечно же, никому нет
дела до того, как эти библиотеки на самом деле устроены — все просто подключают
Глава 9. Конструкции языка 165

их к своим сценариям при помощи require, не задумываясь о возможных последст-


виях.
Но в один прекрасный день одному неизвестному программисту потребовалось рабо-
тать и с документами Word, и с документами Excel. Он, не долго думая, подключил к
своему сценарию обе эти библиотеки (листинг 9.9):

Листинг 9.9. Подключение библиотек xllib.php и wlib.php

<?
require "wlib.php";
require "xllib.php";
$wd=LoadWDocument("document.doc");
$xd=LoadXlDocument("document.xls");
?>

Каково же было его удивление, когда при запуске этого сценария он получил сообще-
ние об ошибке, в котором говорилось, что в файле xlib.php функция LoadXlDoc()
определена дважды!..
Что же произошло? Нетрудно догадаться, если проследить за тем, как транслятор PHP
"разворачивает" код листинга 9.9. Вот как это происходит:
//require "wlib.php";
//require "xllib.php";
Function LoadXlDocument($filename) { . . . }
Function SaveXlDocument($filename,$doc) { . . . }
Function LoadWDocument($filename) { . . . }
Function SaveWDocument($filename,$doc) { . . . }
//require "xllib.php";
Function LoadXlDocument($filename) { . . . }
Function SaveXlDocument($filename,$doc) { . . . }
$wd=LoadWDocument("document.doc");
$xd=LoadXlDocument("document.xls");

Как видим, файл xllib.php был включен в текст сценария дважды: первый раз кос-
венно через wlib.php, и второй раз — непосредственно из программы. Поэтому
транслятор, дойдя до выделенной строки, обнаружил, что функция
LoadXlDocument() определяется второй раз, на что честно и прореагировал.
Конечно, разработчик сценария мог бы исследовать исходный текст библиотеки
wlib.php и понять, что во второй раз xllib.php включать не нужно. Но согласи-
тесь — это не выход. Действительно, при косвенном подключении файлов третьего и
выше уровней вполне могут возникнуть ситуации, когда без модификации кода биб-
лиотек будет уже не обойтись. А это недопустимо. Как же быть?
166 Часть III. Основы языка PHP

Что ж, после столь длительного вступления (возможно, слишком длительного?) нако-


нец настала пора рассказать, что думают по этому поводу разработчики PHP. А они
предлагают простое решение: инструкции include_once и require_once.
Инструкция require_once работает точно так же, как и require, но за одним важ-
ным исключением. Если она видит, что затребованный файл уже был ранее включен,
то она ничего не делает. Разумеется, такой метод работы требует от PHP хранения
полных имен всех подсоединенных файлов где-то в недрах интерпретатора. Так он,
собственно говоря, и поступает.
Инструкция include_once работает совершенно аналогично, но включает файл во
время исполнения программы, а не во время трансляции.

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

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


вообще отказаться от require и include. Это во многом упростит разбиение боль-
шой и сложной программы на относительно независимые модули.
Глава 10

Ассоциативные
массивы

Возможно, вы уже догадались, что ассоциативные массивы — один из самых мощ-


ных инструментов в PHP. Массивы — нечто, что довольно часто реализовывается в
интерпретаторах типа PHP (в Perl ассоциативные массивы устроены даже немного
хуже, чем в PHP). Давайте рассмотрим чуть подробнее, как с ними работать.
Массивы — это своеобразные контейнеры-переменные для хранения сразу несколь-
ких величин, к которым можно затем быстро и удобно обратиться. Конечно, никто не
запрещает вам вообще их не использовать, а, например, давать своеобразные имена
переменным, такие как $a1, $a2 и т. д., но представьте, что получится в этом случае,
если вам нужно держать в памяти, скажем, тысячу таких переменных. Кроме того,
такой способ организации массивов имеет и еще один недостаток — очень трудно
перебрать все его значения в цикле, хотя это и возможно:
for($i=0; ; $i++) {
$v="a$i";
if(!isset($$v)) break;
..делаем что-нибудь с $$v
}

Никогда так не делайте! Этот пример приведен здесь лишь для иллюстрации.
Если вдруг при написании какого-нибудь сценария вам все-таки мучительно
захочется применить этот "трюк", выключите компьютер, подумайте минут 15,
а затем снова включите его.

Здесь мы используем возможность PHP по работе с ссылочными переменными, кото-


рую я категорически не рекомендую где-либо применять. Все это представлено здесь
для того, чтобы проиллюстрировать, насколько неудобно бывает работать без масси-
вов.
168 Часть III. Основы языка PHP

Давайте теперь начнем с самого начала. Пусть у нас в программе нужно описать спи-
сок из нескольких человеческих имен. Можно сделать это так (листинг 10.1):

Листинг 10.1. Инициализация массива

$NamesList[0]="Dmitry";
$NamesList[1]="Helen";
$NamesList[2]="Sergey";
. . .

Таким образом, мы по одному добавляем в массив $NamesList элементы, например,


пронумерованные от 0. PHP узнает, что мы хотим создать массив, по квадратным
скобкам (нужно заметить, что для этого переменная $NamesList в начале не должна
еще быть инициализирована). Я буду в дальнейшем называть массивы, ключи (или,
как их часто называют, индексы — то, что стоит в квадратных скобках) которых ну-
меруются с нуля и идут без пропусков (а это далеко не всегда так, как мы вскоре уви-
дим), списками.
Некоторые стандартные функции PHP, обрабатывающие массивы, требуют переда-
вать в их параметрах именно списки, хотя чаще всего можно это ограничение обойти,
передав им любой другой массив. В таком случае они все равно рассматривают мас-
сив как обычный список, т. е. не обращают никакого внимания на его ключи. Во мно-
гих случаях это бывает нежелательно, на чем мы чуть позже остановимся подробнее.
Давайте теперь посмотрим, как можно распечатать наш список. Самый простой спо-
соб — воспользоваться циклом for:
echo "А вот первый элемент массива: ".$NamesList[0]."<hr>";
for($i=0; $i<кол-во_элементов; $i++)
echo $NamesList[$i]."<br>";
Количество элементов в массиве легко можно определить, задействуя функцию
count() или ее синоним sizeof():
for($i=0; $i<count($NamesList); $i++)
echo $NamesList[$i]."<br>";

Создание массива "на лету". Автомассивы


В примере из листинга 10.1, казалось бы, все гладко. За исключением одного не-
большого недостатка: каждый раз, добавляя имя, мы должны были выбирать для него
номер и заботиться, чтобы ненароком не указать уже существующий. Чтобы этого
избежать, можно написать те же команды так:
$NamesList[]="Dmitry";
$NamesList[]="Helen";
$NamesList[]="Sergey";
Глава 10. Ассоциативные массивы 169

В этом случае PHP сам начнет (конечно, если переменная $NamesList еще не суще-
ствует) нумерацию с нуля и каждый раз будет прибавлять к счетчику по единичке,
создавая список. Согласитесь, довольно удобно. Разумеется, можно использовать []
и не только в таком простом контексте, очень часто они применяются для более об-
щего действия — добавления элемента в конец массива, например:
Unset($FNames); // на всякий случай стираем массив
while($f=очередное_имя_файла_в_текущем каталоге)
if(расширение_$f_есть_txt) $FNames[]=$f;
// теперь $FNames содержит список файлов с расширением txt

Если же нам нужно создать ассоциативный массив (я буду его иногда называть хэш),
все делается совершенно аналогично, только вместо цифровых ключей мы должны
указывать строковые. При этом следует помнить, что в строковых ключах буквы
нижнего и верхнего регистров считаются различными. И еще: ключом может быть
абсолютно любая строка, содержащая пробелы, символы перевода строки, нулевые
символы и т. д. То есть, никаких ограничений на ключи не накладывается.
Поясню сказанное на примере. Пусть нам надо написать сценарий, который работает,
как записная книжка: по фамилии абонента он выдает его имя. Мы можем организо-
вать базу данных этой книжки в виде ассоциативного массива с ключами — фами-
лиями и соответствующими им значениями имен людей:
$Names["Koteroff"] = "Dmitry";
$Names["Ivanov"] = "Ivan";
$Names["Petrov"] = "Peter";

Далее, мы можем распечатать имя любого абонента командой:


echo $Names["Ivanov"];
$f="Koteroff";
echo $Names[$f];

Как видите, тут никаких особенностей нет, все работает совершенно аналогично спи-
скам, только с нецифровыми ключами. Возможно, вы скажете, что это не совсем так:
например, нельзя воспользоваться циклом for, как мы это делали раньше, для выво-
да всех персоналий, и окажетесь правы. Вскоре мы рассмотрим целых три приема, с
помощью которых можно перебрать все элементы массива. Вы, скорее всего, будете
применять их даже и для списков — настолько они удобны и универсальны, а к тому
же и работают быстрее, чем последовательный перебор в цикле for с использовани-
ем $i.
170 Часть III. Основы языка PHP

Инструкция list()
Пусть у нас есть некоторый массив-список $List с тремя элементами: имя человека,
его фамилия и возраст. Нам бы хотелось присвоить переменным $name, $surname и
$age эти величины. Это, конечно, можно сделать так:
$name=$List[0];
$surname=$List[1];
$age=$List[2];
Но гораздо изящнее будет воспользоваться инструкцией list(), предназначенной
как раз для таких целей:
list($name,$surname,$age)=$List;
Согласитесь, выглядит несколько приятнее. Конечно, list() можно задействовать
для любого количества переменных: если в массиве не хватит элементов, чтобы их
заполнить, им просто присвоятся неопределенные значения.
Что, если нам нужны только второй и третий элемент массива $List?
В этом случае имеет смысл пропустить первый параметр в инструкции list(), вот
так:
list(,$surname,$age)=$List;
Таким образом, мы получаем в $surname и $age фамилию и возраст человека, не
обращая внимания на его имя в первом аргументе.

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

Списки и ассоциативные
массивы: путаница?..
Следует сказать несколько слов насчет ассоциативных массивов языка PHP. Во-
первых, на самом деле все "остальные" массивы также являются ассоциативными (в
частности, списки — тоже). Во-вторых, ассоциативные массивы в PHP являются на-
правленными, т. е. в них существует определенный
(и предсказуемый) порядок элементов, не зависящий от реализации. А значит, есть
первый и последний элементы, и для каждого элемента можно определить следую-
щий за ним. Именно по этой причине мне не нравится название "хэш" (в буквальном
переводе — "мешанина"), хотя, конечно, в реализации PHP наверняка используются
алгоритмы хэширования для увеличения быстродействия.
Глава 10. Ассоциативные массивы 171

Операция [] всегда добавляет элемент в конец массива, присваивая ему при этом
такой числовой индекс, который бы не конфликтовал с уже имеющимися в массиве
(точнее, выбирается номер, превосходящий все имеющиеся цифровые ключи в мас-
сиве). Вообще говоря, любая операция $Array[ключ]=значение всегда добавляет
элемент в конец массива, конечно, за исключением тех случаев, когда ключ уже при-
сутствует в массиве. Если вы захотите изменить порядок следования элементов в ас-
социативном массиве, не изменяя в то же время их ключей, это можно сделать одним
из двух способов: воспользоваться функциями сортировки, либо же создать новый
пустой массив и заполнить его в нужном порядке, пройдясь по элементам исходного
массива.

Инструкция array()
и многомерные массивы
Вернемся к предыдущему примеру. Нам необходимо написать программу, которая по
фамилии некоторого человека из группы будет выдавать его имя. Поступим так же,
как и раньше: будем хранить данные в ассоциативном массиве (сразу отбрасывая
возможность составить ее из огромного числа конструкций if-else как неинтерес-
ную):
$Names["Ivanov"] ="Dmitry";
$Names["Petrova"]="Helen";
Теперь можно, как мы знаем, написать:
echo $Names["Petrova"]; // выведет Helen
echo $Names["Oshibkov"]; // ошибка: в массиве нет такого элемента!
Идем дальше. Прежде всего обратим внимание: приведенным выше механизмом мы
никак не смогли бы создать пустой массив. Однако он очень часто может нам пона-
добиться, например, если мы не знаем, что раньше было в массиве $Names, но хотим
его проинициализировать указанным путем. Кроме того, каждый раз задавать массив
указанным выше образом не очень-то удобно — приходится все время однообразно
повторять строку $Names...
Так вот, существует и второй способ создания массивов, выглядящий значительно
компактнее. Я уже упоминал его несколько раз — это использование оператора
array(). Например:
// создает пустой массив $Names
$Names=array();
// создает такой же массив, как в предыдущем примере с именами
$Names=array("Ivanov"=>"Dmitry", "Petrova"=>"Helen");
// создает список с именами (нумерация 0,1,2)
$NamesList=array("Dmitry","Helen","Sergey");
172 Часть III. Основы языка PHP

Теперь займемся вопросом, как формировать двумерные (и вообще многомерные)


массивы. Это довольно просто. В самом деле, я уже говорил, что значениями пере-
менных (и значениями элементов массива тоже, поскольку PHP не делает никаких
различий между переменными и элементами массива) может быть все, что угодно, в
частности — опять же массив. Так, можно создавать ассоциативные массивы (а мож-
но — списки) с любым числом измерений. Например, если кроме имени о человеке
известен также его возраст, то можно инициировать массив $Names так:
$Names["Ivanov"] = array("name"=>"Dmitry","age"=>25);
$Names["Petrova"] = array("name"=>"Helen", "age"=>23);
или даже так:
$Names=array(
"Ivanov" => array("name"=>"Dmitry","age"=>25),
"Petrova"=> array("name"=>"Helen", "age"=>23)
);
Как же добраться до нужного нам элемента в нашем массиве? Нетрудно догадаться
по аналогии с другими языками:
echo $Names["Ivanov"]["age"]; // напечатает "25"
echo $Names["Petrova"]["bad"]; // ошибка: нет такого элемента "bad"
Довольно несложно, не правда ли? Кстати, мы можем видеть, что ассоциативные
массивы в PHP удобно использовать как некие структуры, хранящие данные. Это по-
хоже на конструкцию struct в Си (или record в Паскале). Пожалуй, это единст-
венный возможный способ организации структур, но он очень гибок.

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


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

Доступ по ключу
Как мы уже знаем, ассоциативные массивы — объекты, которые наиболее приспо-
соблены для выборки из них данных путем указания нужного ключа. В PHP и для
всех массивов, и для списков (которые, еще раз напомню, также являются массива-
ми) используется один и тот же синтаксис, что является очень большим достоинст-
вом. Вот как это выглядит:
echo $Arr["anykey"]; // выводит элемент массива $Arr с ключом anykey
echo $Arr["first"]["second"]; // так используются двумерные массивы
echo (SomeFuncThatReturnsArray())[5]; // ОШИБКА! Так нельзя!
Глава 10. Ассоциативные массивы 173

// Вот так правильно:


$Arr= SomeFuncThatReturnsArray();
echo $Arr[5];
Последний пример показывает, что PHP сильно отличается от Си с точки зрения ра-
боты с массивами: в нем нет такого понятия, как "контекст массива", а значит, мы не
можем применить [] непосредственно к значению, возвращенному функцией.
Величина $Arr[ключ] является полноценным "левым значением", т. е. может стоять
в левой части оператора присваивания, от нее можно брать ссылку с помощью опера-
тора &, и т. д. Например:
$Arr["anykey"]=array(100,200); // присваиваем элементу массива 100
$ref=&$Arr["first"]["second"]; // $ref — синоним элемента массива
$Arr[]="for add"; // добавляем новый элемент

Функция count()
Мы можем определить размер (число элементов) в массиве при помощи стандартной
функции count():
$num=count($Names); // теперь в $num — число элементов в массиве
Сразу отмечу, что count() работает не только с массивами, но и с объектами и даже
с обычными переменными (для последних count() всегда равен 1, как будто пере-
менная — это массив с одним элементом). Впрочем, ее очень редко применяют для
чего-либо, отличного от массива — разве что по-ошибке.

Слияние массивов
Еще одна фундаментальная операция — слияние массивов, т. е. создание массива,
содержащего как элементы одного, так и другого массива. Реализуется это при помо-
щи оператора +. Например:
$a=array("a"=>"aa", "b"=>"bb");
$b=array("c"=>"cc", "d"=>"dd");
$c=$a+$b;
В результате в $c окажется ассоциативный массив, содержащий все 4 элемента, а
именно: array("a"=>"aa", "b"=>"bb", "c"=>"cc", "d"=>"dd"), причем
именно в указанном порядке. Если бы мы написали $c=$b+$a, результат бы был не-
много другой, а именно: array("c"=>"cc", "d"=>"dd", "a"=>"aa",
"b"=>"bb"), т. е. элементы расположены в другом порядке. Видите, как проявляется
направленность массивов? Она заставляет оператор + стать некоммутативным, т. е.
$a+$b не равно $b+$a, если $a и $b — массивы.
Будьте особенно внимательны при слиянии таким образом списков. Рассмотрим сле-
дующие операторы:
174 Часть III. Основы языка PHP

$a=array(10,20,30);
$b=array(100,200);
$c=$a+$b;
Возможно, вы рассчитываете, что в $c будет array(10,20,30,100,200)? Это не-
верно: там окажется array(10,20,30). Вот почему так происходит. При конкатена-
ции массивов с некоторыми одинаковыми элементами (то есть, элементами с одина-
ковыми ключами) в результирующем массиве останется только один элемент с таким
же ключом — тот, который был в первом массиве, и на том же самом месте.
Последний факт может слегка озадачить. Казалось бы, элементы массива $b по логи-
ке должны заменить элементы из $a. Однако все происходит наоборот. Окончательно
выбивает из колеи следующий пример:
$a=array('a'=>10, 'b'=>20);
$b=array('c'=>30, 'b'=>'new?');
$a+=$b;
Мы-то ожидали, что оператор += обновит элементы $a при помощи элементов $b. А
напрасно. В результате этих операций значение $a не изменится! Если вы не верите
своим глазам, можете проверить.
Так как же нам все-таки обновить элементы в массиве $a? Получается, только пря-
мым способом — с помощью цикла:
foreach ($b as $k=>$v) $a[$k]=$v;
Что поделать, так уж распорядились разработчики PHP.
Еще несколько слов насчет операции слияния массивов. Цепочка
$z=$a+$b+$c+...и т. д.;
эквивалентна
$z=$a; $z+=$b; $z+=$c; ...и т. д.
Как нетрудно догадаться, оператор += для массивов делает примерно то же, что и
оператор += для чисел, а именно — добавляет в свой левый операнд элементы, пере-
численные в правом операнде-массиве, если они еще не содержатся в массиве сле-
ва.
Итак, в массиве никогда не может быть двух элементов с одинаковыми ключами,
потому что все операции, применимые к массивам, всегда контролируют, чтобы этого
не произошло. Впрочем, на мой взгляд, данное свойство вовсе не достоинство, а не-
достаток — вполне можно было бы позволить оператору + оставлять одинаковые
ключи, а всем остальным — запретить это делать. Что ж, разработчики PHP "пошли
другим путем"...
Глава 10. Ассоциативные массивы 175

Так как списки являются тоже ассоциативными массивами, оператор + будет


работать с ними неправильно! Например, в результате слияния списков
array(10,20) и array(100,200,300) получится список array(10,20,300) —
всего из трех элементов! Согласитесь, ведь это совсем не то, что вы ожидали
увидеть, не правда ли?..

Косвенный перебор элементов массива


Довольно часто при программировании на PHP нам приходится перебирать все без
исключения элементы некоторого массива. Если наш массив — список, то эта задача,
как мы уже знаем, не будет особенно обременительной:
// Пусть $Names — список имен. Распечатаем их в столбик
for($i=0; $i<count($Names); $i++)
echo $Names[$i]."\n";
Я стараюсь везде, где можно, избегать помещения имени переменной-массива в ка-
вычки — например, предыдущий пример я не пишу вот так:
for($i=0; $i<count($Names); $i++)
echo "$Names[$i]\n";
Дело в том, что это, пожалуй, единственный способ, который совместим с PHP вер-
сии 3. А что касается четвертой версии, то мы спокойно можем помещать массивы в
строки, заключив их в фигурные скобки вместе с символом $:
$Names=array(
array(’name’=>’Вася’, ’age’=>20),
array(’name’=>’Билл’, ’age’=>40)
);
for($i=0; $i<count($Names); $i++)
echo "{$Names[$i][’age’]}\n";
Давайте теперь предположим, что массив $Names ассоциативный: его ключи — име-
на людей, а значения, сопоставленные ключам — например, возраст этих людей. Для
перебора такого массива можно воспользоваться конструкцией наподобие следую-
щей:
for(Reset($Names); ($k=key($Names)); Next($Names))
echo "Возраст $k — {$Names[$k]} лет\n";
Эта конструкция опирается на еще одно свойство ассоциативных массивов в PHP. А
именно, мало того, что массивы являются направленными, в них есть еще и такое
понятие, как текущий элемент. Функция Reset() просто устанавливает этот элемент
на первую позицию в массиве. Функция key() возвращает ключ, который имеет те-
кущий элемент (если он указывает на конец массива, возвращается пустая строка, что
176 Часть III. Основы языка PHP

позволяет использовать вызов key() в контексте второго выражения for). Ну а


функция Next() просто перемещает текущий элемент на одну позицию вперед.
На самом деле, две простейшие функции, — Reset() и Next(), — помимо выпол-
нения своей основной задачи, еще и возвращают некоторые значения, а именно:
r функция Reset() возвращает значение первого элемента массива (или пустую
строку, если массив пуст);
r функция Next() возвращает значение элемента, следующего за текущим (или
пустую строку, если такого элемента нет).
Иногда (кстати, гораздо реже) бывает нужно перебрать массив с конца, а не с начала.
Для этого воспользуйтесь такой конструкцией:
for(End($Names); ($k=key($Names)); Prev($Names))
echo "Возраст $k — {$Names[$k]} лет\n";
По контексту несложно сообразить, как это работает. Функция End() устанавливает
позицию текущего элемента в конец массива, а Prev() передвигает ее на один эле-
мент назад.
И еще. В PHP имеется функция current(). Она очень напоминает key(), только
возвращает не ключ, а величину текущего элемента (если он не указывает на конец
массива).

Недостатки косвенного перебора


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

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

Нулевой ключ
А что, если в массиве встретится ключ 0 (хотя для массивов имен это, согласитесь,
маловероятно)? Давайте еще раз посмотрим на первый цикл перебора:
for(Reset($Names); ($k=key($Names)); Next($Names))
echo "Возраст $k — {$Names[$k]} лет\n";
Глава 10. Ассоциативные массивы 177

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


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

Прямой перебор массива


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

Классический перебор
Давайте опять вернемся к нашему примеру, в котором массив $Names хранил связь
имен людей и их возрастов. Вот как можно перебрать этот массив при помощи пря-
мого перебора:
for(Reset($Names); list($k,$v)=each($Names); /*пусто*/)
echo "Возраст $k — $v\n";

В самом начале заголовка цикла мы видим нашу старую знакомую Reset(). Дальше
переменным $k и $v присваивается результат работы функции each(). Третье усло-
вие цикла попросту отсутствует (чтобы это подчеркнуть, я включил на его место ком-
ментарий).
Что делает функция each()? Во-первых, возвращает небольшой массив (я бы даже
сказал, список), нулевой элемент которого хранит величину ключа текущего элемента
массива $Names, а первый — значение текущего элемента. Во-вторых, она продвига-
ет указатель текущего элемента к следующей позиции. Следует заметить, что если
следующего элемента в массиве нет, то функция возвращает не список, а false.
Именно поэтому она и размещена в условии цикла for. Становится ясно, почему мы
не указали третий блок операторов в цикле for: он просто не нужен, ведь указатель
на текущий элемент и так смещается функцией each().

Перебор в стиле PHP 4


Прямой перебор массивов применялся столь часто, что разработчики PHP решили в
четвертой версии языка добавить специальную инструкцию перебора массива —
foreach. Мы уже рассматривали ее ранее. Вот как с ее помощью можно перебрать и
распечатать наш массив людей:
foreach($Names as $k=>$v) echo "Возраст $k — $v\n";
178 Часть III. Основы языка PHP

Просто, не правда ли? Рекомендую везде, где не требуется совместимость с PHP


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

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

Списки и строки
Есть несколько функций, которые чрезвычайно часто используются при программи-
ровании сценариев. Среди них — функции для разбиения какой-либо строки на более
мелкие части (например, эти части разделяются в строке каким-то специфическим
символом типа |), и, наоборот, слияния нескольких небольших строк в одну боль-
шую, причем не впритык, а вставляя между ними разделитель. Первую из этих воз-
можностей реализует стандартная функция explode(), а вторую — implode(). Ре-
комендую обратить особое внимание на указанные функции, т. к. они применяются
очень часто.
Функция explode() имеет следующий синтаксис:
list explode(string $token, string $Str [, int $limit])

Она получает строку, заданную в ее втором аргументе, и пытается найти в ней под-
строки, равные первому аргументу. Затем по месту вхождения этих подстрок строка
"разрезается" на части, помещаемые в массив-список, который и возвращается. Если
задан параметр $limit, то учитываются только первые ($limit-1) участков "раз-
реза". Таким образом, возвращается список из не более чем $limit элементов. Это
позволяет нам проигнорировать возможное наличие разделителя в тексте последнего
поля, если мы знаем, что всего полей, скажем, 6 штук. Вот пример:
$st="4597219361|Иванов|Иван|40|ivan@ivanov.com|Текст, содержащий (|)!";
$A=explode("|",$st,6); // Мы знаем, что там только 6 полей!
// теперь $A[0]="Иванов", ... $A[5]= "Текст, содержащий (|)!"
list($Surname,$Name,$Age,$Email,$Tel)=$A; // распределили по переменным
Глава 10. Ассоциативные массивы 179

Конечно, строкой разбиения может быть не только один символ, но и небольшая


строка. Не перепутайте только порядок следования аргументов при вызове функции!
Функция implode() и ее синоним join() производят действие, в точности обратное
вызову explode().
string implode(string $glue, list $List) или
string join(string $glue, list $List)
Они берут ассоциативный массив (обычно это список) $List, заданный в ее первом
параметре, и "склеивают" его значения при помощи "строки-клея" $glue во втором
параметре. Примечательно, что вместо списка во втором аргументе можно переда-
вать любой ассоциативный массив — в этом случае будут рассматриваться только его
значения.
Рекомендую вам чаще применять функции implode() и explode(), а не писать са-
мостоятельно их аналоги. Работают они очень быстро.

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

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


биться, чтобы упакованные данные занимали минимум объема. Это пригодит-
ся вам в будущем, при работе с Cookies.
Однако вскоре вы поймете, что все не так просто в PHP, в котором работа со ссылоч-
ными переменными очень и очень ограничена. Особенно будет тяжело с функцией
распаковки строки.
И тут нам на помощь опять приходят разработчики PHP. Оказывается, обе функции
давным-давно реализованы, причем весьма эффективно со стороны быстродействия
(но, к сожалению, непроизводительно с точки зрения объема упакованных данных).
Называются они, соответственно, Serialize() и Unserialize().
180 Часть III. Основы языка PHP

Функция Serialize() возвращает строку, являющуюся упакованным эквивалентом


некоего объекта $Obj, переданного во втором параметре.
string Serialize(mixed $Obj)
При этом совершенно не важно, что это за объект: массив, целое число…. Да что
угодно. Например:
$A=array("a"=>"aa", "b"=>"bb", "c"=>array("x"=>"xx"));
$st=Serialize($A);
echo $st;
// выведется что-то типа нечто:
//
a:2:{s:1:"a";s:2:"aa";s:1:"b";s:2:"bb";s:1:"c";a:1:{s:1:"x";s:2:"xx";}}
Вообще-то, я не уверен, что в будущих версиях PHP такой формат "упаковки" сохра-
нится неизменным, хотя это очень и очень вероятно.
Функция Unserialize(), наоборот, принимает в лице своего параметра $st строку,
ранее созданную при помощи Serialize(), и возвращает целиком объект, который
был упакован.
mixed Unserialize(string $st)
Например:
$a=array(1,2,3);
$s=Serialize($a);
$a="bogus";
echo count($a); // выводит 1
$a=Unserialize($s);
echo count($a); // выводит 3
Еще раз отмечу: сериализовать можно не только массивы, но и вообще что угодно.
Однако в большинстве случаев все-таки используются массивы. Механизм сериали-
зации часто применяется также и для того, чтобы сохранить какой-то объект в базе
данных, и тогда без сериализации практически не обойтись.
Глава 11

Функции и области
видимости

По синтаксису описания функций PHP, на мой взгляд, довольно близок к идеальной


концепции, которую многие программисты лелеют в своем воображении. Вот не-
сколько основных достоинств этой концепции:
r вы можете использовать параметры по умолчанию (а значит, функции с перемен-
ным числом параметров);
r области видимости переменных внутри функций представляются в древовидной
форме, как и в других языках программирования;
r существует удобная инструкция return, которой так не хватает в Паскале;
r тип возвращаемого значения может быть любым;
r как мы увидим дальше, функции можно использовать не только по их прямому
назначению, но и для автоматизации создания "библиотекарей" и даже написания
своего собственного интерфейса библиотечных файлов.
К сожалению, разработчики PHP не предусмотрели возможность создания локальных
функций (то есть одной внутри другой), как это сделано, скажем, в Паскале или в
Watcom C++. Однако кое-какая эмуляция локальных функций все же есть: если функ-
цию B() определить в теле функции A(), то она, хоть и не став локальной, все же
будет "видна" для программы ниже своего определения. Замечу для сравнения, что
похожая схема существует и в языке Perl. Впрочем, как показывает практика про-
граммирования на Си (вот уже 30 лет), это не такой уж серьезный недостаток.
В системе определения функций в PHP есть и еще один небольшой недочет, который
особенно неприятен тем, кто до этого программировал на других языках. Дело в том,
что все переменные, которые объявляются и используются в функции, по умолчанию
локальны для этой функции. При этом существует только один (и при том довольно
некрасивый) способ объявления глобальных переменных — инструкция global (на
самом деле есть и еще один, через массив $GLOBALS, но об этом чуть позже). С одной
стороны, это повышает надежность функций в смысле их независимости от основной
программы, а также гарантирует, что они случайно не изменят и не создадут глобаль-
ных переменных. С другой стороны, разработчики PHP вполне могли бы предугадать
нужность инструкции, по которой все переменные функции становились бы по умол-
182 Часть III. Основы языка PHP

чанию глобальными — это существенно упростило бы программирование сложных


сценариев.

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

Листинг 11.1. Пример функции

function GetMaxNum($arr, $max="")


{ // проходимся по всем элементам массива
for($i=0,$n=-1; $i<count($arr); $i++) {
// если этот элемент нам пока подходит, запоминаем его
if((!Isset($m) || $arr[$i]>$m) && ($max==="" || $arr[$i]<$max)) {
// сюда мы попадаем, когда очередной элемент больше текущего,
// либо же текущего элемента еще не существует (первый проход)
$m=$arr[$i]; // запоминаем текущий элемент
$n=$i; // запоминаем его номер
}
}
return $n;
}

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


определенном месте программы, но и прямо среди других операторов. Например,
вполне можно было бы поместить нашу функцию GetMaxNum() прямо в середину
кода, скажем, так:
echo "Программа...";
function GetMaxNum($arr,$max)
{ ... тело функции ...
}
echo "Программа продолжается!";

При таком подходе транслятор, дойдя до определения функции, просто прове-


рит его корректность и оттранслирует во внутреннее представление, но не бу-
Глава 11. Функции и области видимости 183

дет генерировать код для выполнения, а сразу переключится на следующие за


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

Итак, мы создали функцию с именем GetMaxNum() и двумя параметрами, первый из


которых рассматривается ей как массив, а второй — как вещественное число.

На самом деле на этапе создания функции еще никаких предположений о ти-


пах параметров не строится. Однако попробуйте нашей функции вместо мас-
сива в первом аргументе передать число — интерпретатор "заругается", как
только выполнение дойдет до строчки с $arr[$i], и скажет, что "переменная
не является массивом".

Алгоритм работы функции таков: в цикле анализируем очередной элемент на пред-


мет "максимальности": если он больше текущего максимального элемента, но меньше
$max, он сам становится текущим максимумом, а его положение запоминается в $n.
(Обратите внимание, что в описании функции параметр $max задается в виде
$max="". Это означает, что если при вызове он будет опущен, то функция получит
пустую строку в $max.) После окончания цикла в $n окажется номер такого элемента
(либо число -1, которое мы присвоили $n в начале). Его-то мы и возвращаем в каче-
стве значения функции оператором return.
Ну вот, теперь в программе ниже описания функции можно написать:
$a=array(10,20,80,35,22,57);
$m=GetMaxNum($a,50); // теперь $m=3, т. е. $a[$m]=35

В действительности, поскольку фаза трансляции и исполнения в PHP разделе-


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

Зачем может понадобиться функция GetMaxNum() в реальной жизни? Например, для


сортировки массива в порядке убывания с одновременным получением уникальных
элементов. Конечно, это будет очень неоптимальный алгоритм, но для тренировоч-
ных целей он нам вполне подойдет (листинг 11.2):

Листинг 11.2. Сортировка с применением GetMaxNum()

function MySort($Arr)
184 Часть III. Основы языка PHP

{ $m= GetMaxNum($Arr)+1; // число, на 1 большее максимума в массиве


while(($n=GetMaxNum($Arr,$m))!=-1)
$New[]=$m=$Arr[$n]; // добавляем очередной максимальный элемент
return $New;
}
// Пример вызова:
$Sorted=MySort(array(1,2,5,2,4,7,3,7,8));
// Теперь $Sorted===array(8,7,5,4,3,2,1)

Приведенная функция не изменяет исходный массив, а возвращает новый. В силу


устройства функции GetMaxNum() в результирующий массив будут помещены толь-
ко уникальные элементы из $Arr, отсортированные в порядке убывания.

Функцию MySort() можно ускорить примерно в 2 раза, если после каждой


итерации удалять из массива $Arr обработанный элемент при помощи
Unset(). Впрочем, это не так интересно, как может показаться.

Общий синтаксис
определения функции
В общем виде синтаксис определения функции таков:
function имя_функции(арг1[=зн1], арг2[=зн2], ... аргN[=знN])
{ операторы_тела_функции;
}
Имя функции должно быть уникальным с точностью до регистра букв. Это означает,
что, во-первых, имена MyFunction, myfunction и даже MyFuNcTiOn будут считать-
ся одинаковыми, и, во-вторых, мы не можем переопределить уже определенную
функцию (стандартную или нет — не важно), но зато можем давать функциям такие
же имена, как и переменным в программе (конечно, без знака $ в начале). Список
аргументов, как легко увидеть, состоит из нескольких перечисленных через запятую
переменных, каждую из которых мы должны будем задать при вызове функции
(впрочем, когда для этой переменной присвоено через знак равенства значение по
умолчанию (обозначенное =знM), ее можно будет опустить; см. об этом чуть ниже).
Конечно, если у функции не должно быть аргументов вовсе (как это сделано у функ-
ции time()), то следует оставить пустые скобки после ее имени, например:
function SimpleFunction() { ... }
В фигурные скобки заключается тело функции. В нем могут быть любые операторы,
включая даже операторы определения других функций (правда, эти "другие функции"
Глава 11. Функции и области видимости 185

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

Инструкция return
Синтаксис оператора return абсолютно тот же, что и в Си, за исключением одной
очень важной детали. Если в Си функции очень редко возвращают большие объекты
(например, структуры), а массивы они не могут возвратить вовсе (это явный прокол в
концепции Си), то в PHP можно использовать return абсолютно для любых объек-
тов (какими бы большими они ни были), причем без заметной потери быстродейст-
вия. Вот пример простой функции, возвращающей квадрат своего аргумента:
function MySqrt($n)
{ return $n*$n;
}
echo MySqrt(4); // выводит 16
Сразу несколько значений функции, разумеется, возвратить не могут. Однако, если
это все же очень нужно, то можно вернуть ассоциативный массив или же список, на-
пример так (листинг 11.3):

Листинг 11.3. Возвращение массива

function Silly()
{ return array(1,2,3);
}
// присваивает массиву значение array(1,2,3)
$arr=Silly();
// присваивает переменным $a, $b, $c первые значения из списка
list($a,$b,$c)=Silly();

В этом примере использован оператор list(), который мы уже рассматривали.


Если функция не возвращает никакого значения, т. е. инструкции return в ней нет,
то считается, что функция возвратила ложь (то есть, false). Все же часто лучше вер-
нуть false явно (если только функция не объявлена как процедура, или void-функция
по Си-терминологии), например, задействуя return false, потому что это несколь-
ко яснее.
186 Часть III. Основы языка PHP

Параметры по умолчанию
Часто бывают такие случаи, что у некоторой разрабатываемой функции должно быть
довольно много параметров, причем некоторые из них будут задаваться совершенно
единообразно. Например, мы пишем функцию для сортировки массива. Тогда, кроме
очевидного параметра — массива — хотелось бы также задавать и второй параметр,
который бы указывал: сортировать ли в убывающем или в возрастающем порядке.
При этом, скажем, мы знаем, что чаще всего придется сортировать в порядке убыва-
ния. В этом случае мы можем оформить нашу функцию так:
function MySort(&$Arr, $NeedLoOrder=1)
{ ... сортируем в зависимости от $NeedLoOrder...
}
Теперь, имея такую функцию, можно написать в программе:
MySort($my_array,0); // сортирует в порядке возрастания
MySort($my_array); // второй аргумент задается по умолчанию!
То есть, мы можем уже вообще опустить второй параметр у нашей функции, что бу-
дет выглядеть так, как будто мы его задали равным 1. Как видно, значение по умол-
чанию для какого-то аргумента указывается справа от него через знак равенства. За-
метьте, что значения аргументов по умолчанию должны определяться справа налево,
причем недопустимо, чтобы после любого из таких аргументов шел обычный "не-
умолчальный" аргумент. Вот, например, неверное описание:
// Ошибка!
function MySort($NeedLoOrder=1, &$Arr)
{
... сортируем в зависимости от $NeedLoOrder...
}
MySort(,$my_array); // Ошибка! Это вам не Бейсик!

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


Давайте рассмотрим механизм, при помощи которого функции передаются ее аргу-
менты. Пусть, например, у нас есть такая программа:
function Test($a)
{ echo "$a\n";
$a++;
echo "$a\n";
}
. . .
$num=10;
Test($num);
Глава 11. Функции и области видимости 187

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";
188 Часть III. Основы языка PHP

$a++;
echo "$a\n";
}
....
$num=10; // $num=10
Test($num); // а теперь $num=11!
echo $num; // выводит 11!

Советую вам, если вы абсолютно точно уверены в необходимости передачи парамет-


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

Теперь, если вы в программе запустите функцию Test(), передав ей в пара-


метрах не переменную (или ячейку массива), а непосредственное значение
(например, константу 100), это у вас не получится: 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("Меркурий", "Венера", "Земля", "Марс");
Глава 11. Функции и области видимости 189

Обратите внимание на то, что при описании 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 не определена
при первом "обороте" цикла.
190 Часть III. Основы языка PHP

Локальные переменные
Наконец-то мы подошли вплотную к вопросу о "жизни и смерти" переменных. Дейст-
вительно, во многих приводимых выше примерах мы рассматривали аргументы
функции (передаваемые по значению, а не по ссылке) как некие временные объекты,
которые создаются в момент вызова и исчезают после окончания функции. Например
(листинг 11.8):

Листинг 11.8. Локальные переменные (параметры)

$a=100; // Глобальная переменная, равная 100


function Test($a)
{ echo $a; // выводим значение параметра $a
// Этот параметр не имеет к глобальной $a никакого отношения!
$a++; // изменяется только локальная копия значения, переданного в $a
}
Test(1); // выводит 1
echo $a; // выводит 100 — глобальная $a, конечно, не изменилась

В действительности такими же свойствами будут обладать не только аргументы, но и


все другие переменные, инициализируемые или используемые внутри функции. Вот
пример (листинг 11.9):

Листинг 11.9. Локальные переменные

function Silly()
{ $i=rand(); // записывает в $i случайное число
echo $i; // выводит его на экран
// Эта $i не имеет к $i никакого отношения!
}
for($i=0; $i!=10; $i++) Silly();
Здесь переменная $i в функции будет не той переменной $i, которая используется в
программе для организации цикла. Поэтому, собственно, цикл и проработает только
10 "витков", напечатав 10 случайных чисел (а не будет крутиться долго и упорно, по-
ка "в рулетке" функции rand() не выпадет 10.
Собственно говоря, это нас устраивает. Действительно, мало ли какие имена пере-
менных использует функция для своих личных целей... Какое до этого дело програм-
ме (которая вообще может быть написана другим человеком)? Вот и получается, что
каждая функция — "узник" в своем тесном мирке, живущий и обменивающийся с
"окружающим миром" через свои параметры и возвращаемое значение.
Глава 11. Функции и области видимости 191

Глобальные переменные
Если вы, прочитав последние строки, уже начали испытывать сочувствие к функциям
в PHP (или, если вы прикладной программист, сочувствие к разработчикам PHP), то
спешу вас заверить: разумеется, в PHP есть способ, посредством которого функции
могут добраться и до любой глобальной переменной в программе (не считая, конечно,
передачи параметра по ссылке). Однако для этого они должны проделать определен-
ные действия, а именно: до первого использования в своем теле внешней переменной
объявить ее "глобальной" (листинг 11.10):

Листинг 11.10. Использование global

function Silly()
{ global $i;
$i=rand();
echo $i;
}
for($i=0; $i!=10; $i++) Silly();

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

Листинг 11.11. Пример функции

$Monthes[1]="Январь";
$Monthes[1]="Февраль";
... и т. д.
$Monthes[12]="Декабрь";
. . .
// Возвращает название месяца по его номеру. Нумерация начинается с 1!
function GetMonthName($n)
{ global $Monthes;
return $Monthes[$n];
}
. . .
echo GetMonthName(2); // выводит "Февраль"
192 Часть III. Основы языка PHP

Согласитесь, массив $Monthes, содержащий названия месяцев, довольно объемист.


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

Массив $GLOBALS
В принципе, есть и второй способ добраться до глобальных переменных. Это — ис-
пользование встроенного в язык массива $GLOBALS. Последний представляет собой
хэш, ключи которого есть имена глобальных переменных, а значения — их величи-
ны.
Этот массив доступен из любого места в программе — в том числе и из тела функ-
ции, и его не нужно никак дополнительно объявлять. Итак, приведенный выше при-
мер можно переписать более лаконично:
// Возвращает название месяца по его номеру. Нумерация начинается с 1!
function GetMonthName($n) { return $GLOBALS["Monthes"][$n]; }

Кстати, тут мы опять сталкиваемся с тем, что не только переменные, но даже и мас-
сивы могут иметь совершенно любую структуру, какой бы сложной она ни была. На-
пример, предположим, что у нас в программе есть ассоциативный массив $A, элемен-
ты которого — двумерные массивы чисел. Тогда доступ к какой-нибудь ячейке этого
массива с использованием $GLOBALS мог бы выглядеть так:
$GLOBALS["A"][First"][10][20];

То есть получился четырехмерный массив!


Насчет $GLOBALS следует добавить еще несколько полезных сведений. Во-первых,
как я уже говорил, этот массив изначально является глобальным для любой функции,
а также для самой программы. Так, вполне допустимо его использовать не только в
теле функции, но также и в любом другом месте. Во-вторых, с этим массивом допус-
тимы не все операции, разрешенные с обычными массивами. А именно, мы не мо-
жем:
r присвоить этот массив какой-либо переменной целиком, используя оператор =;
r как следствие, передать его функции "по значению" — можно передавать только
по ссылке.
Однако остальные операции допустимы. Мы можем при желании, например, по од-
ному перебрать у него все элементы и, скажем, вывести их значения на экран. И, на-
конец, третье: добавление нового элемента в $GLOBALS равнозначно созданию новой
глобальной переменной (конечно, предваренной символом $ в начале имени, ведь в
самом массиве ключи — это имена переменных без символа доллара), а выполнение
Глава 11. Функции и области видимости 193

операции Unset() для него равносильно уничтожению соответствующей перемен-


ной.
А теперь я скажу нечто весьма интересное все о том же массиве $GLOBALS. Как вы
думаете, какой элемент (то есть, глобальная переменная) всегда в нем присутствует?
Это — элемент GLOBALS, "которая" также является массивом, и в "которой" также
есть элемент GLOBALS... Так что же было первей — курица или яйцо (только не надо
мне говорить, что первым был петух)?
А собственно, почему бы и нет? С чего это мы все привыкли, что в большом содер-
жится малое, а не, скажем, наоборот? Почему множество не может содержать себя же
в качестве элемента? Очень даже может, и $GLOBALS — тому наглядный пример.
В PHP версии 3 такая ситуация была чистой воды шаманством. Однако с появлением в
четвертой версии PHP ссылок все вернулось на круги своя. На самом-то деле элемент с
ключом GLOBALS является не обычным массивом, а лишь ссылкой на $GLOBALS. Вот по-
этому все и работает так, как было описано.
Вооружившись механизмом создания ссылок, мы можем теперь наглядно продемон-
стрировать, как работает инструкция global, а также заметить один ее интересный
нюанс. Как мы знаем, global $a говорит о том, что переменная $a является гло-
бальной, т. е., является синонимом глобальной $a. Синоним в терминах PHP — это
ссылка. Выходит, что global создает ссылку? Да, никак не иначе. А вот как это вос-
принимается транслятором:
function Test()
{ global $a;
$a=10;
}
Приведенное описание функции Test() полностью эквивалентно следующему опи-
санию:
function Test()
{ $a=&$GLOBALS[’a’];
$a=10;
}
Из второго фрагмента видно, что оператор Unset($a) в теле функции не уничтожит
глобальную переменную $a, а лишь "отвяжет" от нее ссылку $a. Точно то же самое
происходит и в первом случае. Вот пример:
$a=100;
function Test()
{ global $a;
Unset($a);
}
Test();
echo $a; // выводит 100, т. е. настоящая $a не была удалена в Test()!
194 Часть III. Основы языка PHP

Эта особенность инструкции global появилась только в PHP версии 4, т. е. когда


начали поддерживаться ссылки! Если вы запустите приведенный только что при-
мер на PHP версии 3, то при исполнении echo увидите предупреждение: $a не оп-
ределена. Помните это при переносе старых сценариев на новый PHP версии 4.

Как же нам удалить глобальную $a из функции? Существует только один способ: ис-
пользовать для этой цели $GLOBALS['a']. Вот как это делается:
function Test() { unset($GLOBALS['a']); }
$a=100;
Test();
echo $a; // Ошибка! Переменная $a не определена!

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

Листинг 11.12. Статические переменные

function Silly()
{ static $a=0;
echo $a;
$a++;
}
for($i=0; $i<10; $i++) Silly();

После запуска будет выведена строка 0123456789, как мы и хотели. Давайте теперь
уберем слово static. Мы увидим: 0000000000. Это и понятно, ведь переменная $a
стала локальной, и ей при каждом вызове функции присваивается одно и то же зна-
чение — 0.
Итак, конструкция static говорит компилятору о том, что уничтожать указанную
переменную для нашей функции между вызовами не надо. В то же время присваива-
ние $a=0 сработает только один раз, а именно — при самом первом обращении к
функции (так уж устроен static).

Рекурсия
Конечно, в PHP поддерживаются рекурсивные вызовы функций, т. е. вызовы функци-
ей самой себя (разумеется, не до бесконечности, а в соответствии с определенным
условием). Это бывает чрезвычайно удобно для таких задач, как, например, обход
Глава 11. Функции и области видимости 195

всего дерева каталогов вашего сервера (с целью подсчитать суммарный объем, кото-
рый занимают все файлы), или для других задач. Рассмотрим для примера функцию,
которая рекурсивно вычисляет факториал из некоторого числа n (обозначается n!).
Алгоритм стандартный: если n=0, то n!=1, а иначе n!=n*((n-1)!).
function Factor($n)
{ if($n<=0) return 1;
else return $n*Factor($n-1);
}
echo Factor(20);
Должен только предупредить вас не применять эту функцию факториала в реальной
жизни — она приведена здесь исключительно для примера. Лучше воспользоваться
следующей функцией — она работает гораздо быстрее:
function Factor($n)
{ for($f=1; $n>1; $n--) $f*=$n;
return $f;
}

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

Листинг 11.13. Вложенные функции

function Parent($a)
{ echo $a;
function Child($b)
{ echo $b+1;
return $b*$b;
}
return $a*$a*Child($a); // фактически возвращает $a*$a*($a+1)*($a+1)
}
// Вызываем функции
Parent(10);
196 Часть III. Основы языка PHP

Child(30);
// Попробуйте теперь ВМЕСТО этих двух вызовов поставить такие
// же, но только в обратном порядке. Что, выдает ошибку?
// Почему, спрашиваете? Читайте дальше!

Мы видим, что нет никаких ограничений на место описания функции — будь то гло-
бальная область видимости программы, либо же тело какой-то другой функции. В то
же время, напоминаю, что понятия "локальная функция" как такового в PHP все же
(пока?) не существует.
Каждая функция добавляется во внутреннюю таблицу функций PHP тогда, когда
управление доходит до участка программы, содержащего определение этой функции.
При этом, конечно, само тело функции пропускается, однако ее имя фиксируется и
может далее быть использовано в сценарии для вызова. Если же в процессе выполне-
ния программы PHP никогда не доходит до определения некоторой функции, она не
будет "видна", как будто ее и не существует — это ответ на вопросы, заданные внутри
комментариев примера.
Давайте теперь попробуем запустить другой пример. Вызовем Parent() два раза
подряд:
Parent(10);
Parent(20);
Последний вызов породит ошибку: функция Child() уже определена. Это произош-
ло потому, что Child() определяется внутри Parent(), и до ее определения управ-
ление программы фактически доходит дважды (при первом и втором вызовах
Parent()). Поэтому-то интерпретатор и "протестует": он не может второй раз доба-
вить Child() в таблицу функций.

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

Условно определяемые функции


Предположим, у нас в программе где-то устанавливается переменная $OS_TYPE в
значение win, если сценарий запущен под Windows 9x, и в unix, если под Unix. Как
известно, в отличие от Unix, в Windows нет такого понятия, как владелец файла, а
значит, стандартная функция chown() (которая как раз и назначает владельца для
указанного файла) там просто не имеет смысла. В некоторых версиях PHP для
Windows ее может в этой связи вообще не быть. Однако, чтобы улучшить переноси-
мость сценариев с одной платформы на другую (без изменения их кода!) можно на-
писать следующую простую "обертку" для функции chown() (листинг 11.14):
Глава 11. Функции и области видимости 197

Листинг 11.14. Условно определяемые функции

if($OS_TYPE=="win")
{ // Функция-заглушка
function MyChOwn($fname,$attr)
{ // ничего не делает
return 1;
}
}
else
{ // Передаем вызов настоящей chown()
function MyChOwn($fname,$attr)
{ return chown($fname,$attr);
}
}

Это — один из примеров условно определяемых функций. Если мы работаем под


Windows, функция MyChOwn() ничего не делает и возвращает 1 как индикатор успе-
ха, в то время как для Unix она просто вызывает оригинальную chown(). Важно то,
что проверка, какую функцию использовать, производится только один раз (в момент
прохождения точки определения функции), т. е. здесь нет ни малейшей потери произ-
водительности. Теперь в сценарии мы должны всюду отказаться от chown() и ис-
пользовать MyChOwn() (можно даже провести поиск/замену этого имени в редакто-
ре) — это обеспечит переносимость.
Если вам совсем не нравится идея поиска/замены (а мне она не нравится категориче-
ски), то существует гораздо более элегантный способ, но только в том случае, если
chown() еще не была нигде определена — в том числе и среди стандартных функ-
ций:
if(!function_exists("chown"))
{ function chown($fname,$mode)
{ // не делаем ничего
return 1;
}
}
Этот способ работает независимо от того, появится ли вдруг в будущих версиях PHP
для Windows "заглушка" для функции chown(), или же нет. (Нужно сказать для спра-
ведливости, что в PHP версии 4 такая заглушка уже существует.)
Знатоки Си могут заметить в приеме условно определяемых функций разительное
сходство с директивами условной компиляции этого языка: #ifndef, #else и
#endif. Действительно, аналогия почти полная, за исключением того факта, что в Си
198 Часть III. Основы языка PHP

эти директивы обрабатываются во время компиляции, а в PHP — во время выполне-


ния. Что ж, на то он и интерпретатор, чтобы позволять себе интерпретацию.

То, что возможно создавать условно определяемые функции, сильно подрыва-


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

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


Я отнюдь не случайно заключил последние два слова названия этого раздела в ка-
вычки — дело в том, что как таковая, передача функции по ссылке в 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 нет такого понятия, как
области видимости для функций (есть только области видимости для локальных пе-
ременных), то конфликтов это не порождает — одному имени может соответствовать
Глава 11. Функции и области видимости 199

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

Возврат функцией ссылки


До сих пор я рассматривал лишь функции, которые возвращают определенные значе-
ния — а именно, копии величин, использованных в инструкции return. Заметьте,
это были именно копии, а не сами объекты. Например:
$a=100;
function R()
{ global $a; // объявляет $a глобальной
return $a; // возвращает значение, а не ссылку!
}
$b=R();
$b=0; // присваивает $b, а не $a!
echo $a; // выводит 100
В то же время мы бы хотели, чтобы функция R() возвращала не величину, а ссылку
на переменную $a, чтобы в дальнейшем с этой ссылкой можно было работать точно
так же, как и с $a. Например, это может очень пригодиться в объектно-
ориентированном программировании на PHP (основы которого мы рассмотрим в пя-
той части книги), когда функция должна возвращать именно объект, а не его копию.
Как же нам добиться нужного результата? Использование оператора $b=&R(), к со-
жалению, не подходит, т. к. при этом мы получим в $b ссылку не на $a, а на ее ко-
пию. Если задействовать return &$a, то появится сообщение о синтаксической
ошибке (PHP воспринимает & только в правой части оператора присваивания сразу
после знака =). Но выход есть. Воспользуемся специальным синтаксисом описания
функции, возвращающей ссылку (листинг 11.15):

Листинг 11.15. Возвращение ссылки

$a=100;
function &R() // & — возвращает ссылку
{ global $a; // объявляет $a глобальной
return $a; // возвращает значение, а не ссылку!
}
$b=&R(); // не забудьте & !!!
$b=0; // присваивает переменной $a!
echo $a; // выводит 0. Это значит, что теперь $b — синоним $a
200 Часть III. Основы языка PHP

Как видим, нужно поставить & в двух местах: перед определением имени функции, а
также в правой части оператора присваивания при вызове функции. Использовать
амперсанд в инструкции return не нужно.

Лично я не нахожу такой синтаксис удобным. Достаточно по-ошибке всего один


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

Пример функции: Dump()


В отладочных целях часто бывает нужно посмотреть, что содержит та или иная пере-
менная. Однако, если эта переменная — массив, да еще многомерный, с выводом ее
содержимого на экран могут возникнуть проблемы. Решить их призвана следующая
функция, которую я назвал Dump(). Пользу от этой функции можно реально почувст-
вовать, лишь поработав с ней некоторое время. Уверяю, потом вы не сможете понять,
как раньше без нее обходились…
Функция выводит содержимое любой, сколь угодно сложной, переменной, будь то
массив, объект или простая переменная. Как уже говорилось, приведенная функция
исключительно полезна при отладке сценариев (которая в PHP пока еще не особенно
развита).

В PHP версии 4 для аналогичных целей существуют две стандартных функ-


ции — print_r() и var_dump(), но листинг, который они выводят, довольно
неудобен для восприятия человеком.

Листинг 11.16. Функция Dump()

// Вспомогательная функция, делающая всю "грязную" работу


function TextDump(&$Var,$Level=0)
{ if(is_array($Var)) $Type="Array[".count($Var)."]";
else if(is_object($Var)) $Type="Object";
else $Type="";
if($Type) {
echo "$Type\n";
for(Reset($Var),$Level++; list($k,$v)=each($Var);) {
if(is_array($v) && $k==="GLOBALS") continue;
Глава 11. Функции и области видимости 201

for($i=0; $i<$Level*3; $i++) echo " ";


echo "<b>".HtmlSpecialChars($k)."</b> => ", TextDump($v,$Level);
}
}
else echo '"',HtmlSpecialChars($Var),'"'."\n";
}

// Основная функция
function Dump(&$Var)
{ // Подфункция, выводящая практически окончательный результат
if((is_array($Var)||is_object($Var)) && count($Var))
echo "<pre>\n",TextDump($Var),"</pre>\n";
else
echo "<tt>",TextDump($Var),"</tt>\n";
}

В реальной жизни следует использовать функцию Dump(). Функция TextDump()


(которая, по правде говоря, и делает всю работу) использует только одну неизвестную
нам еще функцию — HtmlSpecialChars(), заменяющую в строке символы типа <,
> или " на их HTML-эквиваленты (соответственно, &lt;, &gt; и &quot;). Мы при-
менили дополнительную функцию для того, чтобы вывести сам результат, а главная
функция занимается только форматированием этого результата (вставка его в тэги
<pre> или <tt> в зависимости от размера вывода).

Несколько советов
по использованию функций
Хочется напоследок сказать еще несколько слов о функциях.
Первое — не допускайте, чтобы ваши функции разрастались до гигантских размеров.
Дробите их на маленькие, по возможности независимые, части, желательно полезные
и сами по себе. Это повысит "читабельность", устойчивость и переносимость ваших
программ. В идеале каждая функция не должна занимать больше 20—30 строк, воз-
можно, за редким исключением. Этот совет применим вообще ко всем языкам про-
граммирования, а не только к PHP.
Второе: как известно, вызов функции тоже отнимает какое-то время, поэтому распро-
странено мнение, что чем меньше функций, тем быстрее работает программа. Оно в
корне неверно: не стоит обращать внимания на цену вызова функции, пока она сама
об этом не заявит. В конце концов, объединить несколько функций в одну всегда на
порядок проще, чем разбить одну функцию на несколько. Помните об этом.
Наконец, последнее: больше используйте встроенные, стандартные функции. Прежде
чем писать какую-то процедуру, сверьтесь с документацией — возможно, она уже
202 Часть III. Основы языка PHP

реализована в ядре PHP. Если это так, то не думайте, что сможете написать ее эффек-
тивнее на PHP — ведь часто самый неэффективный Си-код работает быстрее, чем
самый изящный на PHP. Возможно, лучше пожертвовать объемом за счет быстродей-
ствия — например, при работе с базами данных и сложными файлами лучше приме-
нять стандартные функции сериализации, чем писать более эффективно упаковы-
вающие, но свои, потому что стандартные работают очень быстро. Правда, из этого
правила существуют и исключения: например, я бы не советовал вам использовать
Serialize() для формирования строки, сохраняющейся в Cookies браузера — здесь
лучше написать свои функции. Опять же, тут действует принцип: чем меньше в про-
грамме собственноручно реализованных функций, тем надежнее она будет работать и
тем меньше ее придется тестировать.
ЧАСТЬ IV.
СТАНДАРТНЫЕ
ФУНКЦИИ PHP
Глава 12

Строковые функции

Строки в PHP — одни из самых универсальных объектов. Как мы уже видели, любой,
сколь угодно сложный объект можно упаковать в строку при помощи функции
Serialize() (и обратно через Unserialize()). Строка может содержать абсолют-
но любые символы с кодами от 0 до 255 включительно. Нет никакого специального
маркера "конца строки", как это сделано в Си (там конец строки помечается симво-
лом с нулевым кодом). А значит, длина строки во внутреннем представлении PHP
хранится где-то отдельно. Для формирования и вставки непечатаемого символа в
строку (например, с кодом 1 или 15) используется функция chr(), которую мы рас-
смотрим ниже.
Наконец, из-за слабого контроля типов в PHP строка может содержать
(и часто содержит) число, причем с ней можно работать, как с числом: прибавлять
другие числа, умножать и т. д. При этом все преобразования (в десятичной системе)
производятся автоматически. Существуют также функции, преобразующие число,
записанное в различных системах счисления (например, в восьмеричной), в обычное
представление, и наоборот. Их мы обсудим позже, в следующей главе.

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


(около 80%), пропуская все остальные. Какие-то из не вошедших в данную гла-
ву функций (например, quotemeta()) мы будем рассматривать в других гла-
вах — там, где это показалось мне наиболее логичным. Так что, не найдя опи-
сание интересующей вас функции здесь, подумайте: возможно, оно лучше
подходит для другой темы и его лучше поискать там? И, наконец, последней
инстанцией для вас, конечно же, должна являться документация PHP.

Конкатенация строк
Самая, пожалуй, распространенная операция со строками — это их конкатенация,
или присоединение к одной строке другой. В ранних версиях PHP для этого, как и для
сложения чисел, использовался оператор +, что постоянно приводило к путанице: ес-
ли к числу прибавляется строка, что должно получиться — число или строка? Если
число, то вдруг наша строка содержала на самом деле не число, а какой-то текст? В
новой — третьей — версии интерпретатора разработчики отказались от этого меха-
низма и объявили, что + следует применять только для сложения чисел, и никак ина-
Глава 12. Строковые функции 207

че. Что же касается конкатенации строк, то для нее ввели специальный оператор "."
(точка).
Оператор "." всегда воспринимает свои операнды как строки и возвращает строку. В
случае, если один из операндов не может быть переведен в строковое представление,
т. е. если это массив или объект, то он воспринимается как строки array и object
соответственно. Вообще говоря, это правило применимо и не только при сцеплении
строк, но и при передаче такого операнда в какую-нибудь стандартную функцию, ко-
торой требуется строка. Например, следующие команды выведут слово array:
$a=array(10,20,30);
echo $a // Внимание! Неожиданный результат!
Есть и другой, более специализированный, способ конкатенации строк. Он обычно
используется, когда значения строковых или числовых переменных перемежаются с
обычными словами. Если, к примеру, у нас в $day хранится текущее число, в
$month — название месяца и в $year — год, то вывести строку вида "Сегодня 8 мая
2000 года" можно так:
echo "Сегодня $day $month $year года";
При этом в строку, вырабатываемую инструкцией echo, автоматически в нужных
местах вставятся значения наших переменных. Это позволяет констатировать тот
факт, что в PHP все переменные начинаются с $.

О сравнении строк
и инструкции 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
208 Часть IV. Стандартные функции PHP

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-программа";
не годится, если <? находится в самом начале строки (в этом случае не будет выдано
наше сообщение, хотя подстрока в действительности найдена, и функция возвратила
0, а не false).
Если вы еще собираетесь работать с PHP версии 3, указанную проблему можно ре-
шить так:
if(strval(strpos($str,"<?"))!="")
echo "это PHP-программа";
Конечно, выглядит это немного "накручено", зато действительно работает. Приятно
отметить, что в PHP версии 4 проблема решается гораздо более изящным образом:
if(strpos($str,"<?")!===false)
echo "это PHP-программа";
Глава 12. Строковые функции 209

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

Обратите внимание, что мы используем оператор !=== именно с константой


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

Функции для работы


с одиночными символами
string chr(int $code)
Возвращает строку из одного символа с кодом $code. Эта функция полезна для
вставки каких-либо непечатаемых символов в строку — например, кода нуля или
символа прогона страницы, а также при работе с бинарными файлами. Пример из
листинга 12.2 позволяет вам просмотреть, какие коды соответствуют всем символам,
которые можно отобразить в браузере. Иногда эта программа оказывается очень по-
лезной.

Листинг 12.2. Программа: печать всей таблицы символов

<?
// Сначала создаем массив того, что мы собираемся выводить,
// не заботясь о форматировании (дизайне) информации
for($i=0,$x=0; $x<16; $x++) {
for($y=0; $y<16; $y++) {
$Chars[$x][$y]=array($i,chr($i));
$i++;
}
}
// Теперь выводим накопленную информацию, используя идеологию
// вставки участков кода в HTML-документ
?>

<table border=1 cellpadding=1 cellspacing=0>


<?for($y=0; $y<16; $y++) {?>
<tr>
<?for($x=0; $x<16; $x++) { ?>
<td>
<?=$Chars[$x][$y][0]?>:
210 Часть IV. Стандартные функции PHP

<b><tt><?=$Chars[$x][$y][1]?></tt></b>
</td>
<?}?>
</tr>
<?}?>
</table>
?>

int ord(char $ch)

Эта функция, наоборот, возвращает код символа в $ch. Например, ord(chr($n))


всегда равно $n — конечно, если $n заключено между нулем и числом 255.
int strrpos(string $where, char $what)

Данная функция, хотя и похожа внешне на strpos() (см. ниже), несет несколько
иную нагрузку. Она ищет в строке $where последнюю позицию, в которой встречает-
ся символ $what (если $what — строка из нескольких символов, то выявляется толь-
ко первый из них, остальные не играют никакой роли — обратите на это особое вни-
мание!). В случае, если искомый символ не найден, возвращается false (см.
замечание по этому поводу для strpos()). Вообще, могу сказать, что функция
strrpos() применяется очень редко. Слишком уж она не универсальна.

Функции отрезания пробелов


По поводу философии написания программ, которые интенсивно обрабатывают дан-
ные, вводимые пользователем (а именно такими программами является большинство
сценариев) есть очень правильное изречение: ваша программа должна быть макси-
мально строга к формату выходных данных и максимально лояльна по отношению ко
входным данным. Это означает, что, прежде чем передавать полученные от пользова-
теля строки куда-то дальше, — например, другим функциям, — нужно над ними не-
много поработать. Самое простое, что можно сделать — это отрезать начальные и
концевые пробелы.
Иногда трудно даже представить, какими могут быть странными пользователи, если
дать им в руки клавиатуру и попросить напечатать на ней какое-нибудь слово. Так
как клавиша пробела — самая большая, то пользователи имеют обыкновение нажи-
мать ее в самые невероятные моменты. Этому способствует также и тот факт, что
символ с кодом 32, обозначающий пробел, как вы знаете, на экране не виден. Если
программа не способна обработать описанную ситуацию, то она, в лучшем случае
после тягостного молчания отобразит в браузере что-нибудь типа "неверные входные
данные", а в худшем — сделает при этом что-нибудь необратимое.
Глава 12. Строковые функции 211

Между тем, обезопасить себя от паразитных пробелов чрезвычайно просто, и разра-


ботчики PHP предоставляют нам для этого ряд специализированных функций. Не
волнуйтесь о том, что их применение замедляет программу. Эти функции работают с
молниеносной скоростью, а главное, одинаково быстро, независимо от объема пере-
данных им строк. Конечно, я не призываю к пароноидальному применению функций
"отрезания" на каждой строчке программы, но в то же время, если есть хоть 1%-ная
возможность того, что строка может содержать лишние пробелы, следует без колеба-
ний от них избавляться. В конце концов, отсекать пробелы один раз или тысячу —
все равно, а вот не отрезать совсем и отрезать однажды — большая разница. Кстати,
если отделять нечего, описанные ниже функции мгновенно заканчивают свою работу,
так что их вызов обходится совсем дешево.
string trim(string $st)
Возвращает копию $st, только с удаленными ведущими и концевыми пробельными
символами. Под пробельными символами я здесь и далее подразумеваю: пробел " ",
символ перевода строки \n, символ возврата каретки \r и символ табуляции \t. На-
пример, вызов trim(" test\n ") вернет строку "test".
Эта функция используется очень широко. Старайтесь применять ее везде, где есть
хоть малейшее подозрение на наличие ошибочных пробелов. Поскольку работает она
очень быстро.
string ltrim(string $st)

То же, что и trim(), только удаляет исключительно ведущие пробелы, а концевые не


трогает. Используется гораздо реже. Старайтесь всегда вместо нее применять
trim(), и не прогадаете.
string chop(string $st)

Удаляет только концевые пробелы, ведущие не трогает. Эта функция будет наверняка
очень популярной у тех, кто раньше программировал на Perl. Однако следует заме-
тить, что в PHP она выполняет другую функцию.

Базовые функции
int strlen(string $st)

Одна из наиболее полезных функций. Возвращает просто длину строки, т. е., сколько
символов содержится в $st. Как уже упоминалось, строка может содержать любые
символы, в том числе и с нулевым кодом (что запрещено в Си). Функция strlen()
будет правильно работать и с такими строками.
int strpos(string $where, string $what, int $fromwhere=0)

Пытается найти в строке $where подстроку (то есть последовательность символов)


$what и в случае успеха возвращает позицию (индекс) этой подстроки в строке. Пер-
212 Часть IV. Стандартные функции PHP

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


$fromwhere можно задавать, если поиск нужно вести не с начала строки $from, а с
какой-то другой позиции. В этом случае следует эту позицию передать в
$fromwhere. Если подстроку найти не удалось, функция возвращает false. Однако
будьте внимательны, проверяя результат вызова strpos() на false — используйте
ля этого только оператор ===.
string substr(string $str, int $from [,int $length])

Данная функция тоже востребуется очень часто. Ее назначение — возвращать уча-


сток строки $str, начиная с позиции $start и длиной $length. Если $length не
задана, то подразумевается подстрока от $start до конца строки $str. Если $start
больше, чем длина строки, или же значение $length равно нулю, то возвращается
пустая подстрока.
Однако эта функция может делать и еще довольно полезные вещи. К примеру, если
мы передадим в $start отрицательное число, то будет считаться, что это число яв-
ляется индексом подстроки, но только отсчитываемым от конца $str (например, -1
означает "начиная с последнего символа строки"). Параметр $length, если он задан,
тоже может быть отрицательным. В этом случае последним символом возвращенной
подстроки будет символ из $str с индексом $length, определяемым от конца стро-
ки.
int strcmp(string $str1, string $str2)

Сравнивает две строки посимвольно (точнее, побайтово) и возвращает: 0, если строки


полностью совпадают; -1, если строка $str1 лексикографически меньше $str2; и 1,
если, наоборот, $str1 "больше" $str2. Так как сравнение идет побайтово, то ре-
гистр символов влияет на результаты сравнений.
int strcasecmp(string $str1, string $str2)

То же самое, что и strcmp(), только при работе не учитывается регистр букв. На-
пример, с точки зрения этой функции "ab" и "AB" равны.

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

Работа с блоками текста


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

string str_replace(string $from, string $to, string $str)

Заменяет в строке $str все вхождения подстроки $from (с учетом регистра) на $to и
возвращает результат. Исходная строка, переданная третьим параметром, при этом не
меняется. Эта функция работает значительно быстрее, чем ereg_replace(), кото-
рую мы рассмотрим в главе о регулярных выражениях PHP, и ее часто используют,
если нет необходимости в каких-то экзотических правилах поиска подстроки. Напри-
мер, вот так мы можем заместить все символы перевода строки на их HTML-
эквивалент — тэг <br>:
$st=str_replace("\n","<br>\n",$st)

Как видим, то, что в строке <br>\n тоже есть символ перевода строки, никак не
влияет на работу функции, т. е. функция производит лишь однократный проход по
строке. Для решения описанной задачи также применима функция nl2br(), которая
работает чуть быстрее.
string nl2br(string $string)

Заменяет в строке все символы новой строки \n на <br>\n и возвращает результат.


Исходная строка не изменяется. Обратите внимание на то, что символы \r, которые
присутствуют в конце строки текстовых файлов Windows, этой функцией никак не
учитываются, а потому остаются на старом месте.
string WordWrap(string $st, int $width=75, string $break="\n")

Эта функция, наконец-то появившаяся в PHP версии 4, оказывается невероятно по-


лезной при форматировании текста письма перед автоматической отправкой его ад-
ресату при помощи mail(). Она разбивает блок текста $st на несколько строк, за-
вершаемых символами $break, так, чтобы на одной строке было не более $width
букв. Разбиение происходит по границе слова, так что текст остается читаемым. Воз-
вращается получившаяся строка с символами перевода строки, заданными в $break.
Давайте рассмотрим пример, как мы можем отформатировать некоторый текст по
ширине поля 60 символов, предварив каждую строку префиксом ">" (то есть, офор-
мить его как цитирование, принятое в электронной переписке):
function Cite($OurText, $prefix="> ")
{ $st=WordWrap($OurText, 60-strlen($prefix), "\n");
$st=$prefix.str_replace("\n","\n$prefix",$st);
// можно было бы сделать это и одной операцией, но так,
// по-моему, несколько универсальнее.
return $st;
}

string strip_tags (string $str [, string $allowable_tags])


214 Часть IV. Стандартные функции PHP

Эта функция удаляет из строки все тэги и возвращает результат. В параметре


$allowable_tags можно передать тэги, которые не следует удалять из строки. Они
должны перечисляться вплотную друг к другу. Вот пример:
$st="
<b>Жирный текст</b>
<tt>Моноширинный текст</tt>
<a href=http://www.dklab.ru>Ссылка</a>";
echo "Исходный текст: $st";
echo "<hr>После удаления тэгов: ".strip_tags($st,"<a><b>")."<hr>";

Запустив этот пример, мы сможем заметить, что тэги <a> и <b> не были удалены
(ровно как и их парные закрывающие), в то время как <tt> исчез.
string str_repeat(string $st, string $number)

Функция "повторяет" строку $st $number раз и возвращает объединенный результат.


Вот пример:
echo str_repeat("test!",3); // выводит test!test!test!

Функции для преобразований


символов
Web-программирование — одна из тех областей, в которых постоянно приходится
манипулировать строками: разрывать их, добавлять и удалять пробелы, перекодиро-
вать в разные кодировки, наконец, URL-кодировать и декодировать. В PHP реализо-
вать все эти действия вручную, используя только уже описанные примитивы, просто
невозможно из соображений быстродействия. Поэтому-то и существуют встроенные
функции, описанные в этом разделе.
string strtr(string $str, string $from, string $to)
Эта функция применяется не столь широко, но все-таки иногда она бывает довольно
полезной. Делает она вот что: в строке $str заменяет все символы, встречающиеся в
$from, на их "парные" (то есть расположенные в тех же позициях, что и во $from) из
$to. Функция работает существенно быстрее, чем ereg_replace(), которую мы
рассмотрим в главе, посвященной регулярным выражениям. Правде, она имеет вме-
сте с тем несколько меньшую функциональность...
Следующие несколько функций предназначены для быстрого URL-кодирования и
декодирования.
string UrlEncode(string $st)
Функция URL-кодирует строку $st и возвращает результат. Эту функцию удобно
применять, если вы, например, хотите динамически сформировать ссылку <a
Глава 12. Строковые функции 215

href=...> на какой-то сценарий, но не уверены, что его параметры содержат только


алфавитно-цифровые символы. В этом случае воспользуйтесь функцией так:
echo "<a href=/script.php?param=".UrlEncode($UserData);
Теперь, даже если переменная $UserData включает символы =, & или даже пробелы,
все равно сценарию будут переданы корректные данные.
string UrlDecode(string $st)
Производит URL-декодирование строки. В принципе, используется значительно реже,
чем UrlEncode(), потому что PHP и так умеет перекодировать входные данные ав-
томатически.
string RawUrlEncode(string $st)
Почти полностью аналогична UrlEncode(), но только пробелы не преобразуются
в +, как это делается при передаче данных из формы, а воспринимаются как обычные
неалфавитно-цифровые символы. Впрочем, этот метод не порождает никаких допол-
нительных несовместимостей в коде.
string RawUrlDecode(string $st)
Аналогична UrlDecode(), но не воспринимает + как пробел.
Давайте теперь рассмотрим функцию, которая обычно используется в комбинации с
echo. Основное ее назначение — гарантировать, что в выводимой строке ни один
участок не будет воспринят как тэг.
string HtmlSpecialChars(string $str)
Заменяет в строке некоторые символы (такие как амперсант, кавычки и знаки "боль-
ше" и "меньше") на их HTML-эквиваленты, так, чтобы они выглядели на странице
"самими собой". Самое типичное применение этой функции — формирование пара-
метра value в различных элементах формы, чтобы не было никаких проблем с ка-
вычками, или же вывод сообщения в гостевой книге, если вставлять тэги пользовате-
лю запрещено. Например, пусть содержимое книги хранится в массиве $Book в
очевидном формате. Тогда следующий фрагмент распечатывает содержимое гостевой
книги, заботясь о том, чтобы тэги не воспринимались браузером как описания форма-
тирования:
<?foreach($Book as $k=>$v) {?>
Имя: <?=$v['name']?><br>
Текст: <?=HtmlSpecialChars($v['text'])?>
<hr>
<?}?>
Используя этот незамысловатый прием, вы гарантированно избавите себя от проблем
с запретом тэгов.
216 Часть IV. Стандартные функции PHP

Начинающие Web-программисты для решения задачи запрета тэгов часто пы-


таются просто удалить их из строки — например, применив функцию
strip_tags(). Это метод довольно плох, потому что всегда существует ве-
роятность того, что злоумышленник сможет "обмануть" эту функцию. Конечно,
еще хуже метод с применением регулярных выражений, потому что, как из-
вестно, с их помощью вовсе невозможно выделить некоторые тэги из строки —
например, тэги такого вида: <a name='a>b'>.

string StripSlashes(string $st)


Заменяет в строке $st некоторые предваренные слэшем символы на их однокодовые
эквиваленты. Это относится к следующим символам: ", ', \ и никаким другим.
string AddSlashes(string $st)
Вставляет слэши только перед следующими символами: ', " и \. Функцию очень
удобно использовать при вызове eval() (эта функция выполняет строку, переданную
ей в параметрах, так, как будто имеет дело с небольшой PHP-программой; о ней
(функции) мы еще поговорим, и при том очень подробно).

Функции изменения регистра


Довольно часто нам приходится переводить какие-то строки, скажем, в верхний ре-
гистр, т. е. делать все прописные буквы в строке заглавными. В принципе, для этой
цели можно было бы воспользоваться функцией strtr(), рассмотренной выше, но
она все же будет работать не так быстро, как нам иногда хотелось бы. В PHP есть
функции, которые предназначены специально для таких нужд. Вот они.
string strtolower(string $str)
Преобразует строку в нижний регистр. Возвращает результат перевода.
Надо заметить, что при неправильной настройке локали (про локаль будет рассказано
чуть позже, а пока скажу только, что это набор правил по переводу символов из одно-
го регистра в другой, переводу даты и времени, денежных единиц и т. д.) функция
будет выдавать, мягко говоря, странные результаты при работе с буквами кириллицы.
Возможно, в несложных программах, а также если нет уверенности в поддержке со-
ответствующей локали операционной системой, проще будет воспользоваться "руч-
ным" преобразованием символов, задействуя функцию strtr():
$st=strtr($st,
"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩљЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэ-
юя");

Главное достоинство данного способа — то, что в случае проблем с кодировкой для
восстановления работоспособности сценария вам придется всего лишь преобразовать
его в ту же кодировку, в которой у вас хранятся документы на сервере.
string strtoupper(string $str)
Глава 12. Строковые функции 217

Переводит строку в верхний регистр. Возвращает результат преобразования. Эта


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

Установка локали (локальных настроек)


string SetLocale(string $category, string $locale)

Функция устанавливает текущую локаль, с которой будут работать функции преобра-


зования регистра символов, вывода даты-времени и т. д. Вообще говоря, для каждой
категории функций локаль определяется отдельно и выглядит по-разному. То, какую
именно категорию функций затронет вызов SetLocale(), задается в параметре
$category. Он может принимать следующие строковые значения:
r LC_CTYPE — активизирует указанную локаль для функций перевода в верх-
ний/нижний регистры;
r LC_NUMERIC — активизирует локаль для функций форматирования дробных чи-
сел — а именно, задает разделитель целой и дробной части в числах;
r LC_TIME — задает формат вывода даты и времени по умолчанию;
r LC_ALL — устанавливает все вышеперечисленные режимы.
Теперь поговорим о параметре $locale. Как известно, каждая локаль, установленная
в системе, имеет свое уникальное имя, по которому к ней можно обратиться. Именно
оно и фиксируется в этом параметре. Однако, есть два важных исключения из этого
правила. Во-первых, если величина $locale равна пустой строке "", то устанавлива-
ется та локаль, которая указана в глобальной переменной окружения с именем, сов-
падающим с именем категории $category (или LANG — она практически всегда
присутствует в Unix). Во-вторых, если в этом параметре передается 0, то новая ло-
каль не устанавливается, а просто возвращается имя текущей локали для указанного
режима.
К сожалению, имена локалей задаются при настройке операционной системы, и для
них, по-видимому, не существует стандартов. Выясните у своего хостинг-провайдера,
как называются локали для разных кодировок русских символов. Но, если следую-
щий фрагмент работает у вашего хостинг-провайдера, это не означает, что он зарабо-
тает, например, под Windows:
setlocale('LC_CTYPE','ru_SU.KOI8-R');

Здесь вызов устанавливает таблицу замены регистра букв в соответствии с кодиров-


кой KOI8-R.
По правде говоря, локаль — вещь довольно непредсказуемая и, как я уже говорил,
довольно плохо переносимая между операционными системами. Так что, если ваш
сценарий не очень велик, задумайтесь: возможно, лучше будет искать обходной путь
(например, использовать strtr()), а не рассчитывать на локаль.
218 Часть IV. Стандартные функции PHP

Преобразование кодировок
Часто встречается ситуация, когда нам требуется преобразовать строку из одной ко-
дировки кириллицы в другую. Например, мы в программе сменили локаль: была ко-
дировка windows, а стала — KOI8-R. Но строки-то остались по-прежнему в кодировке
WIN-1251, а значит, для правильной работы с ними нам нужно их перекодировать в
KOI8-R. Для этого и служит функция преобразования кодировок.
string convert_cyr_string(string $str, char $from, char $to);

Функция переводит строку $str из кодировки $from в кодировку $to. Конечно, это
имеет смысл только для строк, содержащих "русские" буквы, т. к. латиница во всех
кодировках выглядит одинаково. Разумеется, кодировка $from должна совпадать с
истинной кодировкой строки, иначе результат получится неверным. Значения $from
и $to — один символ, определяющий кодировку:
r k — koi8-r
r w — windows-1251
r i — iso8859-5
r a — x-cp866
r d — x-cp866
r m — x-mac-cyrillic
Функция работает достаточно быстро, так что ее вполне можно применять, скажем,
для перекодировки писем в нужную форму перед их отправкой по электронной почте.

Функции форматных преобразований


Как мы знаем, переменные в строках PHP интерполируются, поэтому практически
всегда задача "смешивания" текста со значениями переменных не является пробле-
мой. Например, мы можем спокойно написать что-то вроде:
echo "Привет, $name! Вам $age лет.";

Вспомните, что в Си нам приходилось для аналогичных целей писать следующий


код:
printf("Привет, %s! Вам %s лет",name,age);

Язык PHP также поддерживает ряд функций, использующих такой же синтаксис, как
и их Си-эквиваленты. Бывают случаи, когда их применение дает наиболее красивое и
лаконичное решение, хотя это и случается довольно нечасто.
string sprintf(string $format [, mixed args, ...])
Эта функция — аналог функции sprintf() в Си. Она возвращает строку, состав-
ленную на основе строки форматирования, содержащей некоторые специальные сим-
Глава 12. Строковые функции 219

волы, которые будут впоследствии заменены на значения соответствующих перемен-


ных из списка аргументов.
Строка форматирования $format может включать в себя команды форматирования,
предваренные символом %. Все остальные символы копируются в выходную строку
как есть. Каждый спецификатор формата (то есть, символ % и следующие за ним ко-
манды) соответствует одному, и только одному параметру, указанному после пара-
метра $format. Если же нужно поместить в текст % как обычный символ, необходи-
мо его удвоить:
echo sprintf("The percentage was %d%%",$percentage);
Каждый спецификатор формата включает максимум пять элементов (в порядке их
следования после символа %):
r Необязательный спецификатор размера поля, который указывает, сколько симво-
лов будет отведено под выводимую величину. В качестве символов-заполнителей
(если значение имеет меньший размер, чем размер поля для его вывода) может
использоваться пробел или 0, по умолчанию подставляется пробел. Можно задать
любой другой символ-наполнитель, если указать его в строке форматирования,
предварив апострофом '. (См. примеры, как это делается.)
r Опциональный спецификатор выравнивания, определяющий, будет результат вы-
ровнен по правому или по левому краю поля. По умолчанию производится вырав-
нивание по правому краю, однако можно указать и левое выравнивание, задав
символ - (минус).
r Необязательное число, определяющее размер поля для вывода величины. Если
результат не будет в поле помещаться, то он "вылезет" за края этого поля, но не
будет усечен.
r Необязательное число, предваренное точкой ".", предписывающее, сколько знаков
после запятой будет в результирующей строке. Этот спецификатор учитывается
только в том случае, если происходит вывод числа с плавающей точкой, в против-
ном случае он игнорируется.
r Наконец, обязательный (заметьте — единственный обязательный!) спецификатор
типа величины, которая будет помещена в выходную строку:
· b — очередной аргумент из списка выводится как двоичное целое число;
· c — выводится символ с указанным в аргументе кодом;
· d — целое число;
· f — число с плавающей точкой;
· o — восьмеричное целое число;
· s — строка символов;
· x — шестнадцатеричное целое число с маленькими буквами a—z;
220 Часть IV. Стандартные функции PHP

· X — шестнадцатеричное число с большими буквами A—Z.


Вот как можно указать точность представления чисел с плавающей точкой:
$money1 = 68.75;
$money2 = 54.35;
$money = $money1 + $money2;
// echo $money выведет "123.1"...
$formatted = sprintf ("%01.2f", $money);
// echo $formatted выведет "123.10"!

Вот пример вывода целого числа, предваренного нужным количеством нулей:


$isodate=sprintf("%04d-%02d-%02d",$year,$month,$day);

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


может иногда быть функция sprintf().
void printf(string $format [, mixed args, ...])
Делает то же самое, что и sprintf(), только результирующая строка не возвращает-
ся, а направляется в браузер пользователя.
string number_format(float $number, int $decimals,
string $dec_point=".", string $thousands_sep=",");
Эта функция форматирует число с плавающей точкой с разделением его на триады с
указанной точностью. Она может быть вызвана с двумя или четырьмя аргументами,
но не с тремя! Параметр $decimals задает, сколько цифр после запятой должно
быть у числа в выходной строке. Параметр $dec_point представляет собой раздели-
тель целой и дробной частей, а параметр $thousands_sep — разделитель триад в
числе (если указать на его месте пустую строку, то триады не отделяются друг от дру-
га).
В PHP существует еще несколько функций для выполнения форматных преобразова-
ний, среди них — sscanf() и fscanf(), которые часто применяются в Си. Однако
в PHP их использование весьма ограничено: чаще всего для разбора строк оказывает-
ся гораздо выгоднее привлечь регулярные выражения или функцию explode().
Именно по этой причине я здесь не уделяю повышенного внимания этим функциям.

Работа с бинарными данными


Как мы уже знаем, строки могут содержать любые, в том числе и бинарные, данные
(то есть, символы с кодами, меньшими 32). Для работы с такими строками иногда
удобно использовать функции pack() и unpack().
string pack(string $format [,mixed $args, ...])
Функция pack() упаковывает заданные аргументы в бинарную строку, которая затем
и возвращается. Формат параметров, а также их количество, задается при помощи
Глава 12. Строковые функции 221

строки $format, которая представляет собой набор однобуквенных спецификаторов


форматирования — наподобие тех, которые указываются в sprintf(), но только без
знака %. После каждого спецификатора может стоять число, которое отмечает, сколь-
ко информации будет обработано данным спецификатором. А именно, для форматов
a, A, h и H число задает, какое количество символов будет помещено в бинарную
строку из тех, что находятся в очередном параметре-строке при вызове функции (то
есть, определяется размер поля для вывода строки). В случае @ оно определяет абсо-
лютную позицию, в которую будут помещены следующие данные. Для всех осталь-
ных спецификаторов следующие за ними числа задают количество аргументов, на
которые распространяется действие данного формата. Вместо числа можно указать *,
в этом случае подразумевается, что спецификатор действует на все оставшиеся дан-
ные. Вот полный список спецификаторов формата:
r a — строка, свободные места в поле заполняются символом с кодом 0;
r A — строка, свободные места заполняются пробелами;
r h — шестнадцатеричная строка, младшие разряды в начале;
r H — шестнадцатеричная строка, старшие разряды в начале;
r c — знаковый байт (символ);
r C — беззнаковый байт;
r s — знаковое короткое целое (16 битов, порядок байтов определяется архитекту-
рой процессора);
r S — беззнаковое короткое целое;
r n — беззнаковое целое (16 битов, старшие разряды в конце);
r v — беззнаковое целое (16 битов, младшие разряды в конце);
r i — знаковое целое (размер и порядок байтов определяется архитектурой);
r I — беззнаковое целое;
r l — знаковое длинное целое (32 бита, порядок байтов определяется архитекту-
рой);
r L — беззнаковое длинное целое;
r N — беззнаковое длинное целое (32 бита, старшие разряды в конце);
r V — беззнаковое целое (32 бита, младшие разряды в конце);
r f — число с плавающей точкой (зависит от архитектуры);
r d — число с плавающей точкой двойной точности (зависит от архитектуры);
r x — символ с нулевым кодом;
r X — возврат назад на 1 байт;
r @ — заполнение нулевым кодом до заданной абсолютной позиции.
Немало, не правда ли? Вот пример использования этой функции:
222 Часть IV. Стандартные функции PHP

// Целое, целое, все остальное — символы


$bindata = pack("nvc*", 0x1234, 0x5678, 65, 66);

После выполнения приведенного кода в строке $bindata будет содержаться 6 байтов


в такой последовательности: 0x12, 0x34, 0x78, 0x56, 0x41, 0x42 (в шестнадцатерич-
ной системе счисления).
array unpack(string $format, string $data)

Функция unpack() выполняет действия, обратные pack() — распаковывает строку


$data, пользуясь информацией о формате $format. Возвращает она ассоциативный
массив, содержащий элементы распакованных данных. Строка $format задается
немного в другом формате, чем в функции pack(), а именно, после каждого специ-
фикатора (или после завершающего его числа) должно "впритык" следовать имя
ключа в ассоциативном массиве. Разделяются параметры при помощи символа /.
Например:
$array=unpack("c2chars/nint", $bindata);
В результирующий массив будут записаны элементы с ключами: chars1, chars2 и
int. Как видим, если после спецификатора задано число, то к имени ключа будут
добавлены номера 1, 2 и т. д., т. е. в массиве появятся несколько ключей, отличаю-
щихся суффиксами.
Когда бывают полезны функции pack() и unpack()? Например, вы считали участок
GIF-файла, содержащий его размер в пикселах, и хотите преобразовать бинарную 32-
битную ячейку памяти в формат, понятный PHP. Или, наоборот, стремитесь работать
с файлами с фиксированным размером записи. В этом случае вам и пригодятся рас-
сматриваемые функции. Вообще говоря, функции pack() и unpack() применяются
сравнительно редко. Это связано с тем, что в PHP практически все действия, которые
могут потребовать работы с бинарными данными (например, анализ файла с рисун-
ком с целью определения его размера), уже реализованы в виде встроенных функций
(в нашем примере с GIF-картинкой это GetImageSize()).

Хэш-функции
string md5(string $st)

Возвращает хэш-код строки $st, основанный на алгоритме корпорации RSA Data


Security под названием "MD5 Message-Digest Algorithm". Хэш-код — это просто
строка, практически уникальная для каждой из строк $st. То есть вероятность того,
что две разные строки, переданные в $st, дадут нам одинаковый хэш-код, стремится
к нулю.
Глава 12. Строковые функции 223

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

В то же время, если длина строки $st может достигать нескольких тысяч символов,
то ее MD5-код занимает максимум 32 символа.
Для чего нужен хэш-код и, в частности, алгоритм MD5? Например, для проверки па-
ролей на истинность. Пусть, к примеру, у нас есть система со многими пользователя-
ми, каждый из которых имеет свой пароль. Можно, конечно, хранить все эти пароли
в обычном виде, или зашифровать их каким-нибудь способом, но тогда велика веро-
ятность того, что в один прекрасный день этот файл с паролями у вас украдут. Если
пароли были зашифрованы, то, зная метод шифрования, не составит особого труда их
раскодировать. Однако можно поступить другим способом, при использовании кото-
рого даже если файл с паролями украдут, расшифровать его будет математически
невозможно. Сделаем так: в файле паролей будем хранить не сами пароли, а их
(MD5) хэш-коды. При попытке какого-либо пользователя войти в систему мы вычис-
лим хэш-код только что введенного им пароля и сравним его с тем, который записан
у нас в базе данных. Если коды совпадут, значит, все в порядке, а если нет — что ж,
извините...
Конечно, при вычислении хэш-кода какая-то часть информации о строке $st безвоз-
вратно теряется. И именно это позволяет нам не опасаться, что злоумышленник, по-
лучивший файл паролей, сможет его когда-нибудь расшифровать. Ведь в нем нет са-
мих паролей, нет даже их каких-то связных частей!
Алгоритм MD5 специально был изобретен для того, чтобы как раз и обеспечить опи-
санную выше схему. Так как все же есть вероятность того, что у разных строк MD5-
коды совпадут, то, чтобы не дать возможность злоумышленнику войти в систему,
перебирая пароли с бешеной скоростью, алгоритм MD5 работает довольно медленно.
И его нельзя никак убыстрить, потому что это будет уже не MD5. Так что даже на
самых мощных компьютерах вряд ли получится перебирать более нескольких тысяч
паролей в секунду, а это совсем маленькая скорость, капля в океане возможных MD5-
кодов.
int crc32(string $str)
Функция crc32() вычисляет 32-битную контрольную сумму строки $str. То есть,
результат ее работы — 32-битное (4-байтовое) целое число. Эта функция работает
гораздо быстрее md5(), но в то же время выдает гораздо менее надежные "хэш-коды"
для строки. Так что, теперь, чтобы получить методом случайного подбора для двух
разных строк одинаковые "хэш-коды", вам потребуется не триллион лет работы само-
го мощного компьютера, а всего лишь… год-другой. Впрочем, если не использовать
генератор случайных чисел, а разобраться в алгоритме вычисления 32-битной кон-
224 Часть IV. Стандартные функции PHP

трольной суммы, эту же задачу легко можно решить буквально за секунду, потому
что алгоритм crc32 имеет неизмеримо большую предсказуемость, чем MD5.
string crypt(string $str [,string $salt])
Алгоритм шифрования DES до недавнего времени был стандартным для всех версий
Unix и использовался как раз для кодирования паролей пользователей (тем же самым
способом, о котором мы говорили при рассмотрении функции md5()). Но в последнее
время MD5 постепенно начал его вытеснять. Это и понятно: MD5 гораздо более на-
дежен. Рекомендую и вам везде применять md5() вместо crypt(). Впрочем, функ-
ция crypt() все же может понадобиться вам в одном случае: если вы хотите сгене-
рировать хэш-код для другой программы, которая использует именно алгоритм DES
(например, для сервера Apache).
Хэш-код для одной и той же строки, но с различными значениями $salt (кстати, это
должна быть обязательно двухсимвольная строка) дает разные результаты. Если па-
раметр $salt пропущен, PHP сгенерирует его случайным образом, так что не удив-
ляйтесь работе следующего примера:
$st="This is the test";
echo crypt($st)."<br>"; // можем получить, например, 7N8JKLKbBWEhg
echo crypt($st)."<br>"; // а здесь появится, например, Jsk746pawBOA2
Как видите, два одинаковых вызова crypt() без второго параметра выдают совер-
шенно разные хэш-коды. За деталями работы функции обращайтесь к документации
PHP.

Сброс буфера вывода


void flush()
Эта функция имеет очень и очень отдаленное отношение к работе со строками, но она
еще дальше отстоит от других функций. Именно поэтому я включил ее в данную гла-
ву. Начнем издалека: обычно при использовании echo данные не прямо сразу от-
правляются клиенту, а накапливаются в специальном буфере, чтобы потом транспор-
тироваться большой "пачкой". Так получается быстрее. Однако, иногда бывает нужно
досрочно отправить все данные из буфера пользователю, например, если вы что-то
выводите в реальном времени (так зачастую работают чаты). Вот тут-то вам и помо-
жет функция flush(), которая отправляет содержимое буфера echo в браузер поль-
зователя.
Глава 13

Работа с массивами

В части III книги мы уже рассматривали многие возможности, которые предоставля-


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

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

Сортировка массива по значениям


(asort()/arsort())
Функция asort() сортирует массив, указанный в ее параметре, так, чтобы его зна-
чения шли в алфавитном (если это строки) или в возрастающем (для чисел) порядке.
При этом сохраняются связи между ключами и соответствующими им значениями,
т. е. некоторые пары ключ=>значение просто "всплывают" наверх, а некоторые —
наоборот, "опускаются". Например:
$A=array("a"=>"Zero","b"=>"Weapon","c"=>"Alpha","d"=>"Processor");
asort($A);
foreach($A as $k=>$v) echo "$k=>$v ";
// выводит "c=>Alpha d=>Processor b=>Weapon a=>Zero"
// как видим, поменялся только порядок пар ключ=>значение

Функция arsort() выполняет то же самое, за одним исключением: она упорядочи-


вает массив не по возрастанию, а по убыванию.
Глава 13. Работа с массивами 227

Сортировка по ключам (ksort()/krsort())


Функция ksort() практически идентична функции asort(), с тем различием, что
сортировка осуществляется не по значениями, а по ключам (в порядке возрастания).
Например:
$A=array("d"=>"Zero", "c"=>"Weapon", "b"=>"Alpha", "a"=>"Processor");
ksort($A);
for(Reset($A); list($k,$v)=each($A);) echo "$k=>$v ";
// выводит "a=>Processor b=>Alpha c=>Weapon d=>Zero"
Функция для сортировки по ключам в обратном порядке называется krsort() и
применяется точно в таком же контексте, что и ksort().

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

О функциях мы поговорим в главе 14, а пока, я надеюсь, все должно быть яс-
но из примера (листинг 13.1).

Листинг 13.1. Сортировка с помощью пользовательской функции

// Эта функция должна сравнивать значения $f1 и $f2 и возвращать:


// -1, если $f1<$f2,
// 0, если $f1==$f2
// 1, если $f1>$f2
// Под < и > понимается следование этих имен в выводимом списке
function FCmp($f1,$f2)
{ // Каталог всегда предшествует файлу
if(is_dir($f1) && !is_dir($f2)) return -1;
// Файл всегда идет после каталога
if(!is_dir($f1) && is_dir($f2)) return 1;
// Иначе сравниваем лексикографически
if($f1<$f2) return -1; elseif($f1>$f2) return 1; else return 0;
228 Часть IV. Стандартные функции PHP

}
// Пусть $Files содержит массив с ключами — именами файлов
// в текущем каталоге. Отсортируем его.
uksort($Files,"FCmp"); // передаем функцию сортировки "по ссылке"

Конечно, связи между ключами и значениями функцией uksort() сохраняются, т. е.,


опять же, некоторые пары просто "всплывают" наверх, а другие — "оседают".

Сортировка по значениям
при помощи функции uasort()
Функция uasort() очень похожа на uksort(), с той разницей, что сменной (поль-
зовательской) функции сортировки "подсовываются" не ключи, а очередные значения
из массива. При этом также сохраняются связи в парах ключ=>значение.

Переворачивание массива array_reverce()


Функция array_reverse() возвращает массив, элементы которого следуют в об-
ратном порядке относительно массива, переданного в параметре. При этом связи ме-
жду ключами и значениями, конечно, не теряются. Например, вместо того, чтобы
ранжировать массив в обратном порядке при помощи arsort(), мы можем отсорти-
ровать его в прямом порядке, а затем перевернуть:
$A=array("a"=>"Zero","b"=>"Weapon","c"=>"Alpha","d"=>"Processor");
asort($A);
$A=array_reverse($A);
Конечно, указанная последовательность работает дольше, чем один-единственный
вызов arsort().

Сортировка списка sort()/rsort()


Эти две функции предназначены в первую очередь для сортировки списков (напоми-
наю, что под списками я понимаю массивы, ключи которых начинаются с 0 и не
имеют пропусков). Функция sort() сортирует список (разумеется, по значениям) в
порядке возрастания, а rsort() — в порядке убывания. Например:
$A=array("One", "Two", "Three", "Four");
sort($A);
for($i=0; $i<count($A); $i++) echo "$i:$A[$i] ";
// выводит "0:Four 1:Two 2:Three 3:One"
Глава 13. Работа с массивами 229

Любой ассоциативный массив воспринимается этими функциями как список.


То есть после упорядочивания последовательность ключей превращается в
0,1,2,..., а значения нужным образом перераспределяются. Как видим, связи
между парами ключ=>значение не сохраняются, более того — ключи просто
пропадают, поэтому сортировать что-либо, отличное от списка, вряд ли целе-
сообразно.

Сортировка списка при помощи


функции usort()
Эта функция как бы является "гибридом" функций uasort() и sort(). От sort()
она отличается тем, что критерий сравнения обеспечивается пользовательской функ-
цией. А от uasort() — тем, что она не сохраняет связей между ключами и значе-
ниями, а потому пригодна разве что для сортировки списков. Вот тривиальный при-
мер:
function FCmp($a,$b) { return strcmp($a,$b); }
$A=array("One","Two","Three","Four");
usort($A);
for($i=0; $i<count($A); $i++) echo "$i:$A[$i] ";
// выводит "0:Four 1:One 2:Three 3:Two"

Использованная нами функция strcmp(), как и ее пращур в Си, возвращает -1, если
$a<$b, 0, если они равны, и 1, если $a>$b. В принципе, приведенный здесь пример
полностью эквивалентен простому вызову sort().

Перемешивание списка shuffle()


Функция shuffle() "перемешивает" список, переданный ей первым параметром,
так, чтобы его значения распределялись случайным образом. Обратите внимание,
что, во-первых, изменяется сам массив, а во-вторых, ассоциативные массивы воспри-
нимаются как списки. Пример:
$A=array(10,20,30,40,50);
shuffle($A);
foreach($A as $v) echo "$v ";
Приведенный фрагмент выводит числа 10, 20, 30, 40 и 50 в случайном порядке.

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


к запуску очередность следования чисел не изменяется. Это свойство обу-
словлено тем, что функция shuffle() использует стандартный генератор
230 Часть IV. Стандартные функции PHP

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


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

Ключи и значения
array array_flip(array $Arr)
Эта функция "пробегает" по массиву и меняет местами его ключи и значения. Исход-
ный массив $Arr не изменяется, а результирующий массив просто возвращается.
Конечно, если в массиве присутствовали несколько элементов с одинаковыми значе-
ниями, учитываться будет только последний из них:
$A=array("a"=>"aaa", "b"=>"aaa", "c"=>"ccc");
$A=array_flip($A);
// теперь $A===array("aaa"=>"b", "ccc"=>"c");

list array_keys(array $Arr [,mixed $SearchVal])

Функция возвращает список, содержащий все ключи массива $Arr. Если задан не-
обязательный параметр $SearchVal, то она вернет только те ключи, которым соот-
ветствуют значения $SearchVal.

Фактически, эта функция с заданным вторым параметром является обратной


по отношению к оператору [] — извлечению значения по его ключу.

list array_values(array $Arr)


Функция array_values() возвращает список всех значений в ассоциативном мас-
сиве $Arr. Очевидно, такое действие бесполезно для списков, но иногда оправдано
для хэшей.
bool in_array(mixed $val, array $Arr)
Возвращает true, если элемент со значением $val присутствует в массиве $Arr.
Впрочем, если вам часто приходится проделывать эту операцию, подумайте: не луч-
ше ли будет воспользоваться ассоциативным массивом и хранить данные в его клю-
чах, а не в значениях? На этом вы можете сильно выиграть в быстродействии.
array array_count_values(list $List)
Эта функция подсчитывает, сколько раз каждое значение встречается в списке $List,
и возвращает ассоциативный массив с ключами — элементами списка и значения-
ми — количеством повторов этих элементов. Иными словами, функция
Глава 13. Работа с массивами 231

array_count_values() подсчитывает частоту появления значений в списке $List.


Вот пример:
$List=array(1, "hello", 1, "world", "hello");
array_count_values($array);
// возвращает array(1=>2, "hello"=>2, "world"=>1)

Комплексная замена в строке


В предыдущей главе мы рассматривали функцию strtr(), которая заменяла в стро-
ке одни буквы на другие, и функцию str_replace(), осуществляющую контекст-
ный поиск и замену. В свете ассоциативных массивов эти две функции объединяются
в одну, также называющуюся strtr(), но несущую в себе возможности
str_replace().
string strtr(string $st, array $Substitutes)

Эта функция (заметьте — с двумя параметрами, а не с тремя, как обычная strtr()!)


берет строку $st и проводит в ней контекстный поиск и замену: ищутся подстроки —
ключи в массиве $Substitutes — и замещаются на соответствующие им значения.
Таким образом, теперь мы можем выполнить несколько замен сразу, не используя
str_replace() в цикле:
$Subs=array(
"<name>" => "Larry",
"<time>" => date("d.m.Y")
);
$st="Привет, <name>! Сейчас <time>";
echo strtr($st,$Subs);

А вот как можно "отменить" действие функции HtmlSpecialChars():


$Trans=array_flip(get_html_translation_table());
$st=strtr($st, $Trans);

В результате мы из строки, в которой все спецсимволы заменены на их HTML-


эквиваленты, получим исходную строку во всей ее первозданной красе. Функции
get_html_translation_table() не уделено много внимания в этой книге. Она
возвращает таблицу преобразований, которая применяется при вызове
HtmlSpecialChars().

Функция strtr() начинает поиск с самой длинной подстроки и не проходит по


одному и тому же ключу дважды.
232 Часть IV. Стандартные функции PHP

Слияние массивов
array array_merge(array $A1, array $A2, ...)
Функция array_merge() призвана устранить все недостатки, присущие оператору +
для слияния массивов. А именно, она сливает массивы, перечисленные в ее аргумен-
тах, в один большой массив и возвращает результат. Если в массивах встречаются
одинаковые ключи, в результат помещается пара ключ=>значение из того массива,
который расположен правее в списке аргументов. Однако это не затрагивает число-
вые ключи: элементы с такими ключами помещаются в конец результирующего мас-
сива в любом случае.
Таким образом, с помощью array_merge() мы можем избавиться от всех недостатков
оператора + для массивов. Вот пример, сливающий два списка в один:
$L1=array(10,20,30);
$L2=array(100,200,300);
$L=array_merge($L1,$L2);
// теперь $L===array(10,20,30,100,200,300);
Всегда используйте эту функцию, если вам нужно работать именно со списками, а не
с обычными ассоциативными массивами.

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


array array_slice(array $Arr, int $offset [, int $len])
Эта функция возвращает часть массива ассоциативного массива, начиная с пары
ключ=>значения со смещением (номером) $offset от начала и длиной $len (если
последний параметр не задан, до конца массива).
Параметры $offset и $len задаются по точно таким же правилам, как и аналогич-
ные параметры в функции substr(). А именно, они могут быть отрицательными (в
этом случае отсчет осуществляется от конца массива),
и т. д. Вот несколько примеров из документации PHP:
$input = array ("a", "b", "c", "d", "e");
$output = array_slice ($input, 2); // "c", "d", "e"
$output = array_slice ($input, 2, -1); // "c", "d"
$output = array_slice ($input, -2, 1); // "d"
$output = array_slice ($input, 0, 3); // "a", "b", "c"

Вставка/удаление элементов
Мы уже знаем несколько операторов, которые отвечают за вставку и удаление эле-
ментов. Например, оператор [] (пустые квадратные скобки) добавляет элемент в ко-
Глава 13. Работа с массивами 233

нец массива, присваивая ему числовой ключ, а оператор Unset() вместе с извлече-
нием по ключу удаляет нужный элемент. Язык PHP версии 4 поддерживает и многие
другие функции, которые иногда бывает удобно использовать.
int array_push(alist &$Arr, mixed $var1 [, mixed $var2, …])

Эта функция добавляет к списку $Arr элементы $var1, $var2 и т. д. Она присваива-
ет им числовые индексы — точно так же, как это происходит для стандарных []. Ес-
ли вам нужно добавить всего один элемент, наверное, проще и будет воспользоваться
этим оператором:
array_push($Arr,1000); // вызываем функцию…
$Arr[]=100; // то же самое, но короче

Обратите внимание, что функция array_push() воспринимает массив, как стек, и


добавляет элементы всегда в его конец. Она возвращает новое число элементов в
массиве.
mixed array_pop(list &$Arr)

Функция array_pop(), а противоположность array_push(), снимает элемент с


"вершины" стека (то есть берет последний элемент списка) и возвращает его, удалив
после этого его из $Arr. С помощью этой функции мы можем строить конструкции,
напоминающие стек. Если список $Arr был пуст, функция возвращает пустую стро-
ку.
int array_unshift(list &$Arr, mixed $var1 [, mixed $var2, …])

Функция очень похожа на array_push(), но добавляет перечисленные элементы не


в конец, а в начало массива. При этом порядок следования $var1, $var2 и т. д. оста-
ется тем же, т. е. элементы как бы "вдвигаются" в список слева. Новым элементам
списка, как обычно, назначаются числовые индексы, начиная с 0; при этом все ключи
старых элементов массива, которые также были числовыми, изменяются (чаще всего
они увеличиваются на число вставляемых значений). Функция возвращает новый
размер массива. Вот пример ее применения:
$A=array(10,"a"=>20,30);
array_unshift($A,"!","?");
// теперь $A===array(0=>"!", 1=>"?", 2=>10, a=>20, 3=>30)

mixed array_shift(list &$Arr)

Эта функция извлекает первый элемент массива $Arr и возвращает его. Она сильно
напоминает array_pop(), но только получает начальный, а не конечный элемент, а
также производит довольно сильную "встряску" всего массива: ведь при извлечении
первого элемента приходится корректировать все числовые индексы у всех оставших-
ся элементов…
array array_unique(array $Arr)
234 Часть IV. Стандартные функции PHP

Функция array_unique() возвращает массив, составленный из всех уникальных


значений массива $Arr вместе с их ключами. В результирующий массив помещают-
ся первые встретившиеся пары ключ=>значение:
$input=array("a" => "green", "red", "b" => "green", "blue", "red");
$result=array_unique($input);
// теперь $result===array("a"=>"green", "red", "blue");

array array_splice(array &$Arr, int $offset [, int $len] [, int $Repl])

Эта функция, также как и array_slice(), возвращает подмассив $Arr, начиная с


индекса $offset максимальной длины $len, но, вместе с тем, она делает и другое
полезное действие. А именно, она заменяет только что указанные элементы на то, что
находится в массиве $Repl (или просто удаляет, если $Repl не указан). Параметры
$offset и $len задаются так же, как и в функции substr() — а именно, они могут
быть и отрицательными, в этом случае отсчет начинается от конца массива. За де-
тальными разъяснениями обращайтесь к описанию функции substr(), рассмотрен-
ной в предыдущей главе.
Приведу несколько примеров:
$input=array("red", "green", "blue", "yellow");
array_splice($input,2);
// Теперь $input===array("red", "green")
array_splice($input,1,-1);
// Теперь $input===array("red", "yellow")
array_splice($input, -1, 1, array("black", "maroon"));
// Теперь $input===array("red", "green", "blue", "black", "maroon")
array_splice($input, 1, count($input), "orange");
// Теперь $input===array("red", "orange")

Последний пример показывает, что в качестве параметра $Repl мы можем указать и


обычное, строковое значение, а не массив из одного элемента.

Переменные и массивы
array compact(mixed $vn1 [, mixed $vn2, …])
Функция compact(), впервые появившаяся в PHP версии 4, упаковывает в массив
переменные из текущего контекста (глобального или контекста функции), заданные
своими именами в $vn1, $vn2 и т. д. При этом в массиве образуются пары с ключа-
ми, равными содержимому $vnN, и значениями соответствующих переменных. Вот
пример использования этой функции:
$a="Test string";
Глава 13. Работа с массивами 235

$b="Some text";
$A=compact("a","b");
// теперь $A===array("a"=>"Test string", "b"=>"Some text")
Почему же тогда параме