Котеров Д. В. Самоучитель РНР 4. — СПб.: БХВ-Петербург, 2003. — 576 с.: ил. ISBN 5-94157-071-6 Учебное пособие по использованию языка РНР версии 4 содержит обширную ин- формацию о приемах, призванных в кратчайшие сроки сделать новичка, владею- щего хотя бы одним алгоритмическим языком, Web-профаммистом. Рассматрива- ются основы протоколов HTTP и CGI, схемы разработки крупных сценариев на РНР, синтаксис языка и работа с простейшими функциями, объектно-ориентиро- ванное программирование на РНР с применением идеологии интерфейсов, мани- пуляции со строками и массивами, создание баз данных и многое другое. Для программистов и Web-разработчиков УДК 681.3.06 Группа подготовки издания: Главный редактор Екатерина Кондукова Зав. редакцией Наталья Таркова Редактор Евгений Васильев Компьютерная верстка Натальи Смирновой Корректор Наталия Першакова Дизайн обложки Игоря Цырульникова Зав. производством Николай Тверских Лицензия ИД № 02429 от 24.07.00. Подписано в печать 27.01.03. Формат 70x100V, . Печать офсетная. Усл. печ. л. 46,44. e Доп. тираж 5000 экз. Заказ Me 699 "БХВ-Петербург", 198005, Санкт-Петербург, Измайловский пр., 29. Гигиеническое заключение на продукцию, товар № 77.99.02.953.Д.001537.03.02 от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России. Отпечатано с готовых диапозитивов в Академической типографии "Наука" РАН. 199034, Санкт-Петербург, 9 линия, 12. ISBN 5-94157-071-6 с Котеровд. в., 2001 О Оформление, издательство "БХВ-Петербург", 2001 Содержание Предисловие 1 Чего хочет программист от своей профессии 2 Временные затраты 3 О чем эта книга 4 Общая структура книги 5 ЧАСТЬ I. ОСНОВЫ WEB-ПРОГРАММИРОВАНИЯ 9 Глава 1. Принципы работы Интернета 11 Протоколы передачи данных 11 Семейство TCP/IP 13 Адресация с Сети 14 IP-адрес 14 Доменное имя 16 Порт ' 19 Терминология 20 Сервер 20 Узел 2 1 Порт 2 1 Сетевой демон 22 Провайдер 22 Хост.... 22 Виртуальный хост 23 Хостинг-провайдер (хостер) 23 Хостинг 24 Сайт 24 HTML-документ 24 Страница (или HTML-страница) 24 Web-программирование 25 World Wide Web и URL 25 Протокол 26 Имя хоста 26 Порт 26 Путь к странице 27 Глава 2. Интерфейс CGI 28 Что такое CGI? 28 Секреты URL 29 Заголовки и метод GET 30 GET 32 POST 32 Content-type 32 User-Age n t 33 IV Содержание Referer 33 Content-length ' 33 Cookie 34 Accept 34 Эмуляция браузера через telnet 34 Метод POST 35 Кодировки и форматы данных 36 Что такое формы и для чего они нужны 37 Передача параметров "вручную" 38 Использование формы 38 Абсолютный и относительный путь к сценарию 39 Метод POST формы 40 Глава 3. CGI изнутри 42 Передача документа пользователю 43 Заголовки ответа 44 Пример CGI-сценария 46 Передача информации CGI-сценарию 48 Переменные окружения 48 Передача параметров методом GET 50 Передача параметров методом POST 51 Расшифровка URL-кодированных данных 53 Формы 56 Тэг — различные поля ввода 57 Тэг Как легко видеть, этот тэг имеет закрывающий парный . Параметр width за- дает ширину поля ввода в символах, a height — его высоту. Параметр wrap определяет, как будет выглядеть текст в поле ввода. Он может иметь одно из трех значений (по умолчанию подразумевается попе) . П virtual — наиболее удобный тип вывода. Справа от текстового поля вы- водится полоса прокрутки , и текст, который набирает пользователь, внешне выглядит разбитым на строки в соответствии с шириной поля ввода, причем перенос осуществляется по словам. Однако символ новой строки вставляется в текст только при нажатии . CD Physical — зависит от реализации браузера, обычно очень похож на попе . П None — текст отображается в том виде, в котором заносится. Если он не умещается в текстовое поле, активизируются линейки прокрутки (в том числе, и горизонтальная). После отправки формы текст, который ввел пользователь, будет, как обыч- но, представлен парой имя=текст , аналогично тэгу однострочного поля вво- да . Тэг Мы видим, что и этот тэг имеет парный закрывающий . Кроме того, его су- ществование немыслимо без тэгов можно опускать, если упрощение не создает конфликтов с синтаксисом HTML (в действительно- сти это можно делать почти всегда). Давайте теперь посмотрим, в какой форме пересылаются данные списка сценарию. Ну, со списком одиночного выбора вроде бы ясно — просто пе- редается пара имя=значение , Где имя — ИМЯ ТЭГЗ OflMH
flBa
TpM
Если теперь пользователь установит сразу все флажки, то сценарию посту- пит строка (конечно, в URL-кодированном виде): имя=Один&имя=Два&имя=Три Из всего сказанного следует не очень утешительный вывод: при разборе строки параметров в сценарии мы не можем полагаться на то, что каждой переменной соответствует только одно значение. Нам придется учитывать, что их может быть не "один", а "много". А это очень неприятно с точки зре- ния программирования — особенно на Си. Попутно мы обнаружили , что любой multiple-список может быть представ- лен набором флажков (независимых переключателей), а любой не-miltiple - в виде нескольких радиокнопок. Так что, вообще говоря, тэг — некоторое функциональное излишество, и с точки зрения сценария вполне может заменяться флажками и радиокнопками. ----------------------- Page 79----------------------- 64 Часть I. Основы Web-программирования Загрузка файлов ( Замечание Данный раздел главы предназначен скорее для ознакомления, нежели для при- менения в качестве точной инструкции по загрузке файлов. Он прекрасно демон- стрирует, почему нам так удобно использовать РНР для программирования в Web . Организацию загрузки файлов в РНР мы подробно разберем в части V. Иногда бывает просто необходимо позволить пользователю не только за- полнить текстовые поля формы и установить соответствующие переключа- тели, но также и указать несколько файлов, которые будут впоследствии загружены с компьютера пользователя на сервер. Для этого в языке HTML предусмотрены специальные средства. Рассмотрим их подробнее. Формат данных В свое время я говорил, что все данные из формы при передаче их на сервер упаковываются в строку при помощи символов ?, & и =. Легко видеть, что при загрузке файлов такой способ, хотя и приемлем , но будет существенно увеличивать размер передаваемой информации. Действительно, ведь боль- шинство файлов — бинарные, а мы знаем, что при URL-кодировании дан- ные таких файлов сильно "распухают" - - примерно в три раза (например, простой нулевой байт при URL-кодировании превратится в %оо) . Это силь- но замедлит передачу и увеличит нагрузку на канал. И вот, отчасти специ- ально для решения указанной проблемы был изобретен другой формат пе- редачи данных, отличный от того, который мы до сих пор рассматривали. В нем уже не используются пресловутые символы ? и & . Кроме того, похо- же, в случае применения такого формата передачи может быть задействован только метод POST, но не метод GET . Нас это вполне устроит — ведь файлы обычно большие, и доставлять их через GET вряд ли разумно... Если нужно указать браузеру, что в какой-то форме следует применять дру- гой формат передачи, следует в соответствующем тэге
задать атрибут enctype=muitipart/form-data. (Кстати говоря, если этот атрибут не указан, то форма считается обычной, что эквивалентно enctype=appiication/x - www-form-uriencode d — именно так обозначается привычный нам формат передачи.) После этого данные, поступившие от нашей формы, будут вы- глядеть как несколько блоков информации (по одному на элемент формы). Каждый такой блок очень напоминает HTTP-формат "заголовки-данные", используемый при традиционном формате передачи. Выглядит блок при- мерно так (\п , как всегда, обозначает символ перевода строки): Идентификатор_начала\п Content-Disposition : form-data ; пате="имя"\п ----------------------- Page 80----------------------- Глава 3. CGI изнутри 65 \n значение\n Например, пусть у нас есть форма: Листинг 3.7. Multipart-форма Name : <Ьг> Box: Area : Это какой-то TeKCT
Данные, поступившие по нажатии кнопки submit на сервер, будут иметь следующий вид: 127462537625367\п Content-Disposition : form-data ; name="Name"\n \n Мое имя\п 127462537625367\n Content-Disposition: form-data; name="Box"\n \n l\n 127462537625367\n Content-Disposition: form-data; name="Area"\n \n Это какой-то текстХп Заметьте, что несколько дефисов и число (которое мы ранее назвали Идентификатор_начала) предшествуют каждому блоку. Более того, строка из дефисов и этого числа служит своеобразным маркером, который разделяет блоки. Очевидно, эта строка должна быть уникальной во всех данных. Имен- но так ее и формирует браузер. Правда, сказанное означает, что сегодня иден- тификатор будет одним, а завтра, возможно, совсем другим. Так что нам при- дется, прежде чем анализировать данные, считать этот идентификатор в буфер (им будет последовательность символов до первого символа \п) . Внимание Стандарт протокола HTTP говорит нам, что идентификатор начала также дол- жен быть доступен через одну из переменных окружения. Но я не помню и не ----------------------- Page 81----------------------- 66 Часть I. Основы Web-программирования хочу знать ее название — сейчас объясню, почему. Некоторые браузеры (особенно старые) путают этот идентификатор и присылают его неправильно — с двумя предшествующими минусами (а остальные — без них), так что сцена- рии, не рассчитывающие на такой подвох, перестанут работать. Никогда не по- лагайтесь на эту переменную окружения (даже если узнаете, как она называет- ся)! Вместо этого читайте последовательность символов до первого перевода строки и воспринимайте именно ее как разделитель. Далее алгоритм разбора должен быть следующим: в цикле мы пропускаем сим- волы идентификатора и перевода строки, извлекаем подстроку имя="что-то" (не обращая внимания на Content-Disposition) , дожидаемся двух символов перевода строки и затем считаем значением соответствующего поля все те данные, которые размещены до строки ХпИдентификатор (или же до конца, если такой строки больше нет). Как видите, все довольно просто. Внимание Стандарт HTTP предписывает, чтобы перевод строки содержал два символа — \г\п , а не один \п. Как вы уже, наверное, чувствуете, существуют браузеры, которые об этом и не догадываются и посылают только один \п. Так что, будь- те готовы к тому, чтобы правильно обрабатывать и эту ситуацию. Тэг загрузки файла (file) Теперь вернемся к тому, с чего начали — к загрузке файлов. Сначала выяс- ним, какой тэг надо вставить в форму, чтобы в ней появился соответствую- щий элемент управления — поле ввода текста с кнопкой Browse справа. Та- ким тэгом является разновидность : Пусть пользователь выбрал какой-то файл (скажем , с именем каталог\ имя^файла) и нажал кнопку отправки . В этом случае для нашего элемента формы создается один блок примерно такого вида: —127462537625367\п Content-Disposition : form-data ; пате="имя_элемента" ; Ч> f11епате="каталог\имя файла"\п \п Бинарные данные этого файла любой длины . Здесь могут быть совершенно любые байты без всякого ограничения . \п ----------------------- Page 82----------------------- Глава 3. CGI изнутри 67 Мы видим, что сценарию вместе с содержимым файла передается и его имя в системе пользователя (параметр filename) . На этом, пожалуй, и завершим обозрение возможностей загрузки файлов. Надеюсь, я посеял в вас неприязненное отношение к подобным методам: действительно , программировать это — не самое приятное занятие на свете (укажу только на то, что придется использовать приемы программной буфе- ризации , чтобы правильно найти разделитель). Вот еще один довод в пользу РНР, в котором не нужно выполнять в принципе никакой работы, чтобы создать полноценный сценарий с возможностью загрузки файла. Что такое Cookies и с чем их едят Сначала хотелось бы сказать пару слов насчет самого термина Cookies (это множественное число, произносится как "кукис" или , более "русифи- цировано", "куки"). В буквальном переводе слово звучит как "печенье", и почему компания Netscape так назвала свое изобретение, не совсем ясно. А поскольку писать "печенье" несколько неудобно, чтобы не вызывать несвое- временных гастрономических ассоциаций, везде, где можно, я буду приме- нять именно слово Cookies, с большой буквы, во множественном числе и мужского рода. Кстати, в единственном числе это понятие записывается Cookie и произносится на русский манер -- "кука". Начну с примера. Скажем, мы хотим завести гостевую книгу : пользователь вводит свое имя, E-mail, адрес домашней странички (и другую информацию о себе), наконец, текст сообщения, и после нажатия на кнопку его мысль отправляется в путешествие по проводам и серверам, чтобы в конце концов попасть в некую базу данных на нашем сервере и остаться там на веки веч- ные. М-да. Теперь предположим, что эта наша гостевая книга — довольно часто посе- щаемое место, у нее есть постоянные пользователи, которые несколько раз на дню оставляют там свои сообщения. Что же — им придется каждый раз вводить свое имя, адрес электронной почты и другую информацию в пустые поля? Как бы сделать так, чтобы это все запоминалось где-то, чтобы даже при следующем запуске браузера нужные поля формы инициализировались автоматически, разумеется — у каждого пользователя индивидуально, тем, чем он заполнил их ранее? Чтобы этого добиться, в принципе существуют два метода. Оба они имеют как достоинства, так и недостатки, и вскоре мы увидим , в чем же они за- ключаются. Первый способ: хранить на сервере отдельную базу данных, в которой для каждого пользователя по его IP-адресу можно было бы получить последние им же введенные данные. В принципе, это решение довольно универсально, однако у него есть два существенных недостатка, которые сводят на нет все ----------------------- Page 83----------------------- 68 Часть I. Основы Web-программирования преимущества. Главный из них — то, что большинство пользователей не име- ют фиксированного (как говорят, статического) IP-адреса — каждый раз при входе в Интернет он назначается им (провайдером) автоматически (сервер провайдера обычно имеет контроль над несколькими десятками зарезервиро- ванных IP-адресов, доступных для пользователя, и выбирает для него тот, ко- торый еше не занят кем-то еще). Таким образом, мы вряд ли сможем опреде- лить, кто на самом деле зашел в нашу гостевую книгу. Второй недостаток мало связан с первым — дело в том, что если ваших пользователей очень много, то довольно проблематично в принципе иметь такую базу данных, ведь она занимает место на диске, не говоря уж о издержках на поиск в ней. Второй способ подразумевает использование Cookies. Cookie — это неболь- шая именованная порция информации, которая хранится в каталоге браузе- ра пользователя (а не на сервере, заметьте!), но которую сервер (а точнее, сценарий) волен в любой момент изменить . Кстати, сценарий также получа- ет все Cookies, которые сохранены на удаленном компьютере, при каждом своем запуске, так что он может в любой момент времени узнать, что же там у пользователя установлено. Самым удобным в Cookies является то, что они могут храниться недели и годы до тех пор, пока их не обновит сервер или же пока не истечет срок их жизни (который тоже назначается сценарием при создании Cookie). Таким образом, мы можем иметь Cookies, которые "живут" всего несколько минут (или до того момента, пока не закроют брау- зер), а можем -- "долгожителей". Не правда ли, последний способ представляет собой идеальное решение для нашей проблемы? Действительно, теперь сцеанарию гостевой книги доста- точно получить у пользователя его данные, запомнить их в Cookies (как это сделать — см. ниже), а затем работать, будто ничего и не произошло. Ко- нечно, перед выводом HTML-документа формы обязательно придется про- ставить значения value для некоторых элементов (которые, ясно, извлечены из соответствующих Cookies). Но не все так гладко. Конечно, и у этой схемы есть недостатки. Первый из них — не все браузеры поддерживают Cookies, а пользователи тех, которые под- держивают, иногда имеют обыкновение отключать Cookies — якобы для боль- шей безопасности (хотя безопасность тут совсем ни при чем, дело в самих этих пользователях). Второй недостаток заключается в том, что каждый браузер хра- нит свои Cookies отдельно. То есть Cookies, установленные при пользовании Internet Explorer, не будут "видны" при работе в Netscape, и наоборот. Но, согласитесь, все же это почти не умаляет достоинств Cookies — в конце концов, обычно пользователи работают только в одном из перечисленных браузеров. Кстати, все чаще в Internet Explorer. На момент написания этих строк указанный браузер имеет в несколько раз большие возможности, чем Netscape (работая при этом, правда, несколько медленнее). Что ж... Время покажет, кто из них выживет. ----------------------- Page 84----------------------- Глава 3. CGI изнутри 69 Но я несколько отклонился от темы. Как уже упоминалось, каждому Cookie сопоставлено время его жизни, которое хранится вместе с ним. Кроме это- го, имеется также информация об имени сервера, установившего этот Cookie, и URL каталога, в котором находился сценарий-хозяин в момент инициализации (за некоторыми исключениями). Зачем нужны имя сервера и каталог? Очень просто: дело в том, что сцена- рию передаются только те Cookies, у которых параметры с именем сервера и каталога совпадают соответственно с хостом и каталогом сценария (ну, на самом деле каталог не должен совпадать полностью, он может являться под- каталогом того, который создан для хранения Cookies). Так что совершенно невозможно получить доступ к "чужим" Cookies — браузер просто не будет посылать их серверу. Это и понятно: представьте себе, сколько ненужной информации передавалось бы сценарию, если бы все было не так (особенно если пользователь довольно активно посещает различные серверы, которые не прочь поставить ему свой набор Cookies). Кроме того, "чужие" Cookies не предоставляются в целях защиты информации от несанкционированного доступа — ведь в каком-то Cookie может храниться, скажем, важный пароль (как часто делается при авторизации), а он должен быть доступен только одному определенному хосту. Установка Cookie Мы подошли к вопросу: как же сценарий может установить Cookie в брау- зере пользователя? Ведь он работает "на одном конце провода", а пользова- тель — на другом. Решение довольно логично: команда установки Cookie — это просто один из заголовков ответа, передаваемых сервером браузеру. То есть, перед тем как выводить Content-type , мы можем указать некоторые команды для установки Cookie. Выглядит такая команда следующим обра- зом (разумеется, как и всякий заголовок, записывается она в одну строку): Set-Cookie: name=value ; ехр!гез=дата; с1ота!п=имя_хоста; path=nyTb ; secure Существует и другой подход активизировать Cookie — при помощи HTML- тэга . Соответственно, как только браузер увидит такой тэг, он зай- мется обработкой Cookie. Формат тэга такой: Мы можем видеть, что даже названия параметров в этих двух способах оди- наковы. Какой из них выбрать — решать вам: если все заголовки уже выве- дены к тому моменту, когда вам потребовалось установить Cookie, исполь- зуйте тэг . В противном случае лучше взять на вооружение заголовки, т. к. они не видны пользователю, а чем пользователь меньше видит при ----------------------- Page 85----------------------- 70 Часть I. Основы Web-программирования просмотре исходного текста страницы в браузере — тем лучше нам, про- граммистам. ^ Примечание Возможно, вы спросите, нахмурив брови: "Что же, с точки зрения программиста хороший пользователь — слепой пользователь?" Тогда я отвечу: "Что вы, нет и еще раз нет! Такой пользователь хорош лишь для дизайнера, для программи- ста же желателен пользователь безрукий (или, по крайней мере, лишенный клавиатуры и мыши)". Вот что означают параметры Cookie: name Вместо этой строки нужно задать имя , закрепленное за Cookie. Имя должно быть URL-кодированным текстом, т. е. состоять только из алфавитно-циф- ровых символов . Впрочем , обычно имена для Cookies выбираются именно так, чтобы их URL-кодированная форма совпадала с оригиналом . value Текст, который будет рассматриваться как значение Cookie. Важно отме- тить, что этот текст (ровно как и строка названия Cookie) должен быть URL-кодирован. Таким образом, я должен отметить неприятный факт, что придется писать еще и функцию URL-кодирования (которая, кстати, раза в 2 сложнее, чем функция для декодирования, т. к. требует дополнительного выделения памяти). expires Необязательная пара ехр!гез=дата задает время жизни нашего Cookie. Точ- нее, Cookie самоуничтожится, как только наступит указанная дата. Например, если задать expires=Friday,3i-Dec-9 9 2 3 : 5 9 : 5 9 GMT, то "печенье" будет "жить" только до 31 декабря 1999 года. Кстати, вот вам и вторая неприятность: хорошо, если мы знаем наверняка время "смерти" Cookie. А если нам нужно его вычислять на основе текущего времени (например, если мы хотим, чтобы Cookie существовал 10 дней после его установки, как в подавляющем боль- шинстве случаев и происходит)? Придется использовать функцию , которая формировала бы календарную дату в указанном выше формате. Кстати, если этот параметр не указан, то временем жизни будет считаться вся текущая сес- сия работы браузера, до того момента, как пользователь его закроет. domain Параметр с!ота1п=имя_хоста задает имя хоста, с которого установили Cookie. Ранее я уже говорил про этот параметр. Так вот, оказывается, его можно ----------------------- Page 86----------------------- Глава 3. CGI изнутри 7 1 менять вручную, прописав здесь нужный адрес, и таким образом "подарить" Cookie другому хосту. Только в том случае, если параметр не задан, имя хос- та определяется браузером автоматически. path Параметр path=nyrb обычно описывает каталог (точнее, URI) , в котором расположен сценарий, установивший Cookie. Как мы видим , этот параметр также можно установить вручную , записав в него не только каталог, а вооб- ще все, что угодно. Однако при этом следует помнить : указав хост, отлич- ный от хоста сценария, или путь, отличный от URI каталога (или родитель- ского каталога) сценария, мы тем самым никогда больше не увидим наш Cookie в этом сценарии. secure Этот параметр связан с защищенным протоколом передачи HTTPS, кото- рый в книге не рассматривается. Если вы не собираетесь писать сценарии для проведения банковских операций с кредитными карточками (или иные , требующие повышенной безопасности) , вряд ли стоит обращать на него внимание. После запуска сценария , выводящего соответствующий заголовок (или тэг ), у пользователя появится Cookie с именем name и значением value . Еще раз напоминаю : значения всех параметров Cookie должны быть URL - кодированы, в противном случае возможны неожиданности . Получение Cookies из браузера Получить Cookies для сценария несколько проще: все они хранятся в пере- менной окружения HTTP_COOKIE в таком же формате, как и QUERY_STRING , только вместо & используется ;. Например, если мы установили два Cookies: cookiel=valuel И cookie2=value2 , TO В переменной окружения НТТР_СООК1Е будет следующее: cookiel=valuel;cookie2=value2 . Сценарий должен разобрать эту строку, распаковать ее и затем работать по своему усмотрению. Пример программы для работы с Cookies В заключение приведу простой сценарий, который использует Cookies. Для упрощения в нем не производится URL-кодирование и декодирование - будем считать, что пользователь может печатать только на латинице . ----------------------- Page 87----------------------- 72 Часть I. Основы Web-программирования \ Листинг 3.8. Простой сценарий, использующий Cookies #include #include // начало программы 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 , // пропуская первые 5 символов (часть "name=") - // получим как раз текст пользователя strcpy(Buf, Query + 5); // Пользователь ввел имя — значит , нужно установить Cookie printf("Set-cookie : cook=%s ; " "expires=Friday,31-Dec-Ol 23:59:59 GMT" , Buf) ; // Теперь это — новое значение Cookie Cook=Buf; } // выводим страницу с формой printf("Content-type : text/html\n\n" ) ; printf ("\n") ; // если имя задано (не пустая строка) , приветствие if(strcmp (Cook , "")} printf("<Ь1>Привет , %s!\n",Cook) ; ----------------------- Page 88----------------------- Глава 3. CGI изнутри _ 73 // продолжаем printf ("
\n" ) ; printf("Baine имя: " ) ; printf ("\n" , Cook ) ; printf ( "\n " ) ; printf ( "\n " ) ; printf ("") ; Теперь при первом заходе на этот URL пользователь получит форму с пус- тым полем для ввода имени. Если он что-то туда напечатает и нажмет кноп- ку отправки, его информация запомнится браузером. Итак, посетив в любое время до 3 1 декабря 2001 года этот же URL, он увидит то, что напечатал давным-давно в текстовом поле. И, что самое важное, — его информацию "увидит" также и сценарий. Кстати, у злоумышленника нет никаких шансов получить значение Cookie посетителя, потому что оно хранится у него на компьютере, а не на сервере. И опять я намекаю на то, что использование Си и на этот раз довольно за- труднительно. Неудобно URL-декодировать и кодировать при установке Cookies, накладно разбирать их на части, да и вообще наша простая про- грамма получилась слишком длинной . Не правда ли, приятно будет обнару- жить , что в РНР все это реализовано автоматически: для работы с Cookies существует всего одна универсальная функция setcookie ( ) , а получение Cookies от браузера вообще не вызовет никаких проблем, потому что оно ничем не отличается от получения данных формы. Это логично. В самом деле, какая нам разница, какие данные пришли из формы, а какие — из Cookies? С точки зрения сценария — все равно... Но не буду забегать вперед. Займемся пока теорией авторизации. Авторизация Часто бывает нужно, чтобы на какой-то URL могли попасть только опреде- ленные пользователи. А именно, только те, у которых есть зарегистрирован- ное имя (login) и пароль (password). Механизм авторизации как раз и призван упростить проверку данных таких пользователей. Я не буду здесь рассматривать все возможности этого механизма по трем причинам. Во-первых, существует довольно много типов авторизации, раз- личающихся степенью защищенности передаваемых данных . Во-вторых, при написании обычных CGI-сценариев для того, чтобы включить меха- ----------------------- Page 89----------------------- 74 Часть I. Основы Web-программирования низм авторизации, необходимо провести некоторые манипуляции с на- стройками (файлами конфигурации) сервера, что, скорее всего, будет за- труднительно (ведь обычно компания, которая предоставляет услуги по об- служиванию виртуального хоста, не позволяет вмешиваться в настройки сервера). И наконец, в-третьих, весь механизм авторизации значительно уп- рощается и унифицируется при использовании РНР, и вам не придется ни- чего исправлять в этих злополучных настройках сервера. Так что давайте отложим практическое знакомство с авторизацией и займемся ее теорией. Расскажу вкратце о том, как все происходит на нижнем уровне при одном из самых простых типов авторизации — basic-автпоризации. Итак, предполо- жим , что сценарий посылает браузеру пользователя следующий заголовок: WWW-Authenticate : Basic геа1п\="имя_зоны" НТТР/1.0 401 Unauthorized " Обратите внимание на то, что последний заголовок несколько отличается по форме от обычных заголовков. Так и должно быть. Строка имя_зоны в пер- вом из них задает некоторый идентификатор, который будет определять, к каким ресурсам будет разрешен доступ зарегистрированным пользователям. При программировании CGI-сценариев этот параметр используется в ос- новном исключительно для формирования приветствия (подсказки) в диа- логовом окне, появляющемся в браузере пользователя (там отображается имя зоны), так что мы не будем вдаваться в детали относительно него. Затем, как обычно, посылается тело документа (сразу отмечу, что именно это тело ответа будет выдано пользователю, если он нажмет в диалоговом окне (см. ниже) кнопку Cancel, т. е. отменит вход). В этом случае происхо- дит нечто удивительное: в браузере пользователя появляется небольшое диа- логовое окно, в котором предлагается вести login и password. После того как пользователь это сделает, управление передается обратно серверу, который среди обычных заголовков запроса (которые посылает браузер) получает примерно такой: Authorization : Basic TG9naW46UGFzcw== Это — ни что иное, как закодированные данные, введенные пользователем. Теоретически, далее этот заголовок должен каким-то образом передаться сценарию (для этого как раз и необходимо добавление команд в файлы конфигурации сервера). Сценарий, декодировав его, может решить : то ли повторить всю процедуру сначала (если имя или пароль неправильные) , или же начать работать с сообщением "ОК., все в порядке, вы — зарегистриро- ванный пользователь". Предположим, что сценарий подтвердил верность данных и "пропустил" пользователя. В этом случае происходит еще одна вещь: login и password ----------------------- Page 90----------------------- Глава 3. CGI изнутри 75 пользователя запоминаются в скрытом Cookie, "живущем" в течение одной сессии работы с браузером. Затем, что бы мы ни делали, заголовок Authorization : Basic значение_Соо]<;1е будет присылаться для любого сценария (и даже для любого документа) на нашем сервере. Таким образом, посетителю , зарегистрировавшемуся однаж- ды , нет необходимости каждый раз заново набирать свое имя и пароль в течение текущего сеанса работы с браузером, т. е., пока пользователь его не закроет. И еще: после верной авторизации при вызове любого сценария будет уста- новлена переменная окружения REMOTE_USER , содержащая имя пользователя. Так что в дальнейшем можно ее задействовать для определения того, какой же посетитель зарегистрировался. ----------------------- Page 91----------------------- ----------------------- Page 92----------------------- ЧАСТЬ II. ВЫБОР И НАСТРОЙКА ИНСТРУМЕНТАРИЯ. WEB-СЕРВЕР APACHE ----------------------- Page 93----------------------- ----------------------- Page 94----------------------- Глава 4 Установка Apache Введение: зачем нужен домашний сервер? Эта часть книги поможет вам "скачать" и установить один из лучших серве- ров — Apache, а также те приложения , из-за которых большинство про- граммистов и любят Apache для Windows 95/98. Имеются в виду, конечно , интерпретатор РНР и популярная СУБД MySQL, также работающие под Windows . Прочитав эту часть книги и скачав дистрибутивы (заметьте, со- вершенно бесплатно!), вы будете вооружены всеми инструментами , которые так необходимы для профессиональной работы в Web! Примечание Бытует мнение, что MySQL (а тем более для Windows 95/98) нельзя получить бесплатно, а можно только купить. Так вот, можете вздохнуть с облегчением: недавно разработчики MySQL выпустили бесплатную версию сервера для Windows 95/98 , вы можете загрузить самую последнюю ее версию на офици- альном сайте MySQL: http://www.mysql.com . Даже если вы и не планируете в будущем использовать РНР, а предпочитаете другой язык (например, Perl), то после внимательного ознакомления с этой частью книги вы сможете на порядок упростить себе жизнь — точнее, ее часть, касающуюся написания и отладки сценариев. И это благодаря тому, что все описанное здесь почти на 100% совместимо с тем программным обеспече- нием, которое скорее всего установлено у вашего хостинг-провайдера (а больше половины современных хостинг-провайдеров работают с Unix, но не с Windows). Однако, если вы собираетесь всерьез заняться хостингом На платформе Win32, то лучше, наверное, будет использовать не Apache и РНР, а MIIS (Microsoft Internet Information Server — Информационный сервер Ин- тернета Microsoft) и ASP (Active Server Pages - Активные серверные страницы), про которые, я уверен, написано множество других книг. Эта часть книги , как уже говорилось, будет полезна не только программи- стам на РНР. Ведь часто возникает ситуация, когда необходимо проверить полный вид HTML-страницы. Однако чаше всего это невозможно при работе дома — технологии SSI (Server-Side Includes — Включения на стороне серве- ра), CGI (Common Gateway Interface — Общий шлюзовой интерфейс) и, ко- ----------------------- Page 95----------------------- 80 Часть II. Выбор и настройка инструментария. Web-сервер Apache нечно, РНР требуют использования сервера. Как же быть? Не стоит впадать в апатию — нужно просто установить на ваш домашний компьютер (пусть даже и не подключенный к Интернету) специальную программу -- Web- сервер. Вообще-то серверов существует множество — плохие и хорошие, медленные и быстрые... Я предлагаю вам установить сервер, подпадающий под категории, следующие за "и". А именно — Apache. Самое главное то, что это чуть ли не единственный сервер, который позволяет работать в Windows 95/98 с технологиями PHP, CGI и Perl-сценариями одновременно так же просто и непринужденно, как будто у вас инсталлирована Unix. Дистрибутивы и ссылки Я привожу список ссылок на сайты, на которых всегда можно найти самые свежие версии программных продуктов. Все описываемые здесь программы были загружены и установлены мной именно с этих сайтов. Итак: П официальный сайт Apache: http://www.apache.org; П официальный сайт РНР: http://www.php.net; П официальный сайт MySQL: http://www.mysql.com; П официальный сайт Active Perl: www.activestate.com; И еще несколько ссылок, полезных Web-программисту. П Всероссийский Клуб Вебмастеров: http://www.webclub.ru. П Клуб разработчиков РНР: http://www.phpclub.net. П Лаборатория dk: http://www.dklab.ru. От слов к делу: установка Apache Итак, вы решились установить на свой компьютер Apache для Windows 95/98. В таком случае вам следует запастись терпением и для начала "скачать" ди- стрибутив сервера с официального сайта Apache: http://www.apache.org. Со- ветую вам выбрать самую последнюю версию сервера для платформы Windows. Теперь нам предстоит настройка Apache для вашей системы. ( Замечание ^ Мы попросим вас в точности выполнять перечисленные ниже шаги, не про- пуская и не откладывая ни одного. Дело в том, что конфигурирование и на- стройка Apache — довольно непростая работа, которая обычно поручается профессионалам. Далее приводятся инструкции с довольно скупыми объяс- нениями, почему нужно сделать то или иное действие, в расчете на то, что вы будете соблюдать их буквально. В противном случае вам, скорее всего, ----------------------- Page 96----------------------- Глава 4. Установка Apache 81 придется дополнительно провести пару неприятных часов (или дней) за изу- чением документации Apache, в частности, той ее части, которая касается конфигурирования. Этап первый: установка 1. Запустите только что полученный файл дистрибутива Apache. В появив- шемся диалоговом окне нажмите кнопку Next (рис. 4.1), а затем — кноп- ку Yes, чтобы согласиться с условиями лицензии. Welcome to the Apache Web Server Setup program. This program will install Apache Web Setver on your computer. '. It is strongly recommended thai you exit all Windows pro grams before running this Setup program. Click Cancel to quit Setup onb* then dose emy programs you have running, pick Next to continue with the Setup program. WARNING: This program is protected by copyright faw end international treaties, . Unauthorized reproduction or distribution of this program, or any portion . of it may result in severe civil end criminal penalties, and will be prosecuted to the maximum extent possible under law. fciext> Рис. 4.1. Установка Apache Choose Destination Location Setup will install Apache Web Server in the following folder. ,,.;. . To insteif to this folder, dick Next To install to a different folder, click Browse and select another folder. Yo u can choose not to install Apache Web Server by clicking Cancel to exit Setup- Destination Folder ••-. '.--- •-. C:\Program FilesV^acfie GroupNApache Biowse... | : Адрес JCJ Z:\home\localhost d v Все папки x ; • ' ' " Pi i ! Ш SI (F:) j : as>sft(Z:) cgi ww\v B-Q home Й-Sl locaihost J CJ cgi Cl WWW LiJ Принтеры .d Рис. 4.4. Структура каталогов главного хоста П Задайте значение параметра serverName следующим образом: ServerName locaihost Только не забудьте раскрыть комментарий для поля serverName , т, е. уб- рать символ # перед этим параметром (установленный по умолчанию), поскольку все, что идет после этого символа и до конца строки, Apache игнорирует. П В поле DocumentRoot укажите тот каталог, в котором будут размещены ваши HTML-файлы. Мы ранее договорились, что это будет z:\home\localhost\www) DocumentRoot z:/home/localhost/www П Найдите секцию, начинающуюся строкой и заканчиваю- щийся строкой (такие блоки содержат установки для за- данного каталога и всех его подкаталогов). Этот блок может содержать множество комментариев — не обращайте на них внимания. Его нужно заменить на секцию следующего вида: Options Indexes Includes AllowOverride All Allow from all ----------------------- Page 100----------------------- Глава 4. Установка Apache 85 Этим вы обеспечите, что в данном блоке будут храниться настройки для всех каталогов по умолчанию (так как z : — корневой каталог). А именно, для всех каталогов по умолчанию предоставляется возможность автома- тической генерации индекса — списка содержимого каталога при про- смотре его в браузере, а также поддержка SSI и разрешение использовать файлы .htaccess для индивидуальных настроек каталогов. П Найдите аналогичный блок, начинающийся строкой И ЗЗКЗН- чиваюшийся ограничителем . Там будет много комментари- ев, не обращайте на них внимание. Эту секцию вам нужно удалить, т. к. все настройки для каталога со страничками должны наследоваться от на- строек по умолчанию, которые мы только что установили. П Инициализируйте параметр Directorylndex так: Directorylndex index.htm index.html Это — так называемые файлы индекса, которые автоматически возвраща- ются сервером при обращении к какому-либо каталогу, если не указано имя HTML-документа. В принципе, можно добавить сюда и другие имена, например, index.php , и т.д . Тем не менее, дополнительные настройки все же лучше делать в файлах .htaccess для каждого сайта в отдельности. CU Найдите и исправьте следующий параметр: ScriptAlias /cgi-bin/ "z:/home/localhost/cgi/ " Добавьте после него еще такую строчку: ScriptAlias /cgi/ "z:/home/localhost/cgi/" Да, именно так, с двумя слэшами — в начале и в конце. Это будет тот ка- талог, в котором должны располагаться ваши CGI-сценарии. Подобный параметр говорит Apache о том, что, если будет указан путь вида http : //locaihost/cgi-bin , то на самом деле следует обратиться к ката- логу z: /home/iocaihost/cgi . Мы используем два псевдонима для CGI- каталога потому, что /cgi-bin/ будет доступен не только главному хосту localhost, но и всем остальным виртуальным хостам. В то же время у ка- ждого из них будет дополнительно свой CGI-каталог /cgi/ . П Теперь следует найти блок параметров, начинающийся с И заканчивающийся . Это — настройки для CGI-каталога. Так как мы не соби- раемся указывать никаких дополнительных параметров взамен тех, кото- рые уже установлены по умолчанию, этот блок нужно удалить. П Найдите и настройте (не забудьте раскрыть комментарий!) следующий параметр: AddHandler cgi-script .bat .exe .cgi ----------------------- Page 101----------------------- 86 Часть II. Выбор и настройка инструментария. Web-сервер Apache Он говорит Apache о том, что файлы с расширениями exe , bat и cgi на- до рассматривать как CGI-модули. П И последнее — установите следующие параметры: AddType text/html .shtml AddHandler server-parsed .shtml .html .htm Этим вы заставляете Apache обрабатывать файлы с указанными расшире- ниями процессором SSI. П Теперь не забудьте сохранить изменения и закройте Блокнот. Этап третий: тестирование Apache Поздравляем — вы настроили свой Apache, и он должен уже работать! Для запуска сервера нажмите кнопку Пуск, затем выберите Программы, Apache Web Server, Management и Start Apache, при этом всплывет окно, очень по- хожее на Сеанс MS-DOS, и ничего больше не произойдет. Не закрывайте его и не трогайте до конца работы с Apache. Если окно открывается и тут же закрывается, это означает, что вы допусти- ли какую-то ошибку в файле httpd.conf . В этом случае придется искать неточность. Проще всего это сделать, как указано ниже. 1. Запустите Сеанс MS-DOS. Для этого нажмите кнопку Пуск, затем выбери- те Выполнить. Наберите в появившемся диалоговом окне строку command и нажмите клавишу . Появится подсказка командной строки. 2. Наберите следующие команды DOS: с: cd "\Program FilesXApache Group\Apache " apache.exe 3. Если до этого Apache не выполнялся, то вы получите сообщение об ошибке и номер строки в httpd.conf , где она произошла. Исправьте httpd.conf и повторите описанный процесс сначала, до тех пор, пока в окне не отобразится что-то вроде "Apache/1.3.14 (Win32) running..." Несколько слов о том, как можно упростить запуск и завершение сервера. В Windows можно назначить любому ярлыку функциональную комбинацию клавиш, нажав которые, вы запустите связанное с ним приложение . Так что щелкните правой кнопкой мыши на панели задач, в контекстном меню вы- берите Свойства, затем Настройка меню и кнопку Дополнительно. В от- крывшемся Проводнике присвойте ярлыку Start Apache комбинацию кла- виш ++, а ярлыку Stop Apache — ++. Теперь вы сможете запускать сервер нажатием ++ и останавливать его, нажав ++. Теперь проверим, правильно ли мы настроили сервер. ----------------------- Page 102----------------------- Глава 4. Установка Apache 87 Проверка html В каталоге z:/home/localhost/www , содержащем HTML-документы Apache, создайте файл index.html с любым текстовым наполнением . Теперь запус- тите браузер и наберите: http://localhost/index.html или просто http://localhost/ Должен загрузиться ваш файл. Проверка SSI В каталоге z: /home/iocaihost/www с HTML-документами Apache создайте файл test.shtml со следующим содержанием (внимательно следите за со- блюдением пробелов в директиве include!) : : Листинг 4.1. Файл test.shtml SSI Test!
"aa" , "b"=>"bb" , " c " = > a r r a y ( " x " = > " x x " ) ! ; $st=Senaiize($A) ; echo $st; // выведется что-то типа нечто : // а:2:{s:1:"а";s:2:"аа";s:1:"b";s:2:"bb";s:1:"с";а:1:{s:1:"х";s:2:"хх";}} Вообще-то, я не уверен, что в будущих версиях РНР такой формат "упа- ковки" сохранится неизменным, хотя это очень и очень вероятно. Функция Unserialize о, наоборот, принимает в лице своего параметра $st строку, ранее созданную при помощи serializ e о, и возвращает целиком объект, который был упакован. 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 Еще раз отмечу: сериализовать можно не только массивы , но и вообще что угодно. Однако в большинстве случаев все-таки используются массивы . Ме- ханизм сериализации часто применяется также и для того, чтобы сохранить какой-то объект в базе данных, и тогда без сериализации практически не обойтись. ----------------------- Page 196----------------------- Глава 11 Функции и области видимости По синтаксису описания функций РНР, на мой взгляд, довольно близок к идеальной концепции , которую многие программисты лелеют в своем вооб- ражении . Вот несколько основных достоинств этой концепции : П вы можете использовать параметры по умолчанию (а значит , функции с переменным числом параметров); П области видимости переменных внутри функций представляются в древо- видной форме, как и в других языках программирования; П существует удобная инструкция return , которой так не хватает в Паскале; П тип возвращаемого значения может быть любым; П как мы увидим дальше, функции можно использовать не только по их прямому назначению, но и для автоматизации создания "библиотекарей" и даже написания своего собственного интерфейса библиотечных файлов. К сожалению , разработчики РНР не предусмотрели возможность создания локальных функций (то есть одной внутри другой), как это сделано, скажем, в Паскале или в Watcom C++. Однако кое-какая эмуляция локальных функций все же есть: если функцию в ( ) определить в теле функции д о , то она, хоть и не став локальной, все же будет "видна" для программы ниже своего определения. Замечу для сравнения, что похожая схема существует и в языке Perl. Впрочем, как показывает практика программирования на Си (вот уже 30 лет), это не такой уж серьезный недостаток. В системе определения функций в РНР есть и еще один небольшой недо- чет, который особенно неприятен тем, кто до этого программировал на других языках. Дело в том, что все переменные, которые объявляются и ис- пользуются в функции, по умолчанию локальны для этой функции . При этом существует только один (и при том довольно некрасивый) способ объ- явления глобальных переменных — инструкция global (на самом деле есть и еще один, через массив $GLOBALS, но об этом чуть позже). С одной сторо- ны, это повышает надежность функций в смысле их независимости от ос- новной программы, а также гарантирует, что они случайно не изменят и не ----------------------- Page 197----------------------- 182 Часть III. Основы языка PHP создадут глобальных переменных . С другой стороны, разработчики РНР вполне могли бы предугадать нужность инструкции , по которой все пере- менные функции становились бы по умолчанию глобальными — это суще- ственно упростило бы программирование сложных сценариев. Пример функции Как водится, сразу начну с примера . Предположим, нам необходимо в про- грамме очень часто находить в массиве-списке наибольший элемент, кото- рый в то же время меньше какого-то, наперед заданного числа. А именно , нас интересует его номер в массиве (если такого числа в массиве нет, то номер полагается равным -1). Напишем для этой цели функцию (такое опи - сание называется определением функции, и оно, конечно, должно быть един- ственным в пределах сценария) . ! Листинг 11.1. Пример функции function GetMaxNum($arr , $max="") { // проходимся по всем элементам массива for ($i=0 , $n=-l; $i$m ) & & ($max==="" | | $arr [$i] <$riax) ) { // сюда мы попадаем, когда очередной элемент больше текущего, // либо же текущего элемента еще не существует (первый проход) $m=$arr[$i]; // запоминаем текущий элемент $n=$i; // запоминаем его номер return $n; В отличие от других языков программирования , функцию можно задавать не только в определенном месте программы , но и прямо среди других опе- раторов. Например, вполне можно было бы поместить нашу функцию GetMaxNumO прямо в середину кода, скажем, так: echo "Программа..."; function GetMaxNum($arr,$max ) { .. . тело функции .. . ) echo "Программа продолжается!" ; ----------------------- Page 198----------------------- Глава 11. Функции и области видимости 183 Замечание При таком подходе транслятор, дойдя до определения функции, просто прове- рит его корректность и оттранслирует во внутреннее представление, но не бу- дет генерировать код для выполнения, а сразу переключится на следующие за телом функции команды. Только потом, при вызове функции, интерпретатор начнет исполнять ее команды... Итак, мы создали функцию с именем GetMaxNum o и двумя параметрами , первый из которых рассматривается ей как массив , а второй — как вещест- венное число. Внимание На самом деле на этапе создания функции еще никаких предположений о ти- пах параметров не строится. Однако попробуйте нашей функции вместо мас- сива в первом аргументе передать число — интерпретатор "заругается", как только выполнение дойдет до строчки с $arr [$i] , и скажет, что "переменная не является массивом". Алгоритм работы функции таков: в цикле анализируем очередной элемент на предмет "максимальности" : если он больше текущего максимального элемента, но меньше $та.ч, он сам становится текущим максимумом , а его положение запоминается в $п. (Обратите внимание , что в описании функ- ции параметр $тах задается в виде $тах="" . Это означает, что если при вы- зове он будет опущен , то функция получит пустую строку в $тах.) После окончания цикла в $п окажется номер такого элемента (либо число -1, ко- торое мы присвоили $п в начале). Его-то мы и возвращаем в качестве зна- чения функции оператором return . Ну вот, теперь в программе ниже описания функции можно написать: $а=аггау(10,20,80,35,22,57); $m=GetMaxNum($a,50); // теперь $т=3, т . е . $а[$т]=35 ( Замечание ^ В действительности, поскольку фаза трансляции и исполнения в РНР разделе- ны, мы можем применять вызовы функции еще до того, как она была описана. Однако это работает, конечно же, только в том случае, когда в момент интер- претации вызова функции ее код будет уже оттранслирован (например, вызов и описание функции происходят в одном и том же файле). Тем не менее, не со- ветую вам злоупотреблять данной возможностью — лучше всегда поступать так, как это принято в Паскале: вызывать функции только после того, как они будут определены. Зачем может понадобиться функция GetMaxNum o в реальной жизни ? На- пример, для сортировки массива в порядке убывания с одновременным получением уникальных элементов . Конечно , это будет очень неопти- ----------------------- Page 199----------------------- 184 ^ Часть III. Основы языка РНР мальный алгоритм, но для тренировочных целей он нам вполне подойдет (листинг 11.2): ; Листинг 11.2. Сортировка с применением GetMaxNumO • i , j function MySort($Arr ) { $m= GetMaxNum($Arr)+1 ; // число , на 1 большее максимума в массиве while(($n=GetMaxNum($Arr,$m))!=-!) $New[j=$m=$Arr[$n]; // добавляем очередной максимальный элемент return $New ; } // Пример вызова : $Sorted=MySort(array(l,2,5,2,4,7,3,7,8)); // Теперь $Sorted===array(8,7,5,4 , 3,2 , 1) Приведенная функция не изменяет исходный массив, а возвращает новый. В силу устройства функции GetMaxNumO в результирующий массив будут помещены только уникальные элементы из $АГГ , отсортированные в поряд- ке убывания. ( Замечание ^ Функцию MySort () можно ускорить примерно в 2 раза, если после каждой ите- рации удалять из массива $АГГ обработанный элемент при помощи unset ( ) . Впрочем, это не так интересно, как может показаться. Общий синтаксис определения функции В общем виде синтаксис определения функции таков: function имя_функции(арг![=зн1] , арг2[=зн2] , .. . аргМ[=знМ]) { операторы_тела_функции ; } Имя функции должно быть уникальным с точностью до регистра букв. Это означает, что, во-первых, имена MyFunction , myfunction и даже MyFuNcTion будут считаться одинаковыми, и, во-вторых, мы не можем переопределить уже определенную функцию (стандартную или нет — не важно), но зато можем давать функциям такие же имена, как и переменным в программе (конечно, без знака $ в начале). Список аргументов, как легко увидеть, со- стоит из нескольких перечисленных через запятую переменных, каждую из которых мы должны будем задать при вызове функции (впрочем, когда для ----------------------- Page 200----------------------- Глава 11. Функции и области видимости 185 этой переменной присвоено через знак равенства значение по умолчанию (обозначенное =знм), ее можно будет опустить; см. об этом чуть ниже). Ко- нечно, если у функции не должно быть аргументов вовсе (как это сделано у функции time ( ) ) , то следует оставить пустые скобки после ее имени, на- пример: function SimpleFunction{) { ... } В фигурные скобки заключается тело функции. В нем могут быть любые операторы, включая даже операторы определения других функций (правда, эти "другие функции" не будут локальными, как в Паскале, а станут далее "видны" для всей программы, но только с того момента, как до их описания дойдет управление — об этом мы еще поговорим). Если функция должна возвращать какое-то значение, что среди них должен встретиться оператор return, который мы сейчас рассмотрим. Если же она должна отработать без возврата значений (то есть, выражаясь в терминах Паскаля, это не функция, а процедура), то оператор return можно и не указывать (или указывать без задания возвращаемого значения). Инструкция return Синтаксис оператора return абсолютно тот же, что и в Си, за исключением одной очень важной детали. Если в Си функции очень редко возвращают большие объекты (например, структуры), а массивы они не могут возвратить вовсе (это явный прокол в концепции Си), то в РНР можно использовать return абсолютно для любых объектов (какими бы большими они ни были), причем без заметной потери быстродействия. Вот пример простой функции, возвращающей квадрат своего аргумента: function MySqrt($n ) { return $n*$n; } echo MySqrt(4) ; // выводит 16 Сразу несколько значений функции, разумеется, возвратить не могут. Одна- ко, если это все же очень нужно, то можно вернуть ассоциативный массив или же список, например так (листинг 11.3): ; Листинг 11.3. Возвращение массива function silly() { return array(1,2,3); } // присваивает массиву значение аггау(1,2,3) $arr=Silly(); ----------------------- Page 201----------------------- 186 Часть III. Основы языка PHP // присваивает переменным $а, $Ь , $с первые значения из списка list($a,$b,$c)=Silly(); В этом примере использован оператор list о, который мы уже рассматри- вали. Если функция не возвращает никакого значения, т. е. инструкции return в ней нет, то считается, что функция возвратила ложь (то есть, false) . Все же часто лучше вернуть fals e явно (если только функция не объявлена как процедура, или void-функция по Си-терминологии), например , задействуя return false , потому что это несколько яснее. Параметры по умолчанию Часто бывают такие случаи, что у некоторой разрабатываемой функции должно быть довольно много параметров, причем некоторые из них будут задаваться совершенно единообразно. Например , мы пишем функцию для сортировки массива. Тогда, кроме очевидного параметра — массива — хоте- лось бы также задавать и второй параметр, который бы указывал: сортиро- вать ли в убывающем или в возрастающем порядке. При этом, скажем, мы знаем, что чаще всего придется сортировать в порядке убывания. В этом случае мы можем оформить нашу функцию так: function MySort(&$Arr , $NeedLoOrder=l ) { .. . сортируем в зависимости от $NeedLoOrder.. . } Теперь, имея такую функцию , можно написать в программе : MySort($my_array,0) ; // сортирует в порядке возрастания MySort($my_array) ; // второй аргумент задается по умолчанию ! То есть, мы можем уже вообще опустить второй параметр у нашей функции, что будет выглядеть так, как будто мы его задали равным 1. Как видно, зна- чение по умолчанию для какого-то аргумента указывается справа от него через знак равенства. Заметьте, что значения аргументов по умолчанию должны определяться справа налево, причем недопустимо, чтобы после лю- бого из таких аргументов шел обычный "неумолчальный" аргумент. Вот, на- пример, неверное описание: // Ошибка ! function MySort($NeedLoOrder=l , &$Arr ) f ... сортируем в зависимости от SNeedLoOrder.. . } MySort(,$my_array) ; // Ошибка ! Это вам не Бейсик ! ----------------------- Page 202----------------------- Глава 1 1. Функции и области видимости _ 187 Передача параметров по ссылке Давайте рассмотрим механизм, при помощи которого функции передаются ее аргументы. Пусть, например, у нас есть такая программа: function Test($a) { echo "$a\n"; $a++; echo "$a\n"; $num=10; Test ($num) ; echo $num ; Что происходит перед началом работы функции Test о (которая, кстати, не возвращает никакого значения, т. е. является в чистом виде подпрограммой или процедурой) — как выражаются программисты на Паскале? Все начина- ется с того, что создается переменная $а, локальная для данной функции (про локальные переменные мы поговорим позже), и ей присваивается значение 10 (то, что было в $num). После этого значение 10 выводится на экран, величина $а инкрементируется, и новое значение (И) опять печатается. Так как тело функции закончилось, происходит возврат в вызвавшую программу. А теперь вопрос: что будет напечатано при последующем выводе переменной $num? А напечатано будет 10 (и это несмотря на то, что в переменной $а до возврата из функции было 11!) Ясно, почему это происходит: ведь $а — лишь копия $num, а изменение копии, конечно, никак не отражается на оригинале. В то же время, если мы хотим, чтобы функция имела доступ не к величине, а именно к самой переменной (переданной ей в параметрах), достаточно при пе- редаче аргумента функции перед его именем поставить & (листинг 11.4): ! Листинг 11.4. Передача параметров по ссылке (первый способ) function Test($a ) { echo "$a\n" ; $a++; echo "$a\n" ; } $num=10; // $num=10 Test(S$num); // а теперь $num=ll ! echo $num; // выводит 11 ! ----------------------- Page 203----------------------- 188 _ Часть III. Основы языка PHP Такой способ передачи параметров исторически называется "передачей по ссылке", в этом случае аргумент не является копией переменной, а "ссы- лается" на нее. В седьмой главе мы уже имели дело со ссылками. Вы можете заметить, что передача параметра по ссылке полностью соответствует син- таксису задания ссылочной переменной в РНР. Чтобы не забывать каждый раз писать & перед переменной, передавая ее. функции, существует и другой, более привычный для программистов на Си++ синтаксис передачи по ссылке. А именно, можно символ & перенести прямо в заголовок функции, вот так (листинг 11.5): | Листинг 11.5. Передача параметров по ссылке (второй способ) function Test(S$a ) { echo "$a\n"; $a++; echo "$a\n"; $num=10; // $num=10 Test{$num); // а теперь $num=ll ! echo $num ; // выводит 11 ! Советую вам, если вы абсолютно точно уверены в необходимости передачи параметра именно по ссылке, использовать именно этот синтаксис, т. к. он значительно более "прозрачен" и, к тому же, убережет вас от множества ошибок, связанных с пропуском & в программе. С~ Замечание ^ Теперь, если вы в программе запустите функцию Test () , передав ей в парамет- рах не переменную (или ячейку массива), а непосредственное значение (например, константу 100), это у вас не получится: РНР выведет сообщение об ошибке. Таким образом, в качестве параметров, передаваемых по ссылке, можно задавать только переменные, но не непосредственные значения. Переменное число параметров Как мы уже знаем, функция может иметь несколько параметров, заданных по умолчанию. Они перечисляются справа налево, и их всегда фиксирован- ное количество. Однако иногда такая схема нас устроить не может. Напри- мер, пусть мы захотели написать функцию в стиле echo, т. е., функцию , ко- торая принимает один или более параметров (сколько именно — неизвестно на этапе определения функции). Пусть она должна вывести эти параметры "лесенкой" - каждый следующий на новой строке с отступом от предыду- щего (согласен, пример немного надуман, но все же вполне подходит для ----------------------- Page 204----------------------- Глава 11. Функции и области видимости 189 иллюстрации функций с переменным количеством параметров). Вот как мы можем это сделать (листинг 11.6): Листинг 11.6. Переменное число параметров функции function myecho ( ) ( for($i=0; $i\n"; // выводим элемент // отображаем строки "лесенкой" myecho ("Меркурий" , "Венера" , "Земля", "Марс"); Обратите внимание на то, что при описании myecho ( ) мы указали пустые скобки в качестве списка параметров, словно функция не получает ни одного параметра. На самом деле в РНР при вызове функции можно указывать пара- метров больше, чем задано в списке аргументов — в этом случае никакие пре- дупреждения не выводятся (но если фактическое число параметров меньше, чем указано в описании, РНР выдаст сообщение об ошибке). "Лишние" пара- метры как бы игнорируются, в результате пустые скобки в myecho ( ) позволя- ют нам в действительности передать ей сколько угодно параметров. Для того чтобы все же иметь доступ к "проигнорированным" параметрам, су- ществуют три встроенные в РНР функции, которые я сейчас подробно опишу. О int f unc_num_args ( ) Возвращает общее число аргументов, переданных функции при вызове. П mixed func_get_arg (int $num) Возвращает значение аргумента с номером $num, заданного при вызове функции. Нумерация, как всегда, отсчитывается с нуля. П list func_get_args ( ) Возвращает список всех аргументов, указанных при вызове функции. Думаю, что применение этой функции оказывается практически всегда удобнее, чем первых двух. Перепишем наш пример с применением последней функции (листинг 11.7): | Листинг 11.7. Использование f uct_get_args ( ) function myecho ( ) { foreach (func_get_args () as $v) { for($j=0; $j<9$i ; $j++) echo "Snbsp;"; echo "$v
\n"; ----------------------- Page 205----------------------- 190 Часть III. Основы языка PHP // выводим строки "лесенкой" myecho ("Меркурий" , "Венера" , "Земля", "Марс"); Мы используем здесь цикл foreach для перебора аргументов, а также опера- тор отключения ошибок @, чтобы РНР не "ругался" на то, что переменная $1 не определена при первом "обороте" цикла . Локальные переменные Наконец-то мы подошли вплотную к вопросу о "жизни и смерти" перемен- ных. Действительно, во многих приводимых выше примерах мы рассматри- вали аргументы функции (передаваемые по значению, а не по ссылке) как некие временные объекты, которые создаются в момент вызова и исчезают после окончания функции. Например (листинг 11.8): : Листинг 11.8. Локальные переменные (параметры) $а=100; // Глобальная переменная , равная 100 function Test($a ) { echo $a; // выводим значение параметра $а // Этот параметр не имеет к глобальной $а никакого отношения ! $а++; // изменяется только локальная копия значения , переданного в $а } Test(l); // выводит 1 echo $a; // выводит 100 — глобальная $а , конечно , не изменилась В действительности такими же свойствами будут обладать не только аргу- менты, но и все другие переменные, инициализируемые или используемые внутри функции. Вот пример (листинг 11.9): ; Листинг 11.9. Локальные переменные function Silly( ) { $i=rand() ; // записывает в $i случайное число echo Si ; // выводит его на экран // Эта $1 не имеет к глобальной $1 никакого отношения ! ) for($i=0; $i!=10; $i++) Silly О; ----------------------- Page 206----------------------- Глава 11. Функции и области видимости 191 Здесь переменная $i в функции будет не той переменной $1, которая ис- пользуется в программе для организации цикла. Поэтому, собственно, цикл и проработает только 10 "витков", напечатав 10 случайных чисел (а не будет крутиться долго и упорно , пока "в рулетке" функции rand ( } не выпадет 10. Собственно говоря, это нас устраивает. Действительно, мало ли какие имена переменных использует функция для своих личных целей... Какое до этого дело программе (которая вообще может быть написана другим человеком)? Вот и получается, что каждая функция - "узник" в своем тесном мирке, живущий и обменивающийся с "окружающим миром" через свои параметры и возвращаемое значение . Глобальные переменные Если вы, прочитав последние строки, уже начали испытывать сочувствие к функциям в РНР (или, если вы прикладной программист, сочувствие к раз- работчикам РНР), то спешу вас заверить: разумеется, в РНР есть способ, посредством которого функции могут добраться и до любой глобальной пе- ременной в программе (не считая, конечно, передачи параметра по ссылке). Однако для этого они должны проделать определенные действия, а именно: до первого использования в своем теле внешней переменной объявить ее "глобальной" (листинг 11.10): I Листинг 11.10. Использование global function Silly( ) { global $i; $i=rand(); echo $i; } for($i=0 ; $i!=10 ; $i++) Silly() ; Вот теперь-то переменная $i будет везде едина: что в функции, что во внешнем цикле (для последнего это приведет к немедленному его "зацикливанию", во всяком случае, на ближайшие несколько минут, пока rand ( ) не выкинет 9). А вот еще один пример, который показывает удобство использования гло- бальных переменных внутри функции (листинг 11.11): | Листинг 11.11. Пример функции I - $Monthes[1]="Январь"; $Monthes[2]="Февраль"; ----------------------- Page 207----------------------- 192 Часть III. Основы языка PHP ... и т . д . $Мог±пез[12]="Декабрь"; // Возвращает название месяца по его номеру. Нумерация начинается с 1! function GetMonthName ($n) { global $Monthes ; return $Monthes [$n] ; echo GetMonthName (2) ; // выводит "Февраль " Согласитесь, массив $Monthes , содержащий названия месяцев, довольно объемист. Поэтому описывать его прямо в функции было бы, мягко говоря, неудобно. В то же время функция GetMonthName ( ) представляет собой до- вольно преемлемое средство для приведения номера месяца к его словесно- му эквиваленту (что может потребоваться во многих программах). Она имеет единственный и понятный параметр: это номер месяца. Как бы мы это сде- лали без глобальных переменных? Массив SGLOBALS В принципе, есть и второй способ добраться до глобальных переменных. Это — использование встроенного в язык массива $GLOBALS. Последний представляет собой хэш, ключи которого есть имена глобальных перемен- ных, а значения — их величины . Этот массив доступен из любого места в программе — в том числе и из тела функции, и его не нужно никак дополнительно объявлять. Итак, приведен- ный выше пример можно переписать более лаконично : // Возвращает название месяца по его номеру . Нумерация начинается с 1 ! function GetMonthName ($n) { return $GLOBALS ["Monthes"] [$n] ; } Кстати, тут мы опять сталкиваемся с тем, что не только переменные, но да- же и массивы могут иметь совершенно любую структуру, какой бы сложной она ни была. Например, предположим, что у нас в программе есть ассоциа- тивный массив $А, элементы которого — двумерные массивы чисел. Тогда доступ к какой-нибудь ячейке этого массива с использованием $GLOBALS мог бы выглядеть так: $GLOBALS [ "А" ] [First"] [10] [20] ; То есть получился четырехмерный массив! ----------------------- Page 208----------------------- Глава 11. Функции и области видимости 193 Насчет $GLOBALS следует добавить еще несколько полезных сведений. Во- первых, как я уже говорил, этот массив изначально является глобальным для любой функции, а также для самой программы. Так, вполне допустимо его использовать не только в теле функции, но также и в любом другом мес- те. Во-вторых, с этим массивом допустимы не все операции, разрешенные с обычными массивами. А именно, мы не можем: П присвоить этот массив какой-либо переменной целиком, используя опе- ратор =; П как следствие, передать его функции "по значению" -- можно передавать только по ссылке. Однако остальные операции допустимы. Мы можем при желании , напри- мер, по одному перебрать у него все элементы и, скажем, вывести их значе- ния на экран. И, наконец, третье: добавление нового элемента в $GLOBALS равнозначно созданию новой глобальной переменной (конечно, предварен- ной символом $ в начале имени, ведь в самом массиве ключи — это имена переменных без символа доллара), а выполнение операции Onset о для него равносильно уничтожению соответствующей переменной. А теперь я скажу нечто весьма интересное все о том же массиве $GLOBALS . Как вы думаете, какой элемент (то есть, глобальная переменная) всегда в нем присутствует? Это — переменная GLOBALS, которая также является мас- сивом, и в которой также есть элемент GLOBALS... Так что же было первей — курица или яйцо (только не надо мне говорить, что первым был петух)? А собственно, почему бы и нет? С чего это мы все привыкли, что в большом содержится малое, а не, скажем, наоборот? Почему множество не может со- держать себя же в качестве элемента? Очень даже может, и $ GLOBALS — тому наглядный пример. В РНР версии 3 такая ситуация была чистой воды шаманством. Однако с появ- лением в четвертой версии РНР ссылок все вернулось на круги своя. На самом- то деле элемент с ключом GLOBALS является не обычным массивом, а лишь ссылкой на $GLOBALS. Вот поэтому все и работает так, как было описано. Вооружившись механизмом создания ссылок, мы можем теперь наглядно продемонстрировать, как работает инструкция global , а также заметить один ее интересный нюанс. Как мы знаем, global $a говорит о том, что перемен- ная $а является глобальной, т. е., является синонимом глобальной $а. Сино- ним в терминах РНР — это ссылка. Выходит, что global создает ссылку? Да, никак не иначе. А вот как это воспринимается транслятором: function Test( ) { global $a; $а=10; ----------------------- Page 209----------------------- 194 Часть III. Основы языка PHP Приведенное описание функции Test о полностью эквивалентно следую- щему описанию : function Test( ) { $a=S$GLOBALS['a'] ; $а=10; } Из второго фрагмента видно, что оператор unset ($а ) в теле функции не уничтожит глобальную переменную $а , а лишь "отвяжет" от нее ссылку $а. Точно то же самое происходит и в первом случае. Вот пример: $а=100; function Test( ) ( global $a ; Unset($a) ; } Test () ; echo $a ; // выводит 100, т . е . настоящая $а не была удалена в TestO ! Внимание Эта особенность инструкции global появилась только в РНР версии 4, т. е. когда начали поддерживаться ссылки! Если вы запустите приведенный только что пример на РНР версии 3, то при исполнении echo увидите предупреждение: $а не опреде- лена. Помните это при переносе старых сценариев на новый РНР версии 4. Как же нам удалить глобальную $а из функции? Существует только один способ: использовать для этой цели $GLOBAL S [ 'а 1 ] • Вот как это делается: function Test{ ) { unset($GLOBALS['a']) ; } $a=100; Test () ; echo $a ; // Ошибка ! Переменная $а не определена ! Статические переменные Видимо, чтобы не отставать от других языков, создатели РНР предусмотре- ли еще один вид переменных , кроме локальных и глобальных, — статиче- ские. Работают они точно так же, как и в Си. Рассмотрим следующий при- мер (листинг 11.12) : I Листинг 11.12. Статические переменные function Silly( ) { static $a=0; ----------------------- Page 210----------------------- Глава 11. Функции и области видимости 195 echo $a; $а++; } for($i=0; $i<10 ; Si++ ) Silly (); После запуска будет выведена строка 0123456789 , как мы и хотели. Давайте теперь уберем слово static . Мы увидим : о о о о о о о о о о . Это и понятно, ведь переменная $а стала локальной, и ей при каждом вызове функции присваи- вается одно и то же значение — 0. Итак, конструкция static говорит компилятору о том, что уничтожать ука- занную переменную для нашей функции между вызовами не надо. В то же время присваивание $а=о сработает только один раз, а именно — при самом первом обращении к функции (так уж устроен static) . Рекурсия Конечно, в РНР поддерживаются рекурсивные вызовы функций, т. е. вызо- вы функцией самой себя (разумеется, не до бесконечности, а в соответствии с определенным условием). Это бывает чрезвычайно удобно для таких задач, как, например , обход всего дерева каталогов вашего сервера (с целью под- считать суммарный объем, который занимают все файлы), или для других задач. Рассмотрим для примера функцию, которая рекурсивно вычисляет факториал из некоторого числа п (обозначается n i ) . Алгоритм стандартный: если п=о , то п ! =1, а иначе п ! =п* ( (п-1) !) . function Factor($n ) ( if($n<=0 ) return 1 ; else return $n*Factor($n-l ) ; } echo Factor(20) ; Должен только предупредить вас не применять эту функцию факториала в реальной жизни — она приведена здесь исключительно для примера. Лучше воспользоваться следующей функцией — она работает гораздо быстрее: function Factor($n ) { for($f=l ; $n>l ; $n— ) $f*=$n; return $f; Вложенные функции Стандарт РНР не поддерживает вложенные функции. Однако он поддержи- вает нечто, немного похожее на них. Вместо того чтобы, как и у перемен- ----------------------- Page 211----------------------- 196 Часть III. Основы языка PHP ных, ограничить область видимости для вложенных функций своими "родителями", РНР делает их доступными для всей остальной части про- граммы, но только с того момента, когда "функция-родитель" была из нее вызвана. Позднее, в части V книги, мы увидим, что этот (казалось бы) не- дочет оказывается довольно удобным способом для написания библиотек функций на РНР. Итак, "вложенные" функции выглядят следующим образом (листинг 11.13): I Листинг 11.13. Вложенные функции ; .1 function Parent($a) { echo $а; function Child($b) { echo $b+l; return $b*$b; } return $a*$a*Child($a); // фактически возвращает $a*$a*($a+l)*($a+l) } // Вызываем функции Parent(10); Child(30); // Попробуйте теперь ВМЕСТО этих двух вызовов поставить такие // же , но только в обратном порядке . Что , выдает ошибку? // Почему , спрашиваете? Читайте дальше ! Мы видим, что нет никаких ограничений на место описания функции - будь то глобальная область видимости программы, либо же тело какой-то другой функции. В то же время, напоминаю, что понятия "локальная функ- ция" как такового в РНР все же (пока?) не существует. Каждая функция добавляется во внутреннюю таблицу функций РНР тогда, когда управление доходит до участка программы, содержащего определение этой функции. При этом, конечно, само тело функции пропускается, однако ее имя фиксируется и может далее быть использовано в сценарии для вызова. Егли же в процессе выполнения программы РНР никогда не доходит до опре- деления некоторой функции, она не будет "видна", как будто ее и не сущест- вует — это ответ на вопросы, заданные внутри комментариев примера. Давайте теперь попробуем запустить другой пример. Вызовем Parent ( ) два раза подряд: Parent(10); Parent(20); ----------------------- Page 212----------------------- Глава 11. Функции и области видимости 197 Последний вызов породит ошибку: функция child о уже определена. Это произошло потому, что child о определяется внутри Parent о, и до ее оп- ределения управление программы фактически доходит дважды (при первом и втором вызовах Parent о). Поэтому-то интерпретатор и "протестует": он не может второй раз добавить child ( ) в таблицу функций. ( Замечание ^) Для тех, кто раньше программировал на Perl, этот факт может показаться ужа- сающим. Что ж, действительно, мы не должны использовать вложенные функ- ции РНР также , как делали это в Perl. Условно определяемые функции Предположим, у нас в программе где-то устанавливается переменная $OS_TYPE в значение win , если сценарий запущен под Windows 9x, и в unix , если под Unix. Как известно, в отличие от Unix, в Windows нет такого поня- тия, как владелец файла, а значит, стандартная функция chowno (которая как раз и назначает владельца для указанного файла) там просто не имеет смысла. В некоторых версиях РНР для Windows ее может в этой связи во- обще не быть. Однако, чтобы улучшить переносимость сценариев с одной платформы на другую (без изменения их кода!) можно написать следующую простую "обертку" для функции chown ( ) (листинг 11.14): 1 Листинг 11.14. Условно определяемые функции if ($OS_TYPE=="win") { // Функция-заглушка function MyChOwn($fname,$attr) ( // ничего не делает return 1 ; else { / / Передаем вызов настоящей chown ( ) function MyChOwn($fname , $attr) ( return chown ($fname, $attr) ; Это — один из примеров условно определяемых функций. Если мы работа- ем под Windows, функция MyChOwn O ничего не делает и возвращает 1 как ----------------------- Page 213----------------------- /gg Часть III. Основы языка PHP индикатор успеха , в то время как для Unix она просто вызывает оригиналь- ную chown ( ) . Важно то, что проверка, какую функцию использовать, произ- водится только один раз (в момент прохождения точки определения функ- ции), т. е. здесь нет ни малейшей потери производительности. Теперь в сценарии мы должны всюду отказаться от chown ( ) и использовать MyChOwn ( ) (можно даже провести поиск/замену этого имени в редакторе) - это обеспечит переносимость. Если вам совсем не нравится идея поиска/замены (а мне она не нравится категорически), то существует гораздо более элегантный способ, но только в том случае , если chown о еще не была нигде определена — в том числе и среди стандартных функций: if ( ! function_exists ( "chown " ) ) { function chown ($f name , $mode) { //н е делаем ничего return 1 ; Этот способ работает независимо от того, появится ли вдруг в будущих вер- сиях РНР для Windows "заглушка" для функции c h o w n o , или же нет. (Нужно сказать для справедливости, что в РНР версии 4 такая заглушка уже существует.) Знатоки Си могут заметить в приеме условно определяемых функций рази- тельное сходство с директивами условной компиляции этого языка: ttifndef, telse и #endif . Действительно, аналогия почти полная, за исклю- чением того факта, что в Си эти директивы обрабатываются во время ком- пиляции, а в РНР — во время выполнения . Что ж, на то он и интерпрета- тор, чтобы позволять себе интерпретацию . Примечание То, что возможно создавать условно определяемые функции, сильно подрыва- ет веру в РНР как в истинный транслятор. Как вообще можно устроить трансля- тор так, чтобы он правильно обрабатывал подобные вещи? Я не знаю. Наде- юсь, что разработчики РНР нашли-таки способ, и условно определяемые функции транслируются вместе со всей программой, а не на этапе исполнения, как это было в РНР версии 3. Однако полной уверенности в этом нет, а доку- ментация по этому поводу молчит (пока). Передача функций "по ссылке" Я отнюдь не случайно заключил последние два слова названия этого раздела в кавычки — дело в том, что как таковая, передача функции по ссылке в ----------------------- Page 214----------------------- Глава 11. Функции и области видимости 199 РНР не поддерживается. Однако, т. к. это слишком часто может быть по- лезным , в РНР есть понятие "функциональной переменной". Легче всего его можно рассмотреть на примерах: 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 Второй пример носит довольно прикладной характер. В РНР есть такая стандартная функция — uasort ( } , которая сортирует ассоциативный мас- сив, заданный ее первым параметром, причем критерием сравнения для элементов этого массива служит функция, имя которой передано вторым параметром. Мы будем рассматривать эту функцию в главе 13, а сейчас я приведу простой пример: // Сравнение без учета регистра символов строк function FCmp($a,$b ) ( return strcmp(tolower($a),tolower($b) ) } $a=array("b"=>"bbb", "a"=>"Aaa", "d"=>"ddd); uasort($a,"FCmp"); // Сортировка без учета регистра символов Здесь функция, имя которой получено со вторым параметром uasort о, должна иметь два аргумента, которые являются сравниваемыми значениями в массиве. В общем случае, функциональная переменная — это всего лишь перемен- ная-строка, содержащая имя функции, и ничего больше. Поскольку в РНР нет такого понятия, как области видимости для функций (есть только об- ласти видимости для локальных переменных), то конфликтов это не порож- дает — одному имени может соответствовать не более одной функции. Та- кой подход, на мой взгляд, не очень хорош, но он действительно работает, и это главное. Возврат функцией ссылки До сих пор я рассматривал лишь функции, которые возвращают определен- ные значения — а именно, копии величин, использованных в инструкции return . Заметьте, это были именно копии, а не сами объекты. Например: $а=100; function R( ) { global $a; // объявляет $а глобальной ----------------------- Page 215----------------------- 200 Часть III. Основы языка PHP return $a; // возвращает значение, а не ссылку! } $b=R(); $b=0; // присваивает $Ь , а не $а ! echo $a; // выводит 100 В то же время мы бы хотели, чтобы функция R ( ) возвращала не величину, а ссылку на переменную $а, чтобы в дальнейшем с этой ссылкой можно было работать точно так же, как и с $а. Например, это может очень пригодиться в объектно-ориентированном программировании на РНР (основы которого мы рассмотрим в пятой части книги), когда функция должна возвращать именно объект, а не его копию. Как же нам добиться нужного результата? Использование оператора $ ь = & к ( ) , к сожалению, не подходит, т. к. при этом мы получим в $ь ссылку не на $а, а на ее копию. Если задействовать return &$a , то появится сооб- щение о синтаксической ошибке (РНР воспринимает & только в правой час- ти оператора присваивания сразу после знака =). Но выход есть. Воспользу- емся специальным синтаксисом описания функции, возвращающей ссылку (листинг 11.15): ! Листинг 11.15. Возвращение ссылки $а=100; function &R( ) // & — возвращает ссылку { global $a; // объявляет $а глобальной return $a ; // возвращает , ссылку , а не значение ! } $b=&R(); // не забудьте & !! ! $Ь=0; // присваивает переменной $а ! echo $а; // выводит 0 . Это значит , что теперь $Ь - синоним $а Как видим, нужно поставить & в двух местах: перед определением имени функции, а также в правой части оператора присваивания при вызове функции. Использовать амперсанд в инструкции return не нужно. Внимание Лично я не нахожу такой синтаксис удобным. Достаточно no-ошибке всего один раз пропустить & при вызове функции, как переменной $Ь будет присвоена не ссылка на $а , а только ее копия со всеми вытекающими из этого последствия- ми. При использовании объектно-ориентированного программирования это мо- жет породить логические ошибки, выглядящие крайне странно. Поэтому я ре- комендую применять возврат ссылки как можно реже, и только в тех случаях, когда это действительно необходимо. ----------------------- Page 216----------------------- Глава 1 1. Функции и области видимости _ 20 1 Пример функции: DumpO В отладочных целях часто бывает нужно посмотреть, что содержит та или иная переменная. Однако, если эта переменная — массив, да еще многомер- ный, с выводом ее содержимого на экран могут возникнуть проблемы. Решить их призвана следующая функция, которую я назвал Dump ( ) . Пользу от этой функции можно реально почувствовать, лишь поработав с ней некоторое вре- мя. Уверяю, потом вы не сможете понять, как раньше без нее обходились Функция выводит содержимое любой, сколь угодно сложной, переменной, будь то массив, объект или простая переменная. Как уже говорилось, приве- денная функция исключительно полезна при отладке сценариев (которая в РНР пока еще не особенно развита). ( _ Замечание ^ v В РНР версии 4 для аналогичных целей существуют две стандартных функ- ции — print_r ( ) и var_dump ( ) , но листинг, который они выводят, довольно неудобен для восприятия человеком. \ I Листинг 11.16. Функция Dump () // Вспомогательная функция, делающая всю "грязную" работу function TextDump (&$Var , $Level=0) { if (is__array($Var ) } $Type="Array[" . count ($Var) ."]" ; else if (is_object(SVar) ) $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 ; for($i=0; $i<$Level*3 ; $i++) echo " "; echo "".HtmlSpecialChars ($k) . " => ", TextDump ($v, $Level) ; else echo "" ,HtmlSpecialChars ($Var ) , "" . "\n"; // Основная функция function Dump(&$Var ) if ( (is_array($Var ) | |is_object ($Var) ) && count($Var) ) echo "
\n" , TextDump ($Var) , "
\n"; else ----------------------- Page 217----------------------- 202 Часть III. Основы языка PHP echo "",TextDump($Var),"\n" ; В реальной жизни следует использовать функцию Dump ( ) . Функция TextDump O (которая, по правде говоря, и делает всю работу) использует только одну неизвестную нам еще функцию — Htmispeciaichars о, заме- няющую в строке символы типа <, > или " на их HTML-эквиваленты (соответственно, Sit; , > ; И Squot;) . Мы ПрИМСНИЛИ ДОПОЛНИТбЛЬНуЮ функцию для того, чтобы вывести сам результат, а главная функция занима- ется только форматированием этого результата (вставка его в тэги <рге > или в зависимости от размера вывода). Несколько советов по использованию функций Хочется напоследок сказать еще несколько слов о функциях . Первое — не допускайте, чтобы ваши функции разрастались до гигантских размеров. Дробите их на маленькие, по возможности независимые, части, же- лательно полезные и сами по себе. Это повысит "читабельность", устойчи- вость и переносимость ваших программ. В идеале каждая функция не должна занимать больше 20—30 строк, возможно, за редким исключением. Этот совет применим вообще ко всем языкам программирования, а не только к РНР. Второе: как известно, вызов функции тоже отнимает какое-то время, поэто- му распространено мнение , что чем меньше функций, тем быстрее работает программа. Оно в корне неверно: не стоит обращать внимания на цену вы- зова функции, пока она сама об этом не заявит. В конце концов, объеди- нить несколько функций в одну всегда на порядок проще, чем разбить одну функцию на несколько. Помните об этом. Наконец, последнее: больше используйте встроенные, стандартные функции . Прежде чем писать какую-то процедуру, сверьтесь с документацией — воз- можно, она уже реализована в ядре РНР. Если это так, то не думайте, что сможете написать ее эффективнее на РНР — ведь часто самый неэффектив- ный Си-код работает быстрее, чем самый изящный на РНР. Возможно, лучше пожертвовать объемом за счет быстродействия — например, при работе с ба- зами данных и сложными файлами лучше применять стандартные функции сериализации, чем писать более эффективно упаковывающие, но свои, пото- му что стандартные работают очень быстро. Правда, из этого правила сущест- вуют и исключения : например , я бы не советовал вам использовать serialize () для формирования строки, сохраняющейся в Cookies браузера — здесь лучше написать свои функции. Опять же, туг действует принцип : чем меньше в профамме собственноручно реализованных функций, тем надежнее она будет работать и тем меньше ее придется тестировать. ----------------------- Page 218----------------------- Y ' -чЛ-Л-О \ V ЧАСТЬ IV. СТАНДАРТНЫЕ ФУНКЦИИ PHP ----------------------- Page 219----------------------- ----------------------- Page 220----------------------- Не будет преувеличением сказать, что самой привлекательной чертой языка РНР является набор его стандартных, или встроенных, функций . Пожалуй, без них язык вообще представлял бы очень малую ценность. Главное, что этот набор постоянно пополняется с выходом новых версий языка : напри- мер, еще совсем недавно в РНР не было функций, поддерживающих кон- цепцию сессии (то есть, устойчивого между запусками сценария окружения переменных, связанных по отдельности с каждым пользователем програм- мы), функций для работы с изображениями и регулярными выражениями в формате PCRE (Perl-compatible regular expression — регулярные выражения языка Perl). Сейчас все это (и многое другое!) уже есть, и, конечно, грех не воспользоваться такими возможностями... Мы уже ознакомились с некоторыми базовыми функциями , которые в силу их специализации можно было бы даже назвать операторами. Среди них - функция вывода echo , функции для работы с массивами и переменными и т. д. В этой части книги мы займемся остальными встроенными в РНР процедурами, которые чаще всего требуются в Web-программировании . Ко- нечно, объем книги не позволяет описать абсолютно все функции, да это и невозможно, потому что такое описание тут же устареет с выходом новой версии языка. Так что, как всегда, лучшим другом программиста (может быть, правильнее сказать подругой?) обязательно должна стать документа- ция, поставляемая вместе с дистрибутивами РНР, или ее oruine-версия, рас- положенная по адресу http://www.php.net или http://ru.php.net. Использова- ние документации из Интернета привлекательно еще и потому, что она фактически представляет собой один большой форум, в котором приведены различные комментарии, оставленные энтузиастами. Вы тоже можете внести свою лепту в это общее дело. Что же, разработчики РНР не боги, и иногда даже во встроенных функциях встречаются ошибки. Если вы наткнулись на одну из таких ошибок, радуй- тесь: у вас есть возможность внести вклад в общемировое дело Web- программирования! Вначале постарайтесь локализовать ошибку — напишите небольшой (как можно меньше!) сценарий, который будет работать непра- вильно. Далее, если у вас есть исходные тексты РНР и вы в состоянии в них разобраться (а это несколько проще, чем кажется на первый взгляд), попы- тайтесь найти в них то место, где происходит недоразумение. Наконец, от- правьте накопленный материал по электронной почте разработчикам РНР (адрес можно узнать на сайте http://www.php.net) и, скорее всего, в будущих версиях языка ошибки уже не будет. Однажды со мной случился такой слу- чай, и я был приятно удивлен той скоростью, с которой мне пришел ответ от разработчиков (в течение одного дня). Причем ответ развернутый, а не простая отписка и, главное, в следующей версии РНР, действительно, най- денной ошибки уже не оказалось. ----------------------- Page 221----------------------- Глава 12 Строковые функции • Строки в РНР — одни из самых универсальных объектов. Как мы уже виде- ли , любой , сколь угодно сложный объект можно упаковать в строку при по- мощи функции s e r i a l i z e ; ) (и обратно через unseriaiiz e о ). Строка может содержать абсолютно любые символы с кодами от 0 до 255 включительно . Нет никакого специального маркера "конца строки", как это сделано в Си (там конец строки помечается символом с нулевым кодом). А значит , длина строки во внутреннем представлении РНР хранится где-то отдельно. Для формирования и вставки непечатаемого символа в строку (например, с ко- дом 1 или 15) используется функция chr о , которую мы рассмотрим ниже. Наконец, из-за слабого контроля типов в РНР строка может содержать (и часто содержит) число , причем с 'ней можно работать, как с числом : при- бавлять другие числа, умножать и т. д. При этом все преобразования (в де- сятичной системе) производятся автоматически. Существуют также функ- ции, преобразующие число, записанное в различных системах счисления (например, в восьмеричной), в обычное представление, и наоборот. Их мы обсудим позже, в следующей главе. Замечание В этой главе я описываю только самые употребительные и удобные функции (около 80%), пропуская все остальные. Какие-то из не вошедших в данную гла- ву функций (например, quotemeta ( ) ) мы будем рассматривать в других гла- вах — там, где это показалось мне наиболее логичным. Так что, не найдя опи- сание интересующей вас функции здесь, подумайте: возможно, оно лучше подходит для другой темы и его лучше поискать там? И, наконец, последней инстанцией для вас, конечно же, должна являться документация РНР. Конкатенация строк Самая, пожалуй, распространенная операция со строками — это их конка- тенация, или присоединение к одной строке другой. В ранних версиях РНР для этого, как и для сложения чисел , использовался оператор +, что посто- янно приводило к путанице: если к числу прибавляется строка, что должно получиться — число или строка? Если число, то вдруг наша строка содержа- ла на самом деле не число, а какой-то текст? В новой — третьей — версии ----------------------- Page 222----------------------- Глава 12. Строковые функции 207 интерпретатора разработчики отказались от этого механизма и объявили, что + следует применять только для сложения чисел, и никак иначе. Что же касается конкатенации строк, то для нее ввели специальный оператор "." (точка). Оператор "." всегда воспринимает свои операнды как строки и возвращает строку. В случае, если один из операндов не может быть переведен в стро- ковое представление, т. е. если это массив или объект, то он воспринимает- ся как строки array и object соответственно. Вообще говоря, это правило применимо и не только при сцеплении строк, но и при передаче такого операнда в какую-нибудь стандартную функцию, которой требуется строка. Например, следующие команды выведут слово Array : $a=array(10,20,30); echo $a // Внимание ! Неожиданный результат ! Есть и другой, более специализированный, способ конкатенации строк. Он обычно используется, когда значения строковых или числовых переменных перемежаются с обычными словами. Если, к примеру, у нас в $day хранится текущее число, в $month — название месяца и в $уеаг — год, то вывести строку вида "Сегодня 8 мая 2000 года" можно так: echo "Сегодня $day $month $year года"; При этом в строку, вырабатываемую инструкцией echo , автоматически в нужных местах вставятся значения наших переменных. Это позволяет кон- статировать тот факт, что в РНР все переменные начинаются с $. О сравнении строк и инструкции if-else Теперь я хотел бы рассмотреть одно тонкое место в интерпретаторе РНР, касающееся немного неправильной работы со строками. Заключается оно вот в чем. Если мы используем операторы сравнения == и != (или любые другие, которые могут потребовать перевода строки в число) с операнда- ми-строками, то результат, вопреки ожиданиям , не всегда оказывается верным. Чаще всего это проявляется как раз в инструкции if . Вот приме- ры (листинг 12.1): \ Листинг 12.1. Внимание! Опасное место! $опе=1; // число один $zero=0; // присваиваем число нуль if($one=="") echo 1 ; // очевидно , не равно — не выводит 1 if($zero=="") echo 3 ; // Внимание ! Вопреки ожиданиям печатает 3 ! ----------------------- Page 223----------------------- 208 Часть IV. Стандартные функции РНР if(""==$zero) echo 4 ; // И это тоже не поможет!. . if("$zero"=="") echo 5; // Не работает в некоторых версиях РНР 3 if(strval($zero)==""} echo 6 ; // Вот теперь правильно — не выводит 6 ifC$zero===""} echo 7 ; // Самый лучший способ , но не действует в РНР 3 Получается, что в операциях сравнения пустая строка "" прежде всего трак- туется как 0 (ноль) и уж затем — как "пусто"? Это звучит довольно парадок- сально, но это действительно так. Операнды сравниваются как строки толь- ко в том случае, если они оба — строки, в противном случае идет числовое сравнение. При этом пустая строка воспринимается как 0, впрочем, как и любая другая; которую интерпретатору не удалось перевести в число. Замечание В первых версиях РНР 3 при присоединении к числовому нулю пустой строки этот ноль не менял типа, не становился строкой "О" . Видимо, срабатывала ка- кая-то оптимизация, и РНР просто пропускал этот бессмысленный, на его взгляд, шаг. Проведенные мной тесты показывают, что в РНР версии 3.0.12 и старше эта ошибка исправлена, но все же иногда нужно иметь ее в виду, осо- бенно, если сценарии должны быть хорошо переносимыми. Итак, если вы хотите сравнить две переменные-строки, нужно быть абсо- лютно уверенными, что их типы именно строковые, а не числовые. Впрочем, это не распространяется на новый оператор РНР версии 4 === (тройное равенство, или оператор эквивалентности). Его использование за- ставляет интерпретатор всегда сравнивать величины и по значению, и по их типу. Итак, с точки зрения РНР 0=="" , но 0!=="" . Если вы не собираетесь программировать на РНР версии, не ниже третьей, рекомендую всегда ис- пользовать === вместо strval ( ) , как это было сделано в листинге 12.1. Существует одна стандартная ошибка, которую делают многие. Вот в чем она состоит. Есть такая функция — strpos ($str,$what ) , которая возвраща- ет позицию подстроки $what в строке $str или false , если подстрока не найдена. Пусть нам нужно проверить, встречается ли в некоторой строке $str подстрока ----------------------- Page 225----------------------- 210 Часть IV. Стандартные функции РНР : int ordfchar $ch) Эта функция , наоборот, возвращает код символа в $ch . Например, o r d ( c h r ( $ n ) ) всегда равно $п — конечно, если $п заключено между нулем и числом 255. int strrpos (string $where , char $what ) Данная функция, хотя и похожа внешне на strposo (см. ниже), несет не- сколько иную нагрузку. Она ищет в строке $where последнюю позицию , в которой встречается символ $what (если $what — строка из нескольких сим- волов, то выявляется только первый из них , остальные не играют никакой роли — обратите на это особое внимание!). В случае, если искомый символ не найден, возвращается false (см. замечание по этому поводу для strpos ( ) ). Вообще, могу сказать, что функция strrpos () применяется очень редко. Слишком уж она не универсальна. Функции отрезания пробелов По поводу философии написания программ, которые интенсивно обрабаты- вают данные , вводимые пользователем (а именно такими программами яв- ляется большинство сценариев) есть очень правильное изречение : ваша программа должна быть максимально строга к формату выходных данных и максимально лояльна по отношению ко входным данным . Это означает, что, прежде чем передавать полученные от пользователя строки куда-то дальше, — например, другим функциям , — нужно над ними немного пора- ботать. Самое простое, что можно сделать — это отрезать начальные и кон- цевые пробелы. ----------------------- Page 226----------------------- Глава 12. Строковые функции 211 Иногда трудно даже представить, какими могут быть странными пользова- тели, если дать им в руки клавиатуру и попросить напечатать на ней какое- нибудь слово. Так как клавиша пробела — самая большая, то пользователи имеют обыкновение нажимать ее в самые невероятные моменты. Этому способствует также и тот факт, что символ с кодом 32, обозначающий про- бел, как вы знаете, на экране не виден. Если программа не способна обра- ботать описанную ситуацию, то она, в лучшем случае после тягостного мол- чания отобразит в браузере что-нибудь типа "неверные входные данные", а в худшем — сделает при этом что-нибудь необратимое. Между тем, обезопасить себя от паразитных пробелов чрезвычайно просто, и разработчики РНР предоставляют нам для этого ряд специализированных функций. Не волнуйтесь о том, что их применение замедляет программу. Эти функции работают с молниеносной скоростью, а главное, одинаково быстро, независимо от объема переданных им строк. Конечно, я не призы- ваю к пароноидальному применению функций "отрезания" на каждой строчке программы, но в то же время, если есть хоть 1%-ная возможность того, что строка может содержать лишние пробелы, следует без колебаний от них избавляться. В конце концов, отсекать пробелы один раз или тыся- чу — все равно, а вот не отрезать совсем и отрезать однажды — большая разница. Кстати, если отделять нечего, описанные ниже функции мгновен- но заканчивают свою работу, так что их вызов обходится совсем дешево. string trira(string $st) Возвращает копию $st , только с удаленными ведущими и концевыми про- бельными символами. Под пробельными символами я здесь и далее подра- зумеваю: пробел " ", символ перевода строки \п , символ возврата каретки \г и символ табуляции \t . Например, вызов trim( " test\n ") вернет строку "test" . Эта функция используется очень широко. Старайтесь применять ее везде, где есть хоть малейшее подозрение на наличие ошибочных пробелов. По- скольку работает она очень быстро. string Itrim(string $st) To же, что и trimo , только удаляет исключительно ведущие пробелы, а концевые не трогает. Используется гораздо реже. Старайтесь всегда вместо нее применять trim о , и не прогадаете. string chop(string $st ) Удаляет только концевые пробелы, ведущие не трогает. Эта функция будет наверняка очень популярной у тех, кто раньше программировал на Perl. Од- нако следует заметить, что в РНР она выполняет другую функцию. 8 Зак. 699 ----------------------- Page 227----------------------- 212 Часть IV. Стандартные функции РНР Базовые функции int strlen(string $st) Одна из наиболее полезных функций. Возвращает просто длину строки, т. е., сколько символов содержится в $st. Как уже упоминалось, строка может со- держать любые символы, в том числе и с нулевым кодом (что запрещено в Си). Функция strlen ( ) будет правильно работать и с такими строками. int strpos(string $where , string $what , int $fromwhere=0) Пытается найти в строке $where подстроку (то есть последовательность символов) $what и в случае успеха возвращает позицию (индекс) этой под- строки в строке. Первый символ строки, как и в Си, имеет индекс 0. Необя- зательный параметр $fromwhere можно задавать, если поиск нужно вести не с начала строки $from , а с какой-то другой позиции . В этом случае следует эту позицию передать в $fromwhere . Если подстроку найти не удалось, функция возвращает false . Однако будьте внимательны, проверяя результат вызова strpos о на fals e — используйте ля этого только оператор ===. string substr(string $str , int $from [,int $length] ) Данная функция тоже востребуется очень часто. Ее назначение — возвра- щать участок строки $str , начиная с позиции $start и длиной $iength . Ес- ли $ length не задана, то подразумевается подстрока от $ start до конца строки $str . Если $start больше, чем длина строки, или же значение $ length равно нулю, то возвращается пустая подстрока. Однако эта функция может делать и еще довольно полезные вещи. К при- меру, если мы передадим в $start отрицательное число, то будет считаться, что это число является индексом подстроки, но только отсчитываемым от конца $str (например, - 1 означает "начиная с последнего символа строки"). Параметр $iength , если он задан, тоже может быть отрицательным. В этом случае последним символом возвращенной подстроки будет символ из $str с индексом $iength , определяемым от конца строки. int strcmp(string $strl , string $str2) Сравнивает две строки посимвольно (точнее, побайтово) и возвращает: О, если строки полностью совпадают; -1, если строка $strl лексикографиче- ски меньше $str2 ; и 1, если, наоборот, $stri "больше" $str2 . Так как срав- нение идет побайтово, то регистр символов влияет на результаты сравнений . int strcasecmp(string $strl , string $str2 ) To же самое, что и strcmpo , только при работе не учитывается регистр букв. Например, с точки зрения этой функции "аь" и "АВ" равны. ----------------------- Page 228----------------------- Глава 12. Строковые функции 213 Замечание Если ваша строка состоит только из "английских" букв, проблем не будет. Од- нако в случае использования "русских" букв результат (точнее, правильность) работы функции strcasecmpO сильно зависит от настроек текущей локали (с/и. ниже). Работа с блоками текста Перечисленные ниже функции чаше всего оказываются полезны, если нуж- но проводить однотипные операции с многострочными блоками текста, за- данными в строковой переменной . string str_replace(string $from , string $to , string $str ) Заменяет в строке $str все вхождения подстроки $from (с учетом регистра) на $to и возвращает результат. Исходная строка, переданная третьим пара- метром, при этом не меняется. Эта функция работает значительно быстрее, чем ereg_repiace ( ) , которую мы рассмотрим в главе о регулярных выраже- ниях РНР, и ее часто используют, если нет необходимости в каких-то экзо- тических правилах поиска подстроки. Например, вот так мы можем замес- тить все символы перевода строки на их HTML-эквивалент — тэг
: $st=str_replace("\n","
\n",$st) Как видим, то, что в строке
\n тоже есть символ перевода строки, ни- как не влияет на работу функции, т. е. функция производит лишь однократ- ный проход по строке. Для решения описанной задачи также применима функция ni2br ( ) , которая работает чуть быстрее. string n!2br(string $string ) Заменяет в строке все символы новой строки \п на
\n и возвращает ре- зультат. Исходная строка не изменяется. Обратите внимание на то, что сим- волы \г , которые присутствуют в конце строки текстовых файлов Windows, этой функцией никак не учитываются, а потому остаются на старом месте. string Wordwrap(string $st , int $width=75 , string $break="\n") Эта функция, наконец-то появившаяся в РНР версии 4, оказывается неве- роятно полезной при форматировании текста письма перед автоматической отправкой его адресату при помощи mail о. Она разбивает блок текста $st на несколько строк, завершаемых символами $break , так, чтобы на одной строке было не более $width букв. Разбиение происходит по границе слова, так что текст остается читаемым. Возвращается получившаяся строка с сим- волами перевода строки, заданными в $break . Давайте рассмотрим пример, как мы можем отформатировать некоторый текст по ширине поля 60 сим- ----------------------- Page 229----------------------- 214 _ Часть IV. Стандартные функции РНР волов, предварив каждую строку префиксом " " (то есть, оформить его как цитирование, принятое в электронной переписке): function Cite($OurText , $prefix="> ") ! $st=WordWrap($OurText , 60-strlen (Spref ix ) , "\n") ; $st=$prefix. str_replace ("\n", "\n$prefix" , $st ) ; // можно было бы сделать это и одной операцией , но так , // по-моему , несколько универсальнее . return $st; string strip_tags (string $str [ , string $allowable_tags ] } Эта функция удаляет из строки все тэги и возвращает результат. В парамет- ре $aiiowabie_tags можно передать тэги, которые не следует удалять из строки. Они должны перечисляться вплотную друг к другу. Вот пример: $st=" <Ь>Жирный текст <^>Моноширинный TeKCT
<а href=http : //www.dklab . ги>Ссылка" ; echo "Исходный текст : $st"; echo "<пг>После удаления тэгов : " . strip_tags ($st , "
" ) . "
" ; Запустив этот пример, мы сможем заметить, что тэги <а> и <ь> не были удалены (ровно как и их парные закрывающие), в то время как исчез. string str repeat (string $st , string $number ) Функция "повторяет" строку $st $number раз и возвращает объединенный результат. Вот пример: echo str_repeat ( "test ! ", 3) ; // выводит test ! test ! test ! Функции для преобразований символов Web-программирование — одна из тех областей, в которых постоянно при - ходится манипулировать строками: разрывать их, добавлять и удалять про- белы, перекодировать в разные кодировки, наконец, URL- кодировать и де- кодировать. В РНР реализовать все эти действия вручную, используя только уже описанные примитивы , просто невозможно из соображений быстродей- ствия. Поэтому- то и существуют встроенные функции , описанные в этом разделе. ----------------------- Page 230----------------------- Глава 12. Строковые функции 215 string strtr(string $str , string $from , string $to) Эта функция применяется не столь широко, но все-таки иногда она бывает довольно полезной . Делает она вот что: в строке $str заменяет все символы , встречающиеся в $from , на их "парные" (то есть расположенные в тех же позициях, что и во $from) из $to . Функция работает существенно быстрее, чем ereg_repiace о, которую мы рассмотрим в главе, посвященной регу- лярным выражениям . Правде, она имеет вместе с тем несколько меньшую функциональность... Следующие несколько функций предназначены для быстрого URL- кодирования и декодирования. string UrlEncode(string $st) Функция URL-кодирует строку $st и возвращает результат. Эту функцию удобно применять , если вы , например , хотите динамически сформировать ссылку <а href= . . . > на какой-то сценарий , но не уверены, что его пара- метры содержат только алфавитно-цифровые символы. В этом случае вос- пользуйтесь функцией так: echo "
$v) ( ?> Имя :
Текст :
Используя этот незамысловатый прием, вы гарантированно избавите себя от проблем с запретом тэгов. Замечание Начинающие Web-программисты для решения задачи запрета тэгов часто пы- таются просто удалить их из строки — например, применив функцию strip_tags ( ) . Это метод довольно плох, потому что всегда существует веро- ятность того, что злоумышленник сможет "обмануть" эту функцию. Конечно, еще хуже метод с применением регулярных выражений, потому что, как из- вестно, с их помощью вовсе невозможно выделить некоторые тэги из строки — например, тэги такого вида: <а name= ' a>b ' >. string StripSlashes (string $st) Заменяет в строке $st некоторые предваренные слэшем символы на их од- нокодовые эквиваленты. Это относится к следующим символам: ", ' , \ и никаким другим. string AddSlashes (string Sst ) Вставляет слэши только перед следующими символами: ' , " и \ . Функцию очень удобно использовать при вызове evai o (эта функция выполняет стро- ку, переданную ей в параметрах, так, как будто имеет дело с небольшой РНР- программой; о ней (функции) мы еще поговорим, и при том очень подробно). Функции изменения регистра Довольно часто нам приходится переводить какие-то строки, скажем, в верхний регистр, т. е. делать все прописные буквы в строке заглавными. В принципе, для этой цели можно было бы воспользоваться функцией strtro , рассмотренной выше, но она все же будет работать не так быстро, как нам иногда хотелось бы. В РНР есть функции , которые предназначены специально для таких нужд. Вот они . string strtolower (string $str ) Преобразует строку в нижний регистр. Возвращает результат перевода. ----------------------- Page 232----------------------- Глава 12. Строковые функции 217 Надо заметить, что при неправильной настройке локали (про локаль будет рассказано чуть позже, а пока скажу только, что это набор правил по пере- воду символов из одного регистра в другой, переводу даты и времени, де- нежных единиц и т. д.) функция будет выдавать, мягко говоря, странные результаты при работе с буквами кириллицы. Возможно, в несложных про- граммах, а также если нет уверенности в поддержке соответствующей лока- ли операционной системой, проще будет воспользоваться "ручным" преоб- разованием символов, задействуя функцию strtr ( ) : $st=strtr($st, "AБBГДEЁЖЗИЙKЛMHOПPCTУФXЦЧШЩЬЫЬЭЮЯABCDEFGHIKLMNOPQRSTUVWXYZ", • "абвгдеёжзийклмнопрстуфхцчшщъыьэюяаЬсс!еPghiklmnopqrstuvwxyz"); Главное достоинство данного способа — то, что в случае проблем с коди- ровкой для восстановления работоспособности сценария вам придется всего лишь преобразовать его в ту же кодировку, в которой у вас хранятся доку- менты на сервере. string strtoupper (string $str ) Переводит строку в верхний регистр. Возвращает результат преобразования. Эта функции также прекрасно работает со строками, составленными из "английских" букв, но с "русскими" буквами может возникнуть все та же проблема. Установка локали (локальных настроек) string SetLocale(string $category , string $locale) Функция устанавливает текущую локаль, с которой будут работать функции преобразования регистра символов, вывода даты-времени и т. д. Вообще говоря, для каждой категории функций локаль определяется отдельно и выглядит по-разному. То, какую именно категорию функций затронет вызов SetLocale о, задается в параметре $category . Он может принимать сле- дующие строковые значения: П LC_CTYP E -- активизирует указанную локаль для функций перевода в верхний/нижний регистры; П LC_NUMERIC — активизирует локаль для функций форматирования дроб- ных чисел — а именно, задает разделитель целой и дробной части в чис- лах; П LCJTIME — задает формат вывода даты и времени по умолчанию ; П LC__ALL — устанавливает все вышеперечисленные режимы . Теперь поговорим о параметре $iocaie . Как известно, каждая локаль, уста- новленная в системе, имеет свое уникальное имя, по которому к ней можно ----------------------- Page 233----------------------- 218 Часть IV. Стандартные функции РНР обратиться. Именно оно и фиксируется в этом параметре. Однако, есть два важных исключения из этого правила. Во-первых, если величина $ locale равна пустой строке "", то устанавливается та локаль, которая указана в гло- бальной переменной окружения с именем, совпадающим с именем категории $category (или LANG — она практически всегда присутствует в Unix). Во- вторых, если в этом параметре передается 0, то новая локаль не устанавлива- ется, а просто возвращается имя текущей локали для указанного режима. К сожалению, имена локалей задаются при настройке операционной систе- мы, и для них , по-видимому , не существует стандартов. Выясните у своего хостинг-провайдера , как называются локали для разных кодировок русских символов. Но, если следующий фрагмент работает у вашего хостинг- провайдера, это не означает, что он заработает, например , под Windows : setlocale('LC^CTYPE' , 'ru_SU.KOI8-R' ) ; Здесь вызов устанавливает таблицу замены регистра букв в соответствии с кодировкой KOI8-R. По правде говоря, локаль — вещь довольно непредсказуемая и, как я уже говорил, довольно плохо переносимая между операционными системами . Так что, если ваш сценарий не очень велик, задумайтесь: возможно, лучше будет искать обходной путь (например, использовать strtro) , а не рассчи- тывать на локаль. Преобразование кодировок Часто встречается ситуация, когда нам требуется преобразовать строку из одной кодировки кириллицы в другую . Например , мы в программе сменили локаль : была кодировка windows, а стала — KOI8-R. Но строки-то остались по-прежнему в кодировке WIN-1251 , а значит, для правильной работы с ними нам нужно их перекодировать в KOI8-R. Для этого и служит функция преобразования кодировок. string convert_cyr_string(string $str , char $from , char $to) ; Функция переводит строку $str из кодировки $from в кодировку $to . Ко- нечно, это имеет смысл только для строк, содержащих "русские" буквы, т. к. латиница во всех кодировках выглядит одинаково. Разумеется, кодировка Sfrom должна совпадать с истинной кодировкой строки, иначе результат получится неверным. Значения $from и $to — один символ, определяющий кодировку: П k — koi8-r П w — windows-125 1 П i - iso8859-5 ----------------------- Page 234----------------------- Глава 12. Строковые функции 219 Па — х-ср866 П d — х-ср866 d m — x-mac-cyrillic Функция работает достаточно быстро, так что ее вполне можно применять , скажем, для перекодировки писем в нужную форму перед их отправкой по электронной почте. Функции форматных преобразований Как мы знаем, переменные в строках РНР интерполируются, поэтому прак- тически всегда задача "смешивания" текста со значениями переменных не является проблемой. Например , мы можем спокойно написать что-то вроде: echo "Привет , Sname ! Вам $аде лет." ; Вспомните, что в Си нам приходилось для аналогичных целей писать сле- дующий код: printf("Привет , %s ! Вам %з лет",name , age) ; Язык РНР также поддерживает ряд функций , использующих такой же син- таксис, как и их Си-эквиваленты . Бывают случаи , когда их применение дает наиболее красивое и лаконичное решение , хотя это и случается довольно нечасто. string sprintf(string $format [ , mixed args , ...]) Эта функция — аналог функции sprintf о в Си. Она возвращает строку, составленную на основе строки форматирования, содержащей некоторые специальные символы, которые будут впоследствии заменены на значения соответствующих переменных из списка аргументов. Строка форматирования $ format может включать в себя команды формати- рования, предваренные символом %. Все остальные символы копируются в выходную строку как есть. Каждый спецификатор формата (то есть, символ % и следующие за ним команды) соответствует одному , и только одному па- раметру, указанному после параметра $format . Если же нужно поместить в текст % как обычный символ , необходимо его удвоить: echo sprintf ("The percentage was 9od%%", $percentage ) ; Каждый спецификатор формата включает максимум пять элементов (в по- рядке их следования после символа %) : П Необязательный спецификатор размера поля , который указывает, сколь- ко символов будет отведено под выводимую величину . В качестве симво - лов-заполнителей (если значение имеет меньший размер, чем размер по- ля для его вывода) может использоваться пробел или 0, по умолчанию ----------------------- Page 235----------------------- 220 Часть IV. Стандартные функции РНР подставляется пробел. Можно задать любой другой символ-наполнитель, если указать его в строке форматирования, предварив апострофом ' . П Опциональный спецификатор выравнивания, определяющий, будет ре- зультат выровнен по правому или по левому краю поля. По умолчанию производится выравнивание по правому краю, однако можно указать и левое выравнивание, задав символ - (минус). О Необязательное число, определяющее размер поля для вывода величины . Если результат не будет в поле помещаться, то он "вылезет" за края этого поля, но не будет усечен. О Необязательное число, предваренное точкой ".", предписывающее, сколько знаков после запятой будет в результирующей строке. Этот спе- цификатор учитывается только в том случае, если происходит вывод чис- ла с плавающей точкой, в противном случае он игнорируется. П Наконец, обязательный (заметьте — единственный обязательный!) спе- цификатор типа величины, которая будет помещена в выходную строку: • ь — очередной аргумент из списка выводится как двоичное целое число; • с — выводится символ с указанным в аргументе кодом; • d — целое число; • f — число с плавающей точкой; • о — восьмеричное целое число; • s — строка символов; • х — шестнадцатеричное целое число с маленькими буквами a—f; • х — шестнадцатеричное число с большими буквами A—F. Вот как можно указать точность представления чисел с плавающей точкой: $moneyl = 68.75; $money2 = 54.35; $money = $moneyl + $money2 ; // echo $money выведет "123.1".. . $formatted = sprintf ("%01.2f", $money) ; // echo $formatted выведет "123.10" ! Вот пример вывода целого числа, предваренного нужным количеством нулей: $isodate=sprintf("%04d-%02d-%02d",$year,$month,$day); Последний пример может вам очень пригодиться и показывает, насколько удобной может иногда быть функция sprintf ( ) . ----------------------- Page 236----------------------- Глава 12. Строковые функции 221 void printf(string $format [ , mixed args , ...]) Делает то же самое, что и sprintf ( ) , только результирующая строка не воз- вращается, а направляется в браузер пользователя. string number_format(float $number , int $decimals , string $dec_point="." , string $thousands_sep=",") ; Эта функция форматирует число с плавающей точкой с разделением его на триады с указанной точностью. Она может быть вызвана с двумя или че- тырьмя аргументами, но не с тремя! Параметр $decimais задает, сколько цифр после запятой должно быть у числа в выходной строке. Параметр $dec_point представляет собой разделитель целой и дробной частей, а па- раметр $thousands_sep — разделитель триад в числе (если указать на его месте пустую строку, то триады не отделяются друг от друга). В РНР существует еще несколько функций для выполнения форматных преобразований, среди н и х — s s c a n f o и f s c a n f o , которые часто приме- няются в Си. Однако в РНР их использование весьма ограничено: чаше всего для разбора строк оказывается гораздо выгоднее привлечь регулярные выражения или функцию explode ( } . Именно по этой причине я здесь не уделяю повышенного внимания этим функциям . Работа с бинарными данными Как мы уже знаем, строки могут содержать любые, в том числе и бинарные , данные (то есть, символы с кодами, меньшими 32). Для работы с такими строками иногда удобно использовать функции pack ( ) и unpack ( ) . string pack(string $format [,mixed $args , ...]) Функция pack ( ) упаковывает заданные аргументы в бинарную строку, кото- рая затем и возвращается. Формат параметров, а также их количество, задает- ся при помощи строки $ format , которая представляет собой набор однобук- венных спецификаторов форматирования — наподобие тех, которые указы- ваются в sprintf о, но только без знака %. После каждого спецификатора может стоять число, которое отмечает, сколько информации будет обработано данным спецификатором. А именно, для форматов a, A, h и н число задает, какое количество символов будет помещено в бинарную строку из тех, что находятся в очередном параметре-строке при вызове функции (то есть, опре- деляется размер поля для вывода строки). В случае @ оно определяет абсолют- ную позицию, в которую будут помещены следующие данные. Для всех ос- тальных спецификаторов следующие за ними числа задают количество аргу- ментов, на которые распространяется действие данного формата. Вместо чис- ла можно указать *, в этом случае подразумевается, что спецификатор дейст- вует на все оставшиеся данные. Вот полный список спецификаторов формата: П а — строка, свободные места в поле заполняются символом с кодом 0; ----------------------- Page 237----------------------- 222 Часть IV. Стандартные функции РНР П А — строка, свободные места заполняются пробелами; П ь — шестнадцатеричная строка, младшие разряды в начале; П н — шестнадцатеричная строка, старшие разряды в начале; О с — знаковый байт (символ); П с — беззнаковый байт; П з — знаковое короткое целое (16 битов, порядок байтов определяется ар- хитектурой процессора); П s — беззнаковое короткое целое; П п — беззнаковое целое (16 битов, старшие разряды в конце) ; П v — беззнаковое целое (16 битов, младшие разряды в конце); П i — знаковое целое (размер и порядок байтов определяется архитектурой); П i — беззнаковое целое; П 1 — знаковое длинное целое (32 бита, порядок байтов определяется архи- тектурой); П L — беззнаковое длинное целое; O N — беззнаковое длинное целое (32 бита, старшие разряды в конце); П v — беззнаковое целое (32 бита, младшие разряды в конце); П f — число с плавающей точкой (зависит от архитектуры); П а — число с плавающей точкой двойной точности (зависит от архитектуры); П х — символ с нулевым кодом; П х — возврат назад на 1 байт; П @ — заполнение нулевым кодом до заданной абсолютной позиции . Немало, не правда ли? Вот пример использования этой функции : // Целое , целое , все остальное — символы $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 . Возвращает она ассоциативный массив, содержащий элементы распакованных данных . Строка Sformat задается немного в другом формате, чем в функции p a c k O , а именно, после каждого спецификатора (или после завершающего его чис- ----------------------- Page 238----------------------- Глава 12. Строковые функции 223 ла) должно "впритык" следовать имя ключа в ассоциативном массиве. Раз- деляются параметры при помощи символа / . Например : $array=unpack("c2chars/nint", $bindata) ; В результирующий массив будут записаны элементы с ключами: charsi , chars2 и int . Как видим , если после спецификатора задано число, то к имени ключа будут добавлены номера 1, 2 и т. д., т. е. в массиве появятся несколько ключей, отличающихся суффиксами . Когда бывают полезны функции pack о и unpack',) ? Например, вы считали участок GIF-файла , содержащий его размер в пикселах , и хотите преобразо- вать бинарную 32-битную ячейку памяти в формат, понятный РНР. Или , наоборот, стремитесь работать с файлами с фиксированным размером запи- си. В этом случае вам и пригодятся рассматриваемые функции . Вообще го- воря, функции pack о и unpack о применяются сравнительно редко. Это связано с тем, что в РНР практически все действия , которые могут потребо- вать работы с бинарными данными (например , анализ файла с рисунком с целью определения его размера) , уже реализованы в виде встроенных функ- ций (в нашем примере с GIF-картинкой это Getimagesiz e ( ) ) . Хэш-функции string mc!5 (string $st) Возвращает хэш-код строки $st , основанный на алгоритме корпорации RSA Data Security под названием "MD5 Message-Digest Algorithm" . Хэш-код — это просто строка, практически уникальная для каждой из строк $st . To есть вероятность того, что две разные строки, переданные в $st , дадут нам одина- ковый хэш-код, стремится к нулю . Примечание Я где-то читал об одном опыте, в котором принимали участие более 1000 мощ- ных компьютеров, на протяжении года генерировавшие хэш-коды для строк, и за все время не было обнаружено ни одного совпадения MDS-кодов для раз- личных строк. Более того, математически доказано, что они могли бы с тем же результатом заниматься этим на протяжении еще нескольких тысяч лет. В то же время, если длина строки $st может достигать нескольких тысяч символов, то ее MDS-код занимает максимум 32 символа. Для чего нужен хэш-код и, в частности, алгоритм MD5? Например, для проверки паролей на истинность. Пусть, к примеру, у нас есть система со многими пользователями, каждый из которых имеет свой пароль. Можно , конечно, хранить все эти пароли в обычном виде, или зашифровать их ка- ким-нибудь способом, но тогда велика вероятность того, что в один пре- ----------------------- Page 239----------------------- 224 Часть IV. Стандартные функции РНР красный день этот файл с паролями у вас украдут. Если пароли были за- шифрованы, то, зная метод шифрования , не составит особого труда их рас- кодировать. Однако можно поступить другим способом, при использовании которого даже если файл с паролями украдут, расшифровать его будет мате- матически невозможно. Сделаем так: в файле паролей будем хранить не са- ми пароли, а их (MD5) хэш-коды. При попытке какого-либо пользователя войти в систему мы вычислим хэш-код только что введенного им пароля и сравним его с тем, который записан у нас в базе данных . Если коды совпа- дут, значит, все в порядке, а если нет — что ж, извините... Конечно, при вычислении хэш-кода какая-то часть информации о строке $st безвозвратно теряется. И именно это позволяет нам не опасаться, что зло- умышленник, получивший файл паролей, сможет его когда-нибудь расшиф- ровать. Ведь в нем нет самих паролей, нет даже их каких-то связных частей! Алгоритм MD5 специально был изобретен для того, чтобы как раз и обеспе- чить описанную выше схему. Так как все же есть вероятность того, что у разных строк МО5-коды совпадут, то, чтобы не дать возможность злоумыш- леннику войти в систему, перебирая пароли с бешеной скоростью, алгоритм MD5 работает довольно медленно. И его нельзя никак убыстрить, потому что это будет уже не MD5. Так что даже на самых мощных компьютерах вряд ли получится перебирать более нескольких тысяч паролей в секунду, а это совсем маленькая скорость, капля в океане возможных MDS-кодов. int crc32(string Sstr ) Функция сгс32 о вычисляет 32-битную контрольную сумму строки $str . To есть, результат ее работы — 32-битное (4-байтовое) целое число. Эта функ- ция работает гораздо быстрее md5 о , но в то же время выдает гораздо менее надежные "хэш-коды" для строки. Так что, теперь, чтобы получить методом случайного подбора для двух разных строк одинаковые "хэш-коды", вам по- требуется не триллион лет работы самого мощного компьютера, а всего лишь год-другой. Впрочем, если не использовать генератор случайных чи- сел, а разобраться в алгоритме вычисления 32-битной контрольной суммы, эту же задачу легко можно решить буквально за секунду, потому что алго- ритм сгс32 имеет неизмеримо большую предсказуемость, чем MD5. string crypt(string $str [,string $salt] ) Алгоритм шифрования DES до недавнего времени был стандартным для всех версий Unix и использовался как раз для кодирования паролей пользо- вателей (тем же самым способом, о котором мы говорили при рассмотрении функции mdso) . Но в последнее время MD5 постепенно начал его вытес- нять. Это и понятно: MD5 гораздо более надежен. Рекомендую и вам везде применять md5( ) вместо crypto . Впрочем, функция crypto все же может понадобиться вам в одном случае: если вы хотите сгенерировать хэш-код для другой программы, которая использует именно алгоритм DES (например, для сервера Apache). ----------------------- Page 240----------------------- Глава 12. Строковые функции 225 Хэш-код для одной и той же строки, но с различными значениями $sait (кстати, это должна быть обязательно двухсимвольная строка) дает разные результаты. Если параметр $sait пропущен, РНР сгенерирует его случай- ным образом, так что не удивляйтесь работе следующего примера: $st="This is the test"; echo crypt($st)."
" ; // можем получить , например , 7N8JKLKbBWEhg echo crypt($st)."
" ; // а здесь появится , например , Jsk746pawBOA2 Как видите, два одинаковых вызова crypt o без второго параметра выдают совершенно разные хэш-коды. За деталями работы функции обращайтесь к документации РНР. Сброс буфера вывода void f l u s h ( ) Эта функция имеет очень и очень отдаленное отношение к работе со строка- ми, но она еще дальше отстоит от других функций. Именно поэтому я вклю- чил ее в данную главу. Начнем издалека: обычно при использовании echo данные не прямо сразу отправляются клиенту, а накапливаются в специаль- ном буфере, чтобы потом транспортироваться большой "пачкой". Так получа- ется быстрее. Однако, иногда бывает нужно досрочно отправить все данные из буфера пользователю, например, если вы что-то выводите в реальном вре- мени (так зачастую работают чаты). Вот тут-то вам и поможет функция flush ( ) , которая отправляет содержимое буфера echo в браузер пользователя. ----------------------- Page 241----------------------- Глава 13 Работа с массивами В части III книги мы уже рассматривали многие возможности, которые предоставляет РНР для работы с ассоциативными массивами. В их число входят различные механизмы перебора, получение числа элементов, опери- рование ключами и значениями и т. д. Однако здесь перечислено далеко не все, что можно делать с массивами в РНР. Язык (особенно версии 4) содержит множество других, иногда крайне полезных, функций . В этой главе мы рассмотрим большинство из них . Сортировка массивов Начнем с самого простого — сортировки массивов. В РНР для этого суще- ствует очень много функций. С их помощью можно сортировать ассоциа- тивные массивы и списки в порядке возрастания или убывания , а также в том порядке, в каком заблагорассудится — посредством пользовательской функции сортировки. Сортировка массива по значениям (asort()/arsort()) Функция asort o сортирует массив, указанный в ее параметре, так, чтобы его значения шли в алфавитном (если это строки) или в возрастающем (для чисел) порядке. При этом сохраняются связи между ключами и соответст- вующими им значениями , т. е. некоторые пары ключ=>значение просто "всплывают" наверх, а некоторые — наоборот, "опускаются". Например : $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 о выполняет то же самое, за одним исключением: она упо- рядочивает массив не по возрастанию, а по убыванию. ----------------------- Page 242----------------------- Глава 13. Работа с массивами 227 Сортировка по ключам (ksort()/krsort()) Функция ksort o практически идентична функции asorto , с тем различи- ем, что сортировка осуществляется не по значениями, а по ключам (в по- рядке возрастания). Например: $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 ( ) . Сортировка по ключам при помощи функции uksortQ Довольно часто нам приходится сортировать что-то по более сложному кри- терию, чем просто по алфавиту. Например, пусть в $Fiies хранится список имен файлов и подкаталогов в текущем каталоге. Возможно, мы захотим вывести этот список не только в лексикографическом порядке, но также и чтобы все каталоги предшествовали файлам. В этом случае нам стоит вос- пользоваться функцией u k s o r t o , написав предварительно функцию срав- нения с двумя параметрами, как того требует uksort ( ) . : Листинг 13.1. Сортировка с помощью пользовательской функции // Эта функция должна сравнивать значения $fl и $f2 и возвращать : // -1, если $fl<$f2 , // 0 , если $fl==$f2 // 1 , если $fl>$f2 // Под < и > понимается следование этих имен в выводимом списке function FCmp($fl,$f2) { // Каталог всегда предшествует файлу if(is_dir($fl) && !is_dir($f2)) return -1; // Файл всегда идет после каталога if(!is_dir($fl) && is_dir($f2)) return 1 ; // Иначе сравниваем лексикографически if($fl<$f2) return -1; elseif($fl>$f2) return 1; else return 0; ----------------------- Page 243----------------------- 228 Часть IV. Стандартные функции РНР // Пусть $Files содержит массив с ключами — именами файлов // в текущем каталоге . Отсортируем его . uksort($Files,"FCmp"); // передаем функцию сортировки "по ссылке " Конечно, связи между ключами и значениями функцией uksort ( ) сохраня- ются, т. е., опять же, некоторые пары просто "всплывают" наверх, а дру- гие — "оседают". Сортировка по значениям при помощи функции uasortO Функция uasort о очень похожа на uksort о , с той разницей, что сменной (пользовательской) функции сортировки "подсовываются" не ключи , а оче- редные значения из массива. При этом также сохраняются связи в парах ключ=>значение. Переворачивание массива array_reverce() Функция array_reverse ( ) возвращает массив, элементы которого следуют в обратном порядке относительно массива, переданного в параметре. При этом связи между ключами и значениями, конечно, не теряются. Например, вместо того, чтобы ранжировать массив в обратном порядке при помощи arsort ( ) , мы можем отсортировать его в прямом порядке, а затем перевернуть: $A=array("a"=>"Zero","b"=>"Weapon","c"=>"Alpha","d"=>"Processor"); asort ($A) ; $A=array_reverse($A); Конечно, указанная последовательность работает дольше, чем один-единст- венный ВЫЗОВ a r s o r t ( ) . Сортировка списка sort()/rsort() Эти две функции предназначены в первую очередь для сортировки списков (напоминаю, что под списками я понимаю массивы, ключи которых начи- наются с 0 и не имеют пропусков). Функция sort о сортирует список (разумеется, по значениям) в порядке возрастания, a rsort ( ) -- в порядке убывания. Например: $A=array("One", "Two", "Three" , "Four"); sort ($A) ; for($i=0; $iзначение не сохраняются, более того— ключи просто пропадают, поэтому сортировать что-либо, отличное от списка, вряд ли целесообразно. Сортировка списка при помощи функции usort() Эта функция как бы является "гибридом" функций uasort o и sort о. От sort ( ) она отличается тем, что критерий сравнения обеспечивается пользо- вательской функцией . А от uasort o - - тем , что она не сохраняет связей между ключами и значениями , а потому пригодна разве что для сортировки списков. Вот тривиальный пример: function FCmp($a,$b ) { return strcmp($a,$b) ; } $A=array("One","Two","Three","Four"); usort. ($A ) ; for($i=0; $i$ь. В принципе , приведен- ный здесь пример полностью эквивалентен простому вызову sort о . Перемешивание списка shuffleO Функция s h u f f i e o "перемешивает" список, переданный ей первым пара- метром, так, чтобы его значения распределялись случайным образом. Обра- тите внимание , что, во-первых, изменяется сам массив , а во-вторых, ассо- циативные массивы воспринимаются как списки . Пример : $А=аггау(10,20,30,40,50) ; snuff1е($А); foreach($A as $v ) echo "$v "; Приведенный фрагмент выводит числа 10, 20, 30, 40 и 50 в случайном порядке. Замечание Выполнив этот фрагмент несколько раз, вы можете обнаружить , что от запуска к запуску очередность следования чисел не изменяется. Это свойство обу- словлено тем, что функция shuffle ( ) использует стандартный генератор слу- чайных чисел, который перед работой необходимо инициализировать при по- ----------------------- Page 245----------------------- 230 Часть IV. Стандартные функции РНР мощи вызова srand() . Подробности можно найти в следующей главе (см. функцию mt_srand(». Она— не совсем то, что нам требуется (нам нужна srand ()) , но формы записи обеих функций не различаются. Ключи и значения Эта функция "пробегает" по массиву и меняет местами его ключи и значе- ния . Исходный массив SArr не изменяется , а результирующий массив просто возвращается. Конечно , если в массиве присутствовали несколько элементов с одинаковыми значениями , учитываться будет только послед- ний из них : list array_keys(array $Arr [,mixed $SearchVal] ) Функция возвращает список, содержащий все ключи массива $АГГ . Если задан необязательный параметр $searchvai , то она вернет только те ключи , которым соответствуют значения $searchvai . ( Замечание ^ Фактически, эта функция с заданным вторым параметром является обратной по отношению к оператору [ ] — извлечению значения по его ключу. list array_values(array $Arr) Функция array values о возвращает список всех значений в ассоциатив- ном массиве $АГГ . Очевидно , такое действие бесполезно для списков , но иногда оправдано для хэшей . bool in_array(mixed Sval , array $Arr ) Возвращает true , если элемент со значением $vai присутствует в массиве $АГГ. Впрочем, если вам часто приходится проделывать эту операцию, по- думайте: не лучше ли будет воспользоваться ассоциативным массивом и хранить данные в его ключах , а не в значениях? На этом вы можете сильно выиграть в быстродействии. array array_count_values(list $List) Эта функция подсчитывает, сколько раз каждое значение встречается в спи- ске $List , и возвращает ассоциативный массив с ключами — элементами списка и значениями — количеством повторов этих элементов. Иными ело- ----------------------- Page 246----------------------- Глава 13. Работа с массивами 231 вами, функция array_count_vaiues о подсчитывает частоту появления зна- чений в списке $List . Вот пример: $List=array(l, "hello", I, "world" , "hello"); array_count_values($array) ; // возвращает array(l=>2 , "hello"=>2 , "world"=>l) Комплексная замена в строке В предыдущей главе мы рассматривали функцию s t r t r o , которая заменяла в строке одни буквы на другие, и функцию str_repiace() , осуществляю- щую контекстный поиск и замену. В свете ассоциативных массивов эти две функции объединяются в одну, также называющуюся strtro , но несущую в себе возможности str_replace ( ) . string strtr(string $st , array $Substitutes ) Эта функция (заметьте — с двумя параметрами, а не с тремя, как обычная strtr о!) берет строку $st и проводит в ней контекстный поиск и замену: ищутся подстроки — ключи в массиве $substitutes — и замещаются на соответствующие им значения. Таким образом, теперь мы можем выполнить несколько замен сразу, не используя str_repiace( ) в цикле: $Subs=array ( "" => "Larry" , "