Стратегия Open Source наделала немало шуму в программной отрасли. Распространение исходных текстов программ в массах оказало несомненно благотворное влияние на многие проекты, в первую очередь — Linux, хотя и успех проекта Apache сильно подкрепил позиции сторонников Open Source. Сказанное относится и к истории создания РНР, поскольку поддержка пользователей со всего мира оказалась очень важным фактором в развитии проекта РНР.
Принятие стратегии Open Source и бесплатное распространение исходных текстов РНР оказало неоценимую услугу пользователям. Вдобавок, отзывчивое сообщество пользователей РНР является своего рода «коллективной службой поддержки», и в популярных электронных конференциях можно найти ответы даже на самые сложные вопросы.
В следующем разделе «Рекомендации пользователей» приведены свидетельства трех видных профессионалов в области web-разработок. Из них становится ясно, почему они считают РНР такой замечательной технологией.
Безопасность
РНР предоставляет в распоряжение разработчиков и администраторов гибкие и эффективные средства безопасности, которые условно делятся на две категории: средства системного уровня и средства уровня приложения. Средства безопасности системного уровня
В РНР реализованы механизмы безопасности, находящиеся под управлением администраторов; при правильной настройке РНР это обеспечивает максимальную свободу действий и безопасность. РНР может работать в так называемом безопасном
режиме (safe mode), который ограничивает возможности применения РНР пользователями по ряду важных показателей. Например, можно ограничить максимальное время выполнения и использование памяти (неконтролируемый расход памяти отрицательно влияет на быстродействие сервера). По аналогии с cgi-bin администратор также может устанавливать ограничения на каталоги, в которых пользователь может просматривать и исполнять сценарии РНР, а также использовать сценарии РНР для просмотра конфиденциальной информации на сервере (например, файла passwd).
Средства безопасности уровня приложения
В стандартный набор функций РНР входит ряд надежных механизмов шифрования. РНР также совместим с многими приложениями независимых фирм, что позволяет легко интегрировать его с защищенными технологиями электронной коммерции (e-commerce). Другое преимущество заключается в том, что исходный текст сценариев РНР нельзя просмотреть в браузере, поскольку сценарий компилируется до его отправки по запросу пользователя. Реализация РНР на стороне сервера предотвращает похищение нетривиальных сценариев пользователями, знаний которых хватает хотя бы для выполнения команды View Source.
Тема безопасности настолько важна, что ей посвящена целая глава. За подробной информацией о средствах безопасности РНР обращайтесь к главе 16.
Гибкость
Поскольку РНР является встраиваемым (embedded) языком, он отличается исключительной гибкостью по отношению к потребностям разработчика. Хотя РНР обычно рекомендуется использовать в сочетании с HTML, он с таким же успехом интегрируется и в JavaScript, WML, XML и другие языки. Кроме того, хорошо структурированные приложения РНР легко расширяются по мере необходимости (впрочем, это относится ко всем основным языкам программирования).
Нет проблем и с зависимостью от браузеров, поскольку перед отправкой клиенту сценарии РНР полностью компилируются на стороне сервера. В сущности, сценарии РНР могут передаваться любым устройствам с браузерами, включая сотовые телефоны, электронные записные книжки, пейджеры и портативные компьютеры, не говоря уже о традиционных PC. Программисты, занимающиеся вспомога-тельными утилитами, могут запускать РНР в режиме командной строки.
Поскольку РНР не содержит кода, ориентированного на конкретный web-сервер, пользователи не ограничиваются определенными серверами (возможно, незнакомыми для них). Apache, Microsoft IIS, Netscape Enterprise Server, Stronghold и Zeus — РНР работает на всех перечисленных серверах. Поскольку эти серверы работают на разных платформах, РНР в целом является платформенно-незави-симым языком и существует на таких платформах, как UNIX, Solaris, FreeBSD и Windows 95/98/NT.
Наконец, средства РНР позволяют программисту работать с внешними компонентами, такими как Enterprise Java Beans или СОМ-объекты Win32. Благодаря
этим новым возможностям РНР занимает достойное место среди современных технологий и обеспечивает масштабирование проектов до необходимых пределов.
Характеристики РНР
Как вы, вероятно, уже поняли, главным фактором при проектировании языка РНР является практичность. РНР должен предоставить программисту средства для быстрого и эффективного решения поставленных задач. Практический характер РНР обусловлен пятью важными характеристиками:
традиционностью;
простотой;
эффективностью;
безопасностью;
гибкостью.
Существует еще одна «характеристика», которая делает РНР особенно привлекательным: он распространяется бесплатно!
В этой главе мы рассмотрели
В этой главе мы рассмотрели некоторые ключевые аспекты РНР:
историю и особенности РНР;
установку и конфигурацию;
«переход» в РНР;
комментирование кода РНР.
Эти вопросы закладывают основу для материала следующих глав, в которых будут более подробно описаны проблемы программирования на языке РНР. В конце следующей главы вы будете знать о РНР достаточно, чтобы писать собственные программы. Новые знания будут применены на практике — мы создадим календарь, который можно будет легко вставить в существующую web-страницу. Этот проект подготовит вас к работе над web-приложением РНР Recipes.
Эффективность
Эффективность является исключительно важным фактором при программировании для многопользовательских сред, к числу которых относится и WWW. В РНР 4.0 был реализован механизм выделения ресурсов и обеспечена улучшенная поддержка объектно-ориентированного программирования, а также средства управления сеансом. В последней версии появился и механизм подсчета ссылок (reference counting), предотвращающий выделение лишней памяти.
Комментарии в коде РНР
Комментарии следует использовать даже в относительно простых и незамысловатых сценариях. В РНР существуют два формата комментариев:
Однострочные комментарии обычно используются для коротких пояснений или примечаний, относящихся к локальному коду.
Многострочные комментарии обычно используются при оформлении алгоритмов на псевдокоде и в более подробных объяснениях.
Оба способа в конечном счете приводят к одинаковому результату и совершенно не влияют на общее быстродействие сценария. Выбор варианта остается за вами.
Однострочные комментарии
При оформлении однострочных комментариев используется два стиля комментирования. Оба стиля работают абсолютно одинаково, но в них используются разные служебные символы. В одном случае комментарий начинается с двойного символа «косая черта» (//), а в другом — с символа фунта (#). Ниже приведены примеры обоих стилей:
// Выбрать цвет роз $rose_color = "red";
# Выбрать цвет фиалок $violet_color = "blue";
print "Roses are $rose_color, violets are $violet_color"
?>
Конечно, оба стиля однострочных комментариев могут применяться для построения искусственных многострочных комментариев, как показано в следующем листинге:
// файл: example. php
// автор: У.Дж.Гилмор
// дата: 24 августа 2000 г.
print "An example with comments";
?>
Многострочные комментарии
В РНР существует возможность построения подробных комментариев, занимающих несколько строк. Такие комментарии оформляются в стиле языка С — их начало и конец обозначаются символами /* и */.
/*
Сценарий: multi_coramment_example.php
Назначение : пример использования многострочных комментариев
Автор: У.Дж.Гилмор
Дата: 14 июня 2000 г.
*/
print "A multiline comment can be found at the top of this script!";
?>
Как видите, многострочные комментарии особенно удобны для вывода относительно длинной сводной информации обо всем сценарии или его части.
Конфигурация РНР
РНР будет правильно работать и при конфигурации, выбранной по умолчанию, однако вы можете внести некоторые изменения, чтобы работа пакета лучше соответствовала вашим целям. Все параметры конфигурации находятся в файле php.ini, который по умолчанию копируется в каталог /usr/local/lib/ в процессе установки.
Независимо от платформы и web-сервера, используемого в сочетании с РНР, файл php.ini содержит одинаковый набор стандартных параметров, позволяющих управлять важными аспектами работы РНР. Этот файл содержит все параметры, определяющие поведение пакета при выполнении сценария РНР. Содержимое файла php.ini читается при запуске РНР. В версии 3.0 файл конфигурации назывался php3.ini, но в версии 4.0 ему было присвоено имя php.ini.
Краткая история
История РНР начинается с 1995 года, когда независимый программист-контрактник по имени Расмус Лердорф (Rasmus Lerdorf) написал сценарий Perl/CGI для подсчета количества посетителей сайта, прочитавших его онлайновое резюме. Его сценарий решал две задачи: регистрацию данных посетителя и вывод количества посетителей на web-странице. Развитие WWW еще только начиналось, никаких специальных средств для решения этих задач не было, и к автору хлынул поток сообщений с вопросами. Лердорф начал бесплатно раздавать свой инструментарий, названный Personal Home Page (РНР) или Hypertext Processor (гипертекстовый процессор).
Шумный успех инструментария РНР заставил Лердорфа приступить к разработке расширений РНР. Одно из расширений преобразовывало данные, введенные на форме HTML, в символические переменные, что позволяло экспортировать их в другие системы. Чтобы добиться поставленной цели, Лердорф решил в дальнейших разработках перейти с Perl на С. Расширение существующего инструментария РНР привело к появлению РНР 2.0, или PHP-FI (Personal Home Page — Form Interpretator). В усовершенствовании версии 2.0 принимали участие программисты со всего мира.
Новая версия РНР пользовалась исключительной популярностью, и вскоре образовалась основная команда разработчиков. Они сохранили исходную концепцию внедрения программного кода прямо в HTML и переписали заново механизм лексического анализа, что привело к появлению РНР 3.0. К моменту выхода версии 3.0 в 1997 году свыше 50 000 пользователей применяли РНР для улучшения своих web-страниц. В 1997 году было решено, что сокращение РНР должно означать не «Personal Home page», а «РНР Hypertext Processor»
В течение следующих двух лет стремительное развитие РНР продолжалось. В язык добавлялись сотни новых функций, а количество пользователей стремительно росло. В начале 1999 года служба Netcraft (http://www.netcraft.com) сообщила о том, что, по минимальным оценкам, число пользователей РНР превысило 1 000 000, в результате чего РНР стал одним из самых популярных сценарных языков в мире.
В начале 1999 года было объявлено о предстоящем выходе РНР 4.0. Хотя одной из сильнейших сторон РНР была эффективность выполнения сценариев, при первоначальных разработках не предполагалось, что на базе РНР будут строиться крупномасштабные приложения. По этой причине была начата работа над более устойчивым механизмом лексического анализа, больше известным под названием Zend (http://www.zend.com). Работа шла быстро и завершилась 22 мая 2000 года выпуском РНР версии 4.0.
Кроме лексического анализатора Zend, компания Zend Technologies (Израиль) распространяет оптимизатор Zend, который повышает выигрыш в быстродействии от применения лексического анализатора Zend. Тесты показывают, что ускорение работы программы в результате использования оптимизатора составляет от 40 до 100 %. За дополнительной информацией обращайтесь на сайт Zend.
На момент написания этой книги, по данным Netcraft (http://www.netcraft.com), программное обеспечение РНР было установлено более чем в 3,6 миллиона доменов. Будущее РНР выглядит светлым, поскольку продукт продолжает активно использоваться как на крупных web-сайтах, так и на компьютерах отдельных пользователей.
РНР лучше всего охарактеризовать как работающий на стороне сервера встроенный язык сценариев Web, позволяющий разработчикам быстро и эффективно
строить динамические web-приложения. С позиций грамматики и синтаксиса РНР напоминает язык программирования С, хотя разработчики не постеснялись включить в него средства из других языков, в том числе из Perl, Java и C++. Среди ценных заимствованных возможностей — поддержка регулярных выражений, мощные средства работы с массивами, объектно-ориентированная методология и обширная поддержка работы с базами данных.
При написании приложений, выходящих за рамки традиционной, статической методологии разработки web-страниц (то есть HTML), РНР также может послужить ценным инструментом для создания и управления динамическим содержанием, который используется наряду с JavaScript, стилями, WML (Wireless Markup Language) и другими полезными языками. Благодаря наличию сотен стандартных функций РНР в состоянии решить практически любую задачу, которая может придти в голову разработчику. В нем имеется обширная поддержка создания графики и операций с ней, математических вычислений, средств электронной коммерции и таких популярных технологий, как XML (Extensible Markup Language), ODBC (Open Database Connectivity) и Macromedia Shockwave. Широкий выбор возможностей избавляет от необходимости рутинной и непростой работы по подключению сторонних модулей, поэтому многие разработчики со всего мира останавливают свой выбор на РНР.
Одним из главных достоинств РНР является тот факт, что он внедряется прямо в HTML-код, поэтому программисту не приходится писать программу с множеством команд для простого вывода HTML. Код HTML и РНР можно чередовать по мере необходимости. РНР позволяет написать фрагмент следующего вида:
print "Hello world!"; ?>
Сообщение "Hello world!" выводится в заголовке web-страницы. Интересно то, что команда print внутри конструкции, которая обычно называется экранирующими последовательностями РНР (...?>), представляет собой законченную программу. Ни длинного кода инициализации, ни включения библиотек — программа состоит лишь из того кода, который непосредственно решает поставленную задачу!
Конечно, для выполнения сценариев РНР необходимо предварительно установить и настроить программное обеспечение РНР на сервере. Этот процесс описан в разделе «Загрузка и установка РНР/Apache» настоящей главы. Разделу предшествуют фрагменты из отзывов нескольких пользователей, выступающих в пользу РНР, с кратким обзором языка и его истории. Но прежде чем браться за процесс установки, мы познакомимся с некоторыми характеристиками РНР. Этой теме посвящен следующий раздел.
Общие параметры конфигурации
Подробное описание всех конфигурационных параметров выходит за рамки этой книги, но некоторые директивы используются особенно часто и заслуживают отдельного упоминания. Другие параметры упоминаются в соответствующих местах следующих глав.
short_open_tag [on | off]
Параметр short_open_tab [on | off] определяет возможность использования коротких тегов ...?> наряду со стандартными тегами.
asp_tags [on | off]
Параметр asp_tags [on | off] определяет возможность использования тегов в стиле ASP наряду со стандартными тегами. При использовании тегов в стиле ASP фрагменты кода РНР оформляются следующим образом:
<%
print "This is РНР code.";
%>
precision [integer]
Параметр precision [integer] задает количество значащих цифр, отображаемых в вещественных числах.
safe_mode [on | off]
Безопасный режим особенно удобен в случае, если в вашей системе работают несколько пользователей. Включение безопасного режима гарантирует, что пользователь не сможет применить сценарий РНР для получения доступа к другому файлу в системе — например, файлу passwd на компьютере Linux. Параметр safe_mode работает только в CGI-версии РНР. За дополнительной информацией по этой теме обращайтесь к главе 16.
max_execution_time [integer]
Параметр max_execution_time [integer] определяет максимальную продолжительность выполнения сценариев РНР в секундах. Такое ограничение предотвращает поглощение ценных системных ресурсов сценариями, содержащими ошибки.
error_reporting [1-8]
Параметр error_reporting [1-8] определяет уровень выдачи сообщений об ошибках в РНР. Чем выше значение, тем «чувствительнее» РНР реагирует на ошибки.
Значение
Чувствительность
1
Обычные ошибки
2
Обычные предупреждения
4
Ошибки лексического анализатора
8
Замечания
display_errors [on | off]
Параметр display_errors[on | off] управляет выводом информации об ошибках в браузере.
log_errors
Параметр log_errors определяет, следует ли регистрировать ошибки в файле. При включении параметра log_errors файл, в котором регистрируются ошибки, назначается при помощи параметра error_log.
error_log [filename]
Параметр error_log определяет файл, в котором регистрируются ошибки при включенном параметре log_errors.
magic_quotes_gpc
При активизации параметра magic_quotes_gpc все специальные символы, содержащиеся в пользовательских данных или в базе, автоматически экранируются обратной косой чертой. Кстати, сокращение gрс означает «get/post/cookie».
Лично я считаю, что параметр magic_quotes_gpc эффективнее отключить и использовать явное экранирование специальных символов. Какой бы способ вы ни выбрали, любые компромиссы приведут к порче данных. Если параметр magic_quotes_gpc
включен, никогда не экранируйте специальные символы обратной косой чертой; в противном случае не забывайте делать это всегда.
track_vars
Параметр track_vars включает отслеживание нескольких важных массивов сеансовых переменных, в том числе $HTTP_GET_VARS[], $HTTP_POST_VARS[], $HTTP_POST_FILES, $HTTP_COOKIE_VARS[], $HTTP_ENV_VARS[] и $HTTP_SERVER_VARS[]. Эти массивы подробно описаны в главе 13.
Помните, что кроме перечисленных параметров существует множество других. Выше перечислены лишь те параметры, которые часто применяются большинством пользователей. Многие параметры будут упоминаться в последующих главах.
Основные конструкции РНР
Прежде чем переходить к изложению основного материала, занимающего оставшуюся часть книги, я представлю несколько вспомогательных концепций, относящихся к РНР.
Переход в РНР
Механизм лексического анализа должен как-то отличать код РНР от других элементов страницы. Идентификация кода РНР называется «переходом в РНР» (escaping to РНР). Существуют четыре варианта оформления перехода в РНР:
стандартные теги;
короткие теги;
теги script;
теги в стиле ASP.
Стандартные теги
Стандартные теги используются программистами РНР чаще остальных способов, что объясняется наглядностью и удобством этой формы записи:
У стандартных тегов есть еще одно дополнительное преимущество: за открывающей конструкцией следуют символы php, однозначно определяющие тип дальнейшего кода. Это удобно при использовании в одной странице нескольких технологий — таких, как JavaScript, серверные включения и РНР. Весь текст, расположенный до закрывающего тега ?>, интерпретируется как код РНР.
Короткие теги
Короткие теги обеспечивают наиболее компактную запись для перехода в РНР:
print "Welcome to the world of PHP!"; ?>
По умолчанию короткие теги не используются, их нужно специально активизировать. Это можно сделать двумя способами:
указать ключ -enable-short-tags при компиляции РНР;
включить параметр short_open_tag в файл php.ini.
Теги script
Некоторые текстовые редакторы ошибочно принимают код РНР за код HTML (то есть визуально отображаемый текст), что нарушает работу над web-страницей. Проблема решается использованием тегов script:
Теги в стиле ASP
Четвертый и последний способ оформления внедренного кода РНР — теги в стиле ASP (Active Server Page). Они похожи на короткие теги, описанные выше, однако вместо вопросительного знака используется знак процента (%):
<%php print "Welcome to the world of PHP!"; %>
У тегов в стиле ASP есть одна особенность, делающая запись более компактной. Во внедренный код РНР не обязательно включать команду print — знак равенства (=), расположенный сразу же после открывающего тега в стиле ASP, приказывает лексическому механизму РНР вывести значение указанной переменной:
<%=$variable %>
Этот удобный стиль позволяет использовать в страницах фрагменты вида
<%
// Присвоить значение переменной $recipe
$recipe = "Lasagna":
%>
Luigi's favorite recipe is <%=$recipe;%>
Этот фрагмент содержит два разных сценария РНР. В первом сценарии переменной $recipe присваивается значение "Lasagna". Позднее, когда потребуется вывести значение $гесiре, специально для этой цели используются теги в стиле ASP. Кстати, такая возможность существует и для коротких тегов (...?>).
Простота
Сценарий РНР может состоять из 10 000 строк или из одной строки — все зависит от специфики вашей задачи. Вам не придется подгружать библиотеки, указывать специальные параметры компиляции или что-нибудь в этом роде. Механизм РНР просто начинает выполнять код после первой экранирующей последовательности () и продолжает выполнение до того момента, когда он встретит парную экранирующую последовательность (?>). Если код имеет правильный синтаксис, он исполняется в точности так, как указал программист.
Рекомендации пользователей
«Мы в течение долгого времени поддерживали личные контакты с некоторыми разработчиками РНР и вели с ними обширную переписку. Когда у разработчиков РНР возникали какие-то проблемы, относящиеся к MySQL, мы всегда были готовы помочь им в поиске решения. Кроме того, мы включили в MySQL несколько новых возможностей лишь для того, чтобы улучшить его интеграцию с РНР. Результатом наших усилий стало то, что MySQL превосходно работает с РНР, — и мы позаботимся о том, чтобы это положение сохранилось и в будущем!»
Майкл «Монти» Видениус (Michael «Monty» Widenius),
разработчик MySQL
http://www.mysql.com
«Выбор РНР для реализации mp3.lycos.com был обусловлен несколькими причинами. Главной причиной стали сжатые сроки работы над проектом — ведь РНР ускоряет процесс разработки. Другой причиной была высокая эффективность — мы перешли от 0 к 1,4 миллиона посещений в сутки, и РНР с этим прекрасно справился. Третья причина заключалась в том, что я твердо знал: если на стадии тестирования с повышенной нагрузкой в РНР обнаружатся какие-либо ошибки, я смогу их самостоятельно исправить, поскольку РНР распространяется вместе с исходными текстами».
Стиг Баккен (Stig Bakken),
FAST Search & Transfer ASA
http://www.fast.no
«Я использовал РНР с первых дней, еще с версии PHP/FI 1.x. Мне понравилось, что я могу обрабатывать формы и настраивать страницы «на ходу» при помощи такого простого языка. Вместе с потребностями моей компании развивался и РНР.
В наши дни РНР обладает исключительно богатыми возможностями. Мы используем его практически во всех создаваемых web-сайтах, включая 32bit.com и DevShed.com. Мы даже воспользовались им в Info West для реализации службы поддержки, управления учетными записями и отслеживания портов.
Эволюция РНР и признание его мировым сообществом — классический пример успешного ведения проекта с открытыми исходными текстами. Широта взглядов создателей, поддержка сообщества и хорошее сопровождение кодовой базы привели РНР к успеху, о котором многие коммерческие проекты могут лишь мечтать. Я с оптимизмом смотрю в будущее РНР и рекомендую каждому web-разработчику попробовать его в деле. Возможно, вы, как и я, уже не расстанетесь с ним».
Рэнди Косби (Randy Cosby),
президент nGenuity, Inc. DevShed
http://www.devshed.com
Системы, не входящие в семейство Windows
Независимо от того, какой вариант был выбран, установка начинается с распаковки архивов. Для распаковки выполните два простых действия:
Выполните следующие команды:
gunzip apache_1.3.9.tar.gz
gunzip php-4.0.0.tar.gz
После завершения распаковки остаются файлы с расширением *.tar.
Извлеките файлы поставки из архивов:
tar -zxvf apache_1.3.x.tar
tar -zxvf php-4.0.x.tar
С этого момента начинается основной процесс установки.
Модуль Apache
Установка РНР в виде модуля Apache выполняется довольно просто. Ниже подробно описаны все необходимые действия:
Перейдите в каталог Apache:
cd apache_1.3.x
Настройте Apache. Выберите путь по своему усмотрению, но помните, что за ним не должна следовать косая черта:
./configure -prefix=[путь]
Перейдите в каталог РНР; настройте, соберите и установите поставку. Параметр -with-config-file-path задает каталог, в котором будет находиться файл конфигурации РНР. Обычно этот файл находится в каталоге /usr/local/lib, но вы можете выбрать другой каталог по своему усмотрению:
./configure -with-apache=../apache_1.3.x -with-config-file-path=[путь]
make
make install
Вернитесь в каталог Apache. Теперь вы можете изменить конфигурацию, собрать и установить Apache. Параметр -other-configuration-options относится к любым специальным параметрам конфигурации, которые вы хотели бы передать web-серверу Apache. Данная тема выходит за рамки книги. Полное описание параметров приведено в документации Apache:
./configure -activate-module=src/modules/php4/libphp4.a
-other-configuration-options
make
make install
На последнем этапе происходит редактирование файла Apache httpd.conf. Одни модификации относятся к Apache, другие необходимы для того, чтобы сценарии РНР распознавались и передавались web-серверу. Сначала найдите такую строку:
ServerName new.host.name
Приведите ее к следующему виду:
ServerName localhost
Затем найдите строки
#AddType application/x-httpd-php .php .php4
#AddType application/x-httpd-php-source .php .phps
Чтобы файлы с поддержкой РНР правильно работали на сервере, эти строки необходимо раскомментировать. Для этого достаточно удалить знак фунта (#) в начале каждой строки. Сохраните файл и поднимитесь в иерархии каталогов на один уровень вверх. Запустите сервер Apache следующей командой:
./bin/apachectl start
Voila! PHP и Apache готовы к работе. Для проверки сохраните приведенный ниже фрагмент в файле phpinfo.php в корневом каталоге документов Apache — каталоге htdocs, находящемся в установочном каталоге Apache:
php info();
?>
Откройте файл в браузере на сервере. В окне появляется длинный перечень сведений о конфигурации РНР. Вы успешно установили РНР в виде модуля Apache.
Динамический модуль Apache
Динамические модули удобны тем, что они позволяют обновлять поставку РНР без перекомпиляции web-сервера. Apache рассматривает поддержку РНР как один
из своих многочисленных модулей вроде ModuleRewrite или ModuleSpelling. Этот вариант особенно хорош в ситуациях, когда в РНР позднее будет добавляться поддержка новых возможностей — например, шифрования. Все, что вам придется сделать — переконфигурировать/откомпилировать РНР с поддержкой шифрования, и вы сможете немедленно использовать новую возможность в web-приложениях. Процесс установки описан ниже:
Перейдите в каталог Apache:
cd apache_1.3.x
Настройте Apache. Выберите путь по своему усмотрению, но помните, что за ним не должна следовать косая черта. Параметр -other-configuration-options относится к любым специальным параметрам конфигурации, которые вы хотели бы передать web-серверу Apache. Данная тема выходит за рамки книги. Полное описание параметров приведено в документации Apache.
Соберите сервер Apache. После ввода команды make на экран выводится серия сообщений:
make
Установите сервер Apache. После ввода команды make install на экран выводится следующая серия сообщений. После успешного завершения установки на экране появляется соответствующее сообщение:
make install
Если предыдущие действия прошли без ошибок, можно переходить к редактированию файла httpd.conf. Этот файл находится в подкаталоге conf каталога, указанного на шаге 2. Откройте файл в текстовом редакторе и найдите строку
ServerName new.host.name
Приведите ее к следующему виду:
ServerName local host
Перейдите в тот каталог, где находится пакет РНР. Настройте, соберите и установите РНР. В параметрах указывается путь к каталогу, содержащему файл apxs, — подкаталогу bin каталога, путь к которому был указан на шаге 2:
./configure -with-apxs=[путь/k/apxs]
make
make install
Откройте файл Apache httpd.conf для очередной модификации. Для обеспечения правильного лексического анализа входящих запросов на файлы с поддержкой РНР расширение должно совпадать с одним из расширений, указанных в файле конфигурации сервера Apache, httpd.conf. Файл содержит параметры, которые могут изменяться по усмотрению администратора; некоторые из них имеют прямое отношение к РНР. Откройте файл httpd.conf в своем текстовом редакторе. В конце файла присутствуют две строки следующего вида:
Чтобы файлы с поддержкой РНР правильно работали на сервере, эти строки необходимо раскомментировать. Для этого следует удалить'знак фунта (#) в начале каждой строки.
Сохраните файл и перейдите на один уровень вверх в иерархии каталогов (командой cd). Запустите Apache следующей командой:
./bin/apachectl start
Voila! РНР и Apache готовы к работе.
Для проверки сохраните приведенный ниже фрагмент в файле phpinfo.php в корневом каталоге документов Apache — каталоге htdocs, находящемся в установочном каталоге Apache:
php_info():
?>
Откройте файл в браузере на сервере. В окне появляется длинный перечень сведений о конфигурации РНР. Вы успешно установили РНР в виде динамического модуля Apache.
Традиционность
Язык РНР кажется знакомым программистам, работающим в разных областях. Многие конструкции языка позаимствованы из Си Perl, а нередко код РНР практически неотличим от того, что встречается в типичных программах С или Pascal. Это заметно снижает начальные усилия при изучении РНР.
Установка и настройка
Предполагается, что к настоящему моменту вы успешно загрузили РНР и Apache. Следующий шаг — выбор способа установки. Для компьютеров, не использующих систему Windows, существует три варианта: двоичный файл CGI, статический модуль Apache и динамический модуль Apache. Скорее всего, вы не захотите строить РНР в виде двоичного файла CGI. Более того, построение РНР в виде серверного модуля имеет некоторые преимущества, поэтому я уделю внимание построению РНР в виде статического и динамического модуля. Главное различие между этими двумя вариантами заключается в том, что при любых изменениях в статическом модуле РНР придется заново компилировать и Apache, и РНР, а изменения в динамическом модуле РНР потребуют компиляции только РНР, без сервера.
На компьютерах с системой Windows РНР может устанавливаться в виде либо двоичного файла CGI, либо статического модуля Apache. На этот раз я опишу построение двоичного файла CGI, потому что пользователи Windows обычно используют вместо Apache другие web-серверы (например, Microsoft Internet Information Server или Microsoft Personal Web Server). CGI-версия легко интегрируется с этими серверами. Хотя описанный процесс установки относится к РНР/Apache, установка для перечисленных выше web-серверов выполняется практически так же. Как говорилось ранее, РНР4 поддерживает разнообразные web-серверы, в том числе AOL Server, Netscape Enterprise Server, Microsoft IIS, Zeus и многие другие. Впрочем, я ограничусь описанием установки для сервера Apache. За подробными инструкциями о том, как установить РНР для других серверов, обращайтесь к документации РНР по адресу http://www.php.net.
Установка в Windows 95/98/NT
Если вам когда-нибудь приходилось устанавливать новые приложения в операционной системе Windows, вероятно, это не вызывало у вас особых трудностей. Вы щелкаете на нескольких кнопках, отвечаете на несколько вопросов — и все готово. Так же обстоит дело и с установкой Apache и РНР на компьютерах с системой Windows.
Дважды щелкните на значке исполняемого файла Apache. Запускается процесс установки, и на экране появляется начальное окно программ мастера (wizard) установки. Внимательно прочитайте текст лицензионного соглашения и подтвердите его.
Мастер запрашивает каталог для установки и предлагает вариант по умолчанию (C:\Program Files\Apache Group\Apache). Возможно, вам захочется сократить его до C:\Apache — решайте сами.
Затем вам будет предложено указать имя, под которым программа будет находиться в меню Пуск (Start). Введите имя по своему усмотрению или подтвердите предложенный вариант.
Следующий вопрос относится к типу установки. Выберите вариант Typical (Стандартная установка). После того как решение будет принято, выполняется установка.
На следующем шаге редактируется файл httpd.conf, находящийся в подкаталоге conf каталога, указанного на шаге 2. Откройте файл в текстовом редакторе и найдите строку ServerAdmin yourname@yoursite.com
Замените yourname@yoursite.com правильными данными. Затем укажите правильное имя сервера (если
его нет, воспользуйтесь именем localhost):
ServerName localhost
Запустите Apache и убедитесь в том, что все работает правильно. Конкретные
действия зависят от того, в какой системе вы работаете.
Если вы используете Windows NT, выберите в меню Пуск (Start) команду Install Apache as Service (NT Only). Затем вызовите панель управления, откройте окно Службы (Services), выберите Apache и щелкните на кнопке Пуск (Start). Apache запускается и будет запускаться автоматически при каждой последующей загрузке системы.
Запустите браузер, установленный на сервере, и введите адрес http: /local host/. В окне открывается страница с сообщением о том, что установка была выполнена успешно.
Следующий этап — установка РНР. Перейдите в каталог, где находится пакет РНР, и распакуйте его в произвольный каталог.
Перейдите в каталог, в который были помещены распакованные файлы, и найдите в нем файл php.ini-dist. Переименуйте его в php.ini и переместите в каталог C:\Windows.
Вернитесь в каталог РНР и найдите два других файла, php4ts.dll и Mscvrt.dll. Поместите эти файлы в каталог C:\Windows\System\. Возможно, файл Mscvrt.dll уже существует — если вам будет предложено его перезаписать, не соглашайтесь.
Вернитесь к файлу Apache http.conf и откройте его в текстовом редакторе. Необходимо внести еще несколько изменений. Найдите строку
ScriptAlias /cgi-bin/ "C:/Apache/cgi-bin/"
Непосредственно под этой строкой введите следующую:
ScriptAlias /php4/ "C:/php4/"
Найдите секцию AddType. Вы увидите две закомментированные строки:
#AddType application/x-httpd-php3. phtml
#AddType application/x-httpd-php3-source .phps
Непосредственно под этими строками добавьте следующие:
AddType application/x-httpd-php .phtml .php
AddType application/x-httpd-php-source .phps
Прокрутите содержимое файла и найдите следующий закомментированный фрагмент:
#
# Action lets you define media types that will execute a script whenever
# a matching file is called. This eliminates the need for repeated URL
Для проверки сохраните приведенный ниже фрагмент в файле phpinfo.php в корневом каталоге документов Apache — каталоге htdocs. Он находится в том каталоге, который был указан на шаге 2:
php_info();
?>
Хотя при успешном завершении действий, описанных выше, вы сможете использовать web-сервер и РНР для тестирования, это еще не значит, что ваш web-сервер будет доступен из World Wide Web. За информацией по этому вопросу обращайтесь на официальный сайт Apache (http://www.apache.org). Более того, хотя описанная процедура позволяет работать с пакетом РНР, вероятно, вы захотите изменить конфигурацию РНР так, чтобы она лучше соответствовала вашим потребностям. Эта тема рассматривается в следующем разделе «Конфигурация РНР».
Откройте файл в браузере на сервере — в окне появляется длинный перечень сведений о конфигурации РНР.
Включение нескольких сценариев РНР
Для обеспечения необходимой гибкости при построении динамических web-приложений можно внедрить в страницу несколько сценариев РНР (листинг 1.5). Листинг 1.5.
Включение нескольких сценариев РНР в один документ
Листинг 1.5 начинается как типичная (пусть несколько упрощенная) страница
HTML. При внедрении нескольких сценариев переменные, значения которых были
присвоены в одном сценарии, могут использоваться в другом сценарии той же
страницы.
Внедрение HTML в код РНР
Одной из самых замечательных особенностей HTML является простота использования в сочетании с другими языками — например, HTML и JavaScript (см. листинг 1.2). Листинг 1.2.
Вывод кода HTML средствами РНР
Basic PHP/HTML integration
// Обратите внимание на присутствие тегов HTML в команде print.
print "
PHP/HTML integration is cool.
";
?>
Рис. 1.2. Форматирование даты с использованием функции РНР date()
В листинге 1.2 показано, как код HTML интегрируется прямо в команды РНР. В данном примере в код РНР включаются теги заголовка третьего уровня (
...
). В итоговом документе эти теги ничем не отличаются от обычного кода HTML.
В листинге 1.3 продемонстрировано включение динамической информации в web-страницу на примере вывода текущей даты в заголовке окна (рис. 1.2). Листинг 1.3.
Динамический вывод даты PHP Recipes | print (date("F d, Y")); ?>
Простая функция РНР date() форматирует дату одним из нескольких стандартных способов. Отформатированная дата вставляется в заголовок окна.
РНР также позволяет изменять формат конструкций HTML — для этого соответствующая характеристика тега присваивается переменной, вставляемой в файл. В листинге 1.4 эта возможность продемонстрирована на примере присваивания характеристики шрифта (h3) переменной $big_font и ее последующего использования при выводе текста. Листинг 1.4.
Динамические теги HTML
PHP Recipes | print (date("F d, Y")); ?>
$big_font = "h3";
?>
print "<$big_font>PHP Recipes$big_font>"; ?>
Листинг 1.4 представляет собой разновидность листинга 1.3 — на этот раз тег заголовка (
...
) сначала присваивается переменной, а затем эта переменная используется в команде print. В итоговом документе эти теги ничем не отличаются от обычного кода HTML.
Вводный пример
Пример, приведенный в листинге 1.1, наглядно показывает, как легко РНР интегрируется с HTML-кодом. Листинг 1.1. Создание динамической страницы РНР
// Вывести приветствие с датой и именем пользователя. print "
РНР Recipes | ".dateC'F d. Y")." Greetings, $user_name!
";
?>
На рис. 1.1 показано, как выглядит сценарий при выполнении в браузере. Рис. 1.1. Результаты выполнения сценария в браузере
Неплохо, правда? Я уверен, что читатель уже перебирает в уме новые возможности. Но не будем торопиться — возможно, вам еще придется установить и настроить РНР на своем компьютере. Этой теме посвящены следующие разделы.
Загрузка РНР/Apache
Прежде чем следовать дальше, я рекомендую потратить немного времени на загрузку, установку и настройку РНР и web-сервера на вашем компьютере. Хотя РНР совместим с разными web-серверами, я предполагаю, что вы используете Apache — во-первых, это самый популярный web-сервер на сегодняшний день, во-вторых, он чаще всего работает с РНР. Впрочем, в целом процессы установки для разных web-серверов имеют много общего.
Поставку РНР можно загрузить с официального сайта РНР или с любого из «зеркальных» сайтов по всему миру. Самый свежий список «зеркальных» сайтов находится по адресу http://www.php.net. При загрузке РНР можно выбрать один из двух форматов:
исполняемый формат Win32;
исходный текст.
Исполняемый формат Win32 предназначен для пользователей Windows 95/98/ NT/2000. Хотя исходный текст можно откомпилировать и на платформе Windows, для большинства пользователей это необязательно. Впрочем, если вы настаиваете на компиляции (кстати, в книге этот процесс не рассматривается), вам понадобится компилятор Visual C++ одной из последних версий. За подробностями компиляции обращайтесь по адресу http://www.php.net/ version4/win32build.php. Установка исполняемых файлов Win32 описана далее в этой главе.
Пользователям других систем придется самостоятельно откомпилировать исходный текст программы. Хотя многих новичков эта перспектива приводит в ужас, на самом деле это довольно просто. Возможно, вас интересует, распространяется ли РНР в формате RPM (RedHat Package Manager)? Да, распространяется, хотя эти пакеты не представлены на официальном сайте РНР. За инструкциями и дополнительной информацией о местонахождении различных поставок обращайтесь к материалам электронных конференций. Обобщенный процесс компиляции рассматривается далее.
Зайдите на сайт http://www.php.net и загрузите с него ту поставку, которая лучше всего отвечает вашим потребностям. Время загрузки зависит от типа и скорости подключения. Кроме того, имеется возможность получить документацию. Я настоятельно рекомендую выбрать самую новую версию.
На момент издания книги последней устойчиво работающей версией был РНР 4.0.3. Конечно, пакет РНР постоянно развивается, и номер версии непременно изменится. Я рекомендую загрузить самую свежую надежную версию продукта.
Если вы еще не установили сервер Apache, вам также следует выбрать его последнюю версию. Пакеты находятся в каталоге http://www.apache.org/dist/binaries, содержащем подкаталоги для разных операционных систем. Загрузите вариант, соответствующий вашим потребностям. Подробное описание настройки РНР для всех существующих платформ и web-серверов выходит за рамки этой книги, поэтому я уделю основное внимание серверу Apache. Независимо от того, какой web-сервер вы собираетесь использовать, я рекомендую прочитать дальнейшие разделы, посвященные настройке, — вы получите некоторое представление о тех общих проблемах, с которыми можете столкнуться.
Установка новых программ нередко превращается в непростое испытание для новичков. Однако разработчики РНР предприняли дополнительные усилия для того, чтобы установка РНР проходила относительно просто. В следующих разделах перечислены действия, которые необходимо выполнить для установки и настройки РНР в Win32 и на других платформах.
В дальнейших главах вы познакомитесь с сервером баз данных MySQL, и на примере этого популярного продукта будет продемонстрирован процесс интеграции web-приложений с базами данных. Чтобы поэкспериментировать с этими примерами, вам придется установить пакет MySQL (http://www.mysql.com). MySQL, как и РНР, существует в версиях для Windows и для других платформ. Хотя в документации MySQL приведены подробные инструкции по установке, возможно, вам стоит предварительно просмотреть начало главы 11, где приводится общая информация о сервере баз данных MySQL.
PHP 4 на практике
Целые числа
Целое число не имеет дробной части и представляется последовательностью из одной или нескольких цифр. Примеры целых чисел:
5
591
52
Идентификаторы
Общий термин идентификатор применяется к переменным, функциям и другим объектам, определяемым пользователем. Идентификаторы РНР должны удовлетворять нескольким условиям:
Идентификатор состоит из одного или нескольких символов и начинается с буквы или символа подчеркивания. Идентификатор может содержать только буквы, цифры, символы подчеркивания и другие ASCII-символы с кодами от 127 до 255. Примеры:
Допустимые идентификаторы
Недопустимые идентификаторы
my_function
This&that
Size
!counter
_someword
4ward
В идентификаторах учитывается регистр символов. Следовательно, переменная с именем $recipe отличается от переменных с именами $Recipe, $rEciPe и $recipE.
Длина идентификаторов не ограничивается. Это удобно, поскольку программист может точно описать смысл идентификатора в его имени.
Идентификатор не может совпадать с каким-либо из стандартных ключевых слов РНР.
Константы
Константой называется именованная величина, которая не изменяется в процессе выполнения программы. Константы особенно удобны при работе с заведомо постоянными величинами — например, числом ? (3,141592) или количеством футов в миле (5280).
В РНР константы определяются функцией define( ). После того как константа будет определена, вы не сможете изменить (или переопределить) ее в этой программе.
Например, определение числа я в сценарии РНР может выглядеть так:
define("'PI", "3.141592");
Определенную константу можно использовать в программе:
print "The value of pi is". PI." ";
$pi2 - 2 * PI:
print "Pi doubled equals $pi2.";
Результат работы этого фрагмента будет таким:
The value of pi is 3.141592.
Pi doubled equals 6.283184.
В этом фрагменте следует обратить внимание на два обстоятельства. Во-первых, в именах констант не указывается знак доллара. Во-вторых, константу невозможно модифицировать (например, присвоить ей величину 2*РI); если константа используется в вычислениях, то результат приходится сохранять в другой переменной.
Логические величины (истина/ложь)
Логический тип данных принимает всего два значения: истинное (true) и ложное (false). Логические величины создаются двумя способами: при проверке условий и в виде значений переменных. Обе ситуации достаточно просты.
Сравнения существуют в нескольких формах. Чаще всего они встречаются при использовании оператора = в условной команде if. Пример:
if ($sum == 40) :
...
Результатом проверки является либо истина, либо ложь: переменная $sum либо равна 40, либо не равна. Если переменная $sum равна 40, проверка дает истинный результат. В противном случае результат равен false.
Логические величины также могут определяться явным присваиванием переменной истинного или ложного значения. Пример:
$flag = TRUE;
if ($flag ==
TRUE) :
print "The flag is true!";
else :
print "The flag is false!";
endif;
Если переменная $flag истинна, выводится первое сообщение, а если ложна — второе сообщение.
Возможен и другой вариант — представление истинных и ложных логических величин в виде значений 1 и 0 соответственно. В этом случае предыдущий пример выглядит так:
$flag = 1;
if ($flag == TRUE) ;
print "The flag is true!";
else :
print "The flag is false!";
endif;
Наконец, существует еще один способ:
$flag = TRUE:
// При выполнении этой команды косвенно
// проверяется условие "if ($flag == TRUE)"
if ($flag) :
print "The flag is true!";
else :
print "The flag is false!";
endif:
Массивы
Массив представляет собой список однотипных элементов. Существует два типа массивов, различающиеся по способу идентификации элементов. В массивах первого типа элемент определяется индексом в последовательности. Массивы второго типа имеют ассоциативную природу, и для обращения к элементам используются ключи, логически связанные со значениями. Впрочем, на практике операции с массивами обоих типов выполняются сходным образом. По размерности массивы делятся на одномерные и многомерные.
Многомерные ассоциативные массивы
Многомерные ассоциативные массивы также существуют в РНР (и приносят определенную пользу). Допустим, в массиве $раirings из предыдущего примера должна храниться информация не только о сорте, но и о производителе вина. Это можно сделать следующим образом:
$pairings["Martinelli"]["zinfandel"] = "Broiled Veal Chops";
$pairings["Beringer"]["merlot"] = "Baked Ham";
$pairings["Jarvis"]["sauvignon"] = "Prime Rib";
$pairings["Climens"]["sauternes"] = "Roasted Salmon";
Многомерные индексируемые массивы
Многомерные индексируемые массивы работают практически так же, как и их одномерные прототипы, однако элементы в них определяются несколькими индексами вместо одного. Теоретически размерность индексируемого массива не ограничивается, хотя в большинстве приложений практически не встречаются массивы с размерностью выше 3.
Обобщенный синтаксис элементов многомерного массива:
$имя[индекс1][индекс2]..[индексN];
Пример ссылки на элемент двухмерного индексируемого массива:
$position = $chess_board[5][4];
Научная запись
Научная запись лучше подходит для представления очень больших и очень малых чисел — скажем, межпланетных расстояний или размеров атомов. Примеры:
Зе8
5.9736е24
Объекты
К пятому типу данных РНР относятся объекты. Объект представляет собой переменную, экземпляр которой создается по специальному шаблону, называемому классом. Концепции объектов и классов являются неотъемлемой частью парадигмы объектно-ориентированного программирования (ООП).
В отличие от других типов данных, поддерживаемых в языке РНР, объекты должны объявляться явно. Необходимо понимать, что объект — всего лишь конкретный экземпляр класса, используемого в качестве шаблона для создания объектов с конкретными характеристиками и функциональными возможностями. Следовательно, объявление класса должно предшествовать объявлению объектов, создаваемых на их основе. Пример объявления класса и последующего создания объектов на его основе:
class appliance {
var power:
function set_power($on_off) {
$this->power = $on_off;
}
}
...
$blender = new appliance;
Определение класса задает атрибуты и функции, связанные с некоторой структурой данных — в данном примере это структура с именем appliance (устройство). У этой структуры имеется всего один атрибут power (мощность). Для изменения этого атрибута создается метод set_power.
Помните: определение класса — всего лишь шаблон, и выполнять операции с ним в программе невозможно; сначала нужно создать объекты на основе этого шаблона. Объекты создаются при помощи ключевого слова new. Например, в приведенном выше фрагменте создается объект $blender класса appliance.
После создания объекта $blender можно задать его мощность при помощи метода
set_power: $blender->set_power("on");
Объектно-ориентированное программирование занимает столь важное место в современных стандартах программирования, что его применение в РНР заслуживает отдельной главы. Реализация ООП в РНР описана в главе 6.
Объявление переменных
Переменная представляет собой именованную область памяти, содержащую данные, с которыми можно выполнять операции во время выполнения программы.
Имена переменных всегда начинаются со знака доллара, $. Ниже приведены примеры допустимых имен переменных:
$соlоr
$operating_system
$_some_variable
$model
Имена переменных должны соответствовать тем же условиям, что и идентификаторы. Другими словами, имя переменной начинается с буквы или символа подчеркивания и состоит из букв, символов подчеркивания, цифр или других ASCII-символов в интервале от 127 до 255.
Следует заметить, что переменные в РНР, как и в языке Perl, не требуют специального объявления. Вместо этого переменная объявляется при первом ее использовании в программе. Более того, тип переменной косвенно определяется по типу хранящихся в ней данных. Рассмотрим следующий пример:
$sentence = "This is a sentence."; // $sentence интерпретируется как строка
$price = 42.99: // $price интерпретируется как вещественное число
$weight = 185; // $weight интерпретируется как целое число
Переменные могут объявляться в любой точке сценария РНР, однако от расположения объявления зависит то, откуда можно обращаться к данной переменной.
Область видимости переменных
Область видимости (scope) определяется как область доступности переменной в той программе, в которой она была объявлена. В зависимости от области видимости переменные РНР делятся на четыре типа:
локальные переменные;
параметры функций;
глобальные переменные;
статические переменные.
Локальные переменные
Переменная, объявленная внутри функции, считается локальной;
другими словами, на нее можно ссылаться только в этой функции. При любом присваивании вне функции будет использоваться совершенно другая переменная, которая не имеет ничего общего (кроме имени) с переменной, объявленной внутри функции. При выходе из функции, в которой была объявлена локальная переменная, эта переменная и ее значение уничтожаются.
Основное достоинство локальных переменных — отсутствие непредвиденных побочных эффектов, связанных со случайной или намеренной модификацией глобальной переменной. Рассмотрим следующий пример:
$х = 4;
function assignx () {
$х = 0;
print "\$x inside function is $x. ";
}
assignx();
print "\$x outside of function is $x. ";
При выполнении этого фрагмента выводится следующий результат:
$х inside function is 0.
$х outside of function is 4.
Как видите, программа выводит два разных значения переменной $х. Дело в том, что переменная $х внутри функции assignx имеет локальную природу, и изменение ее значения никак не отражается на значении, существующем за пределами этой функции. Справедливо и обратное — модификация $х за пределами функции никак не отражается на локальных переменных функции assignx().
Параметры функций
В РНР, как и во многих других языках программирования, любые параметры, передаваемые функции при вызове, должны быть объявлены в заголовке функции. Хотя параметрам присваиваются аргументы, переданные извне, после выхода из функции они становятся недоступными.
Параметры объявляются в круглых скобках после имени функции. Объявление параметров практически не отличается от объявления типичной переменной:
// Функция умножает переданное значение на 10 и возвращает результат
function x10 ($value) {
$value = $value * 10;
return $value;
}
Хотя вы можете обращаться к параметрам в той функции, в которой они были объявлены, и выполнять с ними необходимые операции, после завершения функции параметры уничтожаются.
Глобальные переменные
Глобальные переменные,
в отличие от локальных, доступны в любой точке программы. Но чтобы изменить значение глобальной переменной, необходимо специально объявить ее как глобальную в соответствующей функции. Для этого перед именем переменной ставится ключевое слово GLOBAL. Пример:
$somevar = 15;
function addit() {
GLOBAL $somevar;
$somevar++;
print "Somevar is $somevar";
}
addit();
Будет выведено значение $somevar, равное 16. Допустим, вы забыли включить следующую строку:
GLOBAL
$somevar;
В этом случае $somevar будет присвоено значение 1, поскольку эта переменная будет считаться локальной по отношению к функции addit( ). Локальная переменная по умолчанию инициализируется 0, а затем к ней прибавляется 1; таким образом, будет выведено значение 1.
Альтернативный способ объявления глобальных переменных связан с использованием массива РНР $GLOBALS( ). Давайте вернемся к предыдущему примеру и воспользуемся этим массивом для объявления глобальной переменной $somevar: $somevar = 15;
function addit() {
$GLOBALS["somevar"];
$somevar++;
}
addit();
print "Somevar is $somevar";
Каким бы способом ни обеспечивалась глобальная видимость переменной, помните, что неосторожное использование глобальных переменных нередко приводит к неожиданным результатам, причиняющим немало хлопот программистам. Таким образом, хотя глобальные переменные очень удобны, при их использовании необходима умеренность.
Обращение к отдельным символам строк
К отдельным символам строки можно обращаться как к элементам массива с последовательной нумерацией (см. следующий раздел). Пример:
$sequence_number = "04efgh";
$letter = Ssequence_number[4];
Переменной $ letter будет присвоено значение g. Как вы узнаете из следующего раздела, в РНР нумерация элементов массивов начинается с 0. Соответственно, выражение $sequence_number[l] будет равно 4.
Одномерные ассоциативные массивы
Ассоциативные массивы особенно удобны в ситуациях, когда элементы массива удобнее связывать со словами, а не с числами.
Предположим, вы хотите сохранить в массиве лучшие сочетания вин и блюд. Проще всего было бы хранить в массиве пары «ключ/значение» — например, присвоить сорт вина названию блюда. Самым разумным решением будет использование ассоциативного массива:
Spairings["zinfandel"] = "Broiled Veal Chops";
$pairings["merlot"] = "Baked Ham";
$pairings["sauvignon"] = "Prime Rib";
$pairings["sauternes"] = "Roasted Salmon";
Ассоциативный массив заметно экономит время и объем программного кода, необходимого для вывода определенных элементов массива. Допустим, вы хотите узнать, с каким блюдом лучше всего идет «Мерло». Нужная информация выводится простой ссылкой на элемент массива $pairings: print $pairings["merlot"]; // Выводится строка "Baked Ham" Ассоциативные массивы также можно создавать функцией РНР аггау():
Spairings = аrrау(
zinfandel => "Broiled Veal Chops",
merlot => "Baked Ham",
sauvignon => "Prime Rib",
sauternes => "Roasted Salmon");
Отличается только способ создания массива pairings, а функциональные возможности остаются без изменений.
Одномерные индексируемые массивы
При обращении к элементам одномерных индексируемых массивов используется целочисленный индекс, определяющий позицию заданного элемента.
Обобщенный синтаксис элементов одномерного массива:
$имя[индекс1];
Одномерные массивы создаются следующим образом:
$meat[0] = "chicken";
$meat[l] = "steak";
$meat[2] = "turkey";
При выполнении следующей команды:
print $meat[1]:
в браузере выводится строка
steak
При создании массивов также можно воспользоваться функцией array (). Массив $meat из предыдущего примера создается командой
$meat = аrrау("chicken", "steak", "turkey");
Приведенная выше команда pri nt приводит к тому же результату — выводу строки steak.
Чтобы включить новый элемент в конец массива, можно просто присвоить значение переменной массива без указания индекса. Следовательно, массив $meat можно создать еще одним способом:
Smeat[] = "chicken";
$meat[] = "steak";
Smeat[] = "turkey";
Переключение типов
Иногда бывает удобно использовать переменные способами, не предусмотренными при их создании. Допустим, вам захочется прибавить строковое значение "15" к целому числу 12. К счастью, тип переменных РНР может изменяться и без использования механизма явного преобразования. Этот процесс, независимо от того, выполняется ли он прямо или косвенно, называется переключением (juggling) типов. Лучше всего продемонстрировать сказанное на конкретных примерах.
Предположим, вы суммируете две величины — строку и целое число. Как вы думаете, что при этом произойдет? Результат зависит от содержимого строки. Например, при суммировании целого числа со строковым представлением числа будет получено целое число:
$variablel = 1;
$variable2 = "1";
$variable3 = $variablel + $variable2;
// $variable3 присваивается 4.
Другой пример переключения типов — суммирование целого числа с вещественным. При этом целое число преобразуется к вещественному типу, чтобы избежать потери точности:
$variablel = 3;
$variable2 = 5.4;
$variable3 = $variablel + $variable2;
// $variablel интерпретируется как вещественное число.
// и $variable3 присваивается 8.4.
Следует упомянуть о некоторых малоизвестных особенностях переключения типов. Что произойдет при попытке суммирования целого числа и строки, содержащей
целое число, но не являющейся строковым представлением? Рассмотрим следующий пример:
$variablel = 5;
$variable2 = "100 bottles of beer on the wall";
$variable3 = ;variable1 + $variable2;
// $variable3 присваивается 105
В результате переменной ;variable3 присваивается значение 105. Это происходит из-за того, что лексический анализатор РНР определяет тип по началу строки. Допустим, мы привели переменную $variable2 к виду "There are 100 bottles of beer on the wall". Поскольку алфавитные символы трудно интерпретировать как целое число, строка интерпретируется как 0, и переменной $variable3 присваивается 5.
Хотя в большинстве случаев переключение типов обеспечивает желаемый результат, существует способ явного приведения переменных к конкретному типу. Эта тема рассматривается в следующем разделе.
Переменные в переменных
В некоторых ситуациях бывает удобно использовать переменные, содержимое которых может динамически интерпретироваться как имя другой переменной. Рассмотрим типичный случай присваивания:
$recipe = "spaghetti";
Оказывается, строку "spaghetti" можно интерпретировать как имя переменной — для этого в команде присваивания перед именем исходной переменной ставится второй знак $:
$$recipe = "& meatballs";
Эта команда присваивает строку "& meatballs" переменной с именем "spaghetti". Следовательно, следующие две команды выводят одинаковые результаты:
print $recipe $spaghetti;
print $recipe $($recipe);
В обоих случаях будет выведена строка "spaghetti & meatballs".
Переменные
В примерах, приведенных выше, я попутно показал, как происходит присваивание и изменение значений переменных. И все же стоит четко сформулировать правила объявления переменных и выполнения операций с ними. Ниже приводится подробное описание этих правил.
Преобразование типов
Явное приведение переменной к типу, отличному от того, который изначально предназначался для нее, называется преобразованием (casting) типа. Изменение типа может быть как временным, одноразовым, так и постоянным.
Чтобы временно привести переменную к другому типу, достаточно воспользоваться оператором преобразования типа — указать нужный тип перед именем переменной в круглых скобках (табл. 2.2).
Таблица 2.2. Операторы преобразования типа переменных
Оператор преобразования типа
Новый тип
(int) или (integer)
Целое число
(real), (double) или (float)
Вещественное число
(string)
Строка
(array)
Массив
(object)
Объект
Простой пример преобразования типов:
$variable1= 13; // $variable1 присваивается целое число 13
$variable2 = (double) $variable1; // $variable2 присваивается 13.0
Хотя переменная $variable1 первоначально содержала целое число 13, преобразование (double) преобразует ее к вещественному типу (поэтому число 13 превращается в 13.0). Полученное значение присваивается переменной $variable2.
Из предыдущего раздела вы знаете, что при суммировании целого числа с вещественным получается вещественный результат. Однако тип результата можно изменить посредством явного преобразования типа:
$variablel = 4.0;
$variable2 = 5;
$variable3 = (int) $variable1 + $variable2; // $variable3 = 9
Следует заметить, что преобразование вещественного типа к целому всегда сопровождается округлением:
$variablel = 14.7:
$variable2 = (int) $varlable1; // $variable2 = 14:
Строку или переменную другого типа также можно преобразовать в элемент массива. В этом случае преобразованная переменная становится первым элементом массива:
$variable1 = 1114;
$array1 = (array) $varable1;
print $array1[0]; // Выводится значение 1114
Наконец, любой тип данных можно преобразовать в объект. Переменная становится атрибутом объекта, и ей присваивается имя scalar:
$model = "Toyota";
$new_obj = (object) $model;
Ссылка на исходное строковое значение выглядит так:
print $new_obj->scalar;
Присваивание по ссылке
Другой способ заключается в присваивании переменной ссылки на область памяти, занимаемую другой переменной. Вместо конкретного значения переменная-приемник связывается с указателем (или ссылкой) на область памяти, поэтому фактическое копирование не выполняется.
Чтобы присвоить значение по ссылке, укажите перед именем переменной-источника символ & (амперсанд):
$dessert = "cake";
$dessert2 = $Sdessert;
$dessert2 = "cookies";
print "$dessert2 "; // Выводится строка cookies
print Sdessert; // Снова выводится строка cookies
Как видно из приведенного фрагмента, после связывания переменной $dessert2 со ссылкой на область памяти, занимаемую переменной $dessert, любые изменения $dessert2 приводят к автоматической модификации $dessert (и всех остальных переменных, ссылающихся на эту же область памяти).
Присваивание по значению
Это самый распространенный способ присваивания, при котором значение просто заносится в область памяти, представленную именем переменной. Примеры присваивания по значению:
$vehicle = "car";
$amount =10.23;
В результате выполнения этих двух команд по адресу памяти, представленному именем $vehicle, сохраняется строка "car", а по адресу, представленному именем $amount, — значение 10.23.
Присваивание по значению также может выполняться в результате выполнения команды return в функциях:
function simple () {
return 5;
}
$return_value = simple();
Функция simple( ) всего лишь возвращает значение 5, которое присваивается некоторой переменной. В данном примере значение 5 будет присвоено переменной
$return_value.
Присваивание
Вы уже знаете, как присвоить значение переменной в сценарии РНР. Тем не менее, некоторые тонкости, связанные с присваиванием, стоит выделить особо. Вероятно, вам хорошо знаком механизм присваивания по значению, при котором именованной переменной присваивается конкретное значение — например, целое число 1 или строка "ciao". Однако существует и второй механизм — присваивание по ссылке, также открывающее перед программистами немало полезных возможностей. В следующих разделах оба механизма рассматриваются более подробно.
Синтаксис встроенной документации
Второй вариант синтаксиса ограничения строк, представленный в HTML4, называется встроенной документацией (here doc). В этом варианте синтаксиса строка начинается с символов <<<, за которыми следует некоторый идентификатор по вашему выбору, затем строка, присваиваемая переменной. Конструкция заканчивается вторым экземпляром того же идентификатора. Пример:
$paragraph = <<
This is a string that
Will be interpreted exactly
As it is written in the
variable assignment,
DELIM;
Выбранный идентификатор не должен присутствовать в присваиваемой строке. Более того, первый символ завершающего идентификатора должен находиться в первом столбце строки, завершающей конструкцию.
Смешанное индексирование
В многомерных массивах допускается смешанное индексирование (числовое и ассоциативное). Допустим, вы хотите расширить модель одномерного ассоциативного массива для хранения информации об игроках первого и второго состава футбольной команды. Решение может выглядеть следующим образом:
$Buckeyes["quarterback"] [1] = "Bellisari";
$Buckeyes["quarterback"] [2] = "Moherman":
$Buckeyes["quarterback"] [3] = "Wiley";
В РНР существует множество функций для создания массивов и операций с ними — эта тема настолько обширна, что заслуживает отдельной главы. Работа с массивами в РНР подробно описана в главе 13.
Стандартная запись
Стандартная запись удобна для представления типичных вещественных чисел — скажем, денежных величин. Примеры:
12.45
98.6
Стандартные переменные
В РНР поддерживается ряд стандартных переменных, предоставляющих в распоряжение программиста довольно подробную информацию о внутренней
конфигурации. Значения одних переменных задаются РНР, другие изменяются в
зависимости от операционной системы и web-сервера, с которыми работает РНР.
Вместо подробного описания всех стандартных переменных я выделю лишь те переменные и функции, которые используются на практике многими программистами.
Чтобы получить полный список переменных web-сервера, окружения и РНР, определенных для вашей конфигурации системы, достаточно выполнить следующий фрагмент:
while (list($var,$value) = each($GLOBALS)) :
echo " $var => $value";
endwhile;
В результате выводится список наподобие приведенного ниже. Потратьте немного времени на просмотр полученных данных, а затем разберите приведенные примеры.
GLOBALS =>
HTTP_GET_VARS => Array
HTTP_COOKIE_VARS => Array
HOSTSIZE => 1000
HOSTNAME => server1.apress.com
LOGNAME => unstrung
HISTFILESIZE => 1000
REMOTEHOST => apress.com
MAIL -> /var/spool/mail/apress
MACHTYPE => 1386
TERM => vt100
HOSTTYPE => i386-linux
PATH =>
/usr/sbin:/sbin:/usr/local /bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/Java/bin
HOME => /root
INPUTRC => /etc/inputrc
SHELL => /bin/csh
USER => nobody
VENDOR => intel
GROUP => root
HOST => server1.apress.com
OSTYPE => linux
PWD => /www/bin
SHLVL => 3_ => /www/bin/httpd
DOCUMENT_ROOT => /usr/local/apress/site.apress
HTTP_ACCEPT => */*
HTTP_ACCEPT_ENCODING => gzip, deflate
HTTP_ACCEPT_LANGUAGE => it.en-us;q=0.5
HTTP_CONNECTION -> Keep-Alive
HTTP_HOST => www.apress.com
HTTP_USER_AGENT => Mozilla/4.0 (compatible; MSIE 5.0: Windows 98;
CNETHomeBuild051099)
REMOTE_ADOR => 127.0.0.1
REMQTE_PORT => 3207
SCRIPT_FILENAME => /usr/local/apress/site.apress/j/environment_vars.php
SERVER_ADDR => 127.0.0.1
Как видите, стандартные переменные содержат разнообразные сведения — как полезные, так и не очень. Вы можете вывести любую из этих переменных по имени. Например, следующая команда выводит IP-адрес пользователя:
print "Hi! Your IP address is: $REMOTE_ADDR";
IP-адрес выводится в числовой форме (например, 208.247.106.187).
Кроме того, стандартные переменные могут использоваться для сбора информации о браузере и операционной системе пользователя. Команда
print "Your browser is: $HTTP_USER_AGENT";
возвращает информацию следующего вида:
Your browser is: Mozina/4.0 (compatible: MSIE 5.0; Windows 98: CNETHomeBuild051099)
Информация о браузере и операционной системе, в которой он работает, может пригодиться при построении страниц, рассчитанных на специфические форматы конкретных браузеров.
Для работы с массивами стандартных переменных необходимо включить директиву track_vars в файл php.ini. В РНР версии 4.0.3 директива track_vars включена постоянно.
Статические переменные
Последний тип видимости переменных называется статическим. В отличие от переменных, объявленных параметрами и уничтожаемых при выходе из функции, статическая переменная сохраняет свое значение при повторном вызове. Для объявления статической переменной перед ее именем ставится ключевое слово STATIC:
STATIC $somevar;
Рассмотрим пример:
function keep_track() {
STATIC $count = 0;
$count++;
print $count;
print " ";
}
keep_track();
keep_track();
keep_track();
Как будут выглядеть результаты работы этого сценария? Если бы переменная $count не была объявлена статической (то есть являлась локальной), результат выглядел бы так:
1
1
1
Но поскольку переменная $count является статической, при каждом вызове функции будет сохраняться ее предыдущее значение, поэтому результат будет таким:
1
2
3
Статические переменные особенно удобны при написании рекурсивных функций — особого класса функций, которые многократно вызывают сами себя до выполнения некоторого условия. Рекурсивные функции рассматриваются в главе 4.
Строковое присваивание
Строки делятся на две категории в зависимости от типа ограничителя — они могут ограничиваться парой кавычек (" ") или апострофов (' '). Между этими категориями существуют два принципиальных различия. Во-первых, имена переменных в строках, заключенных в кавычки, заменяются соответствующими значениями, а строки в апострофах интерпретируются буквально, даже если в них присутствуют имена переменных,
Два следующих объявления дают одинаковый результат:
$food = "meatloaf";
$food = 'meatloaf';
Однако результаты следующих объявлений сильно различаются:
$sentence = "My favorite food is $food";
$sentence2 = 'My favorite food is $food';
Переменной $sentence присваивается строка
My favorite food is meatloaf.
Обратите внимание: переменная $food автоматически интерпретируется. С другой стороны, переменной $sentence2 присваивается строка
My favorite food is $food.
В отличие от переменной $sentence, в $sentence2 осталась не интерпретированная переменная $food. Различия обусловлены использованием кавычек и апострофов при присваивании переменным $sentence и $sentence2.
Прежде чем рассматривать второе фундаментальное различие между строками, заключенными в апострофы и в кавычки, необходимо познакомиться со служебными символами, используемыми в строках РНР. В РНР, как и в большинстве современных языков программирования, строки могут содержать служебные символы (например, символы табуляции или новой строки), перечисленные в табл. 2.1.
Таблица 2.1. Служебные символы в строках
Последовательность
Смысл
\n
Новая строка
\r
Возврат курсора
\t
Горизонтальная табуляция
\\
Обратная косая черта
\$
Знак доллара
\"
Кавычка
\[0-7]{1,3}
Восьмеричная запись числа (в виде регулярного выражения)
\x[0-9A-Fa-f]{l,2}
Шестнадцатиричная запись числа (в виде регулярного выражения)
Второе принципиальное различие заключается в том, что в строках, заключенных в кавычки, распознаются все существующие служебные символы, а в строках, заключенных в апострофы, — только служебные символы «\\» и «\». Следующий пример наглядно демонстрирует различия между присваиванием строк, заключенных в кавычки и апострофы:
$double_list = "item1\nitem2\nitem2";
$single_list = 'item1\nitem2\nitem2';
Если вывести обе строки в браузере, окажется, что строка в кавычках содержит внутренние символы новой строки, а в строке в апострофах последовательность \n выводится как обычные символы. Хотя многие служебные символы в браузерах
несущественны, при форматировании для других условий они играют очень важную роль. Помните об этом, выбирая между кавычками и апострофами, и вам удастся избежать многих неожиданностей.
Строковые значения
Строкой (string) называется последовательность символов, которая рассматривается как единое целое, но при этом обеспечивает доступ к отдельным символам. Примеры строк:
thesaurus
49ers
abc
&%/$#
Обратите внимание: в РНР не поддерживается символьный тип данных. Строковый тип может рассматриваться как единое представление для последовательностей, состоящих из одного или нескольких символов.
Вещественные числа
Вещественные числа (числа с плавающей точкой) отличаются от целых наличием дробной части. Они используются для представления значений, требующих повышенной точности, — например, температур или денежных величин. В РНР поддерживаются два вещественных формата: стандартная и научная (экспоненциальная) запись.
Восьмеричная и шестнадцатеричная запись
В РНР поддерживается запись целых чисел в восьмеричной (по основанию 8) и шестнадцатеричной (по основанию 16) системах счисления. Восьмеричные числа начинаются с цифры 0, после которой следует серия цифр от 0 до 7. Примеры:
0422
0534
Шестнадцатеричные целые числа имеют префикс 0х или 0Х и могут состоять из цифр от 0 до 9 и букв от а (А) до f (F). Примеры:
0x3FF
0x22abc
PHP 4 на практике
Альтернативное ограничение блоков
В управляющих структурах используются специальные ограничители, определяющие границы блоков. Фигурные скобки ({ }) уже упоминались выше. Для удобства программистов в РНР поддерживается альтернативный формат ограничения блоков:
if (выражение) :
блок
else :
блок
endif;
Следовательно, две приведенных ниже команды if полностью эквивалентны:
if ($а== $b) {
print "Equivalent values!";
}
if ($a == $b) :
print "Equivalent values!";
endif;
Break
Команда break немедленно прерывает выполнение той конструкции while, for или switch, в которой она находится. Эта команда уже упоминалась в предыдущем разделе, однако прерывание текущего цикла не исчерпывает возможностей команды break. В общем виде синтаксис break выглядит так:
break n;
Необязательный параметр n определяет количество уровней управляющих конструкций, завершаемых командой break. Например, если команда break вложена в две команды while и после break стоит цифра 2, происходит немедленный выход из обоих циклов. По умолчанию значение n равно 1; выход на один уровень может обозначаться как явным указанием 1, так и указанием команды break без параметра. Обратите внимание: команда i f не относится к числу управляющих конструкций, прерываемых командой break. Об этом следует помнить при использовании необязательного параметра п.
Рассмотрим пример использования команды break в цикле foreach:
$arr = array(14, 12, 128, 34, 5);
$magic number = 128:
foreach ($arr as $val) :
if (Sval == $magic_number) :
print "The magic number is in the array!";
break;
endif;
print "val is Sval ";
endforeach;
Если значение $magic_number присутствует в массиве $аrr (как в приведенном примере), поиск прерывается. Результат выглядит так:
val is 14
val is 12
The magic number is in the array!
Приведенный пример всего лишь демонстрирует использование команды break. В РНР существует стандартная функция in_array( ), предназначенная для поиска заранее заданной величины в массиве; эта функция подробно описана в главе 5.
Continue
Остается рассмотреть еще одну управляющую конструкцию РНР — continue. При выполнении команды continue в цикле пропускаются все оставшиеся команды текущей итерации и немедленно начинается новая итерация. Синтаксис команды continue в общем виде:
continue n;
Необязательный параметр n указывает, на сколько уровней внешних циклов распространяется действие continue.
Рассмотрим пример использования команды continue. Допустим, вы хотите сосчитать простые числа в интервале от 0 до некоторой заданной границы. Простоты ради предположим, что у нас имеется функция is_prime(), которая проверяет, является число простым или нет:
$boundary = 558;
for ($i = 0; $i <= $boundary: $i++) :
if (! is_prime($i)) :
continue;
endif;
$prime_counter++;
endfor;
Если проверяемое число является простым, блок команды if обходится и переменная $prime_counter увеличивается. В противном случае выполняется команда continue, в результате чего происходит немедленный переход в начало следующей итерации. Использование continue в длинных и сложных алгоритмах приводит к появлению запу- танного и невразумительного кода. В подобных случаях использовать continue не рекомендуется.
Команда continue не является безусловно необходимой в программах, поскольку аналогичного эффекта можно добиться при помощи команды if.
Do. .while
Цикл do. .while работает почти так же, как и цикл while, описанный в предыдущем разделе, однако в do. .while условие проверяется не в начале, а в конце каждой итерации. Учтите, что цикл do. .while всегда выполняется хотя бы один раз, а цикл while может вообще не выполняться, если перед входом в цикл условие окажется ложным:
do:
блок
while (выражение);
Давайте пересмотрим пример с вычислением факториала и перепишем его с использованием конструкции do. .while:
$n = 5:
$ncopy = $n;
$factorial = 1; // Установить начальное значение факториала
do {
$factorial = $n * $factorial;
$n--: // Уменьшить Sn на 1
} while (Sn > 0);
print "The factorial of Sncopy is $factorial.";
При выполнении этого примера будет получен тот же результат, что и при выполнении его прототипа из предыдущего раздела. В
цикле do. .while не поддерживается альтернативный синтаксис (ограничение блоков при помощи : и завершающего ключевого слова), поэтому блок может заключаться только в фигурные скобки.
For
Цикл for обеспечивает еще одну возможность многократного выполнения блоков. Он отличается от цикла while только тем, что условие изменяется в самой
управляющей конструкции, а не где-то внутри блока команд. Как и в случае с циклом while, цикл выполняется до тех пор, пока проверяемое условие остается истинным. Общая форма конструкции for выглядит так:
for (инициализация: условие; приращение) {
блок
}
Условная часть цикла for в действительности состоит из трех компонентов. Инициализация выполняется всего один раз и определяет начальное значение управляющей переменной цикла. Условие проверяется в начале каждой итерации и определяет, должна ли выполняться текущая итерация или нет. Наконец, приращение определяет изменение управляющей переменной при каждой итерации. Возможно, термин «приращение» в данном случае неточен, поскольку переменная может как увеличиваться, так и уменьшаться в соответствии с намерениями программиста. Следующий пример демонстрирует простейший случай применения цикла for:
for ($i = 10; $1 <- 100: $1 +=10) : // Обратная косая черта предотвращает
print "\$i = $i "; endfor; // возможную интерполяцию переменной $1
Выполнение этого фрагмента дает следующий результат:
$i = 10
$i = 20
$i = 30
$i = 40
$i - 50
$i = 60
$i = 70
$i = 80
$i = 90
$i = 100
В этом примере управляющая переменная $i инициализируется значением 10. Условие заключается в том, что цикл продолжается до тех пор, пока $i не достигнет или не превысит пороговую величину 100. Наконец, при каждой итерации значение $i увеличивается на 10. В результате команда print выполняется 10 раз, каждый раз выводя текущее значение $i. Обратите внимание: для увеличения $i на 10 используется оператор сложения с присваиванием. Для этого есть веские причины, поскольку циклы for в РНР не поддерживают более традиционной записи $i = $i + 10.
Кстати, этот пример можно записать и в другом виде, но с теми же результатами:
for ($i = 10; $i <= 100; print "\$i - $i ". $i+=10);
Многие новички не понимают, зачем создавать несколько разновидностей циклов в языке программирования, будь то РНР или какой-нибудь другой язык. Почему нельзя обойтись одной циклической конструкцией? Дело в том, что у цикла for существует несколько специфических особенностей.
Например, вы можете инициализировать несколько переменных одновременно, разделяя команды инициализации запятыми:
for ($x=0,$y=0: $x+$y<10; $x++) :
$У += 2; // Увеличить $у на 2
print "\$y = $y "; // Вывести значение $у
$sum = $x + $y;
print "\surn = $sum "; // Вывести значение $sum
endfor;
Результат:
$y = 2
$sum = 2
Sy = 4
$sum = 5
$y = 6
$sum = 8
$y = 8
$sum = 11
Этот пример выводит текущие значения $y и суммы $х и $у. Как видно из приведенных результатов, выводится значение $sum = 11, хотя эта сумма выходит за границы условия цикла ($х + $у < 10). Это происходит из-за того, что при входе в данную итерацию переменная $у была равна 6, а переменная $х была равна 2. Значения переменных соответствовали условию цикла, поэтому $х и $у были присвоены новые значения, в результате чего была выведена сумма И. При очередной проверке условия сумма 11 превысила пороговое значение 10 и цикл завершился.
В управляющих выражениях циклов for могут отсутствовать любые компоненты. Например, вы можете передать ранее инициализированную переменную прямо в цикл, не присваивая ей определенного начального значения. Возможны и другие ситуации — например, приращение переменной цикла может осуществляться в зависимости от некоторого условия, определяемого в цикле. В этом случае приращение не должно указываться в управляющем выражении. Пример:
$х = 5:
for (: : $х +=2) :
print " $х ";
if ($x == 15) :
break; // Выйти из цикла for
endif;
endfor;
Результат выглядит так:
5 7 9 11 13 15
Хотя циклические конструкции for и while выполняют практически одинаковые функции, считается, что цикл for делает программу более наглядной. Это объясняется тем, что программист при виде команды for немедленно получает всю необходимую информацию о механике и продолжительности цикла. С другой стороны, в командах while приходится тратить лишнее время на поиск обновлений управляющих переменных — в больших программах это может занимать немало времени.
Foreach
Конструкция foreach представляет собой разновидность for, включенную в язык для упрощения перебора элементов массива. Существуют две разновидности команды foreach, предназначенные для разных типов массивов:
foreach (массив as $элемент) {
блок
}
foreach (массив as $ключ => $элемент) {
блок
}
Например, при выполнении следующего фрагмента:
$menu = аrrау("pasta", "steak", "potatoes", "fish", "fries");
foreach ($menu as $item) {
print "$item ";
}
будет выведен следующий результат:
pasta
steak
potatoes
fish
fries
В этом примере следует обратить внимание на два обстоятельства. Во-первых, конструкция foreach автоматически возвращается в начало массива (в других циклических конструкциях этого не происходит). Во-вторых, нет необходимости явно увеличивать счетчик или иным способом переходить к следующему элементу массива — это происходит автоматически при каждой итерации foreach.
Второй вариант используется при работе с ассоциативными массивами:
$wine_inventory = array {
"merlot" => 15,
"zinfandel" => 17,
"sauvignon" => 32
}
foreach ($wine_inventory as $i => $item_count) {
print "$item_count bottles of $i remaining ";
}
В этом случае результат выглядит так:
15 bottles of merlot remaining
17 bottles of zinfandel remaining
32 bottles of sauvignon remaining
Как видно из приведенных примеров, конструкция foreach заметно упрощает работу с массивами. За дополнительной информацией о массивах обращайтесь к главе 5.
Операнды
Операнд представляет собой некоторую величину, обрабатываемую в программе. Операнды могут относиться к любому типу данных, представленному в главе 2. Вероятно, вы уже знакомы с концепциями обработки и использования операндов не только в повседневных математических вычислениях, но и по прежнему опыту программирования. Примеры операндов:
$а++; // $а - операнд
$sum = $val1 + $val2; // $sum. $val1 и $val2 - операнды
Операторы
Оператор представляет собой символическое обозначение некоторого действия, выполняемого с операндами в выражении. Многие операторы известны любому программисту, но вы должны помнить, что РНР выполняет автоматическое преобразование типов на основании типа оператора, объединяющего два операнда, — в других языках программирования это происходит не всегда.
Приоритет и ассоциативность операторов являются важными характеристиками языка программирования (см. раздел «Ассоциативность операторов» этой главы). В табл. 3.1 приведен полный список всех операторов, упорядоченных по убыванию приоритета. Приоритет, ассоциативность и сами операторы подробно рассматриваются в разделах, следующих за таблицей.
Таблица 3.1. Операторы РНР
Оператор
Ассоциативность
Цель
( )
-
Изменение приоритета
new
-
Создание экземпляров объектов
! ~
П
Логическое отрицание, поразрядное отрицание
++ --
П
Инкремент, декремент
@
П
Маскировка ошибок
/ * %
Л
Деление, умножение, остаток
+ - .
Л
Сложение, вычитание, конкатенация
<< >>
Л
Сдвиг влево, сдвиг вправо (поразрядный)
< <= > >=
-
Меньше, меньше или равно, больше, больше или равно
== != === <>
-
Равно, не равно, идентично, не равно
& ^ |
Л
Поразрядные операции AND, XOR и OR
&& ||
Л
Логические операции AND и OR
?:
П
Тернарный оператор
= += *= /= .=
П
Операторы присваивания
%= &= |= ^=
<<= >>=
AND XOR OR
Л
Логические операции AND, XOR и OR
После знакомства с концепциями операторов и операндов следующие примеры выражений выглядят значительно понятнее:
$а = 5; // Присвоить целое число 5 переменной $а
$а = "5": // Присвоить строковую величину "5" переменной $а
$sum = 50 + $some_int; // Присвоить сумму 50 + $some_int переменной $sum
Swine = "Zinfandel"; // Присвоить строку "Zinfandel" переменной $wine
$inventory++: // Увеличить значение $inventory на 1
Объединяя операторы и операнды, вы получите более сложные выражения для выполнения нетривиальных вычислений. Пример:
является характеристикой операторов, определяющей порядок выполнения действий с окружающими операндами. В РНР используются те же правила приоритета, что и в школьном курсе математики. Пример:
$total_cost = $cost + $cost * 0.06;
Приведенная команда эквивалентна следующей:
$total cost = $cost + ($cost * 0.06);
Это объясняется тем, что оператор умножения обладает более высоким приоритетом по сравнению с оператором сложения.
Ассоциативность операторов
Ассоциативность
оператора определяет последовательность выполнения операторов с одинаковым приоритетом (см. табл. 3.1). Выполнение может происходить в двух направлениях: либо слева направо, либо справа налево. При ассоциативности первого типа операции, входящие в выражение, выполняются слева направо. Например, команда
$value = 3*4*5*7*2;
эквивалентна следующей команде:
$value = ((((3 * 4) * 5) * 7) * 2);
Результат вычислений равен 840. Это объясняется тем, что оператор умножения (*) обладает левосторонней ассоциативностью. Операторы с правосторонней ассоциативностью и одинаковым приоритетом обрабатываются справа налево. Например, фрагмент
$с = 5;
$value = $а - $b - $с;
эквивалентен фрагменту
$c = 5;
$value = ($а - ($b - $с));
При обработке этого выражения переменным $value, $a, $b и $с будет присвоено значение 5. Это объясняется тем, что оператор присваивания (=) обладает правосторонней ассоциативностью.
Математические операторы
Математические операторы
(табл. 3.2) предназначены для выполнения различных математических операций и часто применяются в большинстве программ РНР. К счастью, их использование обходится без проблем.
Таблица 3.2.
Математические операторы
Пример
Название
Результат
/p>
РНР содержит широкий ассортимент стандартных математических функций для выполнения основных преобразований и вычисления логарифмов, квадратных корней, геометрических величин и т. д. За обновленным списком таких функций обращайтесь к документации.
Операторы присваивания
Операторы присваивания
задают новое значение переменной. В простейшем варианте оператор присваивания ограничивается изменением величины, в других вариантах (называемых сокращенными операторами присваивания) перед присваиванием выполняется некоторая операция. Примеры таких операторов приведены в табл. 3.3.
Таблица 3.3.
Операторы присваивания
Пример
Название
Результат
$а = 5;
Присваивание
Переменная $а равна 5
$а += 5;
Сложение с присваиванием
Переменная $а равна сумме $а и 5
$а *= 5;
Умножение с присваиванием
Переменная $а равна произведению $а и 5
$а/=5;
Деление с присваиванием
Переменная $а равна частному отделения $а на 5
$а .= 5;
Конкатенация с присваиванием
Переменная $а равна конкатенации $а и 5
Умеренное использование операторов присваивания обеспечивает более наглядный и компактный код.
Строковые операторы
Строковые операторы
РНР (табл. 3.4) обеспечивают удобные средства конкатенации (то есть слияния) строк. Существует два строковых оператора: оператор конкатенации (.) и оператор конкатенации с присваиванием (.=), описанный в предыдущем разделе «Операторы присваивания».
Конкатенацией называется объединение двух и более объектов в единое целое.
Таблица 3.4.
Строковые операторы
Пример
Название
Результат
$a = "abc"."def"
Конкатенация
Переменной $а присваивается результат конкатенации $а и $b
$а - "ghijkl"
Конкатенация с присваиванием
Переменной $а присваивается результат конкатенации ее текущего значения со строкой "ghijkl"
// $а присваивается строковое значение "Spaghetti & Meatballs are delicious" $a .= "are delicious";
Конечно, два строковых оператора не исчерпывают всех возможностей РНР по обработке строк. За полной информацией об этих возможностях обращайтесь к главе 8.
Операторы инкремента и декремента
Удобные вспомогательные операторы инкремента (++) и декремента (--), приведенные в табл. 3.5, делают программу более наглядной и обеспечивают укороченную запись для увеличения или уменьшения текущего значения переменной на 1.
Таблица 3.5.
Операторы инкремента и декремента
Пример
Название
Результат
++$а, $а++
Инкремент
Переменная $а увеличивается на 1
--$а, $а--
Декремент
Переменная $а уменьшается на 1
Интересный факт: эти операторы могут располагаться как слева, так и справа от операнда. Действия, выполняемые оператором, зависят от того, с какой стороны от операнда он находится. Рассмотрим следующий пример:
$inventory = 15; // Присвоить Sinventory целое число 15
$old_inv = Sinventory--; // СНАЧАЛА присвоить $old_inv значение
// Sinventory. а ЗАТЕМ уменьшить Sinventory.
$orig_iinventory = ++inventory;// СНАЧАЛА увеличить Sinventory. а ЗАТЕМ
// присвоить увеличенное значение Sinventory
// переменной $orig_inventory.
Как видите, расположение операторов инкремента и декремента оказывает сильное влияние на результат вычислений.
Логические операторы
Логические операторы
(табл. 3.6) наряду с математическими операторами играют важную роль в любом приложении РНР, обеспечивая средства для принятия решений в зависимости от значения переменных. Логические операторы позволяют управлять порядком выполнения команд в программе и часто используются в управляющих конструкциях (таких, как условная команда i f, а также циклы for и while).
Таблица 3.6.
Логические операторы
Пример
Название
Результат
$а && $b
Конъюнкция
Истина, если истинны оба операнда ,
$aAND$b
Конъюнкция
/p>
Логические операторы часто используются для проверки результата вызова функций:
file_exists("filename.txt") OR print "File does not exist!";
Возможен один из двух вариантов:
файл filename.txt существует;
будет выведено сообщение: «File does not exist!».
Операторы равенства
Операторы равенства
(табл. 3.7) предназначены для сравнения двух величин и проверки их эквивалентности.
Таблица 3.7.
Операторы равенства
Пример
Название
Результат
$a==$b
Проверка равенства
Истина, если $а и $b равны
$а != $b
Проверка неравенства
Истина, если $а и $b не равны
$а === $b
Проверка идентичности
Истина, если $а и $b равны и имеют одинаковый тип
Даже опытные программисты часто допускают одну распространенную ошибку — они пытаются проверять равенство двух величин, используя всего один знак равенства (например, $а = $b). Помните, при такой записи значение $b присваивается $а, и желаемый результат не будет достигнут.
Операторы сравнения
Операторы сравнения
(табл. 3.8), как и логические операторы, позволяют управлять логикой программы и принимать решения при сравнении двух и более переменных.
Таблица 3.8.
Операторы сравнения
Пример
Название
Результат
$a<$b
Меньше
Истина, если переменная $а меньше $b
$a>$b
Больше
Истина, если переменная $а больше $b
$a <= $b
Меньше или равно
Истина, если переменная $а меньше или равна $b
$a >= $b
Больше или равно
Истина, если переменная $а больше или равна $b
($a-12)?5: -1
Тернарный оператор
Если переменная $а равна 12, возвращается значение 5, а если не равна — возвращается 1
Обратите внимание: операторы сравнения предназначены для работы только с числовыми значениями. Хотя возникает искушение воспользоваться ими для сравнения строк, результат, скорее всего, окажется неверным. В РНР существуют стандартные функции для сравнения строковых величин. Эти функции подробно рассматриваются в главе 8.
Поразрядные операторы
Поразрядные операторы
выполняют операции с целыми числами на уровне отдельных битов, составляющих число. Чтобы лучше понять принцип их работы, необходимо иметь хотя бы общее представление о двоичном представлении десятичных чисел. В табл. 3.9 приведены примеры десятичных чисел и соответствующих им двоичных представлений.
Таблица 3.9.
Десятичные числа и их двоичные представления
Десятичное целое
Двоичное представление
2
10
5
101
10
1010
12
1100
145
10010001
1 452 012
1011000100111111101100
Поразрядные операторы, перечисленные в табл. 3.10, представляют собой особый случай логических операторов, однако они приводят к совершенно иным результатам.
Таблица 3.10.
Поразрядные операторы
Пример
Название
Результат
$а&$b
Конъюнкция
С битами, находящимися в одинаковых разрядах $а и $b, выполняется
операция конъюнкции
$а|$Ь
Дизъюнкция
С битами, находящимися в одинаковых разрядах $а и $b, выполняется операция дизъюнкции
$а^$b
Исключающая
С битами, находящимися в одинаковых разрядах $а и $b, выполняется операция исключающей дизъюнкции
~$b
Отрицание
Все разряды переменной $b инвертируются
$а << $b
Сдвиг влево
Переменной $а присваивается значение $b, сдвинутое влево на два бита
$а >> $b
Сдвиг вправо
Переменной $а присваивается значение $b, сдвинутое вправо на два бита
Если вам захочется больше узнать о двоичном представлении и поразрядных операторах, я рекомендую обратиться к обширному электронному справочнику Рэндалла Хайда (Randall Hyde) «The Art of Assembly Language Programming», доступному по адресу http://webster.cs.ucr.edu/Page_asm/Page_asm.html. Это лучший из ресурсов, которые мне когда-либо встречались в Web.
Проект: календарь событий
Для практической демонстрации многих концепций, рассмотренных ранее, я завершаю эту главу описанием программы-календаря. В календаре хранится информация о последних кулинарных мероприятиях, семинарах по дегустации вин и любых других событиях, которые вы сочтете нужным в него включить. В этом проекте задействованы многие концепции, описанные в этой главе, а также представлен ряд новых концепций, которые будут рассматриваться в следующих главах.
Информация о событиях хранится в обычном текстовом файле и выглядит примерно так:
July 21, 2000|8 p. m.|Cooking With Rasmus|PHP creator Rasmus Lerdorf discusses the wonders of cheese.
July 23, 2000|11 a. m.|Boxed Lunch|Valerie researches the latest ham sandwich making techniques (documentary)
July 31, 2000|2:30p.m.|Progressive Gourmet|Forget the Chardonnay: iced tea is the sophisticated gourmet's beverage of choice.
August 1, 2000|7 p.m.|Coder's Critique|Famed Food Critic Brian rates NYC's hottest new Internet cafes.
August 3, 2000|6 p.m.|Australian Algorithms|Matt studies the alligator's diet.
На рис. 3.1 изображен результат работы сценария РНР, приведенного в листинге 3.1.
Рис. З.1. Примерный вид календаря
Прежде чем переходить к подробному анализу кода, потратьте немного времени на изучение алгоритма:
Открыть файл, содержащий информацию о событиях.
Разделить каждую строку на 4 элемента: дату, время, название и краткое описание мероприятия.
Отформатировать и вывести данные.
Закрыть файл.
Листинг 3.1.
Сценарий для вывода содержимого events.txt в браузере
// Приложение: календарь
// Назначение: чтение и анализ содержимого файла
// с последующим форматированием для вывода в браузере
// Открыть файловый манипулятор Sevents для файла events.txt
$events - fopen ("events.txt". "r");
print "
"
print""
print "
Events Calendar:
";
// Читать, пока не будет найден конец файла
while (! feof(Sevents)) :
// Прочитать следующую строку файла
events.txt $event = fgets($events. 4096);
// Разделить компоненты текущей строки на элементы массива
$event_info = explode("|". Jevent);
// Отформатировать и вывести информацию о событии
print "$event_info[0] ( $event_info[1] ) ";
print "$event_info[2] ";
print "$event_info[3]
";
endwhile;
// Завершить таблицу
print "
";
fclose ($events);
?>
Этот короткий пример убедительно доказывает, что РНР позволяет даже неопытным программистам создавать реальные приложения с минимальными усилиями и затратами времени. Если какие-нибудь из представленных концепций покажутся непонятными, не огорчайтесь — на самом деле они очень просты и будут подробно описаны в следующих главах. А если вам не терпится узнать побольше об этих вопросах, обратитесь к главе 7 «Файловый ввод/вывод и файловая система» и главе 8 «Строки и регулярные выражения» поскольку большая часть незнакомого синтаксиса описана именно там.
Проверка условий
Управляющие конструкции обычно проверяют условия на истинность или ложность, и в зависимости от результата проверки выполняется то или иное действие. Рассмотрим выражение $а == $b. Это выражение истинно, если $а равно $b, и ложно в противном случае. Результат истинного выражения считается равным 1, а результат ложного выражения равен 0. Рассмотрим следующий фрагмент:
$а = 5;
$b = 5;
print $а == $b;
В результате выводится значение 1. Если изменить $а или $Ь и присвоить переменной значение, отличное от 5, выводится 0.
if
Команда if представляет собой разновидность команды выбора, которая вычисляет значение выражения и в зависимости от того, будет ли полученный результат истинным или ложным, выполняет (или не выполняет) блок программного кода. Существует две общих формы команды i f:
if (выражение) {
блок
}
и
if (выражение) {
блок
}
else {
блок
}
Как упоминалось в предыдущем разделе, проверка условий дает либо истинный, либо ложный результат. Выполнение блоков зависит от результата проверки, причем блок может состоять как из одной, так и из нескольких команд. В следующем примере после проверки условия выбирается и выводится одно из двух утверждений:
if ($cooking_weight < 200) {
print "This is enough pasta (< 200g) for 1-2 people";
}
else {
print "That's a lot of pasta. Having a party perhaps?";
}
Если в результате проверки условия выполняется всего одна команда, фигурные скобки не обязательны:
if ($cooking_weight < 100) print "Are you sure this is enough?";
elseif
Команда elseif добавляет в управляющую конструкцию if дополнительный уровень проверки и увеличивает количество условий, на основании которых принимается решение:
if (выражение) {
блок
}
elseif (выражение) {
блок
} В РНР существует альтернативное представление команды elself — в виде двух отдельных слов else if. Оба варианта приводят к одинаковым результатам, а альтернативное представление поддерживается исключительно для удобства. Команда elself особенно полезна в тех случаях, когда происходит последовательное уточнение проверяемых условий. Обратите внимание: условие elself вычисляется лишь в том случае, если все предшествующие условия if и elself оказались ложными.
if ($cooking_weight < 200) {
print "This is enough pasta (< 200g) for 1-2 people";
}
elseif ($cooking_weight < 500) {
print "That's a lot of pasta. Having a party perhaps?"; }
}
else {
print "Whoa! Who are you cooking for, a football team?";
}
Вложенные команды if
Вложение команд i f обеспечивает максимальный контроль над проверкой условий. Давайте исследуем эту возможность, усовершенствовав пример из предыдущих разделов. Предположим, вес продукта должен проверяться лишь в том случае, если речь идет о пасте (макаронных изделиях):
// Проверить значение $pasta
if ($food == "pasta") {
// Проверить значение $cooking_weight
if ($cooking_weight < 200) {
print "This is enough pasta (< 200g) for 1-2 people";
}
elseif ($cooking_weight < 500) {
print "That's a lot of pasta. Having a party perhaps?";
}
else {
print "Whoa! Who are you cooking for. a football team?";
}
}
Как видно из приведенного кода, вложенные команды if позволяют лучше управлять логикой работы программы. Вскоре, с увеличением объемов и сложности ваших программ, вы убедитесь, что вложение управляющих конструкций является неоценимым приемом в арсенале программиста.
Вычисление нескольких условий
При выборе логики работы программы в управляющей структуре можно проверять комбинацию сразу нескольких условий:
print "That's a lot of pasta. Having a party perhaps?";
}
else {
print "Whoa! Who are you cooking for, a football team?";
}
Проверка сложных условий позволяет устанавливать интервальные ограничения, обеспечивающие более четкий контроль над логикой выполнения программы и уменьшающие количество лишних управляющих конструкций, в результате чего программа становится более понятной.
Switch
Принцип работы конструкции switch отчасти напоминает if — результат, полученный при вычислении выражения, проверяется по списку потенциальных совпадений.
Это особенно удобно при проверке нескольких значений, поскольку применение switch делает программу более наглядной и компактной. Общий формат команды switch:
switch (выражение) {
case (условие):
блок
case (условие):
блок
...
default:
блок
}
Проверяемое условие указывается в круглых скобках после ключевого слова switch. Результат его вычисления последовательно сравнивается с условиями в секциях case. При обнаружении совпадения выполняется блок соответствующей секции. Если совпадение не будет обнаружено, выполняется блок необязательной секции default.
Как будет показано в следующих главах, одной из сильнейших сторон РНР является обработка пользовательского ввода. Допустим, программа отображает раскрывающийся список с несколькими вариантами и каждая строка списка соответствует некоторой команде, выполняемой в отдельной конструкции case. Реализацию очень удобно построить на использовании команды switch:
$user_input = "recipes"; // Команда,выбранная пользователем
switch ($user_input) :
case("search") :
print "Let's perform a search!";
break;
case("dictionary") :
print "What word would you like to look up?";
break;
case("recipes") :
print "Here is a list of recipes...";
break;
default :
print "Here is the menu...";
break;
endswitch;
Как видно из приведенного фрагмента, команда switch обеспечивает четкую и наглядную организацию кода. Переменная, указанная в условии switch (в данном примере — $user_input), сравнивается с условиями всех последующих секций case. Если значение, указанное в секции case, совпадает Со значением сравниваемой переменной, выполняется блок этой секции. Команда break предотвращает проверку дальнейших секций case и завершает выполнение конструкции switch. Если ни одно из проверенных условий не выполняется, активизируется необязательная секция default. Если секция default отсутствует и ни одно из условий не выполняется, команда switch просто завершается и выполнение программы продолжается со следующей команды.
Вы должны помнить, что при отсутствии в секции case команды break (см. следующий раздел) выполнение switch продолжается со следующей команды до тех пор,
пока не встретится команда break или не будет достигнут конец конструкции switch. Следующий пример демонстрирует последствия отсутствия забытой команды break: $value = 0.4;
switch($value) :
case (0.4) :
print "value is 0.4 ";
case (0.6) :
print "value is 0.6 ";
break;
case (0.3) :
print "value is 0.3 ";
break;
default :
print "You didn't choose a value!";
break;
endswitch;
Результат выглядит так:
value is 0.4
value is 0.6
Отсутствие команды break привело к тому, что была выполнена не только команда print в той секции, где было найдено совпадение, но и команда print в следующей секции. Затем выполнение команд конструкции switch прервалось из-за команды switch, следующей за второй командой print.
Выбор между командами switch и if практически не влияет на быстродействие про-граммы. Решение об использовании той или иной конструкции является скорее личным делом программиста.
Управляющие конструкции
Управляющие конструкции предоставляют в распоряжение программиста средства для построения сложных программ, способных проверять условия и реагировать на изменения значений входных данных во время работы. Короче говоря, эти структуры управляют выполнением программы.
Выражения
Выражение описывает некоторое действие, выполняемое в программе. Каждое выражение состоит по крайней мере из одного операнда и одного или нескольких операторов. Прежде чем переходить к примерам, демонстрирующим использование выражений, необходимо поближе познакомиться с операторами и операндами.
While
Конструкция while предназначена для многократного (циклического) выполнения блока команд. Блок команды while выполняется до тех пор, пока условие цикла остается истинным. Общая форма цикла while выглядит так:
while (выражение) :
блок
endwhile;
Рассмотрим использование цикла while на примере вычисления факториала (n!), где n = 5:
$n = 5;
$nсору = $n;
$factorial = 1; // Установить начальное значение факториала
while ($n > 0) :
$factorial - $n * $factorial;
$n--; // Уменьшить $n на 1
endwhile;
print "The factorial of $ncopy is $factorial.";
Программа выводит следующий результат:
The factorial of 5 is 120.
В этом примере $n уменьшается в конце каждой итерации. Условие цикла не должно быть истинным в тот момент, когда переменная $n станет равна 0, поскольку величина $factorial умножится на 0 — конечно, этого быть не должно. В приведенном примере условие цикла следовало бы оптимизировать и привести его к виду $n > 1, поскольку умножать $factorial на 1 бессмысленно — число от этого не изменится. Хотя ускорение работы программы будет ничтожно малым, такие факторы всегда принимаются во внимание с ростом объема и сложности программ.
PHP 4 на практике
Что такое функция?
Функцией называется фрагмент программного кода, обладающий уникальным именем и предназначенный для решения конкретной задачи. Функция вызывается по имени в разных точках программы, что позволяет многократно выполнять фрагмент с указанным именем. Преимущество такого решения заключается в том, что блок кода пишется всего один раз, а затем легко модифицируется по мере необходимости.
Функции-переменные
Одной из интересных возможностей РНР являются функции-переменные (variable functions), то есть динамические вызовы функций, имена которых определяются во время выполнения программы. Хотя в большинстве web-приложений можно обойтись и без функций-переменных, они значительно сокращают объем и сложность программного кода, а также часто снимают необходимость в условных командах if.
Вызов функции-переменной представляет собой имя переменной, за которым следует пара круглых скобок. В круглых скобках могут перечисляться параметры (однако присутствие параметров не обязательно). Обобщенный синтаксис функции-переменной:
$имя_функции( );
Следующая программа (листинг 4.6) демонстрирует эту непривычную, но полезную возможность. Допустим, программа выводит разную информацию в зависимости от языка, выбранного пользователем. В нашем примере для простоты используются приветственные сообщения для англо- и италоязычных пользователей. Алгоритм на псевдокоде:
Создать сообщение для итальянского языка в функции с именем italian.
Создать сообщение для английского языка в функции с именем english.
Передать информацию о выбранном языке в сценарий, присвоив значение переменной $language.
Переменная $language используется для выполнения функции-переменной (в приведенном примере — italian()). Листинг 4.6. Выбор функции в зависимости от пользовательского ввода
// Приветствие на итальянском языке, function italian( ) {
" print "Benvenuti al PHP Recipes.";
}
// Приветствие на английском языке
function english( ) {
print "Welcome to PHP Recipes.";
}
// Выбрать итальянский язык
$language = "italian":
// Выполнить функцию-переменную
$language( );
Листинг 4.6 демонстрирует интересную концепцию функций-переменных и наглядно показывает, что функции-переменные способствуют уменьшению объема программного кода. Если бы не эта возможность, функцию пришлось бы выбирать командой if или switch; это привело бы к заметному увеличению объема программного кода и риску появления дополнительных ошибок при кодировании.
Определение и вызов функций
Определить новую функцию в РНР несложно. Функции могут создаваться в любой точке программ РНР, однако по соображениям структурной организации кода удобнее разместить все функции, используемые сценарием, в самом начале сценарного
файла. Существует и другой способ, заметно повышающий эффективность программирования и способствующий многократному использованию кода, — выделение функций в отдельный файл (называемый библиотекой). Библиотеки удобны тем, что их функции можно использовать в разных приложениях, не создавая лишних копий и не рискуя допустить ошибки в процессе копирования. Эта тема подробно рассматривается в разделе «Построение библиотек функций» ближе к концу главы.
Определение функции обычно состоит из трех частей:
имени функции;
круглых скобок, в которых перечисляются необязательные входные параметры, разделенные запятыми;
тела функции, заключенного в фигурные скобки.
Обобщенный синтаксис функций РНР выглядит так:
function имя_функции ([$параметр1. $параметр2, .... $параметрn]) {
тело функции
}
Имя функции должно подчиняться условиям, приведенным для идентификаторов в главе 2. После имени функции следуют обязательные круглые скобки, в которые заключается необязательный список входных параметров ($параметр1, $параметр2, .... $параметрn). Вследствие относительно либеральных принципов определения переменных в РНР указывать тип входных параметров не нужно. Хотя такой подход имеет свои преимущества, следует помнить, что механизм РНР не проверяет аргументы на соответствие тем типам, которые должны обрабатываться функцией. Случайные ошибки в использовании входных параметров могут привести к неожиданным последствиям (чтобы убедиться в том, что параметр относится к нужному типу, можно проверить его стандартной функцией gettype( )). После закрывающей круглой скобки следуют фигурные скобки, в которые заключается программный код, ассоциируемый с именем функции.
Рассмотрим простой пример использования функции. Предположим, вы хотите создать функцию для вывода лицензионной информации на web-странице:
Если ваш web- сайт состоит из нескольких страниц, достаточно вызвать эту функцию в конце каждой страницы — и вам не придется заново переписывать один и тот же текст. А когда наступит 2002 год, одно простое изменение текста, выводимого этой функцией, приведет к автоматическому обновлению всех страниц. Если бы не преимущества функционального программирования, вам пришлось бы вручную редактировать все страницы, на которых выводится лицензионная информация.
Рассмотрим разновидность функции display_copyright(), которой при вызове передается параметр. Предположим, вы отвечаете за администрирование нескольких web-сайтов, каждому из которых присвоено отдельное имя. На каждом сайте имеется собственный административный сценарий с несколькими переменными, относящимися к этому сайту; к их числу принадлежит переменная $site_name с именем
сайта. В этом случае функцию display_copyright() можно записать следующим образом:
Переменная $site_name, значение которой присваивается за пределами display_copy-right(), передается функции в качестве параметра. Переданное значение можно использовать и модифицировать в любом месте функции, однако любые изменения будут действовать лишь внутри этой функции. Впрочем, специальные ключевые слова позволяют сделать так, чтобы изменения параметров распространялись и за пределы display_copyright(). Эти ключевые слова были представлены в главе 2, в общем обзоре области видимости переменных и ее отношения к функциям.
Построение библиотек функций
Библиотеки функций — одно из самых эффективных средств экономии времени при построении приложений. Предположим, вы написали серию функций для сортировки массива. Вероятно, эти функции будут неоднократно использоваться в разных приложениях. Вместо того чтобы постоянно переписывать эти функции в новый сценарий или копировать их через текстовый буфер, гораздо удобнее разместить все функции сортировки в отдельном файле и присвоить ему легко узнаваемое имя (например, array_sorting.inc). Пример такого файла приведен в листинге 4.7.
Листинг 4.7.
Пример библиотеки функций (array_sorting.inc)
// Файл: array_sorting.inc
// Назначение: библиотека функций для сортировки массивов.
// Дата: 17 июля 2000 г.
function merge_sort($array. $tmparray, $right, $left) {
...
function bubble_sort($array. $n) {
...
}
function quicksort ($array. $right. $left) {
...
}
?>
Библиотека array_sorting.inc служит накопителем для всех функций сортировки. Это удобно, поскольку функции фактически группируются по своему назначению и при необходимости можно легко найти нужную функцию. Как видно из листинга 4.7, в начало библиотеки обычно включается заголовок из нескольких строк комментария, чтобы при открытии файла библиотеки можно было сразу получить краткую сводку его содержимого. После собственной библиотеки функций можно включить ее в сценарий при помощи команд РНР include( ) и require( ), в результате чего все функции библиотеки становятся доступными. В общем виде синтаксис этих команд выглядит так:
include(путь/имя_файла);
require(путь/имя_файла);
Также существует альтернативный вариант:
include "путь/имя_файла";
require "путь/имя_файла";
где путь определяет относительный или абсолютный путь к файлу. Конструкции include( ) и requirе( ) подробно описаны в главе 9. А пока достаточно запомнить, что эти конструкции используются для включения файла непосредственно в сценарий.
Предположим, вы хотите воспользоваться функциями библиотеки array_sorting.inc в сценарии. Пример включения библиотеки показан в листинге 4.8.
Листинг 4.8.
Включение библиотечного файла (array_sorting.inc) в сценарий
// Предполагается, что библиотека array_sorting.inc
// находится в одном каталоге со сценарием.
include("array_sorting.inc");
// Теперь вы можете использовать любые функции из array_sorting.inc
$some_array = array (50, 42. 35, 46);
// Использовать функцию bubble_sort()
$sorted_array = bubble_sort($some_array, 1);
Рекурсивные функции
Ситуация, при которой функция многократно вызывает сама себя, пока не будет выполнено некоторое условие, открывает замечательные возможности. При правильном использовании рекурсивные функции уменьшают объем программы и делают ее более выразительной. Рекурсивные функции особенно часто используются при выполнении повторяющихся действий — например, при поиске в файлах/массивах и построении графических изображений (например, фракталов). Классическим примером рекурсивных функций, встречающимся во многих курсах программирования, является суммирование чисел от 1 до N. Программа, приведенная в листинге 4.5, суммирует все целые числа от 1 до 10.
Листинг 4.5.
Использование рекурсивной функции для суммирования последовательных целых чисел
function summation ($count) {
if ($count != 0) :
return $count + summation($count-1);
endif;
}
$sum = summation(10);
print "Summation = $sum";
В результате выполнения листинга 4.5 будет выведен следующий результат:
Summation = 55
Если функция вызывается достаточно часто, рекурсия делает программу более эффективной. Тем не менее, при использовании рекурсии необходима осторожность, поскольку ошибки могут привести к зацикливанию программы.
Обратите внимание: функцию display_copyright( ) можно вызвать и за пределами display_footer( ) по аналогии стем, как функция display_footer( ) использовалась в предыдущем примере. Концепция защищенных функций в РНР не поддерживается.
Хотя вложенные функции не защищены от вызова из других точек сценария, они не могут вызываться до вызова своей родительской функции. При попытке вызвать вложенную функцию раньше вызова родительской функции выводится сообщение об ошибке.
Возврат значений из функции
По завершении работы функции часто бывает полезно вернуть некоторое значение, для чего результат вызова функции обычно присваивается некоторой переменной. Функции могут возвращать значения любых типов, в том числе массивы и списки. Пример приведен в листинге 4.2, где функция calculate_cost( ) вычисляет налог с заданной суммы и возвращает общую сумму вместе с налогом. Прежде чем переходить к рассмотрению листинга, просмотрите краткое описание алгоритма на псевдокоде:
Перед вызовом функции задать значения переменных: $price (цена товара) и $tax (налоговая ставка).
Объявить функцию calculate_cost( ). При вызове функция получает два параметра: налоговую ставку и цену товара.
Вычислить цену с учетом налога и вернуть ее командой return.
Вызвать calculate_cost() и присвоить значение, возвращенное функцией, переменной $total_cost.
Вывести соответствующее сообщение.
Листинг 4.2.
Создание функции для вычисления налога
$price = 24.99; $tax = .06;
function calculate_cost($tax, $price) {
$sales_tax = $tax;
return $price + ($price * $sales_tax);
}
// Обратите внимание на возврат значения функцией calculate_cost(). $total_cost = calculate_cost ($tax. $price);
// Округлить цену до двух десятичных цифр.
$total_cost = round($total_cost. 2);
print "Total cost: $".$total_cost;
// $total cost = 26.49 Функции, не возвращающие значений, также называются процедурами.
Существует и другой способ использования возвращаемых значений, при котором вызов функции включается прямо в условную/циклическую команду. В следующей программе (листинг 4.3) сумма счета пользователя сравнивается с предельным размером кредита. Алгоритм на псевдокоде выглядит так:
Объявить функцию check_limit( ), которая при вызове получает два параметра. Первый параметр, $total_cost, определяет общую сумму счета, накопленную пользователем до настоящего момента. Второй параметр, $credit_limit, определяет максимальную сумму, которую может потратить пользователь.
Если накопленная сумма счета превышает предельный размер кредита, функция возвращает ложное значение (0).
Если условие команды i f оказывается ложным, работа функции еще не завершена. В этом случае общая сумма не превышает предельного размера кредита, поэтому функция должна вернуть логическую истину.
Вызвать функцию check_limit( ) в условии команды if. Проверить, какое значение было возвращено при вызове — истинное или ложное. В зависимости от результата проверки выполняется то или иное действие.
Если при вызове check_limit( ) было получено значение TRUE, мы предлагаем пользователю продолжить закупку. В противном случае пользователь информируется о превышении кредита.
Листинг 4.3.
Сравнение текущей суммы счета пользователя с предельным размером кредита
$cost = 1456.22;
$limit = 1000.00;
function check_limit($total_cost. $credit_limit)
if ($total_cost > $credit_limit) :
return 0;
endif;
return 1;
}
if (check_limit($cost. $limit)) :
// Продолжить закупки
print "Keep shopping!";
else :
print "Please lower your total bill to less than $".$limit."!";
endif;
При выполнении листинга 4.3 будет выведено сообщение об ошибке, поскольку значение $cost превышает $limit.
Функция также может возвращать сразу несколько значений при помощи списка. Продолжая кулинарную тему, давайте напишем функцию, которая бы возвращала три лучших года для указанного сорта вина. Функция приведена в листинге 4.4, но сначала прочитайте алгоритм на псевдокоде:
Объявить функцию best_years( ), вызываемую с одним параметром. Параметр $label определяет сорт вина, для которого пользователь хотел бы узнать три рекомендуемых года.
Объявить два массива, $merlot и $zinfandel. В каждом массиве хранится три рекомендуемых года для соответствующего сорта вина.
Написать команду return, которая бы использовала особые возможности переменных. Выражение $$label сначала интерпретирует переменную $label, а затем интерпретирует полученное значение как имя другой переменной. В настоящем примере массив merlot возвращается в виде списка, и каждый возвращаемый год занимает свою позицию в списке, для которого вызывалась функция.
Вывести сообщение с информацией о рекомендуемых годах.
Листинг 4.4. Возвращение функцией нескольких величин
// Сорт вина, для которого выводятся лучшие годы
$label = "merlot";
// Функция использует массивы и "переменную в переменной"
// для возвращения нескольких значений.
function best_years($label) {
$merlot = array("1987", "1983", "1977");
$zinfandel = array("1992", "1990", "1989");
return $$label;
}
// Функция list( ) используется получения возвращаемых значений.
list ($yr_one, $yr_two. $yr_three) = best_years($label);
print "$label had three particularly remarkable years: $yr_one. $yr_two, and $yr_three.";
Программа выводит следующий результат:
merlot has three particularly remarkable years: 1987, 1983 and 1977.
PHP 4 на практике
Добавление и удаление элементов
К счастью, в РНР при создании массива не нужно указывать максимальное количество элементов. Это увеличивает свободу действий при операциях с массивами, поскольку вам не приходится беспокоиться о случайном выходе за границы массива, если количество элементов превысит ожидаемый порог. В РНР существует несколько функций для увеличения размеров массива. Некоторые из них были созданы для удобства программистов, привыкших работать с различными типами очередей и стеков (FIFO, FILO и т. д.), что отражается в названиях функций (push, pop, shift и unshift). Но даже если вы не знаете, что такое «очередь» или «стек», не огорчайтесь — в этих функциях нет ничего сложного. Очередью (queue) называется структура данных, из которой элементы извлекаются в порядке поступления. Стеком (stack) называется структура данных, из которой элементы извлекаются в порядке, обратном порядку их поступления.
array_push( )
Функция array_push( ) присоединяет (то есть дописывает в конец массива) один или несколько новых элементов. Синтаксис функции array_push( ):
int array_push(array массив, mixed элемент [, ...])
Длина массива возрастает прямо пропорционально количеству его элементов. Это продемонстрировано в следующем примере:
$languages = array("Spanish", "English", "French");
array_push($languages, "Russian", "German", "Gaelic");
// $languages = array("Spanish", "English", "French",
// "Russian", "German", "Gaelic")
У функции array_push( ), как и у многих стандартных функций РНР, существует «двойник» — функция аrrау_рор( ), предназначенная для извлечения элементов из массива. Главное различие между этими функциями заключается в том, что array_push( ) может добавлять несколько элементов одновременно, а аrrау_рор( ) удаляет элементы только по одному.
аrrау_рор( )
Результат работы функции аrrау_рор( ) прямо противоположен array_push( ) — эта функция извлекает (то есть удаляет) последний элемент из массива. Извлеченный элемент возвращается функцией. Синтаксис функции аrrау_рор( ):
аrrау_рор(аrrау массив)
При каждом выполнении аrrау_рор( ) размер массива уменьшается на 1. Рассмотрим пример:
Функции array_push( ), и array_pop( ) удобны тем, что с их помощью можно выполнять операции с элементами и управлять размером массива, не беспокоясь о неинициализированных или пустых элементах. Такое решение работает намного эффективнее, чем любые попытки управления этими факторами со стороны программиста.
array_shift( )
Функция array_shift( ) аналогична аrrау_рор( ) с одним отличием: элемент удаляется из начала (левого края) массива. Все остальные элементы массива сдвигаются на одну позицию к началу массива. У функции array_shift( ) такой же синтаксис, как и у аггау_рор( ):
array_shift(array массив)
При работе с функцией array_shift( ) необходимо помнить, что элементы удаляются из начала массива, как показывает следующий пример:
Функция array_unshift( ) дополняет array_shift( ) — новый элемент вставляется в начало массива, а остальные элементы сдвигаются на одну позицию вправо. Синтаксис команды array_unshift( ):
При одном вызове функции можно добавить как один, так и несколько элементов, при этом размер массива возрастает пропорционально количеству добавленных элементов. Пример добавления нескольких элементов:
Функция array_pad( ) позволяет быстро увеличить массив до желаемого размера посредством его дополнения стандартными элементами. Синтаксис функции array_pad( ):
array arrap_pad(array массив, int размер, mixed значение):
Параметр размер определяет новую длину массива. Параметр значение задает стандартное значение, присваиваемое элементам во всех новых позициях массива. При использовании array_pad( ) необходимо учитывать некоторые обстоятельства:
Если размер положителен, массив дополняется справа, а если отрицателен — слева.
Если абсолютное значение параметра размер меньше либо равно длине массива, никаких действий не выполняется.
Абсолютным значением (модулем) целого числа называется его значение без знака. Например, абсолютное значение чисел 5 и -5 равно 5.
В этом разделе описаны некоторые функции, которые не принадлежат ни к какому конкретному разделу, но приносят несомненную пользу.
array_merge( )
Функция arrayjnerge( ) сливает от 1 до N массивов, объединяя их в соответствии с порядком перечисления в параметрах. Синтаксис функции array_merge( ):
array array_merge(array массив1, array массив2, ..., array массивN]
Рассмотрим пример простого объединения массивов функцией arrayjnerge( );
$arr_1 = array("strawberry", "grape", "lemon");
$arr_2 = array("banana", "cocoa", "lime");
$arr_3 = array("peach", "orange");
$arr_4 = array_merge ($arr2, $arr_1, $arr_3):
// $arr_4 = array("banana", "cocoa", "lime", "strawberry", "grape", "lemon", "peach", "orange");
array_slice( )
Функция array_slice( ) возвращает часть массива, начальная и конечная позиция которой определяется смещением от начала и необязательным параметром длины. Синтаксис функции array_slice( ):
array array_slice(array массив, int смещение [, int длина])
Значения параметров задаются по определенным правилам:
Если смещение положительно, начальная позиция возвращаемого фрагмента отсчитывается от начала массива.
Если смещение отрицательно, начальная позиция возвращаемого фрагмента отсчитывается от конца массива.
Если длина не указана, в возвращаемый массив включаются все элементы от начальной позиции до конца массива.
Если указана положительная длина, возвращаемый фрагмент состоит из заданного количества элементов.
Если указана отрицательная длина, возвращаемый фрагмент заканчивается в заданном количестве элементов от конца массива.
array_splice( )
Функция array_spl ice( ) отдаленно напоминает array_slice( ) — она заменяет часть массива, определяемую начальной позицией и необязательной длиной, элементами необязательного параметра-массива. Синтаксис функции array_splice( ):
array_splice(array входной_массив, int смещение, [int длина], [array заменяющий_массив]);
Значения параметров задаются по определенным правилам:
Если смещение положительно, начальная позиция первого удаляемого элемента отсчитывается от начала массива.
Если смещение отрицательно, начальная позиция первого удаляемого элемента отсчитывается от конца массива.
Если длина не указана, удаляются все элементы от начальной позиции до конца массива.
Если указана положительная длина, удаляемый фрагмент состоит из заданного количества элементов.
Если указана отрицательная длина, из массива удаляются элементы от начальной позиции до позиции, находящейся на заданном расстоянии от конца массива.
Если заменяющий_массив не указан, то элементы, заданные смещением и необязательной длиной, удаляются из массива.
Если заменяющий_массив указан, он должен быть заключен в конструкцию аггау() (если он содержит более одного элемента).
Рассмотрим несколько примеров, наглядно демонстрирующих возможности этой функции. В этих примерах будет использоваться массив $pasta (см. выше), с которым будут выполняться различные операции.
Удаление всех элементов с пятой позиции до конца массива:
$pasta = array_splice($pasta, 5);
Удаление пятого и шестого элементов:
$pasta = array_splice($pasta. 5, 2);
Замена пятого и шестого элементов новыми значениями:
Удаление всех элементов, начиная с пятого, до третьего элемента с конца массива:
$pasta = array_splice($pasta, 5, -3);
Как видно из приведенных примеров, функция array_splice( ) обеспечивает гибкие возможности удаления элементов из массива при минимальном объеме кода.
shuffle( )
Функция shuffle( ) сортирует элементы массива в случайном порядке. Синтаксис функции shuffle( ):
void shuffle(array массив);
Многомерные массивы
Со временем ваши программы станут более сложными, и возможностей простых одномерных массивов окажется недостаточно для хранения необходимой информации. Многомерный массив (массив массивов) предоставляет в распоряжение программиста более эффективные средства для хранения информации, требующей дополнительного структурирования. Создать многомерный массив несложно — просто добавьте дополнительную пару квадратных скобок, чтобы вывести массив в новое измерение:
$chessboard [1] [4] = "King"; // Двухмерный массив
$capitals["USA"] ["Ohio"] = "Columbus": // Двухмерный массив
$streets["USA"]["Ohio"]["Columbus"] = "Harrison"; // Трехмерный массив
В качестве примера рассмотрим массив, в котором хранится информация о десертах и особенностях их приготовления. Обойтись одномерным массивом было бы довольно трудно, но двухмерный массив подходит как нельзя лучше:
$desserts = аrrау(
"Fruit Cup" => array (
"calories" => "low",
"served" -> "cold",
"preparation" => "10 minutes"
),
"Brownies" => array (
"calories" -> "high",
"served" => "piping hot",
"preparation" => "45 minutes"
)
);
После создания массива к его элементам можно обращаться по соответствующим ключам:
$desserts["Fruit Cup"]["preparation"] // возвращает "10 minutes"
$desserts["Brownies"]["calories"] // возвращает "high"
Присваивание значений элементам многомерных массивов выполняется так же, как и в одномерных массивах:
$desserts["Cake"]["calories"] = "too many";
// Присваивает свойству "calories" объекта "Cake" значение "too many"
Хотя в многомерных массивах появляются новые уровни логической организации данных, многомерные массивы создаются практически так же, как и одномерные. Впрочем, ссылки на многомерные массивы в строках требуют особого внимания; этой теме посвящен следующий раздел.
Перебор элементов
В РНР существует несколько стандартных функций, предназначенных для перебора элементов массива. В совокупности эти функции обеспечивают гибкие и удобные средства для быстрой обработки и вывода содержимого массивов. Вероятно, вы будете часто использовать эти функции, поскольку они лежат в основе практически всех алгоритмов работы с массивами.
reset( )
Функция reset( ) переводит внутренний указатель текущей позиции в массиве к первому элементу. Кроме того, она возвращает значение первого элемента. Синтаксис функции reset( ):
mixed reset (array массив)
Рассмотрим следующий массив:
$fruits = array("apple", "orange", "banana");
Допустим, указатель текущей позиции в этом массиве установлен на элемент "orange". Команда:
$a_fruit = reset($fruits);
вернет указатель в начало массива, то есть на элемент "apple", и вернет это значение, если результат вызова reset( ) используется в программе. Возможен и упрощенный вариант вызова:
reset($fruits);
В этом случае указатель переходит к первому элементу массива, а возвращаемое значение не используется.
each ( )
Функция each( ) при каждом вызове выполняет две операции: она возвращает пару «ключ/значение», на которую ссылается указатель текущей позиции, и перемещает указатель к следующему элементу. Синтаксис функции each( ):
array each (array массив)
Для удобства each ( ) возвращает ключ и значение в виде массива из четырех элементов; ключами этого массива являются 0, 1, value и key. Возвращаемый ключ ассоциируется с ключами 0 и key, а возвращаемое значение — с ключами 1 и value.
В следующем примере функция each ( ) возвращает элемент, находящийся в текущей позиции:
// Объявить массив из пяти элементов
$spices = array("parsley", "sage", "rosemary", "thyme", "pepper");
// Установить указатель на первый элемент массива
reset($spices);
// Создать массив $a_sp1ce. состоящий из четырех элементов
$a_spice = each($spices);
В результате выполнения приведенного фрагмента массив $a_spice будет содержать следующие пары «ключ/значение»:
0 => 0;
1 => "parsley";
key => 0;
value => "parsley".
После этого строку "parsley" можно вывести любой из следующих команд:
print $a_spice[1]: print $a_spice["value"];
Функция each() обычно используется в сочетании с list( ) в циклических конструкциях для перебора всех или некоторых элементов массива. При каждой итерации each( ) возвращает либо следующую пару «ключ/значение», либо логическую ложь при достижении последнего элемента массива. Вернемся к массиву $spices; чтобы вывести все элементы на экран, можно воспользоваться следующим сценарием:
// Сбросить указатель текущей позиции
reset($spices);
// Перебрать пары "ключ/значение", ограничиваясь выводом значения
while (list ($key, $val) = each ($spices) ) :
print "$val "
endwhile;
Ниже приведен более интересный пример использования each( ) в сочетании с другими функциями, описанными в этой главе. Листинг 5.1 показывает, как при помощи этих функций вывести отформатированную таблицу стран и языков.
Листинг 5.1.
Построение таблицы HTML по содержимому массива
// Объявить ассоциативный массив стран и языков $languages = array ("Country" => "Language",
"Spain" => "Spanish",
"USA" => "English",
"France" => "French",
"Russia" => "Russian");
// Начать новую таблицу
print "
";
// Переместить указатель к позиции первого элемента
reset ($languages);
// Прочитать первый ключ и элемент
$hdl = key ($languages);
Shd2 = $languages[$hd1];
// Вывести первый ключ и элемент в виде заголовков таблицы
print "
$hd1
$hd2
";
next($languages);
// Выводить строки таблицы с ключами и элементами массива
while (list ($ctry,$lang) = each ($languages)) :
print "
Sctry
$lang
";
endwhile;
// Завершить таблицу print "
";
?>
В результате выполнения этого кода будет построена следующая таблица HTML.
Country
Language
Spain
Spanish
USA
English
France
French
Russia
Russian
Этот пример дает представление о самой сильной стороне РНР — возможности объединения динамического кода с HTML для получения наглядного, отформатированного представления прочитанных данных.
end( )
Функция end( ) перемещает указатель к позиции последнего элемента массива. Синтаксис функции end( ):
end (array массив)
next( )
Функция next ( ) смещает указатель на одну позицию вперед, после чего возвращает элемент, находящийся в новой позиции. Если в результате смещения
указатель выйдет за пределы массива, next ( ) возвращает ложное значение. Синтаксис функции next ( ):
mixed next (array массив)
Недостаток функции next ( ) заключается в том, что ложное значение возвращается и для существующих, но пустых элементов массива. Если вы хотите провести обычный перебор, воспользуйтесь функцией each( ).
prev( )
Функция prev( ) аналогична next ( ) за одним исключением: указатель смещается на одну позицию к началу массива, после чего возвращается элемент, находящийся в новой позиции. Если в результате смещения указатель окажется перед первым элементом массива, prev( ) вернет ложное значение. Синтаксис функции prev( ):
mixed prev (array массив)
Недостаток функции prev( ) заключается в том, что ложное значение возвращается и для существующих, но пустых элементов массива. Если вы хотите провести обычный перебор, воспользуйтесь функцией each( ).
array_walk( )
Функция array_walk( ) позволяет применить функцию к нескольким (а возможно, всем) элементам массива. Синтаксис функции array_walk( ):
int array_walk(array массив, string имя_функции [, mixed данные])
Функция, заданная параметром имя_функции, может использоваться для различных целей — например, для поиска элементов с определенными характеристиками или модификации содержимого массива. В ассоциативных массивах функция имя_функции должна получать минимум два параметра — элемент массива и ключ. Если указан необязательный третий параметр данные, он становится третьим параметром. В следующем примере функция array_walk( ) используется для удаления дубликатов из массива:
Помните: функция array_flip( ) не изменяет порядок элементов массива. Для этой цели используется функция array_reverse( ).
Поиск элементов массива
Поиск элементов относится к числу важнейших операций с массивами. В РНР существует несколько стандартных функций, позволяющих легко находить в массиве нужные ключи и значения.
in_array( )
Функция i n_array ( ) проверяет, присутствует ли в массиве заданный элемент. Если поиск окажется удачным, функция возвращает TRUE, в противном случае возвращается FALSE. Синтаксис функции in_array( ):
bool in_array(mixed элемент, array массив)
Эта функция особенно удобна тем, что вам не приходится в цикле перебирать весь массив в поисках нужного элемента. В следующем примере функция in_array( ) ищет элемент "Russian" в массиве $languages:
$languages = array("English", "Gaelic", "Spanish"):
$exists = in_array("Russian", $languages); // $exists присваивается FALSE
$exists = in_array("English", $languages): // $exists присваивается TRUE
Функция in_array( ) часто встречается в управляющих конструкциях, когда ее возвращаемое значение (TRUE/FALSE) используется для выбора одного из двух вариантов продолжения. В следующем примере функция in_array( ) используется для выбора одного из двух вариантов в условной команде if:
// Ввод данных пользователем
$language = "French"; $email = "wjgilmore@hotmail.com";
// Если язык присутствует в массиве
if (in_array($language. $languages)) :
// Подписать пользователя на бюллетень.
// Обратите внимание: в РНР нет стандартной функции с именем
// subscribe_user(). В данном примере эта функция просто имитирует
// процесс подписки.
subscribe_user($email, $language);
print "You are now subscribed to the $language edition of the newsletter.";
// Язык отсутствует в массиве
else :
print "We're sorry, but we don't yet offer a $language edition of the newsletter".
endif;
Что происходит в этом примере? Допустим, переменные $language и $email содержат данные, введенные пользователем. Вы хотите убедиться в том, что указанный язык поддерживается вашей системой, и используете для этой цели функцию in_array( ). Если название языка присутствует в массиве, пользователь подписывается на бюллетень и получает соответствующее сообщение. В противном случае программа сообщает, что на указанном языке бюллетень не распространяется. Конечно, в настоящей программе пользователь не должен гадать, какие языки поддерживаются вашей программой. Задача решается при помощи раскрывающегося списка — эта тема подробно рассматривается в главе 10. Здесь этот пример всего лишь демонстрирует возможности работы с массивами.
array_keys( )
Функция array_keys( ) возвращает массив, содержащий все ключи исходного массива, переданного в качестве параметра. Если при вызове передается дополнительный параметр искомый_элемент, возвращаются только ключи, которым соответствует заданное значение; в противном случае возвращаются все ключи массива. Синтаксис функции array_keys( ):
Функция array_keys( ) позволяет очень легко получить все ключи ассоциативного массива — например, в предыдущем случае ими были названия стран, в которых производятся различные сорта вин.
array_values( )
Функция array_values( ) возвращает массив, состоящий из всех значений исходного массива, переданного в качестве параметра. Синтаксис функции array_values( ):
array array_values(array массив)
Вернемся к предыдущему примеру, в котором функция array_keys( ) использовалась для получения всех значений ключей. На этот раз функция array_values( ) возвращает все значения, соответствующие ключам:
Функции array_keys( ) и array_values( ) дополняют друг друга, позволяя при необходимости получить все составляющие той или иной стороны ассоциативного массива.
Размер массива
Наличие информации о текущем размере массива часто позволяет повысить эффективность сценария. Вероятно, размер массива чаще всего используется при циклическом переборе элементов:
$us_wine_producers = array ("Washington". "New York", "Oregon", "California");
for (Si = 0; Si < sizeof ($us_wine_producers); $i++) :
print "$us_wine_producers[$i]";
endfor;
Поскольку массив $us_wine_producers индексируется целыми числами, мы можем воспользоваться циклом for для циклического увеличения переменной-счетчика ($i) и вывода каждого элемента в массиве.
sizeof( )
Функция sizeof ( ) возвращает количество элементов в массиве. Синтаксис функции sizeof ( ):
int sizeof (array массив)
Вероятно, функция sizeof ( ) будет часто встречаться в ваших web-приложениях. Ниже приведен краткий пример ее использования (кстати, предыдущий пример тоже относится к числу стандартных применений sizeof ( )):
$pasta = array("bowties", "angelhair", "rigatoni");
$pasta_size = sizeof($pasta);
// $pasta_size = 3
У функции sizeof ( ) существует и другая, расширенная форма — count ( ) (см. ниже).
count( )
Функция count( ) выполняет ту же операцию, что и sizeof ( ), — она возвращает количество значений, содержащихся в массиве. Синтаксис функции count ( ):
int count (mixed переменная)
Единственное различие между sizeof ( ) и count( ) заключается в том, что в некоторых ситуациях count ( ) возвращает дополнительную информацию:
если переменная существует и является массивом, count ( ) возвращает количество элементов в массиве;
если переменная существует, но не является массивом, функция возвращает значение 1;
если переменная не существует, возвращается значение 0.
array_count_values( )
Функция array_count_values( ) является разновидностью sizeof ( ) и count ( ). Вместо общего количества элементов она подсчитывает количество экземпляров каждого значения в массиве. Синтаксис функции array_count_values( ):
array array_count_values(array массив):
В возвращаемом массиве ключами будут значения исходного массива, а значениями — их частоты:
$states = аrrау("ОН", "ОК", "СА", "РА", "ОН", "ОН", "РА", "АК");
$state_freq = array_count_values($states);
Массив $state_freq заполняется следующими ассоциативными парами «ключ/значение»:
$state_freq = аrrау("ОН" => 3, "ОК" => 1, "СА" => 1, "РА" => 2, "АК" => 1);
Сортировка массивов
Сортировка занимает важное место в программировании и часто встречается на практике в таких Интернет-приложениях, как коммерческие сайты (сортировка категорий товаров в алфавитном порядке, сортировка цен) или поисковые системы (сортировка программ по количеству загрузок). В РНР существует девять стандартных функций сортировки (табл. 5.1), причем каждая функция сортирует массив особым образом. Таблица 5.1. Функции сортировки
Функция
Сортировка
Обратный порядок
Сохранение пар «ключ/значение»
sort
Значение
Нет
Нет
rsort
Значение
Да
Нет
asort
Значение
Нет
Да
arsort
Значение
Да
Да
ksort
Ключ
Нет
Да
krsort
Ключ
Да
Да
usort
Значение
?
Нет
uasort
Значение
?
Да
uksort
Ключ
?
Да
? относится к применению пользовательских функций сортировки, когда порядок сортировки массива зависит от результатов, возвращаемых пользовательской функцией.
Сортировка элементов массива не ограничивается стандартными критериями, поскольку три функции (usort(), uasort( ) и uksort( )) позволяют задать пользовательский критерий и отсортировать информацию произвольным образом.
sort( )
Простейшая функция sort( ) сортирует элементы массива по возрастанию (от меньших к большим). Синтаксис функции sort ( ):
void sort (array массив)
Нечисловые элементы сортируются в алфавитном порядке в соответствии с ASCII-кодами. Следующий пример демонстрирует применение функции sort( ) при сортировке:
// Создать массив городов.
$cities = array("Aprilia", "Nettuno", "Roma", "Venezia", "Anzio");
// Отсортировать города по возрастанию
sort($cities);
// Перебрать содержимое массива и вывести все пары "ключ/значение".
for (reset($cities); $key = key ($cities); next ($cities)):
print("cities[$key] = $cities[$key] ";
endfor;
Этот фрагмент выводит следующий результат:
cities[0] = Anzio
cities[1] = Aprilia
cities[2] = Nettuno
cities[3] = Roma
cities[4] = Venezia
Как видите, массив $cities сортируется в алфавитном порядке. Одна из разновидностей этого способа сортировки реализована в функции asort( ), описанной ниже.
rsort( )
Функция rsort ( ) работает точно так же, как функция sort ( ), за одним исключением: элементы массива сортируются в обратном порядке. Синтаксис функции rsort ( ):
void rsort (array массив)
Вернемся к массиву $cities из предыдущего примера:
В результате сортировки массива $cities функцией rsort( ) элементы будут расположены в следующем порядке:
cities[0] = Venezia
cities[1] = Roma
cities[2] = Nettuno
cities[3] = Aprilia
cities[4] = Anzio
Массив $cities также сортируется, но на этот раз в порядке, обратном алфавитному. Одна из разновидностей этого способа сортировки реализована в функции arsort( ), описанной ниже.
asort( )
Функция asort( ) работает почти так же, как упоминавшаяся выше функция sort( ), однако она сохраняет исходную ассоциацию индексов с элементами независимо от нового порядка элементов. Синтаксис функции asort( ):
В результате сортировки массива $cities функцией rsort() элементы будут расположены в следующем порядке:
cities[4] = Anzio
cities[0] = Aprilia
cities[1] = Nettuno
cities[2] = Roma
cities[3] = Venezia
Обратите внимание на индексы и сравните их с приведенными в описании функции sort ( ). Именно в этом и состоит различие между двумя функциями.
arsort( )
Функция arsort ( ) представляет собой разновидность asort( ), которая сохраняет исходную ассоциацию индексов, но сортирует элементы в обратном порядке. Синтаксис функции arsort( ):
void arsort (array
массив)
Воспользуемся функцией arsort( ) для сортировки массива $cities:
В результате сортировки элементы будут расположены в следующем порядке:
"Italy" => "Tuscany"
"France" => "Loire"
"Chile" => "Rapel Valley"
"Australia" => "Ruthgerlen"
"America" => "Napa Valley"
Вероятно, описанных выше функций сортировки будет вполне достаточно для большинства случаев. Тем не менее, в некоторых ситуациях может возникнуть необходимость в определении собственных критериев сортировки. В РНР такая возможность реализована в трех стандартных функциях: usort( ), uasort( ) и uksort( ).
usort( )
Функция usort( ) дает возможность отсортировать массив на основании критерия, определяемого программистом. Для этого usort( ) в качестве параметра передается имя функции, определяющей порядок сортировки. Синтаксис функции usort( ):
void usort (array массив, string имя_функции)
В параметре массив передается имя сортируемого массива, а в параметре имя_функции — имя функции, на основании которой будет осуществляться сортировка. Допустим, у вас имеется длинный список греческих имен, которые необходимо выучить к предстоящему экзамену по истории. Вы хотите отсортировать слова по длине, чтобы начать с самых длинных, а затем учить короткие, когда вы уже устанете. Для сортировки массива по длине можно воспользоваться функцией usort( ).
Листинг 5.2.
Определение критерия сортировки для функции usort( )
// Вызвать usort() с указанием функции compare_length()
// в качестве критерия сортировки
usort ($vocab, "compare_length") :
// Вывести отсортированный список
while (list ($key, $val) = each ($vocab)) {
echo "$val ";
}
В листинге 5.2 функция compare_length ( ) определяет критерий сортировки массива. В данном случае это делается посредством сравнения длины передаваемых элементов. Функция-критерий должна получать два параметра, представляющих
сравниваемые элементы массива. Также обратите внимание на то, как эти элементы неявно передаются функции-критерию при вызове usort( ), и на то, что все элементы автоматически сравниваются этой функцией.
Функции uasort( ) и uksort( ) представляют собой разновидности usort( ) с тем же синтаксисом. Функция uasort() сортирует массив по пользовательскому критерию с сохранением ассоциаций «ключ/значение». Функция uksort( ) также сортирует массив по пользовательскому критерию, однако сортируются не значения, а ключи.
Создание массивов
Массив представляет собой совокупность объектов, имеющих одинаковые размер и тип. Каждый объект в массиве называется элементом массива. Создать новый массив в РНР несложно. При объявлении индексируемого массива после имени переменной ставится пара квадратных скобок ([ ]):
$languages [ ] = "Spanish";
// $languages[0] = "Spanish"
После этого в массив можно добавлять новые элементы, как показано ниже. Обратите внимание: новые элементы добавляются без явного указания индекса. В этом случае новый элемент добавляется в позицию, равную длине массива плюс 1:
$languages[ ] = "English"; // $1anguages[l] = "English";
$languagest ] = "Gaelic"; // $languages[2] = "Gaelic";
Кроме того, новые элементы можно добавлять в конкретную позицию массива. Для этого указывается индекс нрвого элемента:
$languages[15] = "Italian";
$languages[22] = "French";
Ассоциативные массивы создаются аналогичным образом:
$languages["Spain"] = "Spanish";
$languages["France"] = "French";
При создании массивов используются три стандартные языковые конструкции:
аrrау( );
list( );
range( ).
Хотя все три случая приводят к одному результату — созданию массива, в некоторых случаях одна конструкция может оказаться предпочтительнее других. Далее приведены описания и примеры использования каждой конструкции.
аггау( )
Функция array( ) получает ноль или более элементов и возвращает массив, состоящий из указанных элементов. Ее синтаксис:
array array ( [элемент1, элемент2...] )
Вероятно, array( ) является всего лишь более наглядной записью для создания массива, используемой для удобства программиста. Ниже показан пример использования array( ) для создания индексируемого массива:
$languages = array ("English". "Gaelic". "Spanish");
// $languages[0] = "English". $languages[1] = "Gaelic",
// $languages[2] = "Spanish"
А вот как array( ) используется при создании ассоциативных массивов:
$languages = array("Spain" => "Spanish",
"Ireland" => "Gaelic".
"United States" => "English");
// $languages["Spain"] = "Spanish"
// $languages["Ireland"] = "Gaelic"
// $languages["United States"] = "English"
Ассоциативные массивы особенно удобны в тех ситуациях, когда числовые индексы не имеют логического соответствия. Например, в предыдущем примере названия стран вполне естественно ассоциируются с языками. Попробуйте-ка воплотить эту логическую связь при помощи цифр!
list( )
Конструкция list( ) похожа на аrrау( ), однако ее главная задача — одновременное присваивание значений, извлеченных из массива, сразу нескольким переменным. Синтаксис команды list( ):
void list (переменная1 [. переменная2 , ...] )
Конструкция list() особенно удобна при чтении информации из базы данных или файла. Допустим, вы хотите отформатировать и вывести данные, прочитанные из текстового файла. Каждая строка файла содержит сведения о пользователе (имя,
профессия и любимый цвет); компоненты записи разделяются вертикальной чертой (|). Типичная строка выглядит так:
Nino Sanzi|Professional Golfer|green
При помощи list ( ) можно написать простой цикл, который будет читать каждую строку, присваивать ее компоненты переменным, форматировать и отображать данные. Приведенный ниже листинг демонстрирует возможность одновременного присваивания нескольким переменным с использованием list ( ):
// Читать строки, пока не будет достигнут конец файла
while ($line = fgets ($user_file. 4096)) :
// Разделить строку функцией split( ).
// Компоненты присваиваются переменным Sname. $occupation и Scolor.
list ($name, $occupation, $color) = split( "|", $line);
// Отформатировать и вывести данные
print "Name: Sname ";
print "Occupation: Soccupation ";
print "Favorite color: Scolor ";
endwhile;
Каждая строка файла читается, форматируется и выводится в следующем виде:
Name: Nino Sanzi
Occupation: Professional Golfer
Favorite color: green
В приведенном примере применение list( ) зависит от разделения строки на элементы функцией split( ). Элементы, полученные в результате деления, присваиваются, соответственно, переменным $name, $occupation и $color. Дальше все сводится к форматированию данных для вывода в конкретном браузере. Удобные средства лексического анализа текстовых файлов являются одной из сильных сторон РНР. Эта тема подробно рассматривается в главах 7 и 8.
range ( )
Конструкция range( ) позволяет легко и быстро создать массив целых чисел из интервала, определяемого верхней и нижней границами. Range( ) возвращает массив, состоящий из всех целых чисел указанного интервала. Синтаксис range( ):
array range (int нижняя_граница, int верхняя граница)
Следующий пример наглядно показывает, насколько удобна эта конструкция:
$lottery = range(0,9);
// $lottery = array(0,1,2,3,4,5,6,7,8,9)
Как видно из приведенного фрагмента, в параметрах range( ) был указан интервал от 0 до 9 и массив $lottery был заполнен целыми числами из этого интервала.
Ссылки на многомерные массивы
Ссылки на элементы многомерных массивов внутри строк несколько отличаются от ссылок на другие типы данных. Возможны два варианта. Во-первых, можно воспользоваться оператором конкатенации:
print "Brownies are good, but the calories content is ".
$desserts["Brownies"]["calories"];
Во-вторых, ссылку на элемент многомерного массива можно заключить в фигурные скобки ({ }):
print "Brownies are good, but the calories content is
{$desserts[Brownies][calories]}";
Обратите внимание на отсутствие кавычек вокруг ключей. Также следует помнить, что между фигурными скобками и ссылкой не должно быть лишних пробелов. Если хотя бы одно из этих условий не выполняется, произойдет ошибка. Впрочем, годятся оба способа. Я рекомендую выбрать один формат и придерживаться его, чтобы ваши программы выглядели более последовательно. Если не использовать какой-либо из этих способов форматирования, ссылки на многомерные массивы будут интерпретироваться буквально, что наверняка приведет к непредвиденным результатам.
PHP 4 на практике
Абстрактные классы
В некоторых ситуациях бывает удобно создать класс, объекты которого никогда не создаются (данный класс нужен всего лишь как базовый для создания производных классов). Такие классы называются абстрактными. Абстрактные классы
обычно применяются в тех случаях, когда разработчик программы хочет обеспечить обязательную поддержку некоторых функциональных возможностей всеми классами, производными от абстрактного базового класса.
В РНР отсутствует прямая поддержка абстрактных классов, однако существует простое обходное решение — достаточно определить в «абстрактном» классе конструктор и включить в него вызов die( ). Вернемся к классам из листинга 6.4. Скорее всего, вам никогда не придется создавать экземпляры классов Land и Vehicle, поскольку они не могут представлять физические объекты. Для представления реальных объектов (например, автомобилей) следует создать класс, производный от этих классов. Следовательно, чтобы предотвратить возможное создание объектов классов Land и Vehicle, необходимо включить в их конструкторы вызовы die( ), как показано в листинге 6.5.
Листинг 6.5. Создание абстрактных классов
class Vehicle {
Объявления атрибутов...
function Vehicle() }
die ("Cannot create Abstract Vehicle class!");
}
Объявления других методов...
}
class Land extends Vehicle {
Объявления атрибутов...
function Land() }
die ("Cannot create Abstract Land class!");
}
Объявления других методов. } class Car extends Land {
Объявления атрибутов...
Объявления методов...
}
?>
Попытка создания экземпляра этих абстрактных классов приведет к выдаче сообщения об ошибке и завершению программы.
Деструкторы
Как упоминалось ранее, в РНР отсутствует непосредственная поддержка деструкторов. Тем не менее, вы можете легко имитировать работу деструктора, вызывая функцию РНР unset( ). Эта функция уничтожает содержимое переменной и возвращает занимаемые ею ресурсы системе. С объектами unset( ) работает так же, как и с переменными. Допустим, вы работаете с объектом $Webpage. После завершения работы с этим конкретным объектом вызывается функция
unset($Webpage);
Эта команда удаляет из памяти все содержимое $Webpage. Действуя в духе инкапсуляции, можно поместить вызов unset( ) в метод с именем destroy( ) и затем вызвать его:
$Website->destroy( );
Помните: необходимость в вызове деструкторов возникает лишь при работе с объектами, использующими большой объем ресурсов, поскольку все переменные и объекты автоматически уничтожаются по завершении сценария.
Функции для работы с классами и объектами
В РНР существует несколько стандартных функций для работы с классами и объектами; эти функции рассматриваются в следующих разделах. Все они часто используются на практике, особенно в процессе разработки интерфейса, администрирования кода и диагностики ошибок.
get_class_methods( )
Функция get_class_methods( ) возвращает массив имен методов класса с заданным именем. Синтаксис функции get_class_methods( ):
array get_class_methods (string имя_класса)
Простой пример использования get_class_methods( ) приведен в листинге 6.7.
Листинг 6.7. Получение списка методов класса
...
class Airplane extends Vehicle {
var $wingspan;
function setWingSpan($wingspan) {
$this->wingspan = $wingspan;
}
function getWingSpan() {
return $this->wingspan;
}
}
$cls_methods = get_class_methods(Airplane);
// Массив $cls_methods содержит имена всех методов,
// объявленных в классах "Airplane" и "Vehicle"
?>
Как видно из листинга 6.7, функция get_class_methods( ) позволяет легко получить информацию обо всех методах, поддерживаемых классом.
get_class_vars( )
Функция get_class_vars( ) возвращает массив имен атрибутов класса с заданным именем. Синтаксис функции get_class_vars( ):
array get_class_vars (string имя_класса)
Пример использования get_class_vars( ) приведен в листинге 6.8.
Листинг 6.8. Получение списка атрибутов класса функцией get_class_vars( )
class Vehicle {
var $model;
var $current_speed; }
class Airplane extends Vehicle {
var Swingspan; } $a_class = "Airplane";
$attribs = get_class_vars($a_class);
// $attribs = array ( "wingspan", "model", "current_speed")
?>
Массив $attribs заполняется именами всех атрибутов класса Airplane.
get_object_vars( )
Функция get_object_vars( ) возвращает ассоциативный массив с информацией обо всех атрибутах объекта с заданным именем. Синтаксис функции get_object_vars( ):
array get_object_vars (object имя_обьекта)
Пример использования функции get_object_vars( ) приведен в листинге 6.9.
Листинг 6.9.
Получение информации о переменных объекта
class Vehicle {
var Swheels;
}
class Land extends Vehicle {
var Sengine;
}
class car extends Land {
var $doors:
function car($doors, $eng, $wheels) {
$this->doors = $doors;
$this->engine = $eng;
$this->wheels = $wheels;
}
function get_wheels() {
return $this->wheels;
}
}
$toyota = new car(2,400,4);
$vars = get_object_vars($toyota);
while (list($key, $value) = each($vars)) :
print "$key ==> $value ";
endwhile;
// Выходные данные:
// doors ==> 2
// engine ==> 400
// wheels ==> 2
?>
Функция get_object_vars( ) позволяет быстро получить всю информацию об атрибутах конкретного объекта и их значениях в виде ассоциативного массива.
method_exists( )
Функция method_exists( ) проверяет, поддерживается ли объектом метод с заданным именем. Если метод поддерживается, функция возвращает TRUE, в противном случае возвращается FALSE. Синтаксис функции method_exists( ):
Пример использования метода method_exists( ) приведён в листинге 6.10.
Листинг 6.10.
Проверка поддержки метода объектом при помощи функции method_exists()
class Vehicle {
...
}
class Land extends Vehicle {
var $fourWheel;
function setFourWheel Drive() {
$this->fourWeel = 1;
}
}
// Создать объект с именем $саr
$car = new Land;
// Если метод "fourWheelDrive" поддерживается классом "Land"
// или "Vehicle", вызов method_exists возвращает TRUE;
// в противном случае возвращается FALSE.
// В данном примере method_exists() возвращает TRUE.
if (method_exists($car, "setfourWheelDrive")) :
print "This car is equipped with 4-wheel drive";
else :
print "This car is not equipped with 4-wheel drive";
endif;
?>
В листинге 6.10 функция method_exists ( ) проверяет, поддерживается ли объектом $car метод с именем setFourWheelDrive( ). Если метод поддерживается, функция возвращает логическую истину и фрагмент выводит соответствующее сообщение. В противном случае возвращается FALSE и выводится другое сообщение.
get_class( )
Функция get_class( ) возвращает имя класса, к которому относится объект с заданным именем. Синтаксис функции get_class( ):
string get_class(object имя_объекта);
Пример использования get_class( ) приведен в листинге 6.11.
Как и следовало ожидать, при вызове get_parent_class( ) переменной $parent будет присвоена строка "Vehicle".
is_subclass_of( )
Функция is_subclass_of( ) проверяет, был ли объект создан на базе класса, имеющего родительский класс с заданным именем. Функция возвращает TRUE, если проверка дает положительный результат, и FALSE в противном случае. Синтаксис функции is_subclass_of( ):
Использование is_subclass_of( ) продемонстрировано в листинге 6.13.
Листинг 6.13.
Использование функции is_subdass_of( )
class Vehicle {
...
}
class Land extends Vehicle {
...
}
$auto = new Land;
// Переменной $is_subclass присваивается TRUE
$is_subclass = is_subclass_of($auto, "Vehicle");
?>
В листинге 6.13 переменной $is_subclass( ) присваивается признак того, принадлежит ли объект $auto к субклассу родительского класса Vehicle. В приведенном фрагменте $auto относится к классу Vehicle; следовательно, переменной $is_subclass( ) будет присвоено значение TRUE.
get_declared_classes( )
Функция get_declared_classes( ) возвращает массив с именами всех определенных классов (листинг 6.14). Синтаксис функции get_declared_classes( ):
array get_declared_classes( )
Листинг 6.14.
Получение списка классов функцией get_declared_classes( )
class Vehicle {
...
}
class Land extends Vehicle {
...
}
$declared_classes = get_declared_classes();
// $declared_classes = array("Vehicle", "Land")
?>
Классы, объекты и объявления методов
Классы образуют синтаксическую базу объектно-ориентированного программирования. Их можно рассматривать как своего рода «контейнеры» для логически связанных данных и функций (обычно называемых методами — см. ниже). Класс представляет собой шаблон, по которому создаются конкретные экземпляры, используемые в программе. Экземпляры классов называются объектами.
Чтобы лучше понять связь между классами и объектами, можно представить класс как «чертеж» для создания объектов. По чертежу «изготавливаются» разные объекты, обладающие одними и теми же базовыми характеристиками (например, при строительстве дома — одна дверь, два окна и определенная толщина стены). Тем не менее, каждый объект существует независимо от других — изменение его характеристик никак не влияет на характеристики других объектов; например, в уже построенном доме можно прорубить дополнительное окно. Важно помнить, что у объектов все равно остается общая характеристика — количество окон.
Класс также можно рассматривать как тип данных (см. главу 2), а объект — как переменную (по аналогии с тем, как переменная $counter относится к целому, а переменная $last_name — к строковому типу). Программа может одновременно работать с несколькими объектами одного класса как с несколькими переменными целого типа. Общий формат классов РНР приведен в листинге 6.1. Листинг 6.1. Объявление классов в РНР
class Class_name {
var $attribute_1;
...
var $attribute_N;
function function1() {
...
}
...
function functionN() {
...
} // end Class_name
Подведем итоги: объявление класса должно начинаться с ключевого слова class (подобно тому, как объявление функции начинается с ключевого слова function). Каждому объявлению атрибута, содержащегося в классе, должно предшествовать ключевое слово van. Атрибуты могут относиться к любому типу данных, поддерживаемых в РНР; их можно рассматривать как переменные с небольшими различиями, о которых вы узнаете в этой главе. После объявлений атрибутов следуют объявления методов, очень похожие на типичные объявления функций.
По общепринятым правилам имена классов ООП начинаются с прописной буквы, а все слова в именах методов, кроме первого, начинаются с прописных букв (первое слово начинается со строчной буквы). Разумеется, вы можете использовать любые обозначения, которые сочтете удобными; главное — выберите стандарт и придерживайтесь его.
Методы часто используются для работы с атрибутами классов. При ссылках на атрибуты внутри методов используется специальная переменная $this. Синтаксис методов продемонстрирован в следующем примере:
class Webpage {
van $bgcolor;
function setBgColor($color) {
$this->bgcolor = $color;
}
function getBgColor() {
return $this->bgcolor;
}
}
?>
Переменная $this ссылается на экземпляр объекта, для которого вызывается метод. Поскольку в любом классе может существовать несколько экземпляров объектов, уточнение $this необходимо для ссылок на атрибуты, принадлежащие текущему объекту. При использовании этого синтаксиса обратите внимание на два обстоятельства:
атрибут, на который вы ссылаетесь в методе, не нужно передавать в виде параметра функции;
знак доллара ($) ставится перед переменной $this, но не перед именем атрибута (как у обычной переменной).
Конструкторы
Довольно часто при создании объекта требуется задать значения некоторых атрибутов. К счастью, разработчики технологии ООП учли это обстоятельство и реализовали его в концепции конструкторов. Конструктор представляет собой метод, который задает значения некоторых атрибутов (а также может вызывать другие методы). Конструкторы вызываются автоматически при создании новых объектов. Чтобы это стало возможным, имя метода-конструктора должно совпадать с именем класса, в котором он содержится. Пример конструктора приведен в листинге 6.2.
Листинг 6.2. Использование конструктора
class Webpage {
var $bgcolor;
function Webpage($color) {
$this->bgcolor = $color;
}
}
// Вызвать конструктор класса Webpage
$page = new Webpage("brown");
?>
Раньше создание объекта и инициализация атрибутов выполнялись раздельно. Конструкторы позволяют выполнить эти действия за один этап.
Интересная подробность: в зависимости от количества передаваемых параметров могут вызываться разные конструкторы. Например, в листинге 6.2 объекты класса Webpage могут создаваться двумя способами. Во-первых, вы можете вызвать конструктор, который просто создает объект, но не инициализирует его атрибуты:
$page = new Webpage;
Во-вторых, объект можно создать при помощи конструктора, определенного в классе, — в этом случае вы создаете объект класса Webpage и присваиваете значение его атрибуту bgcolor:
$page = new Webpage("brown");
Нарушение инкапсуляции
Допустим, вы создали класс, один из атрибутов которого представляет собой массив. Но вместо того чтобы работать с массивом через промежуточные методы (например, предназначенные для создания, удаления, модификации элементов и т. д.), вы в случае необходимости напрямую обращаетесь к массиву. В течение месяца вы уверенно программируете большое «объектно-ориентированное» приложение и благосклонно принимаете хвалу коллег-программистов. Будущее сулит много радостей — премии, оплачиваемый отпуск и даже отдельный кабинет.
Но вот через месяц после успешного запуска вашего web-приложения ваш начальник вдруг решает, что массивы в данном случае не годятся и работать с информацией нужно только через базу данных.
Какая неприятность! Поскольку вы решили работать с атрибутами напрямую, вам теперь придется просматривать всю программу и везде, где происходят обращения к данным, вносить исправления в соответствии с новым интерфейсом. Задача весьма хлопотная, к тому же чревата риском внесения новых ошибок.
А теперь давайте посмотрим, что произошло бы при работе с данными с использованием методов. Все, что вам пришлось бы сделать при переходе от массива к базе данных — перепрограммировать методы. Модификация автоматически распространяется на все точки программы, в которых присутствуют вызовы методов.
Перегрузка методов
Перегрузкой методов называется определение нескольких методов с одинаковыми именами, но разным количеством или типом параметров. Как и в случае с абстрактными классами, в РНР эта возможность не поддерживается, но существует простое обходное решение, приведенное в листинге 6.6.
Листинг 6.6. Перегрузка методов
class Page {
var $bgcolor;
var $textcolor;
function Page() {
// Определить количество переданных аргументов
// и вызвать метод с нужным именем
$name = "Page".func_num_args();
// Call $name with correct number of arguments passed in
if ( func_num_args() == 0 ) :
$this->$name();
else :
$this->$name(func_get_arg(0));
endif;
}
function Page0() {
$this->bgcolor = "white";
$this->textcolor = "black";
print "Created default page";
}
function Page1($bgcolor) {
$this->bgcolor = $bgcolor;
$this->textcolor = "black";
print "Created custom page";
}
}
$html_page - new Page("red");
?>
В этом примере при создании нового объекта с именем $html_page передается один аргумент. Поскольку в классе был определен конструктор по умолчанию (Раgе( )), вызывается именно он. Однако конструктор по умолчанию всего лишь выбирает, какому из конструкторов (Page0( ) или Page1( )) следует передать управление. При выборе конструктора используются функции func_num_args( ) и func_get_arg( ), которые, соответственно, определяют количество аргументов и читают эти аргументы.
Конечно, такое решение вряд ли можно назвать полноценной перегрузкой, но оно подойдет для тех, кто не может жить без этого важного аспекта ООП.
Простое и иерархическое наследование
Как говорилось выше, класс является шаблоном, по которому создаются реальные объекты с определенными характеристиками и функциями. Нетрудно представить себе ситуацию, при которой такой объект является частью другого объекта. Например, автомобиль можно считать частным случаем категории «транспортное средство», к которой относятся и самолеты. Хотя разные типы транспортных средств сильно отличаются друг от друга, все они характеризуются атрибутами из общего набора (количество колес, мощность, максимальная скорость, модель и т. д.). Пусть конкретные значения этих атрибутов сильно различаются — атрибуты все равно присущи всем транспортным средствам. Таким образом, субклассы «автомобиль» и «самолет» наследуют общий набор базовых характеристик от суперкласса
«транспортное средство». Концепция получения классом характеристик от другого, более общего класса называется наследованием.
Наследование является исключительно полезным средством программирования, поскольку его применение предотвращает копирование кода, совместно используемого структурами данных, — например, общих характеристик различных типов транспортных средств, упоминавшихся в предыдущем абзаце. В общем случае синтаксис наследования характеристик другого класса в РНР выглядит так:
class Class_name2 extends Class_name1 {
объявления атрибутов;
объявления методов;
}
Ключевое слово extends говорит о том, что класс Class_name2 наследует все характеристики класса Class_name1.
Помимо возможности многократного использования кода, наследование обладает еще одним важным преимуществом — снижается вероятность ошибок при
модификации программы. Например, в иерархии, изображенной на рис. 6.1, изменения в классе «автомобиль» никак не отразятся на коде (и данных) класса «самолет», и наоборот. Вызов конструктора производного класса не приводит к автоматическому вызову конструктора базового класса.
Рис. 6.1. Иерархия транспортных средств
В листинге 6.3 приведены классы, моделирующие иерархию, изображенную на рис. 6.1.
Листинг 6.3.
Представление различных типов транспортных средств при помощи наследования
// Транспортное средство
class Vehicle {
var $model;
var $current_speed;
function setSpeed($mph) {
$this->current_speed = $mph;
}
function getSpeed() {
return $this->current_speed;
}
}
// Автомобиль
class Auto extends Vehicle {
var $fue1_type;
function setFuelType($fuel) {
$this->fuel_type = $fuel;
}
function getFuelType() {
return $this->fuel_type;
}
}
// Самолет
class Airplane extends Vehicle {
var $wingspan;
function setWingSpan($wingspan) {
$this->wingspan = $wingspan;
}
function getWingSpan() {
return $this->wingspan;
}
}
?>
Объекты этих классов создаются следующим образом:
$tractor = new Vehicle;
$gulfstream = new Airplane;
Приведенные команды создают два объекта. Первый объект, $tractor, относится к классу Vehicle. Второй объект, $gulfstream, относится к классу Airplane и потому обладает как общими характеристиками класса Vehicle, так и уточненными характеристиками класса Airplаne.
Ситуация, при которой класс наследует свойства нескольких родительских классов, называется множественным наследованием. К сожалению, в РНР множественное наследование не поддерживается. Например, следующая конструкция невозможна в РНР:
class Airplane extends Vehicle extends Building...
Многоуровневое наследование
С увеличением размеров и сложности программ может возникнуть необходимость в многоуровневом наследовании. Иначе говоря, класс будет наследовать свои свойства от других классов, которые, в свою очередь, будут наследовать от третьих классов и т. д. Многоуровневое наследование развивает модульную структуру программы, обеспечивая простоту сопровождения и более четкую логическую структуру. Скажем, при использовании примера с транспортными средствами в большой программе может появиться необходимость в дополнительном разбиении на субклассы суперкласса Vehicle, продолжающем логическое развитие иерархии. Например, транспортные средства можно дополнительно разделить на наземные, морские и воздушные, чтобы суперкласс специализированных субклассов выбирался в зависимости от среды, в которой перемещается данное транспортное средство. Новый вариант иерархии показан на рис. 6.2.
Краткий пример, приведенный в листинге 6.4, подчеркивает некоторые важные аспекты многоуровневого наследования в РНР.
Листинг 6.4.
Многоуровневое наследование
class Vehicle {
Объявления атрибутов...
Объявления методов...
}
class Land extends Vehicle {
Объявления атрибутов...
Объявления методов...
}
class Саr extends Land {
Объявления атрибутов...
Объявления методов...
}
$nissan = new Car;
?>
Объект $nissan содержит все атрибуты и методы классов Саr, Land и Vehicle. Как видите, программа получается исключительно модульной. Допустим, когда-то в будущем вы захотите добавить в класс Land новый атрибут. Нет проблем: внесите соответствующие изменения в класс Land, и этот атрибут немедленно становится доступным для классов Land и Саr, не влияя на функциональность других классов. Таким образом, модульность кода и гибкость относятся к числу основных преимуществ ООП.
Рис. 6.2.
Многоуровневое наследование в иерархии Vehicle
Хотя масс наследует свои характеристики от цепочки родителей, конструкторы родительских классов не вызываются автоматически при создании объектов класса-наследника. Эти конструкторы могут вызываться классом-наследником в виде методов.
РНР и ООП
Хотя РНР обладает общими объектно-ориентированными возможностями, он не является полноценным ОО-языком (например, таким, как C++ или Java). В частности, в РНР не поддерживаются следующие объектно-ориентированные возможности:
множественное наследование;
автоматический вызов конструкторов (если вы хотите, чтобы при конструировании объекта производного класса вызывался конструктор базового класса, вам придется вызвать его явно);
абстрактные классы;
перегрузка методов;
перегрузка операторов (это связано с тем, что РНР является языком со свободной типизацией, — за дополнительной информацией обращайтесь к главе 2);
закрытый и открытый доступ, виртуальные функции;
деструкторы;
полиморфизм.
Но и без всего перечисленного вы все равно сможете извлечь пользу из объектно-ориентированных возможностей, поддерживаемых РНР. Реализация ООП в РНР оказывает колоссальную помощь в модульном оформлении функциональности вашей программы.
Создание объектов и работа с ними
Объекты создаются оператором new. Например, объект класса Webpage создается следующей командой:
$home_page = new Webpage;
Новый объект с именем $some_page обладает собственным набором атрибутов и методов, перечисленных в классе Webpage. Для изменения значения атрибута $bgcolor, принадлежащего этому конкретному объекту, можно воспользоваться определенным в классе методом setBgColor( ):
$some_page->setBgColor("black");
Следует помнить, что РНР также позволяет явно получить значение атрибута указанием имен объекта и атрибута:
$some_page->bgcolor;
Однако второй способ противоречит принципу инкапсуляции, и при работе с ООП поступать так не следует. Чтобы понять, почему это так, прочитайте следующий раздел.
PHP 4 на практике
Чтение файла в массив
Функция file( ) загружает все содержимое файла в индексируемый массив. Каждый элемент массива соответствует одной строке файла. Синтаксис функции filе ( ):
array file (string файл [, int включение_пути])
Если необязательный третий параметр включение_пути равен 1, то путь к файлу определяется по отношению к каталогу включаемых файлов, указанному в файле php.ini (см. главу 1). В листинге 7.5 функция file( ) используется для загрузки файла pastry.txt (см. листинг 7.1).
Листинг 7.5. Загрузка файла pastry.txt функцией file( )
$file_array = file( "pastry.txt" );
while ( list( $line_num. $line ) = eacht($file_array ) ):
print "Line $line_num: ", htmlspecialchars($line ), " \n"
endwhile;
?>
Каждая строка массива выводится вместе с номером:
Line 0: Recipe: Pastry Dough
Line 1: 1 1/4 cups all-purpose flour
Line 2: 3/4 stick (6 tablespoons) unsalted butter, chopped
Line 3: 2 tablespoons vegetable shortening
Line 4: 1/4 teaspoon salt
Line 5: 3 tablespoons water
Чтение из файла
Несомненно, чтение является самой главной операцией, выполняемой с файлами. Ниже описаны некоторые функции, повышающие эффективность чтения из файла. Синтаксис этих функций практически точно копирует синтаксис аналогичных функций записи.
is_readable( )
Функция i s_readable( ) позволяет убедиться в том, что файл существует и для него разрешена операция чтения. Возможность чтения проверяется как для файла, так и для каталога. Синтаксис функции is_readable( ):
boo! is_readable (string файл]
Скорее всего, РНР будет работать под идентификатором пользователя, используемым web-сервером (как правило, «nobody»), поэтому для того чтобы функция is_readable( ) возвращала TRUE, чтение из файла должно быть разрешено всем желающим. Следующий пример показывает, как убедиться в том, что файл существует и доступен для чтения:
if ( is_readable($filename) ) :
// Открыть файл и установить указатель текущей позиции в конец файла
$fh = fopen($filename, "r");
else :
print "$filename is not readable!";
endif;
fread( )
Функция fread( ) читает из файла, заданного файловым манипулятором, заданное количество байт. Синтаксис функции fwrite( ):
int fread(int манипулятор, int длина)
Манипулятор должен ссылаться на открытый файл, доступный для чтения (см. описание функции is_readable( )). Чтение прекращается после прочтения заданного количества байт или при достижении конца файла. Рассмотрим текстовый файл pastry.txt, приведенный в листинге 7.1. Чтение и вывод этого файла в браузере осуществляется следующим фрагментом:
$fh = fopen('pastry.txt', "r") or die("Can't open file!");
$file = fread($fh, filesize($fh));
print $file;
fclose($fh);
Используя функцию fllesize( ) для определения размера pastry.txt в байтах, вы гарантируете, что функция fread( ) прочитает все содержимое файла.
Листинг 7.1. Текстовый файл pastry.txt
Recipe: Pastry Dough
1 1/4 cups all-purpose flour
3/4 stick (6 tablespoons) unsalted butter, chopped
2 tablespoons vegetable shortening 1/4 teaspoon salt
3 tablespoons water
fgetc( )
Функция fgetc( ) возвращает строку, содержащую один символ из файла в текущей позиции указателя, или FALSE при достижении конца файла. Синтаксис функции fgetc( ):
string fgetc (int манипулятор)
Манипулятор должен ссылаться на открытый файл, доступный для чтения (см. описание функции is_readable( ) ранее в этой главе). В следующем примере продемонстрированы посимвольное чтение и вывод файла с использованием функции fgetc( ):
$fh = fopen("pastry.txt", "r"); while (! feof($fh)) :
$char = fgetc($fh):
print $char; endwhile;
fclose($fh);
fgets( )
Функция fgets( ) возвращает строку, прочитанную от текущей позиции указателя в файле, определяемом файловым манипулятором. Файловый указатель должен ссылаться на открытый файл, доступный для чтения (см. описание функции is_readable( ) ранее в этой главе). Синтаксис функции fgets( ):
string fgets (int манипулятор, int длина)
Чтение прекращается при выполнении одного из следующих условий:
из файла прочитано длина — 1 байт;
из файла прочитан символ новой строки (включается в возвращаемую строку);
из файла прочитан признак конца файла (EOF).
Если вы хотите организовать построчное чтение файла, передайте во втором параметре значение, заведомо превышающее количество байт в строке. Пример построчного чтения и вывода файла:
$fh = fopen("pastry.txt", "r");
while (! feof($fh));
$line = fgets($fh, 4096);
print $line. " ";
endwhile;
fclose($fh):
fgetss( )
Функция fgetss( ) полностью аналогична fgets( ) за одним исключением — она пытается удалять из прочитанного текста все теги HTML и РНР:
string fgetss (Int манипулятор, int длина [, string разрешенные_теги])
Прежде чем переходить к примерам, ознакомьтесь с содержимым листинга 7.2 — этот файл используется в листингах 7.3 и 7.4.
Листинг 7.2.
Файл science.html
Breaking News - Science
Alien lifeform discovered
August 20. 2000
Early this morning, a strange new form of fungus was found growing in the closet of W. J. Gilmore's old apartment refrigerator. It is not known if powerful radiation emanating from the tenant's computer monitor aided in this evolution.
Листинг 7.З.
Удаление тегов из файла HTML перед отображением в браузере
$fh = fopen("science.html", "r");
while (! feof($fh)) :
print fgetss($fh, 2048);
endwhile;
fclose($fh);
?>
Результат приведен ниже. Как видите, из файла science.html были удалены все теги HTML, что привело к потере форматирования:
Breaking News - Science Alien lifeform discovered August 20. 2000 Early this morning, a strange new form of fungus was found growing in the closet of W. J. Gilmore's old apartment refrigerator. It is not known if powerful radiation emanating from the tenant's computer monitor aided in this evolution.
В некоторых ситуациях из файла удаляются все теги, кроме некоторых — например, тегов разрыва строк . Листинг 7.4 показывает, как это делается.
Листинг 7.4.
Выборочное удаление тегов из файла HTML
$fh = fopenC'science.html", "r");
$allowable = " ";
while (! feof($fh)) :
print fgetss($fh. 2048, $allowable);
endwhile;
fclose($fh);
?>
Результат:
Breaking News - Science Alien lifeform discovered August 20. 2000 Early this morning, a strange new form of fungus was found growing in the closet of W. J. Gilmore's old apartment refrigerator. It is not known if powerful radiation emanating from the tenant's computer monitor aided in this evolution.
Как видите, функция fgetss( ) упрощает преобразование файлов, особенно при наличии большого количества файлов HTML, отформатированных сходным образом.
Копирование и переименование файлов
К числу других полезных системных функций, которые могут выполняться в сценариях РНР, относятся копирование и переименование файлов на сервере. Эти операции выполняются двумя функциями: сору( ) и rename( ).
сору( )
Скопировать файл в сценарии РНР ничуть не сложнее, чем при помощи команды UNIX ср. Задача решается функцией РНР сору( ). Синтаксис функции сору( ):
int copy (string источник, string приемник)
Функция сору( ) пытается скопировать файл источник в файл приемник; в случае успеха возвращается TRUE, а при неудаче — FALSE. Если файл приемник не существует, функция сору( ) создает его. Следующий пример показывает, как создать резервную копию файла при помощи функции сору( ):
$data_file = "datal.txt";
copy($data_file. $data_file'.bak') or die("Could not copy $data_file");
rename ( )
Функция rename( ) переименовывает файл. В случае успеха возвращается TRUE, a при неудаче — FALSE. Синтаксис функции rename( ):
bool rename (string старое_имя, string новое_имя)
Пример переименования файла функцией rename( ):
$data_file = "datal.txt";
rename($data file, $datafile'.old') or die ("Could not rename $data file");
Обратные апострофы
Существует и другой способ выполнения системных команд, не требующий вызова функций, — выполняемая команда заключается в обратные апострофы (` `), а результаты ее работы отображаются в браузере. Пример:
$output = `ls`;
print "
$output
";
Этот фрагмент выводит в браузер содержимое каталога, в котором находится сценарий. Внутренний параметр ping -с 5 (-п 5 в системе Windows) задает количество опросов сервера.
Если вы хотите просто вернуть неформатированные результаты выполнения команды, воспользуйтесь функцией passthru( ), описанной ниже.
passthru( )
Функция passthru( ) работает почти так же, как ехес( ), за одним исключением — она автоматически выводит результаты выполнения команды. Синтаксис функции passthru( ):
void passthru(string команда [, int возврат])
Если при вызове passthru( ) передается необязательный параметр возврат, этой переменной присваивается код возврата выполненной команды.
escapeshellcmd( )
Функция escapeshellcmd( ) экранирует все потенциально опасные символы, которые могут быть введены пользователем (например, на форме HTML), для выполнения команд exec( ), passthru( ), system( ) или рореn( ). Синтаксис:
string escapeshellcmd (string команда)
К пользовательскому вводу всегда следует относиться с определенной долей осторожности, но даже в этом случае пользователи могут вводить команды, которые будут исполняться функциями запуска системных команд. Рассмотрим следующий фрагмент:
$user_input = `rm -rf *`; // Удалить родительский каталог и все его подкаталоги
ехес($user_input); // Выполнить $user_input !!!
Если не предпринять никаких мер предосторожности, такая команда приведет к катастрофе. Впрочем, можно воспользоваться функций escapeshellcmd( ) для экранирования пользовательского ввода:
$user_input = `rm - rf *`; // Удалить родительский каталог и все его подкаталоги
ехес( escapeshellcmd($user_input)); // Экранировать опасные символы
Функция escapeshellcmd( ) экранирует символ *, предотвращая катастрофические последствия выполнения команды. Безопасность является одним из важнейших аспектов программирования в среде Web, поэтому я посвятил целую главу этой теме и ее отношению к программированию РНР. За дополнительной информацией обращайтесь к главе 16.
Открытие файлового манипулятора процесса
popen( )
Наряду с обычными файлами можно открывать файловые манипуляторы для взаимодействия с процессами на сервере. Задача решается функцией рореn( ), которая имеет следующий синтаксис:
int popen (string команда, string режим)
Параметр команда определяет выполняемую системную команду, а параметр режим описывает режим доступа:
// Открыть файл "spices.txt" для записи
$fh = fopen("spices.txt","w");
// Добавить несколько строк текста
fputs($fh, "Parsley, sage, rosemary\n");
fputs($fh, "Paprika, salt, pepper\n");
fputs($fh, "Basil, sage, ginger\n");
// Закрыть манипулятор
fclose($fh);
// Открыть процесс UNIX grep для поиска слова Basil в файле spices.txt
$fh - popen("grep Basil < spices.txt", "r");
// Вывести результат работы grep
fpassthru($fh);
?>
Результат выглядит так:
Basil, sage, ginger
Функция fpassthru( ) является аналогом функции passthru( ), рассматриваемой в разделе «Запуск внешних программ» этой главы.
pclose( )
После выполнения всех операций файл или процесс необходимо закрыть. Функция pclose( ) закрывает соединение с процессом, заданным манипулятором, по аналогии с тем, как функция fclose( ) закрывает файл, открытый функцией fopen( ). Синтаксис функции pclose( ):
int pclose (int манипулятор}
В параметре манипулятор передается манипулятор, полученный ранее при успешном вызове рореn( ).
Открытие и закрытие файлов
Прежде чем выполнять операции ввода/вывода с файлом, необходимо открыть его функцией fopen( ).
fopen( )
Функция fopen( ) открывает файл (если он существует) и возвращает целое число — так называемый файловый манипулятор (file handle). Синтаксис функции fopen( ):
int fopen (string файл, string режим [, int включение_пути])
Открываемый файл может находиться в локальной файловой системе, существовать в виде стандартного потока ввода/вывода или представлять файл в удаленной системе, принимаемой средствами HTTP или FTP.
Параметр файл может задаваться в нескольких формах, перечисленных ниже:
Если параметр содержит имя локального файла, функция fopen( ) открывает этот файл и возвращает манипулятор.
Если параметр задан в виде php://stdin, php://stdout или php://stderr, открывается соответствующий стандартный поток ввода/вывода.
Если параметр начинается с префикса http://, функция открывает подключение HTTP к серверу и возвращает манипулятор для указанного файла.
Если параметр начинается с префикса ftp://, функция открывает подключение FTP к серверу и возвращает манипулятор для указанного файла. В этом случае следует обратить особое внимание на два обстоятельства: если сервер не поддерживает пассивный режим FTP, вызов fopen( ) завершается неудачей. Более того, FTP-файлы открываются либо для чтения, либо для записи. При работе в пассивном режиме сервер ЯР ожидает подключения со стороны клиентов. При работе в активном режиме сервер сам устанавливает соединение с клиентом. По умолчанию обычно используется активный режим.
Параметр режим определяет возможность выполнения чтения и записи в файл. В табл. 7.1 перечислены некоторые значения, определяющие режим открытия файла.
Таблица 7.1.
Режимы открытия файла
Режим
Описание
r
Только чтение. Указатель текущей позиции устанавливается в начало файла
r+
Чтение и запись. Указатель текущей позиции устанавливается в начало файла
w
Только запись. Указатель текущей позиции устанавливается в начало файла, а все содержимое файла уничтожается. Если файл не существует, функция пытается создать его
w+
Чтение и запись. Указатель текущей позиции устанавливается в начало файла, а все содержимое файла уничтожается. Если файл не существует, функция пытается создать его
a
Только запись. Указатель текущей позиции устанавливается в конец файла. Если файл
не существует, функция пытается создать его
a+
Чтение и запись. Указатель текущей позиции устанавливается в конец файла. Если файл не существует, функция пытается создать его
<
/p>
Если необязательный третий параметр включение_пути равен 1, то путь к файлу определяется по отношению к каталогу включаемых файлов, указанному в файле php.ini (см. главу 1).
Ниже приведен пример открытия файла функцией fopen( ). Вызов die( ), используемый в сочетании с fopen( ), обеспечивает вывод сообщения об ошибке в том случае, если открыть файл не удастся:
$file = "userdata.txt"; // Некоторый файл
$fh = fopen($file, "a+") or die("File ($file) does not exist!");
Следующий фрагмент открывает подключение к сайту РНР (http://www.php.net):
$site = "http://www.php.net": // Сервер, доступный через HTTP
$sh = fopen($site., "r"); //Связать манипулятор с индексной страницей Php.net
После завершения работы файл всегда следует закрывать функцией fclose( ).
fclose ( )
Функция fclose( ) закрывает файл с заданным манипулятором. При успешном закрытии возвращается TRUE, при неудаче — FALSE. Синтаксис функции fclose( ):
int fclose(int манипулятор)
Функция fclose( ) успешно закрывает только те файлы, которые были ранее открыты функциями fopen( ) или fsockopen( ). Пример закрытия файла:
$file = "userdata.txt";
if (file_exists($file)) :
$fh = fopen($file, "r");
// Выполнить операции с файлом
fclose($fh);
else :
print "File Sfile does not exist!";
endif;
Открытие соединения через сокет
РНР не ограничивается взаимодействием с файлами и процессами — вы также можете устанавливать соединения через сокеты. Сокет (socket) представляет собой программную абстракцию, позволяющую устанавливать связь с различными службами другого компьютера.
fsockopen( )
Функция fsockopen( ) устанавливает сокетное соединение с сервером в Интернете
через протокол TCP или UDP. Синтаксис функции fsockopen( ):
int fsockopen (string узел, int порт [, int код_ошибки [, string текст_ошибки [, int тайм-аут]]])
Необязательные параметры код_ошибки и текст_ошибки содержат информацию, которая будет выводиться в случае неудачи при подключении к серверу. Оба параметра должны передаваться по ссылке. Третий необязательный параметр, тайм-аут, задает продолжительность ожидания ответа от сервера (в секундах). В листинге 7.6 продемонстрировано применение функции fsockopen( ) для получения информации о сервере. Однако перед рассмотрением листинга 7.6 необходимо познакомиться еще с одной функцией — socket_set_blocking( ). UDP (User Datagram Protocol) — коммуникационный протокол, не ориентированный на соединение.
socket_set_blocking( )
Функция socket_set_b1ocki ng( ) позволяет установить контроль над тайм-аутом для операций с сервером:
socket_set_blocking(int манипулятор, boolean режим)
Параметр манипулятор задает открытый ранее сокет, а параметр режим выбирает режим, в который переключается сокет (TRUE для блокирующего режима, FALSE для неблокирующего режима). Пример использования функций fsockopen( ) и socket_set_blocking( ) приведен в листинге 7.6.
Листинг 7.6. Использование функции fsockopen() для получения информации о сервере
function getthehost($host.$path) {
// Открыть подключение к узлу
$fp - fsockopen($host, 80, &$errno, &$errstr, 30);
// Перейти в блокирующий режим
socket_set_blocking($fp, 1),
// Отправить заголовки
fputs($fp,"GET $path HTTP/1.1\r\n");
fputs ($fp, "Host: $host\r\n\r\n"); $x = 1;
// Получить заголовки
while($x < 10) :
$headers = fgets ($fp, 4096);
print $headers;
$x++;
endwhile;
// Закрыть манипулятор
fclose($fp);
}
getthehost("www. apress.com", "/");
?>
В результате выполнения листинга 7.6 выводится следующий результат:
НТТР/1.1 200 OK Server: Microsoft-IIS/4.0 Content-location:
http://www.apress.com/0efault. htm Date: Sat. 19 Aug 2000 23:03:25 GMT
Функция pfsockopen( ) представляет собой устойчивую (persistent) версию fsockopen( ). Это означает, что соединение не будет автоматически разорвано по завершении сценария, в котором была вызвана функция. Синтаксис функции pfsockopen( ):
int pfsockopen (string узел, int порт [, int код_ошибки [, string текст _ошибки [, int тайм-аут]]])
В зависимости от конкретных целей вашего приложения может оказаться удобнее использовать pfsockopen( ) вместо fsockopen( ).
Отображение и изменение характеристик файлов
У каждого файла в системах семейства UNIX есть три важные характеристики:
принадлежность группе;
владелец;
разрешения (permissions).
Все эти характеристики можно изменить при помощи соответствующих функций РНР. Функции, описанные в этом разделе, не работают в системах семейства Windows. Если у вас нет опыта работы в операционных системах UNIX, информацию о характеристиках файловой системы UNIX можно получить по адресу http://sunsite.auc.dk/linux-newbie/FAQ2.htm. Темы принадлежности группе, владения и разрешений рассматриваются в разделе 3.2.6.
chgrp( )
Функция chgrp( ) пытается сменить группу, которой принадлежит заданный файл. Синтаксис функции chgrp( ):
int chgrp (string имя_файла, mixed группа)
filegroup( )
Функция filegroup( ) возвращает идентификатор группы владельца файла с заданным именем или FALSE в случае ошибки. Синтаксис функции filegroup( ):
int filegroup (string имя_файла)
chmod( )
Функция chmod( ) изменяет разрешения файла с заданным именем. Синтаксис функции chmod( ):
int chmod (string имя_файла, int разрешения)
Разрешения задаются в восьмеричной системе. Специфика задания параметра функции chmod ( ) продемонстрирована в следующем примере:
chmod("data_file.txt", g+r); // He работает
chmod("data_file.txt", 766); // Не работает
chmod("data_file.txt", 0766); // Работает
fileperms( )
Функция fileperms( ) возвращает разрешения файла с заданным именем или FALSE в случае ошибки. Синтаксис функции fileperms( ):
int fileperms (string имя_файла)
chown( )
Функция chown( ) пытается сменить владельца файла. Право изменения владельца файла предоставляется только привилегированному пользователю. Синтаксис функции chown( ):
int chown (string имя_файла, mixed пользователь)
fileowner( )
Функция fileowner( ) возвращает идентификатор пользователя для владельца файла с заданным именем. Синтаксис функции fileowner( ):
int fileowner (string имя_файла)
Перенаправление файла в стандартный выходной поток
Функция readfile( ) читает содержимое файла и направляет его в стандартный вывод (в большинстве случаев — в браузер). Синтаксис функции readfile( ):
int readfile (string файл [, int включение_пути])
Функция возвращает количество прочитанных байтов. Файл может находиться в локальной файловой системе, существовать в виде стандартного потока ввода/вывода или представлять файл в удаленной системе, принимаемой средствами HTTP или FTP. Параметр файл задается по тем же правилам, что и в функции fopen( ).
Предположим, у вас имеется файл latorre.txt, содержимое которого вы хотите вывести в браузере:
Restaurant "La Тоrrе." located in Nettuno, Italy, offers an eclectic blend of style. history, and fine seafood cuisine. Within the walls of the medieval borgo surrounding the city, one can dine while watching the passersby shop in the village boutiques. Comfort coupled with only the freshest seafare make La Torre one of Italy's finest restaurants.
При выполнении следующего фрагмента все содержимое latorre.txt направляется в стандартный выходной поток:
$restaurant_file = "latorre.txt";
// Направить весь файл в стандартный выходной поток
readfile($restaurant_filе);
?>
Проект 1: простой счетчик обращений
Сценарий, представленный в этом разделе, подсчитывает количество обращений к странице, в которой он находится. Прежде чем переходить к программному коду в листинге 7.9, просмотрите алгоритм, написанный на псевдокоде:
Присвоить переменной $access имя файла, в котором будет храниться значение счетчика.
Использовать функцию filе( ) для чтения содержимого $access в массив $visits. Префикс @ перед именем функции подавляет возможные ошибки (например, отсутствие файла с заданным именем).
Присвоить переменной $current_visitors значение первого (и единственного) элемента массива $visits.
Увеличить значение $current_visitors на 1.
Открыть файл $access для записи и установить указатель текущей позиции в начало файла.
Записать значение $current_visitors в файл $access.
Закрыть манипулятор, ссылающийся на файл $access.
Листинг 7.9. Простой счетчик обращений
// Сценарий: простой счетчик обращений
// Назначение: сохранение количества обращений в файле
$access = "hits.txt"; // Имя файла выбирается произвольно
$visits = @file($access); // Прочитать содержимое файла в масссив
$current_visitors = $visits[0]; // Извлечь первый (и единственный) элемент
++$current_visitors; // Увеличить счетчик обращений
$fh = fopen($access. "w"); // Открыть файл hits.txt и установить
// указатель текущей позиции в начало файла
@fwrite($fh, $current_visitors);// Записать новое значение счетчика
// в файл "hits.txt"
fclose($fh); // Закрыть манипулятор файла "hits.txt"
?>
Проект 2: построение карты сайта
Сценарий, приведенный в листинге 7.10, строит карту сайта — иерархическое изображение всех папок и файлов на сервере, начиная с заданного каталога. При вычислении отступов элементов, из которых состоит карта сайта, используются функции, определенные в этой и предыдущих главах. Прежде чем переходить к программе, просмотрите алгоритм, написанный на псевдокоде:
Объявить служебные переменные для хранения родительского каталога, имени графического файла с изображением папки, названия страницы и флага серверной ОС (Windows или другая система).
Объявить функцию display_directory( ), которая читает содержимое каталога и форматирует его для вывода в браузере.
Построить путь к каталогу объединением имени, передаваемого в переменной $dir1, с $dir.
Открыть каталог и прочитать его содержимое. Отформатировать имена каталога и файлов и вывести их в браузере.
Если текущий файл является каталогом, рекурсивно вызвать функцию display_di rectory( ) и передать ей имя нового каталога для вывода. Вычислить отступ, используемый при форматировании вывода.
Если файл не является каталогом, он форматируется для отображения в виде гиперссылки (а также вычисляется отступ, используемый при форматировании).
Листинг 7.10. Программа sitemap.php
// Файл: sitemap.php
// Назначение: построение карты сайта
// Каталог, с которого начинается построение карты
$beg_path = "C:\Program FilesVApache Group\Apache\htdocs\phprecipes";
// Файл с графическим изображением папки.
// Путь должен задаваться Относительно* корневого каталога сервера Apache
$folder_location = "C:\My Documents\PHP for Programmers\FINAL CHPS\graphics\folder.gif";
// Текст в заголовке окна $page_name = "PHPRecipes SiteMap";
// В какой системе будет использоваться сценарий - Linux или Windows?
// (0 - Windows; 1 - Linux)
$usingjinux = 0;
// Функция: display_directory
// Назначение: чтение содержимого каталога, определяемого параметром
// $dir1, с последующим форматированием иерархии каталогов и файлов.
// Функция может вызываться рекурсивно.
function display_directory ($dir1, $folder_location, $using_linux, $init_depth) {
На рис. 7.1 изображен результат выполнения сценария для каталога с несколькими главами этой книги.
Рис. 7.1.
Вывод структуры каталога на сервере с использованием сценария sitemap.php
Проверка существования и размера файла
Прежде чем пытаться работать с файлом, желательно убедиться в том, что он существует. Для решения этой задачи обычно используются две функции:
file_exists( ) и is_file( ).
file_exists( )
Функция f ilе_ехists ( ) проверяет, существует ли заданный файл. Если файл существует, функция возвращает TRUE, в противном случае возвращается FALSE. Синтаксис функции file_exists( ):
bool file_exists(string файл)
Пример проверки существования файла:
if (! file_exists ($filename)) :
print "File $filename does not exist!";
endif: is_file( )
Функция is_file( ) проверяет существование заданного файла и возможность выполнения с ним операций чтения/записи. В сущности, is_file( ) представляет собой более надежную версию file_exists( ), которая проверяет не только факт существования файла, но и то, поддерживает ли он чтение и запись данных:
bool is_file(string файл)
Следующий пример показывает, как убедиться в существовании файла и возможности выполнения операций с ним:
$file = "somefile.txt";
if (is_file($file)) :
print "The file $file is valid and exists!";
else :
print "The file $file does not exist or it is not a valid file!";
endif:
Убедившись в том, что нужный файл существует и с ним можно выполнять различные операции чтения/записи, можно переходить к следующему шагу — открытию файла.
filesize( )
Функция filesize( ) возвращает размер (в байтах) файла с заданным именем или FALSE в случае ошибки. Синтаксис функции filesize( ):
int filesize(string имя_файла)
Предположим, вы хотите определить размер файла pastry.txt. Для получения нужной информации можно воспользоваться функцией filesize( ):
$fs = filesize("pastry.txt"); print "Pastry.txt is $fs bytes.";
Выводится следующий результат:
Pastry.txt is 179 bytes.
Прежде чем выполнять операции с файлом, необходимо открыть его и связать с файловым манипулятором, а после завершения работы с файлом его следует закрыть. Эти темы рассматриваются в следующем разделе.
Работа с файловой системой
В РНР существуют функции для просмотра и выполнения различных операций с файлами на сервере. Информация об атрибутах серверных файлов (местонахождение, владелец и привилегии) часто бывает полезной.
basename( )
Функция basename( ) выделяет имя файла из переданного полного имени. Синтаксис функции basename( ):
string basename(string полное_имя)
Выделение базового имени файла из полного имени происходит следующим образом:
$path = "/usr/local/phppower/htdocs/index.php"; $file = basename($path); // $file = "index.php"
Фактически эта функция удаляет из полного имени путь и оставляет только имя файла.
getlastmod( )
Функция getlastmod( ) возвращает дату и время последней модификации страницы, из которой вызывается функция. Синтаксис функции getlastmod( ):
int getlastmod(void)
Возвращаемое значение соответствует формату даты/времени UNIX, и для его форматирования можно воспользоваться функцией date( ). Следующий фрагмент выводит дату последней модификации страницы:
echo "Last modified: ".date( "H:i:s a". getlastmod( ) );
stat( )
Функция stat( ) возвращает индексируемый массив с подробной информацией о файле с заданным именем:
array stat(string имя_файла)
В элементах массива возвращается следующая информация:
0 Устройство
1 Индексный узел (inode)
2 Режим защиты индексного узла
3 Количество ссылок
4 Идентификатор пользователя владельца
5 Идентификатор группы владельца
6 Тип устройства индексного узла
7 Размер в байтах
8 Время последнего обращения
9 Время последней модификации
10 Время последнего изменения
11 Размер блока при вводе/выводе в файловой системе
12 Количество
выделенных блоков
Таким образом, если вы хотите узнать время последнего обращения к файлу, обратитесь к элементу 8 возвращаемого массива. Рассмотрим пример:
$file - "datafile.txt";
list($dev, $inode, $inodep, $nlink, $uid, $gid, $inodev, $size, $atime, $mtime, $ctime,
$bsize) = stat($file);
print "$file is $size bytes. ";
print "Last access time: $atime ";
print "Last modification time: $mtime ";
Результат:
popen.php is 289 bytes.
Last access time: August 15 2000 12:00:00
Last modification time: August 15 2000 10:07:18
В этом примере я воспользовался конструкцией list () для присваивания имен каждому возвращаемому значению. Конечно, с таким же успехом можно вернуть массив, в цикле перебрать элементы и вывести всю необходимую информацию. Как видите, функция stat ( ) позволяет получить различные полезные сведения о файле.
Работа с каталогами
Функции РНР позволяют просматривать содержимое каталогов и перемещаться по ним. В листинге 7.8 изображена типичная структура каталогов в системе UNIX.
Листинг 7.8. Типичная структура каталогов
drwxr-xr-x 4 root wheel 512 Aug 13 13:51 book/
drwxr-xr-x 4 root wheel 512 Aug 13 13:51 code/
-rw-r--r-- 1 root wheel 115 Aug 4 09:53 index.html
drwxr-xr-x 7 root wheel 1024 Jun 29 13:03 manual/
-rw-r--r-- 1 root wheel 19 Aug 12 12:15 test.php
dirname( )
Функция dirname( ) дополняет basename( ) — она извлекает путь из полного имени файла. Синтаксис функции dirname( ):
string dirname (string путь)
Пример использования dirname( ) для извлечения пути из полного имени:
$path = "/usr/locla/phppower/htdocs/index.php";
$file = dirname($path); // $file = "usr/local/phppower/htdocs"
Функция dirname( ) иногда используется в сочетании с переменной $SCRIPT_FILENAME для получения полного пути к сценарию, из которого выполняется команда:
$dir - dirname($SCRIPT_FILENAME);
is_dir( )
Функция is_dir( ) проверяет, является ли файл с заданным именем каталогом:
bool is_dir (string имя_файла)
В следующем примере используется структура каталогов из листинга 7.8:
$isdir = is_dir("index.html"); // Возвращает FALSE
$isdir = is_dir("book"); // Возвращает TRUE
mkdir()
Функция mkdir( ) делает то же, что и одноименная команда UNIX, — она создает новый каталог. Синтаксис функции mkdir( ):
int mkdir (string путь, int режим)
Параметр путь определяет путь для создания нового каталога. Не забудьте завершить параметр именем нового каталога! Параметр режим определяет разрешения, назначаемые созданному каталогу.
opendir( )
Подобно тому как функция fopen( ) открывает манипулятор для работы с заданным файлом, функция opendir( ) открывает манипулятор для работы с каталогом. Синтаксис функции opendir( ):
int opendir (string путь)
closedir( )
Функция closedir( ) закрывает манипулятор каталога, переданный в качестве параметра. Синтаксис функции closedir( ):
void closedir(int манипулятор_каталога)
readdir( )
Функция readdir( ) возвращает очередной элемент заданного каталога. Синтаксис:
string readdir(int манипулятор_каталога)
С помощью этой функции можно легко вывести список всех файлов и подкаталогов, находящихся в текущем каталоге:
$dh = opendir(' . );
while ($file = readdir($dh)) :
print "$file "; endwhile;
closedir($dh);
chdir( )
Функция chdir( ) работает так же, как команда UNIX cd, — она осуществляет переход в каталог, заданный параметром. Синтаксис функции chdir( ):
int chdir (string каталог)
В следующем примере мы переходим в подкаталог book/ и выводим его содержимое:
$newdir = "book";
chdir($newdir) or die("Could not change to directory ($newdir)"); $dh = opendir(' . ');
print "Files:";
while ($file = readdir($dh)) ;
print "$file ";
endwhile;
closedir($dh);
rewinddir( )
Функция rewlnddir( ) переводит указатель текущей позиции в начало каталога, открытого функцией opendir( ). Синтаксис функции rewinddir( ):
void rewinddir (int нанипулятор_каталога)
Удаление файлов
unlink( )
Функция unlink( ) удаляет файл с заданным именем. Синтаксис:
int unlink (string файл)
Если вы работаете с РНР в системе Windows, при использовании этой функции иногда возникают проблемы. В этом случае можно воспользоваться описанной выше функцией system( ) и удалить файл командой DOS del:
system ("del filename.txt");
Запись в файл
С открытыми файлами выполняются две основные операции — чтение и запись.
is_writeable( )
Функция is_writeable( ) позволяет убедиться в том, что файл существует и для него разрешена операция записи. Возможность записи проверяется как для файла, так и для каталога. Синтаксис функции is_writeable( ):
bool is_writeable (string файл)
Одно важное обстоятельство: скорее всего, РНР будет работать под идентификатором пользователя, используемым web-сервером (как правило, «nobody»). Пример использования is_writeable( ) приведен в описании функции fwrite( ).
fwrite ( )
Функция fwrite( ) записывает содержимое строковой переменной в файл, заданный файловым манипулятором. Синтаксис функции fwrite( ):
int fwrite(int манипулятор, string переменная [, int длина])
Если при вызове функции передается необязательный параметр длина, запись останавливается либо после записи указанного количества символов, либо при достижении конца строки. Проверка возможности записи в файл продемонстрирована в следующем примере:
// Информация о трафике на пользовательском сайте
$data = "08:13:00|12:37:12|208.247.106.187|Win98";
$filename = "somefile.txt";
// Если файл существует и в него возможна запись
if ( is_writeable($filename) ) :
// Открыть файл и установить указатель текущей позиции в конец файла
$fh = fopen($filename, "a+");
// Записать содержимое $data в файл
$success - fwrite($fh, $data);
// Закрыть файл
fclose($fh); else :
print "Could not open Sfilename for writing";
endif;
?> Функция fputs( ) является псевдонимом fwrite( ) и может использоваться всюду, где используется fwrite( ).
fputs( )
Функция fputs( ) является псевдонимом fwrite( ) и имеет точно такой же синтаксис. Синтаксис функции fputs( ):
int fputs(int манипулятор, string переменная [, int длина])
Лично я предпочитаю использовать fputs( ). Следует помнить, что это всего лишь вопрос стиля, никак не связанный с какими-либо различиями между двумя функциями.
Запуск внешних программ
Сценарии РНР также могут выполнять программы, находящиеся на сервере. Такая возможность особенно часто используется при администрировании системы через web-браузер, а также для более удобного получения сводной информации о системе.
ехес( )
Функция ехес( ) запускает заданную программу и возвращает последнюю строку ее выходных данных. Синтаксис функции ехес( ):
string exec (string команда [, string массив [, int возврат]])
Обратите внимание: функция ехес( ) только выполняет команду, не выводя результатов ее работы. Все выходные данные команды можно сохранить в необязательном параметре массив. Кроме того, если при заданном параметре массив также задается переменная возврат, последней присваивается код возврата выполненной команды.
Листинг 7.7 показывает, как использовать функцию ехес( ) для выполнения системной функции UNIX ping.
Листинг 7.7. Проверка связи с сервером с применением функции ехес( )
exec("ping -с 5 www.php.net", $ping);
// В Windows - exec("ping -n 5 www.php.net. $ping);
for ($i=0; $i< count($ping);$i++) :
print " $ping[$i]";
endfor;
?>
Результат:
PING www.php.net (208.247.106.187): 56 data bytes
64 bytes from 208.247.106.187: icmp_seq=0 ttl=243 time=66.602 ms
64 bytes from 208.247.106.187: icmp_seq=1 ttl=243 time=55.723 ms
64 bytes from 208.247.106.187: icmp_seq=2 ttl=243 time=70.779 ms
64 bytes from 208.247.106.187: icmp_seq=3 ttl=243 time=55.339 ms
64 bytes from 208.247.106.187: icmp_seq=4 ttl=243 time=69.865 ms
-- www.php.net ping statistics --
5 packets transmitted. 5 packets received. 0% packet loss
round-trip min/avg/max/stddev - 55.339/63.662/70.779/6.783 ms
PHP 4 на практике
Дополнение и сжатие строк
В процессе форматирования часто возникает необходимость в изменении длины строки посредством дополнения или удаления символов. В РНР существует несколько функций, предназначенных для решения этой задачи.
chop ( )
Функция chop( ) возвращает строку после удаления из нее завершающих пропусков и символов новой строки. Синтаксис функции chop( ):
string chop(string строка)
В следующем примере функция chop( ) удаляет лишние символы новой строки:
$header = "Table of Contents\n\n";
$header = chop($header);
// $header = "Table of Contents"
str_pad( )
Функция str_pad( ) выравнивает строку до определенной длины заданными символами и возвращает отформатированную строку. Синтаксис функции str_pad( ):
string str_pad (string строка, int длина_дополнения [, string дополнение [, int тип_дополнения]])
Если необязательный параметр дополнение не указан, строка дополняется пробелами. В противном случае строка дополняется заданными символами. По умолчанию строка дополняется справа; тем не менее, вы можете передать в параметре тип_дополнения константу STR_PAD_RIGHT, STR_PAD_LEFT или STR_PAD_BOTH, что приведет к дополнению строки в заданном направлении. Пример демонстрирует дополнение строки функцией str_pad( ) с параметрами по умолчанию:
$food = "salad";
print str_pad ($food, 5): // Выводит строку "salad
В следующем примере используются необязательные параметры функции str_pad( ):
$header = "Table of Contents";
print str_pad ($header, 5, "=+=+=", STR_PAD_BOTH);
// В браузере выводится строка =+=+= Таbе of Contents=+=+="
trim ( )
Функция trim( ) удаляет псе пропуски с обоих краев строки и возвращает полученную строку. Синтаксис функции trim( ):
string trim (string страна]
К числу удаляемых пропусков относятся и специальные символы \n, \r, \t, \v и \0.
ltrim( )
Функция lrim( ) удаляет все пропуски и специальные символы с левого края строки и возвращает полученную строку. Синтаксис функции ltrim( ):
string ltrim (string строка)
Функция удаляет те же специальные символы, что и функция trim( ).
Другие строковые функции
Кроме функций для работы с регулярными выражениями, описанными в первой части этой главы, в РНР существует более 70 функций для выполнения практически всех мыслимых операций со строками. Подробное перечисление и описание всех функций выходит за рамки этой книги и приведет к обычному повторению информации, приведенной в документации РНР. По этой причине я превратил оставшуюся часть главы в своего рода список FAQ из вопросов, часто встречающихся во многих электронных конференциях РНР и на многих сайтах этой тематики. На мой взгляд, этот способ позволяет гораздо эффективнее описать общие принципы громадной библиотеки строковых функций РНР.
Функции РНР для работы с регулярными выражениями (POSIX-совместимые)
В настоящее время РНР поддерживает семь функций поиска с использованием регулярных выражений в стиле POSIX:
еrеg( );
еrеg_rерlасе( );
eregi( );
eregi_replace( );
split( );
spliti( );
sql_regcase( ).
Описания этих функций приведены в следующих разделах.
ereg( )
Функция еrеg( ) ищет в заданной строке совпадение для шаблона. Если совпадение найдено, возвращается TRUE, в противном случае возвращается FALSE. Синтаксис функции ereg( ):
int ereg (string шаблон, string строка [, array совпадения])
Поиск производится с учетом регистра алфавитных символов. Пример использования ereg( ) для поиска в строках доменов .соm:
$is_com - ereg("(\.)(com$)", $email):
// Функция возвращает TRUE, если $email завершается символами ".com"
// В частности, поиск будет успешным для строк
// "www.wjgilmore.com" и "someemail@apress.com"
Обратите внимание: из-за присутствия служебного символа $ регулярное выражение совпадает только в том случае, если строка завершается символами .com. Например, оно совпадет в строке "www.apress.com", но не совпадет в строке "www.apress.com/catalog".
Необязательный параметр совпадения содержит массив совпадений для всех подвыражений, заключенных в регулярном выражении в круглые скобки. В листинге 8.1 показано, как при помощи этого массива разделить URL на несколько сегментов.
Листинг 8.1.
Вывод элементов массива $regs
$url = "http://www.apress.com";
// Разделить $url на три компонента: "http://www". "apress" и "com"
$www_url = ereg("^(http://www)\.([[:alnum:]+\.([[:alnum:]]+)". $url, $regs);
if ($www_url) : // Если переменная $www_url содержит URL
echo $regs[0]; // Вся строка "http://www.apress.com"
print " ";
echo $regs[l]; // "http://www"
print " ";
echo $regs[2]; // "apress"
print " ";
echo $regs[3]; // "com" endif;
При выполнении сценария в листинге 8.1 будет получен следующий результат:
В РНР существует пять функций поиска по шаблону с использованием Perl-совместимых регулярных выражений:
preg_match( );
preg_match_all( );
preg_replace( );
preg_split( );
preg_grep( ).
Эти функции подробно описаны в следующих разделах.
preg_match( )
Функция pregjnatch( ) ищет в заданной строке совпадение для шаблона. Если совпадение найдено, возвращается TRUE, в противном случае возвращается FALSE. Синтаксис функции pregjnatch( ):
int pregjnatch (string шаблон, string строка [, array совпадения})
При передаче необязательного параметра совпадения массив заполняется совпадениями различных подвыражений, входящих в основное регулярное выражение. В следующем примере функция preg_match( ) используется для проведения поиска без учета регистра:
$linе = "Vi is the greatest word processor ever created!";
// Выполнить поиск слова "Vi" без учета регистра символов:
if (preg_match("/\bVi\b\i", $line, $matcn)) :
print "Match found!";
endif;
// Команда if в этом примере возвращает TRUE
preg_match_all( )
Функция preg_match_all( ) находит все совпадения шаблона в заданной строке.
Синтаксис функции preg_match_all( ):
Int preg_match_all (string шаблон, string строка, array совпадения [, int порядок])
Порядок сохранения в массиве совпадения текста, совпавшего с подвыражениями, определяется необязательным параметром порядок. Этот параметр может принимать два значения:
PREG_PATTERN_ORDER — используется по умолчанию, если параметр порядок не указан. Порядок, определяемый значением PREG_PATTERN_ORDER, на первый взгляд выглядит не совсем логично: первый элемент (с индексом 0) содержит массив совпадений для всего регулярного выражения, второй элемент (с индексом 1) содержит массив всех совпадений для первого подвыражения в круглых скобках и т. д.;
PREG_SET_ORDER — порядок сортировки массива несколько отличается от принятого по умолчанию. Первый элемент (с индексом 0) содержит массив с текстом, совпавшим со всеми подвыражениями в круглых скобках для первого найденного совпадения. Второй элемент (с индексом 1) содержит аналогичный массив для второго найденного совпадения и т. д.
http://www.apress.com http://www apress com
ereg_replace( )
Функция ereg_replace( ) ищет в заданной строке совпадение для шаблона и заменяет его новым фрагментом. Синтаксис функции ereg_replace( ):
Функция ereg_replace( ) работает по тому же принципу, что и ereg( ), но ее возможности расширены от простого поиска до поиска с заменой. После выполнения замены функция возвращает модифицированную строку. Если совпадения
отсутствуют, строка остается в прежнем состоянии. Функция ereg_replace( ), как и еrеg( ), учитывает регистр символов. Ниже приведен простой пример, демонстрирующий применение этой функции:
У средств поиска с заменой в языке РНР имеется одна интересная возможность — возможность использования обратных ссылок на части основного выражения, заключенные в круглые скобки. Обратные ссылки похожи на элементы необязательного параметра-массива совпадения функции еrеg( ) за одним исключением: обратные ссылки записываются в виде \0, \1, \2 и т. д., где \0 соответствует всей строке, \1 — успешному совпадению первого подвыражения и т. д. Выражение может содержать до 9 обратных ссылок. В следующем примере все ссылки на URL в тексте заменяются работающими гиперссылками:
Функция eregi( ) ищет в заданной строке совпадение для шаблона. Синтаксис функции eregi( ):
int eregi (string шаблон, string строка [, array совпадения])
Поиск производится без учета регистра алфавитных символов. Функция eregi( ) особенно удобна при проверке правильности введенных строк (например, паролей). Использование функции eregi( ) продемонстрировано в следующем примере:
$password = "abc";
if (! eregi("[[:alnum:]]{8.10}, $password) :
print " Invalid password! Passwords must be from 8 through 10 characters in length.";
endif;
// В результате выполнения этого фрагмента выводится сообщение об ошибке.
// поскольку длина строки "abc" не входит в разрешенный интервал
// от 8 до 10 символов.
eregi_replace( )
Функция eregi_replасе( ) работает точно так же, как ereg_replace( ), за одним исключением: поиск производится без учета регистра символов. Синтаксис функции ereg_replace( ):
Функция split( ) разбивает строку на элементы, границы которых определяются по заданному шаблону. Синтаксис функции split( ):
array split (string шаблон, string строка [, int порог])
Необязательный параметр порог определяет максимальное количество элементов, на которые делится строка слева направо. Если шаблон содержит алфавитные символы, функция spl it( ) работает с учетом регистра символов. Следующий пример демонстрирует использование функции split( ) для разбиения канонического IP-адреса на триплеты:
$ip = "123.345.789.000"; // Канонический IP-адрес
$iparr = split ("\.", $ip) // Поскольку точка является служебным символом.
// ее необходимо экранировать.
print "$iparr[0] "; // Выводит "123"
print "$iparr[1] "; // Выводит "456"
print "$iparr[2] "; // Выводит "789"
print "$iparr[3] "; // Выводит "000"
spliti( )
Функция spliti( ) работает точно так же, как ее прототип split( ), за одним исключением: она не учитывает регистра символов. Синтаксис функции spliti( ):
array spliti (string шаблон, string строка [, int порог])
Разумеется, регистр символов важен лишь в том случае, если шаблон содержит алфавитные символы. Для других символов выполнение spliti( ) полностью аналогично split( ).
sql_regcase( )
Вспомогательная функция sql_regcase( ) заключает каждый символ входной строки в квадратные скобки и добавляет к нему парный символ. Синтаксис функции sql_regcase( ):
string sql_regcase (string строка)
Если алфавитный символ существует в двух вариантах (верхний и нижний регистры), выражение в квадратных скобках будет содержать оба варианта; в противном случае исходный символ повторяется дважды. Функция sql_regcase( ) особенно удобна при использовании РНР с программными пакетами, поддерживающими регулярные выражения в одном регистре. Пример преобразования строки функцией sql_regcase( ):
$version = "php 4.0";
print sql_regcase($version);
// Выводится строка [Pp][Hh][Pp][ ][44][..][00]
Следующий пример показывает, как при помощи функции preg_match_al( ) найти весь текст, заключенный между тегами HTML ...:
Функция preg_repl ace( ) работает точно так же, как и ereg_replасе( ), за одним исключением — регулярные выражения могут использоваться в обоих параметрах, шаблон и замена. Синтаксис функции preg_replace( ):
Необязательный параметр порог определяет максимальное количество замен в строке. Интересный факт: параметры шаблон и замена могут представлять собой масивы. Функция preg_replace( ) перебирает элементы обоих массивов и выполняет замену по мере их нахождения.
preg_split( )
Функция preg_spl it( ) аналогична split( ) за одним исключением — параметр шаблон может содержать регулярное выражение. Синтаксис функции preg_split( ):
array preg_split (string шаблон, string строка [, int порог [, int флаги]])
Необязательный параметр порог определяет максимальное количество элементов, на которые делится строка. В следующем примере функция preg_split( ) используется для выборки информации из переменной.
Функция preg_grep( ) перебирает все элементы заданного массива и возвращает все элементы, в которых совпадает заданное регулярное выражение. Синтаксис функции preg_grep():
array preg_grep (string шаблон, array массив)
Пример использования функции preg_grep( ) для поиска в массиве слов, начинающихся на р:
Одной из интересных особенностей Perl является использование метасимволов при поиске. Метасимвол
[Следует отметить, что авторское толкование термина «метасимвол» противоречит не только всем традициям, по и официальной документации РНР. — Примеч. перев.] представляет собой алфавитный символ с префиксом \ — признаком особой интерпретации следующего символа. Например, метасимвол \d может использоваться при поиске денежных сумм:
/([d]+)000/
Комбинация \d обозначает любую цифру. Конечно, в процессе поиска часто возникает задача идентификации алфавитно-цифровых символов, поэтому в Perl для них был определен метасимвол \w:
/<([\w]+)>/
Этот шаблон совпадает с конструкциями, заключенными в угловые скобки, — например, тёгами HTML. Кстати, метасимвол \W имеет прямо противоположный смысл и используется для идентификации символов, не являющихся алфавитно-цифровыми.
Еще один полезный метасимвол, \b, совпадает с границами слов:
/sa\b/
Поскольку метасимвол границы слова расположен справа от текста, этот шаблон совпадет в строках salsa и lisa, но не в строке sand. Противоположный метасимвол, \В, совпадает с чем угодно, кроме границы слова:
/sa\B/
Шаблон совпадает в таких строках, как sand и Sally, но не совпадает в строке salsa.
Модификаторы
Модификаторы заметно упрощают работу с регулярными выражениями. Впрочем, модификаторов много, и в табл. 8.1 приведены лишь наиболее интересные из них. Модификаторы перечисляются сразу же после регулярного выражения — например, /string/i.
Таблица 8.1.
Примеры модификаторов
Модификатор
Описание
m
Фрагмент текста интерпретируется как состоящий из нескольких «логических строк». По умолчанию специальные символы ^ и $ совпадают только в начале и в конце всего фрагмента. При включении «многострочного режима» при помощи модификатора m^ и $ будут совпадать в начале и в конце каждой логической строки внутри фрагмента
s
По смыслу противоположен модификатору m — при поиске фрагмент интерпретируется как одна строка, а все внутренние символы новой строки игнорируются
i
Поиск выполняется без учета регистра символов
Вводный курс получился очень кратким, поскольку полноценное описание по регулярным выражениям выходит за рамки этой книги и требует нескольких глав вместо нескольких страниц. За дополнительной информацией о синтаксисе регулярных выражений обращайтесь к следующим ресурсам Интернета:
Обработка строковых данных без применения регулярных выражений
При обработке больших объемов информации функции регулярных выражений сильно замедляют выполнение программы. Эти функции следует применять лишь при обработке относительно сложных строк, в которых регулярные выражения действительно необходимы. Если же анализ текста выполняется по относительно простым правилам, можно воспользоваться стандартными функциями РНР, которые заметно ускоряют обработку. Все эти функции описаны ниже.
strtok( )
Функция strtok( ) разбивает строку на лексемы по разделителям, заданным вторым параметром. Синтаксис функции strtok( ):
string strtok (string строка, string разделители)
У функции strtok( ) есть одна странность: чтобы полностью разделить строку, функцию необходимо последовательно вызвать несколько раз. При очередном вызове функция выделяет из строки следующую лексему. При этом параметр строка задается всего один раз — функция отслеживает текущую позицию в строке до тех пор, пока строка не будет полностью разобрана на лексемы или не будет задан новый параметр строка. Следующий пример демонстрирует разбиение строки по нескольким разделителям:
$info = "WJ Gi1more:wjgilmore@hotmail.com | Columbus, Ohio";
// Ограничители - двоеточие (:), вертикальная черта (|) и запятая (.) $tokens = ":|,";
$tokenized = strtok($info, $tokens);
// Вывести элементы массива $tokenized
while ($tokenized) :
echo "Element = $tokenized ";
// Обратите внимание: при последующих вызовах strtok
// первый аргумент не передается
$tokenized = strtok($tokens);
endwhile;
Результат:
Element = WJGilmore
Element = wjgilmore@hotmail.com
Element = Columbus
Element = Ohio
parse_str( )
Функция parse_str( ) выделяет в строке пары «переменная-значение» и присваивает значения переменных в текущей области видимости. Синтаксис функции parse_str( ):
void parse_str (string строка)
Функция parse_str( ) особенно удобна при обработке URL, содержащих данные форм HTML или другую расширенную информацию. В следующем примере анализируется информация, переданная через URL. Строка представляет собой стандартный способ передачи данных между страницами либо откомпилированных в гиперссылке, либо введенных в форму HTML:
$url = "fname=wj&lname=gilmore&zip=43210";
parse_str($url);
// После выполнения parse_str( ) доступны следующие переменные:
// $fname = "wj":
// $lname = "gilmore";
// $zip = "43210"
Поскольку эта функция создавалась для работы с URL, она игнорирует символ амперсанд (&).
Работа с формами HTML в РНР описана в главе 10.
explode ( )
Функция explode( ) делит строку на элементы и возвращает эти элементы в виде массива. Синтаксис функции explode( ):
array explode (string разделитель, string строка [, int порог])
Разбиение происходит по каждому экземпляру разделителя, причем количество полученных фрагментов может ограничиваться необязательным параметром порог. Разделение строки функцией explode( ) продемонстрировано в следующем примере:
$info = "wilson | baseball | indians";
$user = explode("|", $info);
// $user[0] = "wilson";
// $user[1] = "baseball";
// $user[2] = "Indians";
Функция explode( ) практически идентична функции регулярных выражений POSIX split( ), описанной выше. Главное различие заключается в том, что передача регулярных выражений в параметрах допускается только при вызове split( ).
implode ( )
Если функция explode( ) разделяет строку на элементы массива, то ее двойник — функция implode( ) - объединяет массив в строку. Синтаксис функции implode( ):
Функция strpos( ) находит в строке первый экземпляр заданной подстроки. Синтаксис функции strpos( ):
int strpos (string строка, string подстрока [, int смещение])
Необязательный параметр offset задает позицию, с которой должен начинаться поиск. Если подстрока не найдена, strpos( ) возвращает FALSE (0).
В следующем примере определяется позиция первого вхождения даты в файл журнала:
$log = "
206.169.23.11:/www/:2000-08-10
206.169.23.11:/www/logs/:2000-02-04
206.169.23.11:/www/img/:1999-01-31";
// В какой позиции в журнале впервые встречается 1999 год?
$pos = strpos($log, "1999");
// $pos = 95. поскольку первый экземпляр "1999"
// находится в позиции 95 строки, содержащейся в переменной $log
strrpos( )
Функция strrpos( ) находит в строке последний экземпляр заданного символа. Синтаксис функции strrpos( ):
int strpos (string строка, char символ)
По возможностям эта функция уступает своему двойнику — функции strpos( ), поскольку она позволяет искать только отдельный символ, а не всю строку. Если во втором параметре strrpos( ) передается строка, при поиске будет использован только ее первый символ.
str_replace( )
Функция str_replace( ) ищет в строке все вхождения заданной подстроки и заменяет их новой подстрокой. Синтаксис функции str_replace( ):
Функция substr_replace( ), описанная ниже в этом разделе, позволяет провести заме ну лишь в определенной части строки. Ниже показано, как функция str_replace( ) используется для проведения глобальной замены в строке.
Если подстрока ни разу не встречается в строке, исходная строка не изменяется:
$favorite_food = "My favorite foods are ice cream and chicken wings";
Функция substr( ) возвращает часть строки, начинающуюся с заданной начальной позиции и имеющую заданную длину. Синтаксис функции substr( ):
string substr (string строка, int начало [, int длина])
Если необязательный параметр длина не указан, считается, что подстрока начинается с заданной начальной позиции и продолжается до конца строки. При использовании этой функции необходимо учитывать четыре обстоятельства:
если параметр начало положителен, возвращаемая подстрока начинается с позиции строки с заданным номером;
если параметр начало отрицателен, возвращаемая подстрока начинается с позиции (длина строки - начало);
если параметр длина положителен, в возвращаемую подстроку включаются все символы от позиции начало до позиции начало+длина. Если последняя величина превышает длину строки, возвращаются символы до конца строки;
если параметр длина отрицателен, возвращаемая подстрока заканчивается на заданном расстоянии от конца строки.
Помните о том, что параметр начало определяет смещение от первого символа строки; таким образом, возвращаемая строка в действительности начинается с символа с номером (начало + 1).
Следующий пример демонстрирует выделение части строки функцией substr( ):
$car = "1944 Ford"; Smodel = substr($car, 6);
// Smodel = "Ford"
Пример с положительным параметром длина:
$car = "1944 Ford";
$model = substr($car, 0, 4);
// $model = "1944"
Пример с отрицательным параметром длина:
$car = "1944 Ford";
$model = substr($car, 2, -5);
// $model = "44"
substr_count( )
Функция substr_count( ) возвращает количество вхождений подстроки в заданную строку. Синтаксис функции substr_count( ):
int substr_count (string строка, string подстрока)
В следующем примере функция substr_count( ) подсчитывает количество вхождений подстроки ain:
$tng_twist = "The rain falls mainly on the plains of Spain";
$count = substr_count($tng_twist, "ain");
// $count = 4
substr_replace( )
Функция substr_replace( ) заменяет часть строки, которая начинается с заданной позиции. Если задан необязательный параметр длина, заменяется фрагмент заданной длины; в противном случае производится замена по всей длине заменяющей строки. Синтаксис функции substr_replace( ):
string substr_replace (string строка, string замена, int начало [, int длина])
Параметры начало и длина задаются по определенным правилам:
если параметр начало положителен, замена начинается с заданной позиции;
если параметр начало отрицателен, замена начинается с позиции (длина строки -начало);
если параметр длина положителен, заменяется фрагмент заданной длины;
если параметр длина отрицателен, замена завершается в позиции (длина строки -длина).
Простая замена текста функцией substr_replace( ) продемонстрирована в следующем примере:
$favs = " 's favorite links";
$name = "Alessia";
// Параметры "0, 0" означают, что заменяемый фрагмент начинается
// и завершается в первой позиции строки.
$favs - substr_replace($favs, $name, 0, 0);
print $favs:
Результат:
Alessia's favorite links
Определение длины строки
Длину строки в символах можно определить при помощи функции strlen( ). Синтаксис .функции strlen( ):
int strlen (string строка)
Следующий пример демонстрирует определение длины строки функцией strlen( ):
$string = "hello";
$length = strlen($string);
// $length = 5
Преобразование HTML в простой текст
Иногда возникает необходимость преобразовать файл в формате HTML в простой текст. Функции, описанные ниже, помогут вам в решении этой задачи.
strip_tags( )
Функция strip_tags( ) удаляет из строки все теги HTML и РНР, оставляя в ней только текст. Синтаксис функции strip_tags( ):
string strip_tags (string строка [, string разрешенные_тerи])
Необязательный параметр разрешенные_теги позволяет указать теги, которые должны пропускаться в процессе удаления.
Ниже приведен пример удаления из строки всех тегов HTML функцией strip_tags( ):
$user_input = "I just love РНР and gourment recipes!";
$stripped_input = strip_tags($user_input);
// $stripped_input = "I just love PHP and gourmet recipes!";
В следующем примере удаляются не все, а лишь некоторые теги:
$input = "I love to eat!!";
$strip_input = strip_tags ($user_input, "");
// $strip_input = "I love to eat!!"; Удаление тегов из текста также производится функцией fgetss( ), описанной в главе 7. get_meta_tags( )
Хотя функция get_meta_tags( ) и не имеет прямого отношения к преобразованию текста, зто весьма полезная функция, о которой следует упомянуть. Синтаксис функции get_meta_tags( ):
array get_meta_tags (string имя_файла/URL [, int включение_пути])
Функция get_meta_tags( ) предназначена для поиска в файле HTML тегов МЕТА.
Теги МЕТА содержат информацию о странице, используемую главным образом поисковыми системами. Эти теги находятся внутри пары тегов
.... Применение тегов МЕТА продемонстрировано в следующем фрагменте (назовем его example.html, поскольку он будет использоваться в листинге 8.2):
PHP Recipes
Функция get_meta_tags( ) ищет в заголовке документа теги, начинающиеся словом МЕТА, и сохраняет имена тегов и их содержимое в ассоциативном массиве. В листинге 8.2 продемонстрировано применение этой функции к файлу example.html.
Листинг 8.2.
Извлечение тегов МЕТА из файла HTML функцией get_meta_tags( )
$meta_tags = get_meta_tags("example.html"):
// Переменная $meta_tags содержит массив со следующей информацией:
// $meta_tags["description"] = "PHP Recipes provides savvy readers with the latest in PHP
programming and gourmet cuisine";
// $meta_tags["author"] = "WJ Gilmore";
Интересная подробность: данные тегов МЕТА можно извлекать не только из файлов, находящихся на сервере, но и из других URL.
Теги МЕТА и их использование превосходно описаны в статье Джо Берна (Joe Burn) «So, You Want a Meta Command, Huh?» на сайте HTML Goodies: http://htmlgoodies.earthweb.com/tutors/meta.html.
Преобразование строк и файлов к формату HTML и наоборот
Преобразовать строку или целый файл к формату, подходящему для просмотра в web-браузере (или наоборот), проще, чем может показаться на первый взгляд. В РНР для этого существуют специальные функции.
Преобразование текста в HTML
Быстрое преобразование простого текста к формату web-браузера — весьма распространенная задача. В ее решении вам помогут функции, описанные в этом разделе.
nl2br( )
Функция nl2br( ) заменяет все символы новой строки (\n) эквивалентными конструкциями HTML .
Синтаксис функции nl2br( ):
string nl2br (string строка)
Символы новой строки могут быть как видимыми (то есть явно включенными в строку), так и невидимыми (например, введенными в редакторе). В следующем примере текстовая строка преобразуется в формат HTML посредством замены символов \n разрывами строк:
// Текстовая строка, отображаемая в редакторе.
$text_recipe = "
Party Sauce recipe:
1 can stewed tomatoes
3 tablespoons fresh lemon juice
Stir together, server cold.";
// Преобразовать символы новой строки в
$htinl_recipe = nl2br($text_recipe)
При последующем выводе $html_recipe браузеру будет передан следующий текст в формате HTML:
Party Sauce recipe:
1 can stewed tomatoes
3 tablespoons fresh lemon juice
Stir together, server cold.
htmlentities( )
Функция htmlentities( ) преобразует символы в эквивалентные конструкции HTML. Синтаксис функции htmlentities:
string htmlentities (string строка)
В следующем примере производится необходимая замена символов строки для вывода в браузере:
$user_input = "The cookbook, entitled Cafe Francaise' costs < $42.25.";
$converted_input = htmlentities($user_input);
// $converted_input = "The cookbook, entitled 'Cafè
// Fracçiaise' costs < 42.25."; Функция htmlentities( ) в настоящее время работает только для символов кодировки ISO-8559-1 (ISO-Latin-1). Кроме того, она не преобразует пробелы в , как следовало бы ожидать.
htmlspecialchars( )
Функция htmlspecialchars( ) заменяет некоторые символы, имеющие особый смысл в контексте HTML, эквивалентными конструкциями HTML. Синтаксис функции htmlspecialchars( ):
string htmlspecialchars (string строка)
Функция html special chars( ) в настоящее время преобразует следующие символы:
& преобразуется в &; " " преобразуется в ";
< преобразуется в <; > преобразуется в >.
В частности, эта функция позволяет предотвратить ввод пользователями разметки HTML в интерактивных web-приложениях (например, в электронных форумах). Ошибки, допущенные в разметке HTML, могут привести к тому, что вся страница будет формироваться неправильно. Впрочем, у этой задачи существует и более эффективное решение — полностью удалить теги из строки функцией strip_tags( ).
Следующий пример демонстрирует удаление потенциально опасных символов функцией htmlspeclalchars( ):
$user_input = "I just can't get «enough» of PHP & those fabulous cooking recipes!";
$conv_input = htmlspecialchars($user_input);
// $conv_input = "I just can't <<enough>> of PHP & those fabulous cooking
recipes!"
Если функция htmlspecialchars( ) используется в сочетании с nl2br( ), то последнюю следует вызывать после htmlspecialchars( ). В противном случае конструкции , сгенерированные при вызове nl2br( ), преобразуются в видимые символы.
get_html_translation_table ( )
Функция get_html_translation_table( ) обеспечивает удобные средства преобразования текста в эквиваленты HTML Синтаксис функции get_htrril_translation_table( ):
string get_html_translation_table (int таблица)
Функция get_html_translation_table( ) возвращает одну из двух таблиц преобразования (определяется параметром таблица), используемых в работе стандартных функций htmlspecialchars( ) и htmlentities( ). Возвращаемое значение может использоваться в сочетании с другой стандартной функцией, strtr( ) (см. далее), для преобразования текста в код HTML.
Параметр таблица принимает одно из двух значений:
HTML_ENTITIES;
HTML_SPECIALCHARS.
В следующем примере функция get_html_translation_table( ) используется при преобразовании текста в код HTML:
$string = "La pasta e il piatto piu amato in Italia";
// Специальные символы преобразуются в конструкции HTML
// и правильно отображаются в браузере.
Кстати, функция array_flip( ) позволяет провести преобразование текста в HTML в обратном направлении и восстановить исходный текст. Предположим, что вместо вывода результата strtr( ) в предыдущем примере мы присвоили его переменной $translated string.
В следующем примере исходный текст восстанавливается функцией array_flip( ):
$translate = array_flip($translate);
$translated_string - "La pasta é il piatto piú amato in Italia";
// $original_string = "La pasta e il piatto piu amato in Italia";
strtr( )
Функция strtr( ) транслирует строку, то есть заменяет в ней все символы, входящие в строку источник, соответствующими символами строки приемник. Синтаксис функции strtr( ):
Если строки источник и приемник имеют разную длину, длинная строка усекается до размеров короткой строки.
Существует альтернативный синтаксис вызова strtr( ) с двумя параметрами; в этом случае второй параметр содержит ассоциативный массив, ключи которого соответствуют заменяемым подстрокам, а значения — заменяющим подстрокам. В следующем примере теги HTML заменяются XML-подобными конструкциями:
$source = array("" => "
". "
=> "");
$string = "
Today In PHP-Powered News"
";
print strtr($string, $source);
// Выводится строка "Today in PHP-Powered News"
Преобразование строки к верхнему и нижнему регистру
В РНР существует четыре функции, предназначенных для изменения регистра строки:
strtolower( );
strtoupper( );
ucfirst( );
ucwords( ).
Все эти функции подробно описаны ниже. strtolower( )
Функция strtolower( ) преобразует все алфавитные символы строки к нижнему регистру. Синтаксис функции strtolower( ):
string strtolower(string строка)
Неалфавитные символы функцией не изменяются. Преобразование строки к нижнему регистру функцией strtolower( ) продемонстрировано в следующем примере:
$sentence = "COOKING and PROGRAMMING PHP are my TWO favorite pastimes!";
$sentence = strtolower($sentence);
// После вызова функции $sentence содержит строку
// "cooking and programming php are my two favorite pastimes!"
strtoupper( )
Строки можно преобразовывать не только к нижнему, но и к верхнему регистру. Преобразование выполняется функцией strtoupper( ), имеющей следующий синтаксис:
string strtoupper (string строка)
Неалфавитные символы функцией не изменяются. Преобразование строки к верхнему регистру функцией strtoupper( ) продемонстрировано в следующем примере:
$sentence = "cooking and programming PHP are my two favorite pastimes!";
$sentence = strtoupper($sentence);
// После вызова функции $sentence содержит строку
// "COOKING AND PROGRAMMING PHP ARE MY TWO FAVORITE PASTIMES!"
ucfirst( )
Функция ucfirst( ) преобразует к верхнему регистру первый символ строки — при условии, что он является алфавитным символом. Синтаксис функции ucfirst( ):
string ucfirst (string строка)
Неалфавитные символы функцией не изменяются. Преобразование первого символа строки функцией ucfirst( ) продемонстрировано в следующем примере:
&sentence = "cooking and programming PHP are my two favorite pastimes!";
$sentence = ucfirst($sentence);
// После вызова функции $sentence содержит строку
// "Cooking and programming PHP are mу two favorite pastimes!"
ucwords( )
Функция ucwords( ) преобразует к верхнему регистру первую букву каждого слова в строке. Синтаксис функции ucwords( ):
string ucwords (string строка")
Неалфавитные символы функцией не изменяются. «Слово» определяется как последовательность символов, отделенная от других элементов строки пробелами. В следующем примере продемонстрировано преобразование первых символов слов функцией ucwords( ):
$sentence = "cooking and programming PHP are my two favorite pastimes!";
$sentence = ucwords($sentence);
// После вызова функции $sentence содержит строку
// "Cooking And Programming PHP Are My Two Favorite Pastimes!"
Проект: идентификация браузера
Каждый программист, пытающийся создать удобный web-сайт, должен учитывать различия в форматировании страниц при просмотре сайта в разных браузерах и операционных системах. Хотя консорциум W3 (http://www.w3.org) продолжает публиковать стандарты, которых должны придерживаться программисты при создании web-приложений, разработчики браузеров любят дополнять эти стандарты своими маленькими «усовершенствованиями», что в конечном счете вызывает хаос и путаницу. Разработчики часто решают эту проблему, создавая разные страницы для каждого типа браузера и операционной системы — при этом объем работы значительно увеличивается, но зато итоговый сайт идеально подходит для любого пользователя. Результат — хорошая репутация сайта и уверенность в том, что пользователь посетит его снова.
Чтобы пользователь мог просматривать страницу в формате, соответствующем специфике его браузера и операционной системы, из входящего запроса на получение страницы извлекается информация о браузере и платформе. После получения необходимых данных пользователь перенаправляется на нужную страницу.
Приведенный ниже проект (sniffer.php) показывает, как использовать функции РНР для работы с регулярными выражениям с целью получения информации по запросам. Программа определяет тип и версию браузера и операционной системы, после чего выводит полученную информацию в окне браузера. Но прежде чем переходить к непосредственному анализу программы, я хочу представить один из главных ее компонентов — стандартную переменную РНР $HTTP_USER_AGENT. В этой переменной в строковом формате хранятся различные сведения о браузере и операционной системе пользователя — именно то, что нас интересует. Эту информацию можно легко вывести на экран всего одной командой:
echo $HTTP USER_AGENT;
?>
При работе в Internet Explorer 5.0 на компьютере с Windows 98 результат будет выглядеть так:
Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
Для Netscape Navigator 4.75 выводятся следующие данные:
Mozilla/4.75 (Win98; U)
Sniffer. php извлекает необходимые данные из $HTTP_USER_AGENT при помощи функций обработки строк и регулярных выражений. Алгоритм программы на псевдокоде:
Определить две функции для идентификации браузера и операционной системы: browser_info( ) и opsys_info( ). Начнем с псевдокода функции browser_info( ).
Определить тип браузера, используя функцию егед( ). Хотя эта функция работает медленнее упрощенных строковых функций типа strstr( ), в данном случае она удобнее, поскольку регулярное выражение позволяет определить версию браузера.
Воспользоваться конструкцией if/elseif для идентификации следующих браузеров и их версий: Internet Explorer, Opera, Netscape и браузер неизвестного типа.
Вернуть информацию о типе и версии браузера в виде массива.
Функция opsys_info( ) определяет тип операционной системы. На этот раз используется функция strstr( ), поскольку тип ОС определяется и без применения регулярных выражений.
Воспользоваться конструкцией if/elseif для идентификации следующих систем: Windows, Linux, UNIX, Macintosh и неизвестная операционная система.
Вернуть информацию об операционной системе.
Листинг 8.3.
Идентификация типа браузера и операционной системы клиента
/*
Файл : sniffer.php
Назначение: Идентификация типа/версии браузера и платформы
Автор: В. Дж. Гилмор
Дата : 24 августа 2000 г.
*/
// Функция: browser_info
// Назначение: Возвращает тип и версию браузера
function browser_info ($agent) {
// Определить тип браузера
// Искать сигнатуру Internet Explorer
if (ereg('MSIE ([0-9].[0-9]{1,2})', $agent, $version))
// Если это не Internet Explorer, Opera или Netscape.
// значит, мы обнаружили неизвестный браузер,
else :
$browse_type = "Unknown";
$browse_version = "Unknown";
endif:
// Вернуть тип и версию браузера в виде массива
return array ($browse_type, $browse_version);
} // Конец функции browser_info
// Функция: opsys_info
// Назначение: Возвращает информацию об операционной системе пользователя
function opsys_info($agent) {
// Идентифицировать операционную систему
// Искать сигнатуру Windows
if ( strstr ($agent. 'win') ) :
$opsys = "windows";
// Искать сигнатуру Linux
elseif ( strstr($agent, 'Linux') ) :
$opsys = "Linux";
// Искать сигнатуру UNIX
elseif ( strstr (Sagent, 'Unix') ) :
$opsys = "Unix";
// Искать сигнатуру Macintosh
elseif ( strstr ($agent, 'Mac') ) :
$opsys = "Macintosh";
// Неизвестная платформа else :
$opsys = "Unknown";
endif;
// Вернуть информацию об операционной системе
return $opsys;
} // Конец функции opsys_info
// Сохранить возвращаемый массив в списке
list ($browse_type. $browse_version) = browser_info ($HTTP_USER_AGENT); Soperating_sys = opsysjnfo ($HTTP_USER_AGENT);
print "Browser Type: $browse_type ";
print "Browser Version: $browse_version ";
print "Operating System: $operating_sys ":
?>
Вот и все! Например, если пользователь работает в браузере Netscape 4.75 на компьютере с системой Windows, будет выведен следующий результат:
Browser Type: Netscape
Browser Version: 4.75
Operating System: Windows
В следующей главе вы научитесь осуществлять переходы между страницами и даже создавать списки стилей (style sheets) для конкретных операционной системы и браузера.
Регулярные выражения
Регулярные выражения лежат в основе всех современных технологий поиска по шаблону. Регулярное выражение представляет собой последовательность простых и служебных символов, описывающих искомый текст. Иногда регулярные выражения бывают простыми и понятными (например, слово dog), но часто в них присутствуют служебные символы, обладающие особым смыслом в синтаксисе регулярных выражений, — например, <(?)>.*<\/.?>.
В РНР существуют два семейства функций, каждое из которых относится к определенному типу регулярных выражений: в стиле POSIX или в стиле Perl. Каждый тип регулярных выражений обладает собственным синтаксисом и рассматривается в соответствующей части главы. На эту тему были написаны многочисленные учебники, которые можно найти как в Web, так и в книжных магазинах. Поэтому я приведу лишь основные сведения о каждом типе, а дальнейшую информацию при желании вы сможете найти самостоятельно. Если вы еще не знакомы с принципами работы регулярных выражений, обязательно прочитайте краткий вводный курс, занимающий всю оставшуюся часть этого раздела. А если вы хорошо разбираетесь в этой области, смело переходите к следующему разделу.
Синтаксис регулярных выражений (POSIX)
Структура регулярных выражений POSIX чем-то напоминает структуру типичных математических выражений — различные элементы (операторы) объединяются друг с другом и образуют более сложные выражения. Однако именно смысл объединения элементов делает регулярные выражения таким мощным и выразительным средством. Возможности не ограничиваются поиском литерального текста (например, конкретного слова или числа); вы можете провести поиск строк с разной семантикой, но похожим синтаксисом — например, всех тегов HTML в файле.
Простейшее регулярное выражение совпадает с одним литеральным символом — например, выражение g совпадает в таких строках, как g, haggle и bag. Выражение, полученное при объединении нескольких литеральных символов, совпадает по тем же правилам — например, последовательность gan совпадает в любой строке, содержащей эти символы (например, gang, organize или Reagan).
Оператор | (вертикальная черта) проверяет совпадение одной из нескольких альтернатив. Например, регулярное выражение php | zend проверяет строку на наличие php или zend.
Квадратные скобки
Квадратные скобки ([ ]) имеют особый смысл в контексте регулярных выражений — они означают «любой символ из перечисленных в скобках». В отличие от регулярного выражения php, которое совпадает во всех строках, содержащих литеральный текст php, выражение [php] совпадает в любой строке, содержащей символы р или h. Квадратные скобки играют важную роль при работе с регулярными выражениями, поскольку в процессе поиска часто возникает задача поиска символов из заданного интервала. Ниже перечислены некоторые часто используемые интервалы:
[0-9] — совпадает с любой десятичной цифрой от 0 до 9;
[a-z] — совпадает с любым символом нижнего регистра от а до z;
[A-Z] — совпадает с любым символом верхнего регистра от А до Z;
[a -Z] — совпадает с любым символом нижнего или верхнего регистра от а до Z.
Конечно, перечисленные выше интервалы всего лишь демонстрируют общий принцип. Например, вы можете воспользоваться интервалом [0-3] для обозначения любой десятичной цифры от 0 до 3 или интервалом [b-v] для обозначения любого символа нижнего регистра от b до v. Короче говоря, интервалы определяются совершенно произвольно.
Квантификаторы
Существует особый класс служебных символов, обозначающих количество повторений отдельного символа или конструкции, заключенной в квадратные скобки. Эти служебные символы (+, * и {...}) называются квантификаторами. Принцип их действия проще всего пояснить на примерах:
р+ означает один или несколько символов р, стоящих подряд;
р* означает ноль и более символов р, стоящих подряд;
р? означает ноль или один символ р;
р{2} означает два символа р, стоящих подряд;
р{2,3} означает от двух до трех символов р, стоящих подряд;
р{2,} означает два и более символов р, стоящих подряд.
Прочие служебные символы
Служебные символы $ и ^ совпадают не с символами, а с определенными позициями в строке. Например, выражение р$ означает строку, которая завершается символом р, а выражение ^р — строку, начинающуюся с символа р.
Конструкция [^a-zA-Z] совпадает с любым символом, не входящим в указаные интервалы (a-z и A-Z).
Служебный символ . (точка) означает «любой символ». Например, выражение р.р совпадает с символом р, за которым следует произвольный символ, после чего опять следует символ р.
Объединение служебных символов приводит к появлению более сложных выражений. Рассмотрим несколько примеров:
^.{2}$ — любая строка, содержащая ровно два символа;
(.*) — произвольная последовательность символов, заключенная между <Ь> и Ь> (вероятно, тегами HTML для вывода жирного текста);
p(hp)* — символ р, за которым следует ноль и более экземпляров последовательности hp (например, phphphp).
Иногда требуется найти служебные символы в строках вместо того, чтобы использовать их в описанном специальном контексте. Для этого служебные символы экранируются обратной косой чертой (\). Например, для поиска денежной суммы в долларах можно воспользоваться выражением \$[0-9]+, то есть «знак доллара, за которым следует одна или несколько десятичных цифр». Обратите внимание на обратную косую черту перед $. Возможными совпадениями для этого регулярного выражения являются $42, $560 и $3.
Стандартные интервальные выражения (символьные классы)
Для удобства программирования в стандарте POSIX были определены некоторые стандартные интервальные выражения, также называемые символьными классами (character classes). Символьный класс определяет один символ из заданного интервала — например, букву алфавита или цифру:
[[:alpha:]] — алфавитный символ (aA-zZ);
[[:digit:]]-цифра (0-9);
[[:alnum:]] — алфавитный символ (aA-zZ) или цифра (0-9);
[[:space:]] — пропуски (символы новой строки, табуляции и т. д.).
Синтаксис регулярных выражений в стиле Perl
Perl (http://www.perl.com) давно считается одним из самых лучших языков обработки текстов. Синтаксис Perl позволяет осуществлять поиск и замену даже для самых сложных шаблонов. Разработчики РHР сочли, что не стоит заново изобретать уже изобретенное, а лучше сделать знаменитый синтаксис регулярных выражений Perl доступным для пользователей РНР. Так появились функции для работы с регулярными выражениями в стиле Perl.
Диалект регулярных выражений Perl не так уж сильно отличается от диалекта POSIX. В сущности, синтаксис регулярных выражений Perl является отдаленным потомком реализации POSIX, вследствие чего синтаксис POSIX почти совместим с функциями регулярных выражений стиля Perl.
Оставшаяся часть этого раздела будет посвящена краткому знакомству с диалектом регулярных выражений Perl. Рассмотрим простой пример:
/food/
Обратите внимание: строка food заключена между двумя косыми чертами. Как и в стандарте POSIX, вы можете создавать более сложные шаблоны при помощи квантификаторов:
/fo+/
Этот шаблон совпадает с последовательностью fo, за которой могут следовать дополнительные символы о. Например, совпадения будут обнаружены в строках food, fool и fo4. Рассмотрим другой пример использования квантификатора:
/fo{2,4}/
Шаблон совпадает с символом f, за которым следуют от 2 до 4 экземпляров символа о. К числу потенциальных совпадений относятся строки fool , fooool и foosball .
В регулярных выражениях Perl могут использоваться все квантификаторы, упомянутые в предыдущем разделе для регулярных выражений POSIX.
Сравнение двух строк
Сравнение двух строк принадлежит к числу важнейших строковых операций любого языка. Хотя эту задачу можно решить несколькими разными способами, в РНР существуют четыре функции сравнения строк:
strcmp( );
strcasecmp( );
strspn( );
strcspn( ).
Все эти функции подробно описаны в следующих разделах.
strcmp( )
Функция strcmp( ) сравнивает две строки с учетом регистра символов. Синтаксис функции strcmp( ):
int strcmp (string строка1, string строка2)
После завершения сравнения strcmp( ) возвращает одно из трех возможных значений:
0, если строка1 и строка2 совпадают;
< 0, если строка1 меньше, чем строка2;
> 0, если строка2 меньше, чем строка1.
В следующем фрагменте сравниваются две одинаковые строки:
$sthng1 = "butter";
$string2 = "butter";
if ((strcmp($string1. $string2)) == 0) :
print "Strings are equivalent!"; endif;
// Команда if возвращает TRUE
strcasecmp( )
Функция strcasecmp( ) работает точно так же, как strcmp( ), за одним исключением — регистр символов при сравнении не учитывается. Синтаксис функции strcasecmp( ):
int strcasecmp (string cтpoкa1, string строка2)
В следующем фрагменте сравниваются две одинаковые строки:
$string1 = "butter";
$string2 = "Butter";
if ((strcmp($string1, $string2)) == 0) :
print "Strings are equivalent!";
endif;
// Команда if возвращает TRUE
strspn( )
Функция strspn( ) возвращает длину первого сегмента строки1, содержащего символы, присутствующие в строке2. Синтаксис функции strspn( ):
int strspn (string строка1, string строка2)
Следующий фрагмент показывает, как функция strspn( ) используется для проверки пароля:
$password = "12345";
if (strspn($password, "1234567890") != strlen($password)) :
print "Password cannot consist solely of numbers!";
endif:
strcspn( )
Функция strcspn( ) возвращает длину первого сегмента строки1, содержащего символы, отсутствующие в строке2. Синтаксис функции strcspn( ):
int strcspn (string строка1, string строка2)
В следующем фрагменте функция strcspn( ) используется для проверки пароля:
$password = "12345";
if (strcspn($password, "1234567890") == 0) :
print "Password cannot consist solely of numbers!";
endif;
PHP 4 на практике
Файловые компоненты (шаблоны)
Мы подошли к одной из моих любимых возможностей РНР. Шаблоном (применительно к web-программированию) называется часть web-документа, которую вы собираетесь использовать в нескольких страницах. Шаблоны, как и функции РНР, избавляют вас от лишнего копирования/вставки фрагментов содержания страницы и программного кода. С увеличением масштабов сайта значение шаблонов возрастает, поскольку они позволяют легко и быстро проводить модификации на уровне целого сайта. В этом разделе будут описаны некоторые возможности, которые открываются при использовании простейших шаблонов.
Как правило, общие фрагменты содержания/кода (то есть шаблоны) сохраняются в отдельных файлах. При построении web-документа вы просто «включаете» эти файлы в соответствующие места страницы. В РНР для этого существуют две функции: include( ) и require( ).
Функции
В РНР существуют четыре функции для включения файлов в сценарии РНР:
include( );
include_once( );
require( );
require_once( ).
Несмотря на сходство имен, эти функции решают разные задачи.
include( )
Функция include( ) включает содержимое файла в сценарий. Синтаксис функции include( ):
include (file файл]
У функции include( ) есть одна интересная особенность — ее можно выполнять условно. Например, если вызов функции включен в блок команды if. то файл включается в программу лишь в том случае, если условие i f истинно. Если функция includeO используется в условной команде, то она должна быть заключена в фигурные скобки или в альтернативные ограничители. Сравните различия в синтаксисе листингов 9.1 и 9.2.
Листинг 9.1. Неправильное использование include( )
if (some_conditional)
include ('text91a.txt'); else
include ('text91b.txt');
Листинг 9.2. Правильное использование include( )
if (some_conditional) :
include ('text91a.txt');
else :
include ('text91b.txt');
endif;
Весь код РНР во включаемом файле обязательно заключается в теги РНР. Не стоит полагать, что простое сохранение команды РНР в файле обеспечит ее правильную обработку:
print "this is an invalid include file";
Вместо этого необходимо заключить команду в соответствующие теги, как показывает следующий пример:
print "this is an invalid include file";
?>
include_once( )
Функция include_once( ) делает то же, что и include( ), за одним исключением: прежде чем включать файл в программу, она проверяет, не был ли он включен ранее. Если файл уже был включен, вызов include_once( ) игнорируется, а если нет — происходит стандартное включение файла. Во всем остальном include_once( ) ничем не отличается от include( ). Синтаксис функции include_once( ):
include_once (file файл)
require ( )
В целом функция require( ) похожа на include( ) — она тоже включает шаблон в тот файл, в котором находится вызов require( ). Синтаксис функции require( ):
require (file файл)
Тем не менее, между функциями require( ) и include( ) существует одно важное различие. Файл, определяемый параметром require( ), включается в сценарий независимо от местонахождения require( ) в сценарии. Например, при вызове requi ге( ) в блоке if при ложном условии файл все равно будет включен в сценарий!
Во многих ситуациях бывает удобно создать файл с переменными и другой информацией, которая используется в масштабах сайта, и затем подключать его по мере необходимости. Хотя имя этого файла выбирается произвольно, я обычно называю его init.tpl (сокращение от «initializaion.template»). В листинге 9.3 показано, как выглядит очень простой файл init.tpl. В листинге 9.4 содержимое init.tpl включается в сценарий командой require( ).
Передача URL при вызове require( ) допускается лишь при включенном режиме «URL fopen wrappers» (этот режим включен по умолчанию).
С увеличением размеров сайта может оказаться, что некоторые файлы включаются в сценарий по несколько раз. Иногда это не вызывает проблем, но в некоторых случаях повторное включение файла приводит к сбросу значений изменившихся переменных. Если во включаемом файле определяются функции, могут возникнуть конфликты имен. Учитывая сказанное, мы приходим к следующей функции — require_once( ).
require_once( )
Функция require_once( ) гарантирует, что файл будет включаться в сценарий всего один раз. После вызова requi rе_оnсе( ) все дальнейшие попытки включения того же файла игнорируются. Синтаксис функции requiге_оnсе( ):
require_once(file файл)
Если не считать дополнительной проверки, в остальном эта функция аналогична
require( ).
Вероятно, вы станете чаще использовать функции включения файлов по мере того, как ваши web-приложения начнут увеличиваться в размерах. Эти функции часто встречаются в примерах данной книги, чтобы сократить избыточность программного кода. Первые примеры рассматриваются в следующем разделе, посвященном принципам построения базовых шаблонов.
Include( ) и require( )
Одним из самых выдающихся аспектов РНР является возможность построения шаблонов и программных библиотек и их последующей вставки в новые сценарии. Применение библиотек экономит время и усилия по использованию общих функциональных возможностей на разных web-сайтах. Читатели, обладающие
опытом программирования на других языках (например, С, C++ или Java), хорошо знакомы с концепцией библиотек функций и их использованием в программах для расширения функциональных возможностей.
Включение одного или нескольких файлов в сценарий осуществляется стандартными функциями РНР require( ) и include( ). Как будет показано в следующем разделе, каждая из этих функций применяется в определенной ситуации.
Обратите внимание на использование глобальной переменной $site_email в файле колонтитула. Значение этой переменной действует в масштабах всей страницы, а мы предполагаем, что файлы header.tpl и footer.tpl будут включены в одну итоговую страницу. Также обратите внимание на присутствие пути $site_path в ссылке Privacy (Конфиденциальность). Я всегда включаю в шаблоны полные пути ко всем ссылкам — если бы URL ссылки состоял из одного имени privacy.php, то файл колонтитула был бы жестко привязан к конкретному каталогу.
Оптимизация шаблонов
Во втором (на мой взгляд, более предпочтительном) варианте шаблоны оформляются в виде функций, находящихся в отдельном файле. Тем самым обеспечивается дополнительное структурирование ваших шаблонов. Я называю этот файл инициализационным файлом и храню в нем другую полезную информацию. Поскольку мы уже рассмотрели относительно длинные примеры заголовка и колонтитула, содержимое листингов 9.10 и 9.11 было слегка сокращено для наглядной демонстрации новой идеи.
Листинг 9.10. Оптимизированный шаблон сайта (site_init.tpl)
// Файл: site_init.tpl
// Назначение: инициализационный файл PhpRecipes
// Дата: 22 августа 2000 г.
$site_name = "PHPRecipes";
$site_email = "wjgilmore@hotmail.com";
$site_path = "http://localhost/phprecipes/";
function show_header($site_name) {
print $site_name: ?>
This is the header
function show footer ()
?>
This Is the footer
}
?>
Листинг 9.11. Применение инициализационного файла
// Включить инициализационный файл
include("site_init.tpl");
// Вывести заголовок
show header ($site_name);
?>
// Содержимое основной части This is some body information
// Вывести колонтитул Show_footer( );
?>
Основная часть
В основной части страницы подключается содержимое заголовка и колонтитула. В сущности, именно основная часть содержит информацию, интересующую посетителей сайта. Заголовок эффектно выглядит, колонтитул содержит полезные сведения, но именно ради основной части страницы пользователи снова и снова возвращаются на сайт. Хотя я не смогу предоставить каких-либо рекомендаций по поводу конкретной структуры страниц, шаблоны, подобные приведенному в листинге 9.7, основательно упрощают администрирование страниц.
Листинг 9.7.
Пример основной части страницы (index_body.tpl)
Welcome to PHPRecipes. the starting place for PHP scripts, tutorials,
and information about gourmet cooking!
Построение компонентов
При определении структуры типичной web-страницы я обычно разбиваю ее на три части: заголовок, основную часть и колонтитул. Как правило, в большинстве правильно организованных web-сайтов присутствует заголовок, который практически не изменяется; в основной части выводится запрашиваемое содержание сайта, поэтому она часто изменяется; наконец, колонтитул содержит информацию об авторских правах и навигационные ссылки. Колонтитул, как и заголовок, обычно остается неизменным. Не поймите меня превратно — я вовсе не пытаюсь подавлять ваши творческие устремления. Мне встречалось немало великолепных сайтов, не следовавших этим принципам. Я всего лишь пытаюсь выработать общую структуру, которая может послужить отправной точкой для дальнейшей работы.
Проект: генератор страниц
Хотя в большинстве созданных мною web-сайтов основное содержимое страниц формировалось на основании информации, прочитанной из базы данных, всегда найдется несколько страниц, которые практически не изменяются. В частности, на них могут выводиться сведения о команде разработчиков, контактные данные, реклама и т. д. Я обычно храню эту «статическую» информацию в отдельной папке и использую сценарий РНР для ее загрузки при поступлении запроса. Конечно, у вас возникает вопрос — если это статическая информация, для чего нужен сценарий РНР? Почему бы не загружать обычные страницы HTML? Преимущество РНР заключается в том, что вы можете использовать шаблоны и вставлять статические фрагменты по мере необходимости.
Ссылки для загрузки различных статических файлов строятся динамически. В обобщенной форме ссылка выглядит так:
<а href = "=$site_path:?>/static.php?content=$content">Static Page Name
Начнем с создания статических страниц. Для простоты я ограничусь тремя страницами, содержащими информацию о сайте (листинг 9.12), рекламу (листинг 9.13) и контактные данные (листинг 9.14).
Листинг 9.12. Информация о сайте (about.html)
About PHPRecipes
What programmer doesn't mix all night programming with gourmet cookies. Here at PHPRecipes. hardly a night goes by without one of our coders mixing a little bit of HTML with a tasty plate of Portobello Mushrooms or even Fondue. So we decided to bring you the best of what we love most: PHP and food!
That's right, readers. Tutorials, scripts, souffles and more. 0nly at PHPRecipes.
Листинг 9.13. Рекламная информация (advert_info.html)
Advertising Information
Regardless of whether they come to learn the latest PHP techniques or for brushing up on how
to bake chicken, you can bet our readers are decision makers. They are the Industry
professionals who make decisions about what their company purchases.
For advertising information, contact
">ads@phprecipes.com.
Переходим к построению страницы static.php, которая выводит запрашиваемую статическую информацию. В этот файл (см. листинг 9.15) включаются компоненты страниц нашего сайта и инициализационный файл site_init.tpl.
Если щелкнуть на любой из этих ссылок, в браузере загружается соответствующая статическая страница, внедренная в static.php!
Простые ссылки
По ссылкам пользователь может переходить как на обычные страницы HTML, так и на страницы, содержащие код РНР:
<а href = "date.php">
Если щелкнуть на ссылке, в браузере будет загружена страница с именем date.php. Просто, не правда ли? Развивая приведенный пример, можно воспользоваться переменной для построения динамической ссылки:
$link = "date.php";
print "<а href = \"$link\">View today's date \n"
?>
Вероятно, у вас возник вопрос — почему в коде ссылки перед кавычками (") ставится обратная косая черта (\)? Дело в том, что кавычки в РНР являются специальными символами и используются в качестве ограничителей строк. Следовательно, кавычки-литералы в строках должны экранироваться. Если необходимость экранировать кавычки вас раздражает, просто включите режим magic_quotes_gpc в файле php.ini. В результате все апострофы, кавычки, обратные косые черты и нуль-символы. в тексте автоматически экранируются!
Разовьем приведенный пример. Для быстрого вывода списка ссылок в браузере можно воспользоваться массивом:
// Создать массив разделов
$contents - array("tutorials", "articles", "scripts", "contact");
// Перебрать и последовательно вывести каждый элемент массива
for ($i = 0; $i < sizeof($contents; $i++)
print " ".$contents[$i]." \n";
// - специальное обозначение точки-маркера endfor;
?>
Заголовок
Заголовочный файл (вроде приведенного в листинге 9.5) присутствует практически в каждом из моих web-сайтов с поддержкой РНР. В этом файле содержится
информация, действующая на уровне всего сайта, — например, заголовок, контактные данные и некоторые компоненты кода HTML-страницы.
Листинг 9.5. Пример файла заголовка
// Файл: header.tpl
// Назначение: заголовочный файл для сайта PhpRecipes .
// Дата: 22 августа 2000 г.
$site_name = "PHPRecipes";
$site_email= "wjgnmore@hotrnail.com";
$site_path = "http://localhost/phprecipes";
?>
print $site_name; ?>
PHPRecipes
// Вывести текущую дату и время
print date ("F d, h:i a");
?>
Довольно часто доступ к включаемым файлам со стороны посетителей ограничивается, особенно если эти файлы содержат конфиденциальную информацию (например, пароли). В Apache можно запретить просмотр некоторых файлов редактированием файлов http.conf или htaccess. Следующий пример показывает, как запретить просмотр всех файлов с расширением .tpl:
Order allow,deny
Allow from 127.0.0.1
Deny from all
РНР и проблемы безопасности сайтов подробно описаны в главе 16.
PHP 4 на практике
Динамическое конструирование форм
До настоящего момента я программировал все формы вручную. Любому программисту известно, что ручное кодирование — это плохо, поскольку оно увеличивает вероятность ошибок, не говоря уже о лишних затратах времени.
В следующем разделе я представлю сценарий, в котором раскрывающийся список строится динамически по содержимому массива. Этот прием несложен, однако
он экономит немало времени как при исходном программировании, так и при последующем сопровождении программы.
Пример 7: построение раскрывающегося списка
Предположим, у вас имеется список сайтов, которые вы хотите порекомендовать посетителю из-за классного дизайна. Вместо того чтобы жестко кодировать каждую строку списка, можно создать массив и воспользоваться его содержимым для заполнения списка.
В листинге 10.9, как и в предыдущих примерах, реализован вариант с одним сценарием. Сначала мы проверяем, было ли присвоено значение переменной $site. Если проверка дает положительный результат, вызывается функция header( ) с параметром, в котором значение $site присоединяется к строке «Location:http://». При передаче этой команды функция header О перенаправляет браузер на указанный URL. Если значение переменной $site не задано, форма выводится в браузере. Раскрывающийся список строится в цикле, количество итераций зависит от размера массива Sfavsites. В листинге 10.9 я включил в этот массив пять своих любимых сайтов. Конечно, вы можете добавить в него сколько угодно своих сайтов. Запомните одно важное обстоятельство — функция header( ) должна вызываться до вывода данных в браузере. Ее нельзя просто вызвать в любой точке сценария РНР. Несвоевременные вызовы header( ) порождают столько проблем у неопытных программистов РНР, что я рекомендую повторить это правило раз пять, чтобы лучше запомнить его.
Листинг 10.9. Динамическое построение раскрывающегося списка
Динамическое конструирование форм особенно удобно при обработке больших объемов данных, которые в любой момент могут измениться, что приведет к устареванию всей жестко закодированной информации форм. Впрочем, я рекомендую жестко кодировать все статические данные (например, список штатов США), поскольку это ускорит работу программы.
Формы и РНР
Обработка данных в формах имеет много общего с обработкой переменных, передаваемых в URL, — эта тема подробно рассматривалась в предыдущей главе.
Элементы форм, ориентированные на ввод с мыши
В других элементах форм пользователь выбирает один из заранее определенных вариантов при помощи мыши. Я ограничусь описанием флажков, переключателей и раскрывающихся списков.
Флажок
Флажки (checkboxes) используются в ситуациях, когда пользователь выбирает один или несколько вариантов из готового набора — по аналогии с тем, как ставятся «галочки» в анкетах. Синтаксис определения флажка:
Определение флажка включает три атрибута:
type — тип элемента (для флажков — checkbox);
name — имя переменной, в которой сохраняются введенные данные (в данном случае — состояние элемента);
value — значение, присваиваемое переменной по умолчанию. Если флажок установлен, именно это значение будет присвоено переменной с указанным именем. Если флажок не установлен, значение атрибута value не используется.
Флажок изображен на рис. 10.3.
Рис. 10.3. Флажок
Переключатель
Переключатель (radio button) представляет собой разновидность флажка; он работает практически так же за одним исключением — в любой момент времени в группе может быть установлен лишь один переключатель. Синтаксис определения переключателя:
Как видите, синтаксис почти не отличается от определения флажка. Определение переключателя поля включает три атрибута:
type — тип элемента (для переключателей — radio);
name — имя переменной, в которой сохраняются введенные данные (в данном случае — состояние элемента);
value — значение, присваиваемое переменной по умолчанию. Если переключатель установлен, именно это значение будет присвоено переменной с указанным именем. Если флажок не установлен, значение атрибута value не используется.
Переключатель изображен на рис. 10.4.
Рис. 10.4. Переключатель
Раскрывающийся список
Раскрывающиеся списки особенно удобны в ситуации, когда у вас имеется длинный перечень допустимых вариантов, из которого пользователь должен выбрать один вариант. Как правило, раскрывающиеся списки применяются при работе с относительно большими наборами данных — например, при перечислении американских штатов или стран. Синтаксис определения раскрывающегося списка:
Определение переключателя поля включает три атрибута:
name — имя переменной, в которой сохраняются введенные данные (в данном случае — строка, выбранная в списке);
value — значение, отображаемое в списке по умолчанию.
Раскрывающийся список изображен на рис. 10.5.
Рис. 10.5.
Раскрывающийся список
Скрытые поля
Скрытые поля не отображаются в браузере и обычно используются для передачи данных между сценариями. Хотя передача в скрытых полях работает вполне нормально, в РНР существует другое, более удобное средство — сеансовые переменные (см. главу 13). Впрочем, скрытые поля также используются в некоторых ситуациях и потому заслуживают упоминания.
Синтаксис определения скрытого поля практически идентичен синтаксису текстовых полей, отличается только атрибут типа. Поскольку скрытые поля не отображаются в браузере, привести пример на страницах книги невозможно. Синтаксис определения скрытого поля:
Определение скрытого поля включает три атрибута:
type — тип элемента (для скрытых полей — hidden);
name — имя переменной, в которой сохраняются скрытые данные;
value — значение, по умолчанию сохраняемое в скрытом поле.
Вообще говоря, название этого элемента — скрытое поле — несколько неточно. Хотя скрытые поля не отображаются в браузерах, пользователь может просто выполнить команду View Source и увидеть, какие скрытые значения хранятся в форме.
Кнопка отправки данных
Кнопка отправки данных инициирует действие, заданное атрибутом action тега
// Форма уже отображалась - добавить данные в текстовый файл.
К числу основных преимуществ модульной разработки приложений относится простота адаптации для других систем. Допустим, вы решили перейти от хранения данных в текстовом файле к использованию базы данных. Стоит изменить содержимое add_guest( ) и view_guest( ), и ваша гостевая книга перейдет на работу с базой данных.
На рис. 10.8 показано, как выглядит гостевая книга после сохранения пары записей.
Рис. 10.8. Просмотр гостевой книги (view_guest.php)
Информация, показанная на рис. 10.8, хранится в файле данных в следующем виде:
Oct 29 00|Michele|michelle@latorre.com|I love cheese!
Oct 29 00|Nino|nino@latorre.com|Great site!
Проверка ошибок
Обработка пользовательских данных дает осмысленный результат лишь в том случае, если данные имеют правильную структуру. Проверить достоверность введенных данных невозможно, однако вы можете проверить их целостность (например, убедиться в том, что адрес электронной почты соответствует стандартному шаблону). Хотя для проверки данных часто применяется технология JavaScript, могут возникнуть проблемы с несовместимостью браузеров. Поскольку код РНР выполняется на стороне сервера, вы всегда можете быть уверены в том, что проверка данных формы даст нужный результат (конечно, при условии правильности вашей программы).
При обнаружении ошибки в данных необходимо сообщить об этом пользователю и предложить внести исправления. Существует несколько возможных решений, в том числе простой вывод сообщения об ошибке и предложение альтернативных вариантов (например, если пользователь выбирает имя, которое уже было выбрано другим пользователем). В этом разделе рассматривается процедура проверки и вывода сообщений,
Пример 6: вывод информации о пустых или ошибочно заполненных полях формы
Ни один разработчик сайта не захочет раздражать пользователя невразумительными сообщениями об ошибках в данных — особенно если пользователь запрашивает дополнительную информацию о товаре или оформляет покупку! Чтобы пользователь понял, какие поля формы остались пустыми или были заполнены неверно, сообщения должны быть четкими и конкретными.
Мы последовательно проверяем все поля формы и убеждаемся в том, что они не остались пустыми. Там, где это возможно, проверяется правильность структуры введенных данных. Если проверка прошла успешно, мы переходим к следующему полю; в противном случае программа выводит сообщение об ошибке, устанавливает флаг, который позднее используется для повторного отображения формы, и переходит к следующему полю. Процедура повторяется до тех пор, пока не будут проверены все поля формы (листинг 10.8).
Листинг 10.8. Проверка данных формы и вывод сообщений об ошибках
// Убедиться в том. что поле имени содержит информацию
if ($name == "") :
print "* You forgot to enter your name!
":
$error_flag = "y";
endif:
// Убедиться в том. что поле адреса содержит информацию
if ($email == "") :
else :
print "* You forgot to enter your email !
"
$error_flag = "y";
// Преобразовать все алфавитные символы в адресе
// электронной почты к нижнему регистру
$email = strtolower(trim($email)):
// Убедиться в правильности синтаксиса
// адреса электронной почты
if (! @eregi('^[0-9a-z]+'.
'([0-9a-z-]+\.)+'.
'([0-9a-z]){2.4}$'. $email)) :
print "* You entered an invalid email address!
" :
$error_flag = "y";
endif;
endif;
// Если флаг ошибки $error_flag установлен.
// заново отобразить форму
if ($error_flag == "у") : print "$form";
else :
// Обработать данные пользователя
print "You entered valid form information!";
endif;
endif;
?>
Программа в листинге 10.8 убеждается в том, что поля имени и адреса электронной почты не остались пустыми, а также проверяет правильность синтаксиса вве-, денного адреса. Если в результате каких-либо проверок в форме обнаруживаются ошибки, программа выводит соответствующие сообщения и отображает форму заново — при этом вся введенная ранее информация остается в форме, благодаря чему пользователю будет проще внести исправления. Если вывести пустую форму и предложить пользователю заполнить ее заново, он может отправиться
за необходимым товаром или услугой в другое место.
Все вместе: пример формы
От описания базовых компонентов форм мы переходим к практическому примеру — построению формы для обработки данных, введенных пользователем. Допустим, вы хотите создать форму, в которой пользователь может высказать мнение о вашем Сайте. Пример такой формы приведен в листинге 10.1.
Листинг 10.1. Пример формы для сбора данных
Внешний вид формы в браузере изображен на рис. 10.7.
Рис. 10.7. Пример формы для ввода данных
Вроде бы все понятно. Возникает вопрос — как получить данные, введенные пользователем, и сделать с ними что-нибудь полезное? Этой теме посвящен следующий раздел, «Формы и РНР».
Не забывайте: все сказанное ранее — не более чем вводный курс. Приведенная информация ни в коем случае не исчерпывает всех возможностей, предоставляемых различными компонентами форм. За дополнительной информацией обращайтесь к многочисленным учебникам по работе с формами, опубликованным в Web, а также книгам по HTML.
От предварительного знакомства с формами HTML мы переходим к самому интересному — применению РНР для обработки данных, введенных пользователем в форме.
Вводные примеры
Следующие практические примеры помогут вам быстрее освоить различные аспекты обработки форм в РНР. В этих примерах продемонстрированы разные подходы к реализации интерактивных возможностей на сайте.
Пример 1: передача данных формы из одного сценария в другой
В первом примере представлена характерная ситуация — когда пользовательские данные вводятся на одной странице и отображаются на другой. В листинге 10.2 приведен код формы для ввода имени пользователя и адреса электронной почты. Когда пользователь щелкает на кнопке отправки данных (кнопка Go!), форма обращается к странице, приведенной в листинге 10.3. В свою очередь, листинг 10.3 выводит переменные $name и $mail, переданные с запросом.
Листинг 10.2. Простая форма
Listing 10-2
Листинг 10.3. Отображение данных, введенных в листинге 10.1
Listing 10-3
// Вывести имя и адрес электронной почты.
print "Hi. $name!. Your email address is $email";
?>
В общих чертах происходит следующее: пользователь заполняет поля формы и нажимает кнопку отправки данных. Управление передается странице, приведенной в листинге 10.3, где происходит форматирование и последующее отображение данных. Как видите, все просто.
Существует и другой способ обработки данных форм, при котором используется всего один сценарий. К недостаткам этого способа относятся увеличение сценария и как следствие — затруднения с редактированием и сопровождением. Впрочем, есть и достоинства — уменьшение количества файлов, с которыми вам приходится работать. Более того, в этом варианте сокращается избыточный код при проверке ошибок (эта тема рассматривается ниже в данной главе). Конечно, в некоторых ситуациях работать с одним сценарием неудобно, но, по крайней мере, вы должны знать об этой возможности. В примере 2 воспроизводится пример 1, но с использованием лишь одного сценария.
Пример 2: альтернативная обработка формы (с одним сценарием)
Обработка данных формы в одном сценарии реализуется относительно просто. Вы проверяете, были ли присвоены значения переменным формы. Если значения присвоены, сценарий обрабатывает их (в нашем примере — просто выводит), а если нет — отображает форму. Решение о том, было ли задано значение переменной или нет, принимается при помощи функции strcmp( ), описанной в главе 8. Пример реализации формы с одним сценарием приведен в листинге 10.4. Обратите внимание: атрибут action формы ссылается на ту же страницу, в которой определяется сама форма. Условная команда i f проверяет состояние переменной скрытого поля с именем $seenform. Если значение $seenform не задано, форма отображается в браузере, а если задано — значит, форма была заполнена пользователем и введенные данные обрабатываются сценарием (в данном примере — просто выводятся в браузере).
Листинг 10.4.
Ввод данных на форме в одном сценарии
Listing 10-4
// Все кавычки внутри $form должны экранироваться,
// в противном случае произойдет ошибка.
$form = "
";
// Если форма ранее не отображалась, отобразить ее.
// Для проверки используется значение скрытой переменной $seenform.
if ($seenform != "у"):
print "$form";
else :
print "Hi. $name!. Your email address is $email";
endif;
?>
Учтите, что этот вариант создает определенные неудобства, поскольку при повторной загрузке страницы пользователь ничего не узнает о том, правильно ли были заполнены поля формы. Процедура проверки ошибок рассматривается далее в этой главе, а пока достаточно запомнить, что ввод данных можно осуществить при помощи одного сценария.
Теперь, когда вы представляете, как просто выполняются операции с формами, мы переходим к интересному примеру — автоматической отправке данных пользователя по заданному адресу электронной почты. Эта возможность реализована в примере 3.
Пример 3: автоматическая отправка данных по электронной почте
Вывести пользовательские данные в браузере несложно, но вряд ли это можно назвать содержательной обработкой пользовательского ввода. Один из способов обработки информации заключается в ее отправке по электронной почте — например, администратору сайта. Хотя при помощи гиперссылки mailto: можно отправить сообщение прямо из браузера, следует учитывать, что внешние приложения электронной почты настроены не на каждом компьютере. Следовательно, отправка сообщений с web-формы более надежно гарантирует, что сообщение будет доставлено адресату.
В следующем разделе, mail( ), создается небольшая форма, в которой пользователь вводит информацию и комментарии по поводу сайта. Затем данные форматируются соответствующим образом и передаются стандартной функции РНР mail( ). Но прежде чем переходить к построению формы, необходимо предварительно рассмотреть синтаксис функции mail( ).
mail ( )
Функция mail( ) отправляет сообщение заданному адресату по электронной почте. Синтаксис функции mail( ):
boolean mail (string получатель, string тема, string сообщение [, string доп_заголовки])
В параметре тема, как нетрудно предположить, передается тема сообщения. Параметр сообщение содержит текст сообщения, а необязательный параметр доп_за головки предназначен для включения дополнительной информации (например, атрибутов форматирования HTML), пересылаемой с сообщением.
В системе UNIX функция mail( ) использует утилиту sendmail. В Windows эта функция работает лишь при наличии установленного почтового сервера или если функция mail( ) связана с работающим сервером SMTP. Эта задача решается модификацией переменной SMTP в файле php.ini.
Если вы сделали все необходимое и функция mail( ) работает в вашей системе, попробуйте выполнить следующий фрагмент (конечно, адрес youraddress@yourserver.com заменяется вашим настоящим адресом электронной почты):
$email = "youraddress@yourserver.com";
$subject = "This is the subject";
$message = "This is the message";
$headers = "From: somebody@somesite.com";
mail ($email, $subject, $message, $headers);
Хотя при обширной переписке, конечно, следует использовать специализированные почтовые программы вроде majordomo (http://www.greatcircle.com/majordomo), в простых случаях функции РНР mail( ) оказывается вполне достаточно.
Итак, после знакомства с функцией mail( ) можно применить ее на практике. В листинге 10.5 показано, как получить информацию от пользователя и отправить ее по адресу, заданному администратором сценария.
Листинг 10.5.
Пересылка пользовательских данных функцией mail( )
Listing 10-5
// Все кавычки внутри $form должны экранироваться.
// в противном случае произойдет ошибка.
$form = "
// Если форма ранее не отображалась, отобразить ее.
//
Для проверки используется значение скрытой переменной $seenform.
if ($seenform != "у") :
print "$form"; else :
// Переменная $recipient определяет получателя данных формы
$recipient = "yourname@youremail.com";
// Тема сообщения
$subject = "User Comments ($name)";
// Дополнительные заголовки $headers = "From: $email";
// Отправить сообщение или выдать сообщение об ошибке
mail($recipient, $subject, $comments, $headers) or die("Could not send email!");
// Вывести сообщение для пользователя
print "Thank you $name for taking a moment to send us your comments!";
endif;
?>
Неплохо, правда? Листинг 10.5 работает так же, как листинг 10.4; сначала мы проверяем, отображалась ли форма ранее. Если это происходило, программа вызывает функцию mail( ) и пользовательские данные отправляются по адресу, определяемому переменной $recipient. Затем в браузере выводится благодарственное сообщение для пользователя.
Простейшим расширением этого примера будет отправка благодарственного сообщения по электронной почте (вторым вызовом mail( )). Следующий пример развивает эту идею — пользователю предлагается на выбор несколько бюллетеней. Выбранные бюллетени отправляются по электронной почте.
Пример 4: отправка запрашиваемой информации по электронной почте
В этом примере в форме создается несколько флажков, каждый из которых соответствует отдельному документу с информацией о сайте. Пользователь устанавливает один, два или три флажка, вводит свой адрес, и запрашиваемые брошюры отправляются ему по электронной почте. Обратите внимание на применение массива при работе с флажками — это упрощает проверку выбранных флажков, а также улучшает структуру программы.
Информационные сообщения хранятся в отдельных файлах. В нашем примере используются три текстовых файла:
site.txt — информация о сайте;
team.txt — информация о талантливых разработчиках сайта;
events.txt — приглашение на очередное мероприятие.
Исходный текст примера приведен в листинге 10.6.
Листинг 10.6.
Отправка информации, запрашиваемой пользователем
Listing10-5
$form = "
":
if ($seenform != "y") :
print "$form"; else :
$headers = "From: devteam@yoursite.com";
// Перебрать все пары "ключ/значение"
while ( list($key, Sval) = each ($information) ) :
// Сравнить текущее значение с "у" if ($val == "у") :
// Построить имя файла, соответствующее текущему ключу
$filename = "$key.txt":
$subject = "Requested $key information";
// Открыть файл
$fd = fopen ($filename, "r");
// Прочитать содержимое всего файла в переменную $contents = fread ($fd. filesize ($filename));
// Отправить сообщение
mail($email, $subject, $contents, $headers) or die("Can't send email!");; fclose($fd);
В листинге 10.6 мы перебираем пары «ключ/значение» в цикле while и отправляем только те бюллетени, у которых значение равно у. Следует помнить, что имена текстовых файлов должны соответствовать ключам массива
(site.txt, team.txt и events.txt). Имя файла строится динамически по ключу, после чего файл открывается по имени и его содержимое загружается в переменную ($contents). Затем переменная $contents передается функции mail( ) в качестве параметра.
В следующем примере пользовательские данные сохраняются в текстовом файле.
Пример 5: сохранение пользовательских данных в текстовом файле
Пользовательские данные сохраняются в текстовом файле для последующего статистического анализа, поиска и т. д. — короче, любой обработки по вашему усмотрению. В листинге 10.7, как и в предыдущих примерах, данные формы обрабатываются в одном сценарии. Пользователю предлагается ввести четыре объекта данных: имя, адрес электронной почты, язык и профессию. Введенная информация сохраняется в текстовом файле user_information.txt. Элементы данных разделяются символами «вертикальная черта» (|).
Листинг 10.7.
Сохранение пользовательской информации в текстовом файле
// Создать форму
$form = "
";
// Заполнялась ли форма ранее? if ($seenform != "у") :
print "$form"; else :
$fd = fopen("useMnformation.txt", "a");
// Убедиться, что во введенных данных не встречается
fwrite($fd, $user_row) or die("Could not write to file!");
fclose($fd);
print "Thank you for taking a moment to fill out our brief questionnaire!":
endif;
?>
Обратите внимание на фрагмент, в котором мы проверяем, что пользователь не включил в имя или адрес электронной почты символы «вертикальная черта» (|). Функция str_replace( ) удаляет эти символы, заменяя их пустой строкой. Если бы это не было сделано, пользовательские символы | нарушили бы структуру файла данных и существенно затруднили (а то и сделали невозможным) его правильную обработку.
При работе с относительно малыми объемами информации вполне можно обойтись текстовыми файлами. Однако при большом количестве пользователей или объеме сохраняемой информации для хранения и обработки данных, введенных в форме, лучше воспользоваться базой данных. Эта тема подробно рассматривается в главе 11.
До настоящего момента предполагалось, что пользователь всегда вводит правильные данные и не действует злонамеренно. В высшей степени оптимистичное предположение! В следующем разделе мы усовершенствуем рассмотренные примеры и организуем проверку целостности данных форм. Проверка ошибок не только обеспечивает удаление неполной и неправильной информации, но и обеспечивает более эффективный и удобный интерфейс.
PHP 4 на практике
Что такое SQL?
SQL обычно описывается как стандартный язык, используемый для взаимодействия с реляционными базами данных (см. ниже). Однако SQL не является языком программирования, как С, C++ или РНР. Скорее, это интерфейсное средство для выполнения различных операций с базами данных, предоставляющее в распоряжение пользователя стандартный набор команд. Возможности SQL не ограничиваются выборкой данных из базы. В SQL поддерживаются разнообразные возможности для взаимодействия с базой данных, в том числе:
определение структуры данных
— определение конструкций, используемых при хранении данных;
выборка данных
— загрузка данных из базы и их представление в формате, удобном для вывода;
обработка данных
— вставка, обновление и удаление информации;
контроль доступа
— возможность разрешения/запрета выборки, вставки, обновления и удаления данных на уровне отдельных пользователей;
контроль целостности данных
— сохранение структуры данных при возникновении таких проблем, как параллельные обновления или системные сбои.
Обратите внимание: в определении SQL было сказано, что этот язык предназначен для работы с реляционными базами данных. В реляционных СУБД данные организуются в виде набора взаимосвязанных таблиц. Связи между таблицами реализуются в виде ссылок на данные других таблиц. Таблицу можно представить себе как двухмерный массив, в котором расположение каждого элемента характеризуется определенными значениями строки и столбца. Пример реляционной базы данных изображен на рис. 11.1. Рис. 11.1.
Пример реляционной базы данных
Как видно из рис. 11.1, каждая таблица состоит из строк (записей) и столбцов (полей). Каждому полю присваивается уникальное (в рамках данной таблицы) имя. Обратите внимание на связь между таблицами customer и orders, обозначенную стрелкой. В информацию о заказе включается короткий идентификатор клиента, что позволяет избежать избыточного хранения имени и прочих реквизитов клиента. В изображенной базе данных существует еще одна связь — между таблицами orders и products. Эта связь устанавливается по полю prod_id, в котором хранится идентификатор товара, заказанного данным клиентом (определяемого полем custjd). Наличие этих связей позволяет легко ссылаться на полные данные клиента и товара по простым идентификаторам. Правильно организованная база данных превращается в мощное средство организации и эффективного хранения данных с минимальной избыточностью. Запомните эту базу данных, я буду часто ссылаться на нее в дальнейших примерах.
Итак, как же выполняются операции с реляционными базами данных? Для этого в SQL существует специальный набор общих команд — таких, как SELECT, INSERT, UPDATE и DELETE. Например, если вам потребуется получить адрес электронной почты клиента с идентификатором 2001cu (см. рис. 11.1), достаточно выполнить следующую команду SQL:
SELECT cust_email FROM customers WHERE custjd = '2001cu'
Все вполне логично, не правда ли? В обобщенном виде команда выглядит так:
SELECT имя_поля FROM имя_таблицы [ WHERE условие ]
Квадратные скобки означают, что завершающая часть команды является необязательной. Например, для получения адресов электронной почты всех клиентов из таблицы customers достаточно выполнить следующий запрос:
SELECT cust_email FROM customers
Предположим, вы хотите включить в таблицу products новую запись. Простейшая команда вставки выглядит так:
INSERT into products VALUES ('1009pr', 'Red Tomatoes', '1.43');
Если позднее эти данные потребуется удалить, воспользуйтесь следующей командой:
DELETE FROM products WHERE prod_id = 1009r';
Существует много разновидностей команд SQL, и полное их описание выходит за рамки этой книги. На эту тему вполне можно написать отдельную книгу! Я постарался сделать так, чтобы команды SQL, используемые в примерах, были относительно простыми, но достаточно реальными. В Web существует много учебной информации и ресурсов, посвященных SQL. Некоторые ссылки приведены в конце этого раздела.
Записывать команды SQL символами верхнего регистра необязательно. Впрочем, я предпочитаю именно такую запись, поскольку она помогает различать компоненты запроса.
Раз вы читаете эту книгу, вероятно, вас интересует вопрос, как же организуется работа с базами данных в среде Web? Как правило, сначала при помощи какого-
либо интерфейсного языка (РНР, Java или Perl) создается соединение с базой данных, после чего программа обращается к базе с запросами, используя стандартный набор средств. Интерфейсный язык можно рассматривать как своего рода «клей», связывающий базу данных с Web. Я перехожу к своему любимому интерфейсному языку — РНР.
Дополнительные ресурсы
Ниже перечислены некоторые ресурсы Интернета, посвященные SQL. Они пригодятся как новичкам, так и опытным программистам.
Учебники по SQL: http://perl.about.com/compute/perl/cs/beginningsql/index.htm.
SQLCourse.com (с примером базы данных): http://www.sqlcourse.com.
SQL для вундеркиндов Web: http://www.arsdigita.com/books/sql.
Введение в SQL (применительно к MySQL): http://www.devshed.com/Server_side/MySQL/Intro.
PHP 4 на практике
Базы данных
Средства эффективного хранения и выборки больших объемов информации внесли огромный вклад в успешное развитие Интернета. Обычно для хранения информации используются базы данных. Работа таких известных сайтов, как Yahoo, Amazon и Ebay, в значительной степени зависит от надежности баз данных, хранящих громадные объемы информации. Конечно, поддержка баз данных ориентирована не только на интересы гигантских корпораций — в распоряжении web-программистов имеется несколько мощных реализаций баз данных, распространяемых по относительно низкой цене (а то и бесплатно).
Правильная организация базы данных обеспечивает более быстрые и гибкие возможности выборки данных. Она существенно упрощает реализацию средств поиска и сортировки, а проблемы прав доступа к информации решаются при помощи средств контроля за привилегиями, присутствующими во многих системах управления базами данных (СУБД). Кроме того, упрощаются процессы репликации и архивации данных.
Глава начинается с подробного описания выборки и обновления данных в MySQL — вероятно, самой популярной СУБД, используемой в PHP (http://www.mysql.com). На примере MySQL будет показано, как в РНР происходят загрузка и обновление данных в базе; мы рассмотрим базовые средства поиска и сортировки, используемые во многих web-приложениях. Затем мы перейдем к реализованной в РНР поддержке ODBC (Open Data Base Connectivity) — обобщенного интерфейса, который может использоваться для одновременного соединения с разными СУБД. Поддержка ODBC в РНР будет продемонстрирована на примере соединения и выборки данных из базы данных Microsoft Access. Глава завершается проектом, в котором РНР и СУБД MySQL используются для создания иерархического каталога с информацией об избранных сайтах. При включении в каталог новых сайтов пользователь относит их к одной из стандартных категорий, определяемых администратором сайта.
Прежде чем переходить к обсуждению MySQL, я хочу сказать несколько слов об SQL — самом распространенном языке для работы с базами данных. Язык SQL заложен в основу практически всех существующих СУБД. Чтобы перейти к рассмотрению примеров работы с базами данных, необходимо хотя бы в общих чертах представлять, как работает SQL.
Microsoft Access и РНР
Популярность СУБД Microsoft Access (http://www.microsoft.com/office/access) отчасти объясняется ее удобным графическим интерфейсом. Помимо использования Access в качестве самостоятельной СУБД, вы можете использовать ее графический интерфейс для организации работы с другими базами данных — например, MySQL или Microsoft SQL Server.
Чтобы продемонстрировать поддержку ODBC в РНР, я опишу процесс подключения к базам данных Microsoft Access на РНР. Делается это на удивление просто, но благодаря популярности Microsoft Access это станет полезным дополнением в вашем творческом арсенале. Я опишу этот процесс шаг за шагом:
Создайте базу данных Access. Предполагается, что вы уже умеете это делать, а если не умеете, но все равно хотите проследить за выполнением этого примера, — воспользуйтесь услугами программы-мастера. Я создал стандартную базу данных для хранения контактных данных при помощи мастера. Обязательно создайте в таблице несколько записей и запомните имя таблицы, оно нам вскоре понадобится!
Сохраните базу данных на своем компьютере.
На следующем шаге мы организуем доступ к базе Access средствами ODBC. Выполните команду Пуск > Настройка > Панельуправления (Start > Settings > Control Panel). Найдите на панели управления значок Источники данных ODBC (32) (ODBC Data Sources (32 bit)). При помощи этого значка запускается Администратор ODBC, предназначенный для настройки различных драйверов и источников данных в вашей системе. Запустите программу, дважды щелкнув на этом значке. Окно Администратора по умолчанию открывается на вкладке Пользовательский DSN (User DSN). На этой вкладке перечисляются источники данных-, которые относятся к конкретному пользователю и могут использоваться только на этом компьютере. В данном примере будет использоваться именно такой источник данных.
Нажмите кнопку Добавить... (Add...) в правой части окна. Открывается новое окно для выбора драйвера, предназначенного для работы с новым источником. Выберите строку Microsoft Access Driver (*.mdb) и нажмите кнопку Finish (Готово).
На экране появляется новое окно Установка драйвера ODBC для Microsoft Access (ODBC Microsoft Access Setup). Найдите в форме текстовое поле Имя источника данных (Data Source Name) и введите в нем имя созданной вами базы данных Access. При желании можете ввести описание в текстовом поле, расположенном непосредственно под полем Имя источника данных.
Нажмите кнопку Выбрать... (Select...) — появляется окно в стиле Проводника Windows. В нем вам предлагается найти базу данных, доступ к которой будет осуществляться средствами ODBC.
Найдите в дереве каталогов свою базу данных и дважды щелкните на ней. Вы снова возвращаетесь к окну Установка драйвера ODBC для Microsoft Access. Путь к выбранной базе данных отображается над кнопкой Выбрать.... Нажмите кнопку ОК.
Готово! Теперь вы можете работать с базой данных Access средствами ODBC.
Все, что вам остается сделать — создать сценарий, в котором вы будете работать с базой данных через ODBC. В приведенном ниже сценарии общие функции ODBC (см. выше) будут использоваться для вывода всей информации из таблицы контактов, созданной при помощи мастера Access. Однако перед рассмотрением сценария желательно знать, как таблица Contacts выглядит в Access (рис. 11.3).
Рис. 11.3.
Таблица Contacts в MS Access
Теперь вы знаете, какая информация будет извлекаться из базы данных, и мы можем перейти к сценарию. Если вы забыли, что делает та или иная функция, обращайтесь к описанию в начале этой главы. Результаты работы листинга 11.7 представлены на рис. 11.4.
Листинг 11.7.
Применение функций ODBC для работы с MS Access
// Подключиться к источнику данных ODBC 'ContactDB' ;connect = odbc_connect("ContactDB", "","")
or die("Couldn't connect to datasource.");
// Создать текст запроса
$query = "SELECT First_Name, Last_Name, Cell_Phone, Email FROM Contacts";
// Обработка результатов закончена, освободить память odbc_free_result($result);
// Закрыть соединение odbc_close($connect);
?>
Не правда ли, все просто? А самое замечательное — то, что этот сценарий полностью совместим с любой другой СУБД с поддержкой ODBC. Для тренировки попробуйте повторить все описанные действия для другой СУБД, запустите сценарий — и вы получите те же результаты, которые изображены на рис. 11.4.
Рис. 11.4.
Содержимое таблицы Contacts в web-браузере
MySQL
MySQL (http://www.mysql.com) — надежная СУБД на базе SQL, разработанная и сопровождаемая фирмой Т.с.Х DataKonsultAB (Стокгольм, Швеция). Начиная с 1995 года, MySQL стала одной из самых распространенных СУБД в мире, что отчасти обусловлено ее скоростью, надежностью и гибкой лицензионной политикой (см. ниже).
Благодаря хорошим характеристикам и обширному набору стандартных интерфейсных функций, очень простых в использовании, MySQL стала самым популярным средством для работы с базами данных в РНР. MySQL распространяется на условиях общей лицензии GNU (GPL, GNU Public License). Полное описание текущей лицензионной политики MySQL приведено на сайте MySQL (http://www.mysql.com).
Настройка MySQL
После успешной установки сервер MySQL необходимо настроить. Процесс настройки в основном состоит из создания новых баз данных и редактирования таблиц
привилегий MySQL. Таблицы привилегий управляют доступом к базам данных MySQL. Правильная настройка таблиц играет чрезвычайно важную роль в безопасности ваших баз данных, поэтому перед запуском сайта в рабочем режиме необходимо полностью освоить систему привилегий.
На первый взгляд, таблицы привилегий MySQL выглядят устрашающе, но если в них как следует разобраться, дальнейшее сопровождение становится очень простой задачей. Полное описание таблиц привилегий выходит за рамки этой книги. Впрочем, в Web существует немало ресурсов, предназначенных для помощи начинающим пользователям MySQL. За дополнительной информацией обращайтесь на сайт MySQL (http://www.mysql.com).
После успешной установки и настройки пакета MySQL можно начинать эксперименты с базами данных в среде Web! Именно этой теме и посвящен следующий раздел. Начнем с изучения поддержки MySQL в РНР.
ODBC
Специализированные функции хорошо подходят для работы с одним конкретным типом СУБД. Но что делать, если вам приходится подключаться к MySQL, Microsoft SQL Server и IBM DB2, притом в одном приложении? Аналогичная проблема возникает при разработке приложений, которые не должны зависеть от СУБД; такие приложения работают «над» существующей инфраструктурой клиентской базы данных. ODBC (сокращение от «Open Database Connectivity», то есть «открытая архитектура баз данных») представляет собой интерфейс прикладных программ (API), позволяющий использовать общий набор абстрактных функций для работы с разными типами баз данных. Преимущества подобного подхода очевидны — вам не придется многократно переписывать один и тот же фрагмент кода только для того, чтобы выполнять одинаковые операции с разнотипными базами данных.
Работа с сервером баз данных через ODBC возможна лишь в том случае, если этот сервер является ODBC-совместимым. Другими словами, для него должны существовать драйверы ODBC. За дополнительной информацией о драйверах ODBC обращайтесь к документации СУБД. Возможно, вам придется дополнительно загрузить их из Интернета и установить на своем компьютере. Хотя стандарт ODBC, разработанный компанией Microsoft, стал открытым стандартом, он в основном используется для работы с СУБД на платформе Windows; впрочем, драйверы ODBC также существуют и на платформе Linux. Ниже приведены ссылки на драйверы для некоторых популярных СУБД.
Драйверы баз данных для Windows 95/98/NT: http://www.microsoft.com/data/odbc
Драйверы ODBC для MySQL (MyODBC): http://www.mysql.com
OpenLinkSoftware: http://www.openlinksw.com
Драйверы ODBC различаются по целям, платформе и назначению. За информацией о различных аспектах работы с этими драйверами обращайтесь к документации по конкретным пакетам. Впрочем, невзирая на все различия, использование этих драйверов в РНР обходится без проблем.
Когда вы определите, какой комплект драйверов ODBC лучше подходит для ваших целей, загрузите его и выполните все инструкции по установке и настройке. После этого можно переходить к следующему разделу — «Поддержка ODBC в РНР».
Поддержка баз данных в РНР
Если бы мне предложили назвать самый важный аспект РНР, вероятно, я бы остановился на поддержке баз данных. В РНР реализована обширная поддержка практически всех существующих серверов баз данных, в том числе:
Adabas D
Informix
PostgreSQL
Dbase
Ingres
Solid
Direct MS-SQL
InterBase
Sybase
Empress
mSQL
UNIX dbm
File-Pro (read-only)
MySQL
Velods
FrontBase
ODBC
IBM DB2
Oracle (OCI7 и OC18)
Как показывает этот список, поддержка баз данных в РНР простирается от совместимости с базами данных, известных всем (например, Oracle), до тех, о которых многие даже не слышали. Мораль — если вы собираетесь использовать серьезную СУБД для хранения информации, распространяемой через Web, скорее всего, эта база данных поддерживается в РНР. Поддержка базы данных в РНР представлена набором стандартных функций для соединения с базой, обработки запросов и разрыва связи.
Подробное описание всех поддерживаемых баз данных явно выходит за рамки книги. Впрочем, сервер MySQL дает неплохое представление об общих возможностях поддержки баз данных в РНР. По этой причине в примерах оставшейся части этой и всех остальных глав книги будет использоваться синтаксис MySQL. Независимо от того, с каким сервером баз данных вы будете работать, адаптация примеров не вызовет особых сложностей.
Поддержка ODBC в РНР
Функции ODBC в РНР, обычно называемые общими функциями ODBC, не только обеспечивают типовую поддержку ODBC, но и позволяют работать с некоторыми СУБД, обладающими собственным API, через стандартный ODBC API. К числу последних относятся следующие СУБД:
Adabas D;
IODBC;
IBM DB2;
Solid;
Sybase SQL Anywhere.
Обратите внимание: при работе с перечисленными СУБД стандартный механизм ODBC на самом деле не используется. Просто поддерживаемые в РНР общие функции ODBC применяются для взаимодействия с базами данных. Это удобно, поскольку при переходе на другую ODBC-совместимую СУБД (или СУБД из приведенного выше списка) все ваши сценарии будут нормально работать. Поддержка ODBC встроена в комплект поставки РНР, поэтому вам за редкими исключе-ниями практически не придется заниматься специальной настройкой.
В настоящее время существует около 40 общих функций ODBC. Впрочем, для выборки информации из ODBC-совместимой базы данных вполне достаточно нескольких функций, описанных ниже. Полный список общих функций ODBC в РНР приведен в документации (http://www.php.net/manual).
odbc_connect( )
Перед тем как обращаться к ODBC-совместимой базе данных с запросами, необходимо сначала установить с ней связь. Соединение создается функцией ocbc_connect( ). Синтаксис функции odbc_connect( ):
int odbc_connect (string источник_данных, string имя_пользователя, string пароль [, int тип_курсора])
Параметр источник_данных определяет ODBC-совместимую базу данных, с которой вы пытаетесь установить связь. В параметрах имя_пользователя и пароль, как нетрудно догадаться, передаются имя пользователя и пароль, необходимые для подключения к источнику данных. Необязательный параметр тип_курсора предназначе'н для устранения проблем, возникающих при работе с некоторыми драйверами ODBC. Он может принимать одно из четырех возможных значений:
SQL_CUR_USE_IF_NEEDED;
SQL_CURSOR_USE_ODBC;
SQL_CUR_USE_DRIVER;
SQL CUR DEFAULT.
Возможно, вам не придется использовать этот параметр, но все же о нем следует помнить на случай, если ваш пакет ODBC не справится с обработкой того или иного запроса.
Использовать odbc_connect( ) в программе несложно. Пример:
obdc_connect("myAccessDB", "user", "secret")
or die( " Could not connect to ODBC database");
?>
Функция используется для открытия восстанавливаемых (persistent) соединений с базами данных. Она экономит системные ресурсы, поскольку odbc_pconnect( ) проверяет, не было ли данное соединение открыто ранее, и если было, использует предыдущее соединение.
odbc_close( )
После завершения работы с ODBC-совместимой базой данных соединение необходимо закрыть, чтобы освободить все ресурсы, используемые открытым соединением. Соединение закрывается функцией odbc_close( ):
void odbc_close ([int идентификатор_соединения])
В параметре идентификатор_соединения передается идентификатор открытого соединения. Рассмотрим небольшой пример:
obdc_connect("myAccessDB", "user", "secret")
or die("Could not connect to ODBC database");
print "Currently connected to ODBC database!"; odbc_close($connect);
?>
odbc_prepare( )
Перед непосредственной обработкой запрос необходимо «подготовить». Задача решается функцией odbc_prepare( ):
int odbc_prepare (int идентификатор_соединения , string запрос)
В параметре идентификатор_соединения передается идентификатор соединения, возвращаемый функцией odbc_connect( ). В параметре запрос передается текст запроса, который должен быть выполнен сервером. Если запрос не может быть выполнен, функция возвращает FALSE; в противном случае возвращается идентификатор результата, в дальнейшем используемый при вызове функции odbc_execute( ) (см. ниже).
int odbc_execute (int идентификатор результата [, array параметры])
В параметре идентификатор_результата передается идентификатор результата, возвращаемый при успешном вызове odbc_prepare( ). Необязательный параметр передается лишь в том случае, если при вызове функции передаются дополнительные данные.
$query = "UPDATE customers SET cust_id = \"Milano, Inc.\"
WHERE cust_id \"2000cu\"";
$result = odbc_prepare($connect, $query) or die("Couldn't prepare query!");
$result = odbc_execute($result) or die("Couldn't execute query!");
odbc_close($connect);
?>
В приведенном примере продемонстрирована транзакция ODBC, при которой данные запроса не отображаются в браузере (как это обычно делается при использовании команды SELECT). Транзакция ODBC с использованием запроса QUERY описана ниже, в разделе «odbc_result_all( )».
odbc_exec( )
Функция odbc_exec( ) объединяет odbc_prepare( ) и odbc_execute( ). Синтаксис функции odbc_exec( ):
int odbc_exec (int идентификатор_соединения , string запрос)
В параметре идентификатор_соединения передается идентификатор соединения, возвращаемый функцией odbc_connect( ). В параметре запрос передается текст запроса, который должен быть выполнен сервером. Если запрос не может быть выполнен, функция возвращает FALSE; в противном случае возвращается идентификатор результата, используемый при последующих вызовах функций:
obdc_connect("myAccessDB", "user", "secret")
or die("Could not connect to ODBC database");
$query = "SELECT * FROM customers";
$result = odbc_exec($connect, $query) or die("Couldn't execute query!");
odbc_close($connect) ;
?>
В этом примере функция odbc_exec( ) пытается выполнить запрос, текст которого содержится в переменной $query. При успешном выполнении переменной $result присваивается идентификатор результата; в случае неудачи ей присваивается значение FALSE и выводится сообщение, передаваемое при вызове die( ).
odbc_result_all( )
Очень удобная функция odbc_result_all( ) форматирует и отображает все записи для идентификатора результата, полученного при вызове odbc_exec( ) или odbc_execute( ). Синтаксис функции odbc_result_all( ):
int odbc_result_all (int идентификатор_результата [, string формат_таблицы])
В параметре идентификатор_результата передается идентификатор результата, возвращаемый при успешном вызове odbc_exec( ) или odbc_execute( ). В необязательном параметре формат_таблицы передаются характеристики таблицы HTML. Рассмотрим следующий пример:
obdc_connect("myAccessDB", "user", "secret")
or die("Could not connect to ODBC database");
$query = "SELECT * FROM customers";
$result = odbc_exec($connect, $query) or die("Couldn't execute query!");
В результате выполнения этого примера содержимое таблицы customers будет выведено в таблице на светло-сером фоне с рамкой толщиной 1. Внешний вид таблицы для данных, приведенных на рис. 11.1, изображен на рис. 11.2.
Рис. 11.2.
Данные ODBC в браузере
odbc_free_result ( )
Хороший стиль программирования требует освобождения всех ресурсов, связанных с завершенными операциями. При работе с запросами ODBC эта задача решается функцией odbc_free_result( ). Синтаксис функции odbc_free_result( ):
int odbc_free_result (int идентификатор_результата)
В параметре функции передается идентификатор результата, который больше не будет использоваться в программе. Следует помнить, что все ресурсы памяти автоматически освобождаются по завершении сценария; таким образом, вызов функции odbc_free_result( ) необходим лишь для очень больших запросов, расходующих очень большие объемы памяти. Ниже приведен пример, демонстрирующий применение odbc_free_result( ). Помните, что без этой функции обычно можно обойтись, если только вы не собираетесь выполнять несколько запросов в одном сценарии, поскольку с завершением сценария вся память будет автоматически освобождена.
obdc_connect("myAccessDB", "user", "secret")
or die("Could not connect to ODBC database");
$query = "SELECT * FROM customers":
$result = odbc_exec($connect, $query) or die("Couldn't execute query!");
После того как функция odbc_result_all( ) завершила использование идентификатора результата, память возвращается в систему при помощи odbc_free_result( ).
На этом завершается наше знакомство с функциями ODBC в РНР, незаменимыми при создании простых интерфейсов на базе ODBC для доступа через Web. В следующем разделе многие из этих функций будут использованы для демонстрации того, как легко на базе РНР организуется взаимодействие с одной из самых популярных СУБД — Microsoft Access.
Проект: каталог ссылок
Самый простой способ наполнить ваш сайт реальным содержанием — дать пользователю возможность сделать это за вас. Конечно, для ввода данных удобнее всего воспользоваться формой HTML.
Введенную пользователем информацию необходимо обработать и сохранить. В проекте предыдущей главы было показано, как легко эта задача решается при помощи РНР и текстового файла. Бесспорно, текстовые файлы хорошо подходят для хранения относительно маленьких и простых фрагментов данных, но в полноценных web-приложениях информация обычно хранится в базах данных. В настоящем примере база данных MySQL используется для хранения информации о web-сайтах. Для упрощения навигации сайты разделены на несколько категорий. Пользователь может ввести информацию о сайте в форме HTML и отнести его к одной из стандартных категорий, определенных администратором сайта. Кроме того, пользователь может загрузить индексную страницу и просмотреть список всех сайтов той или иной категории, щелкнув на ее названии.
Прежде всего вы должны решить, какие сведения о сайтах будут храниться в базе данных SQL. Для простоты я ограничусь следующим набором атрибутов: название, URL, категория, дата включения и описание. Таким образом, определение таблицы в MySQL выглядит примерно так:
mysql>create table bookmarks ( category INT, site_name char(35), url char(50),
date_added date, description char(254) );
В определении таблицы bookmarks имеется пара моментов, заслуживающих внимания. Во-первых, информация о категории сайта почему-то хранится в виде целого числа — но разве не правильнее было бы сделать названия категорий более содержательными и понятными для пользователя? Не беспокойтесь, в ини-циализационном файле будет создан массив, связывающий целочисленные индексы с названиями категорий. В будущем администратор может изменять и даже удалять отдельные категории. Хранение информации о категориях заметно упрощает эту задачу. Кроме того, целочисленное поле обеспечивает экономию места на диске, поскольку название категории многократно сохраняется в таблице. Другое обстоятельство, относящееся к структуре таблицы, — ограничение длины описания 254 символами. В зависимости от этого объема описаний вместо типа char( ) можно воспользоваться типом medium или text. За дополнительной информацией о типах полей обращайтесь к документации MySQL.
Следующим шагом в работе над этим приложением будет создание инициализа-ционного файла. Помимо глобальных переменных, в инициализационном файле определяются две функции: add_bookmark( ) и view_bookmark( ). Функция add_bookmark( ) получает пользовательский ввод из формы и включает его в базу. Функция view_bookmark( ) получает индекс категории, читает из базы данных все записи, относящиеся к указанной категории, и отображает их в браузере. Инициализацион-ный файл приложения init.inc с комментариями приведен в листинге 11.8.
Листинг 11.8.
Инициализационный файл каталога ссылок (init.inc)
// Файл: init.inc
// Назначение: глобальные переменные и функции.
// используемые в проекте
// Стандартный заголовок страницы $title = "My Bookmark Repository":
// Цвет фона $bg_color = "white";
// Дата
$post_date = date("Ymd");
// Категории $categories = array(
"computers",
"entertainment",
"dining",
"lifestyle",
"government",
"travel");
// Данные сервера MySQL $host = "localhost"; $user = "root"; $pswd = "";
// Имя базы данных $database = "book";
// Имя таблицы $bookmark_table = "bookmarks";
// Цвет ячеек таблицы $cell_color = "#c0c0c0";
// Установить соединение с сервером MySQL
@mysql_pconnect($host, $user, $pswd) or die("Couldn't connect to MySQL server!");
// Выбрать базу данных
@mysql_select_db($database) or die("Couldn't select Sdatabase database!");
// Функция: add_bookmark( )
// Назначение: включение новой ссылки в таблицу bookmark.
function add_bookmark (Scategory, Ssitejname. $url, $description) {
Следующая страница, add_bookmark.php (листинг 11.9), предназначена для ввода информации о новой ссылке, включаемой в базу данных. Для обработки пользовательских данных вызывается функция add_bookmark( ).
При исходной загрузке страницы в браузере отображается форма (рис. 11.5).
После сохранения ссылки в базе программа выдает соответствующее сообщение и создает ссылку для перехода к домашней странице приложения index.php (листинг 11.11).
Рис. 11.5.
Форма, отображаемая страницей add_bookmark.php
Следующая страница, view_bookmark.php, просто вызывает функцию view_bookmark( ). Код этой страницы приведен в листинге 11.10.
Листинг 11.10.
Программа view_bookmark.php
INCLUDE("Listing11-8.php"); ?>
=$title:?>
view_bookmark($category) :
?>
Если занести в категорию dining информацию о нескольких сайтах, страница view_bookmark. php будет выглядеть примерно так, как показано на рис. 11.6.
Рис. 11.6.
Выполнение страницы view_bookmark.php для категории dining
Остается лишь создать страницу, на которой пользователь выбирает ссылки из списка. Я назвал этот файл index.php (листинг 11.11).
Листинг 11.11.
Программа index.php
INCLUDE("init.inc");
?>
alink="#808040">
Choose bookmark category to view:
// Перебрать категории и создать соответствующие ссылки
Если оставить в массиве $categories значения, сохраненные в файле init.inc, в результате выполнения листинга 11.11 в браузер будет отправлен код HTML, приведенный в листинге 11.12.
Листинг 11.12.
Выходные данные, сгенерированные при выполнении index.php
Если щелкнуть на любой ссылке из приведенного выше,фрагмента HTML, в браузере загружается файл view_bookmark.php, который вызывает функцию view_bookmark( ) и передает ей значение переменной $category.
Простейшая поисковая система
Всем нам неоднократно приходилось пользоваться поисковыми системами в Web, но как устроены такие системы? Простейшая поисковая система принимает по крайней мере одно ключевое слово. Это слово включается в запрос SQL, который затем используется для выборки информации из базы данных. Результат поиска форматируется поисковой системой по тому или иному критерию (скажем, по категории или степени соответствия).
Поисковая система, приведенная в листинге 11.5, предназначена для поиска информации о клиентах. В форме пользователь вводит ключевое слово и выбирает категорию (имя, идентификатор или адрес электронной почты клиента), в которой будет производиться поиск. Если введенное пользователем имя, идентификатор или адрес существует, поисковая система извлекает из базы данных остальные атрибуты. Затем по идентификатору покупателя из таблицы orders выбирается
история заказов. Все заказы, оформленные этим клиентом, отображаются по убыванию объема. Если заданное ключевое слово не встречается в категории, указанной пользователем, поиск прекращается, программа выводит соответствующее сообщение и снова отображает форму.
Листинг 11.5. Простейшая поисковая система (searchengine.php)
$form =
"
// Если форма еще не отображалась - отобразить ее
if (Sseenform != "у") :
print $form; else :
// Подключиться к серверу MySQL и выбрать базу данных
@mysql_connect("localhost", "web", "ffttss")
or die(" Could not connect to MySQL server!");
@mysql_select_db("company")
or die("Could not select company database!");
// Построить и выполнить запрос
$query = "SELECT cust_id. cust_name, cust_email
FROM customers WHERE $category = '$keyword'";
$result = mysql_query($query);
// Если совпадения не найдены, вывести сообщение
// и заново отобразить форму
if (mysql_num_rows($result) == 0) :
print "Sorry, but no matches were found. Please try your search again:";
print $form;
// Найдены совпадения. Отформатировать и вывести результаты, else :
// Построить и выполнить запрос к таблице 'orders'
$query = "SELECT order_id, prod_id, quantity
FROM orders WHERE cust_id = '$id'
ORDER BY quantity DESC";
$result = mysql_query($query):
print "
";
print "
0rder ID
Product ID
Quantity
";
// Отформатировать и вывести найденные записи.
while (list($order_id, $prod_id, $quantity) = mysql_fetch_row($result));
print "
";
print "
$order_id
$prod_id
$quantity
";
print "
";
endwhile;
print "
";
endif;
endif;
?>
Если ввести ключевое слово Mi 1 апо и выбрать в раскрывающемся списке категорию Customer Name (Имя клиента), программа выводит следующую информацию:
Customer information:
Name:Milano
Identification#:2000cu
Email:felix@milano.com
Order History:
Order Id
Product Id
Quantity
100003
1000pr
12
100005
1002pr
11
Конечно, мы рассмотрели простейшую реализацию поисковой системы. Существует немало дополнительных возможностей — поиск по нескольким ключевым словам, поиск по неполным ключевым словам или автоматическая выборка записей с похожими ключевыми словами. Попробуйте применить свою творческую фантазию и реализовать их самостоятельно.
Сортировка таблиц
При выводе данных из базы необходимо предусмотреть возможность их сортировки по различным критериям. В качестве примера рассмотрим результаты, выведенные нашей поисковой системой, — обратим особое внимание на следующие после заголовка Order History: (История заказов). Допустим, список получился очень длинным, и вы хотите отсортировать данные по идентификатору товара (или идентификатору заказа). Чтобы вы лучше поняли, о чем идет речь, рекомендую посетить один из моих любимых сайтов, http://download.cnet.com. Если в процессе просмотра программ конкретной категории щелкнуть на заголовке столбца (название, дата размещения, количество загрузок или размер файла), то список автоматически упорядочивается по содержимому указанного столбца. Далее показано, как реализовать подобную возможность.
В листинге 11.6 мы производим выборку данных из таблицы orders. По умолчанию данные сортируются по убыванию объема заказа (поле quantity). Однако щелчок на любом заголовке таблицы приводит к тому, что страница загружается заново с упорядочением таблицы по указанному столбцу.
Листинг 11.6. Сортировка таблиц (tablesorter.php)
// Подключиться к серверу MySQL и выбрать базу данных
@mysql_connect("localhost". "web", "ffttss")
or die("Could not connect to MySQL server!");
@mysql_select_db( "company")
or die("Could not select company database!");
// Если значение переменной $key не задано, по умолчанию
// используется значение 'quantity' if (! isset($key)) :
$key = "quantity"; endif;
// Создать и выполнить запрос.
// Выбранные данные сортируются по убыванию столбца $key
$query = "SELECT order_id, cust_id, prod_id, quantity FROM orders ORDER BY $key DESC" $result = mysql_query($query);
// Создать заголовок таблицы
print "
// Отформатировать и вывести каждую строку таблицы
while (list($order_id,$cust_id,$prod_id, $quantity)
= mysql_fetch_row($result)) :
print "
";
print "
$order_id
$cust_id
$prod_id
$quantity
";
print "
";
endwhile;
// Завершить таблицу
print "
";
Для базы данных company, изображенной на рис. 11.1, стандартные выходные данные листинга 11.6 выглядят следующим образом:
Order ID
Customer ID
Product ID
Quantity
100003
2000cu
1000pr
12
100005
2000cu
1002pr
11
100004
2000cu
1000pr
9
100002
2000cu
1001pr
5
100001
2000cu
1002pr
3
Обратите внимание: заголовки таблицы представляют собой гиперссылки. Поскольку по умолчанию сортировка осуществляется по полю quantity, записи отсортированы по убыванию последнего столбца. Если щелкнуть на ссылке Order_ID, страница загружается заново, но на этот раз записи сортируются по убыванию идентификатора заказа. Таблица будет выглядеть так:
Order ID
Customer ID
Product ID
Quantity
100005
2000cu
1002pr
11
100004
2000cu
1000pr
9
100003
2000cu
1000pr
12
100002
2000cu
1001pr
5
100001
2000cu
1002pr
3
Сортировка выходных данных приносит огромную пользу при форматировании баз данных. Простая модификация запроса SELECT позволяет упорядочить данные по любому критерию — по возрастанию, по убыванию или с группировкой записей.
На этом наше знакомство с MySQL подходит к концу. Учтите, что материал этой главы отнюдь не исчерпывает всего, что необходимо знать о MySQL. Полный список команд MySQL в РНР приведен в документации (http://www.php.net/manuat).
Стандартные функции РНР для работы с MySQL
Итак, вы успешно создали и протестировали все необходимые разрешения; все готово для работы с сервером MySQL. В этом разделе я представлю стандартные функции РНР, при помощи которых вы сможете легко организовать взаимодействие сценариев РНР с сервером MySQL. Общая последовательность действий при взаимодействии с сервером MySQL выглядит так:
Установить соединение с сервером MySQL. Если попытка завершается неудачей, вывести соответствующее сообщение и завершить процесс.
Выбрать базу данных сервера MySQL. Если попытка выбора завершается неудачей, вывести соответствующее сообщение и завершить процесс. Допускается одновременное открытие нескольких баз данных для обработки запросов.
Обработать запросы к выбранной базе (или базам).
После завершения обработки запросов закрыть соединение с сервером баз данных.
В примерах этого раздела используются таблицы products, customers и orders (см. рис. 11.1). Если вы захотите самостоятельно проверить все примеры, создайте эти таблицы или скопируйте страницу с описанием структуры, чтобы вам не приходилось постоянно листать книгу.
Итак, начнем с самого начала — то есть с подключения к серверу MySQL.
mysql_connect()
Функция mysql_connect( ) устанавливает связь с сервером MySQL После успешного подключения к MySQL можно переходить к выбору баз данных, обслуживаемых этим сервером. Синтаксис функции mysql_connect( ):
int mysql_connect ([string хост [:порт] [:/путь//к/сокету] [, string имя пользователя] [, string пароль])
В параметре хост передается имя хостового компьютера, указанное в таблицах привилегий сервера MySQL. Конечно, оно же используется для перенаправления запросов на web-сервер, на котором работает MySQL, поскольку к серверу MySQL можно подключаться в удаленном режиме. Наряду с именем хоста могут указываться необязательные параметры — номер порта, а также путь к сокету (для локального хоста). Параметры имя_пользователя и пароль должны соответствовать имени пользователя и паролю, заданным в таблицах привилегий MySQL. Обратите внимание: все параметры являются необязательными, поскольку таблицы привилегий можно настроить таким образом, чтобы они допускали соединение без проверки. Если параметр хост не задан, mysql_connect( ) пытается установить связь с локальным хостом.
Параметр имя_базы_данных определяет выбираемую базу данных, идентификатор
которой возвращается функцией mysql_select_db( ). Обратите внимание: параметр
идентификатор_соединения необязателен лишь при одном открытом соединении с
сервером MySQL. При наличии нескольких открытых соединений этот параметр
должен указываться. Пример выбора базы данных функцией mysql_select_db( ):
@mysql_connect("localhost", "web". "4tf9zzzf")
or die("Could not connect to MySQL server!");
@mysql_select_db("company") or die("Could not select company database!");
?>
Если в программе выбирается только одна база данных, сохранять ее идентификатор не обязательно. Однако при выборе нескольких баз данных возвращаемые идентификаторы сохраняются, чтобы вы могли сослаться на нужную базу при обработке запроса. Если идентификатор не указан, используется последняя выбранная база данных.
mysql_close( )
После завершения работы с сервером MySQL соединение необходимо закрыть. Функция mysql_close( ) закрывает соединение, определяемое необязательным параметром. Если параметр не задан, функция mysql_close( ) закрывает последнее открытое соединение. Синтаксис функции mysql_close( ):
int mysql_close ([int идентификатор_соединения])
Пример использования mysql_close( ):
@mysql_connect("localhost", "web", "4tf9zzzf")
or die("Could not connect to MySQL server!");
@mysql_select_db("company") or die("Could not select company database!"); print "You're connected to a MySQL database!";
?>
В этом примере указывать идентификатор соединения не нужно, поскольку на момент вызова mysql_close( ) существует лишь одно открытое соединение с сервером.
Соединения, открытые функцией mysql_pconnect( ), закрывать не обязательно.
mysql_query( )
Функция mysql_query( ) обеспечивает интерфейс для обращения с запросами к базам
данных. Синтаксис функции mysql_query( ):
int mysql_query (string запрос [, int идентификатор_соединения])
Параметр запрос содержит текст запроса на языке SQL. Запрос передается либо соединению, определяемому необязательным параметром идентификатор_соедине-ния, либо, при отсутствии параметра, последнему открытому соединению.
Неопытные программисты часто ошибочно думают, что функция mysql_query( ) возвращает результаты обработки запроса. Это не так — в зависимости от типа запроса вызов mysql_query( ) может приводить к разным последствиям. При
успешном выполнении команды SQL SELECT возвращается идентификатор результата, который впоследствии передается функции mysql_result( ) для последующего форматирования и отображения результатов запроса. Если обработка запроса завершилась неудачей, функция возвращает FALSE. Функция mysql_result( ) описана в одном из следующих разделов. Количество записей, участвующих в запросе, определяется при помощи функции mysql_num_rows( ). Эта функция также описана далее.
Учитывая сказанное, я приведу примеры использования mysql_query( ) лишь после описания функций mysql_result( ) и mysql_affected_rows( ).
Если вас беспокоит то, что при обработке запросов расходуется слишком много памя-ти, вызовите стандартную функцию РНР mysql_free_result. При вызове ей передается идентификатор результата, возвращаемый mysql_query( ). Функция mysql_free_result( ) освобождает всю память, связанную с данным запросом.
mysqLaff ected_rows ( )
Во многих ситуациях требуется узнать количество записей, участвующих в запросе SQL с командами INSERT, UPDATE, REPLACE или DELETE. Задача решается функцией mysql_affected_rows( ). Синтаксис функции:
int mysql_affected_rows ([int идентификатор_соединения])
Обратите внимание: параметр идентификатор_соединения не является обязательным. Если он не указывается, mysql_affected_rqws( ) пытается использовать последнее открытое соединение. Пример:
// Подключиться к серверу и выбрать базу данных
@mysql_connect("localhost", "web". "4tf9zzzf")
or die(" Could not connect to MySQL server!");
@mysql_select_db("company") or die("Could not select company database!");
// Создать запрос
$query = "UPDATE products SET prod_name = \"cantaloupe\"
При выполнении этого фрагмента будет выведен следующий результат:
Total row updated: 1
Функция mysql_affected_rows( ) не работает с запросами, основанными на команде SELECT. Для определения количества записей, возвращенных при вызове SELECT, используется функция mysql_num_rows( ), описанная в следующем разделе.
В одной специфической ситуации функция mysql_affected_rows( ) работает с ошибкой. При выполнении команды DELETE без секции WHEREmysql_affected_rows( ) всегда возвращает 0.
mysql_num_rows( )
Функция mysql_num_rows( ) определяет количество записей, возвращаемых командой SELECT. Синтаксис функции mysql_num_rows( ):
int mysql_num_rows(int результат)
Пример использования mysql_num_rows( ):
// Подключиться к серверу и выбрать базу данных @mysql_connect("localhost", "web", "4tf9zzzf")
or die("Could not connect to MySQL server!");
@mysql_select_db("company") or die("Could not select company database!");
// Выбрать все товары, названия которых начинаются с 'р'
$query = "SELECT prod_name FROM products WHERE prod_name LIKE \"p*\"";
Поскольку таблица содержит лишь один товар, название которого начинается с буквы р (pears), возвращается только одна запись. Результат:
Total rows selected: 1
mysql_result( )
Функция mysql_result() используется в сочетании с mysql_query( ) (при выполнении запроса с командой SELECT) для получения набора данных. Синтаксис функции mysql_resu1t():
int mysql_result (int идентификатор_результата, int запись [. mixed поле"]')
В параметре идентификатор_результата передается значение, возвращенное функцией mysql_query( ). Параметр запись ссылается на определенную запись набора данных, определяемого параметром идентификатор_результата. Наконец, в необязательном параметре поле могут передаваться:
смещение поля в таблице;
имя поля;
имя поля в формате имя_поля_имя_тдблицы.
В листинге 11.1 используется база данных, изображенная на рис. 11.1.
Листинг 11.1.
Выборка и форматирование данных в базе данных MySQL
@mysql_connect("localhost", "web", "ffttss")
or die("Could not connect to MySQL server!");
@mysql_select_db("company")
or die("Could not select products database!");
// Выбрать все записи из таблицы products
$query = "SELECT * FROM products"; $result = mysql_query($query);
$x = 0;
print "
\n";
print "
\n
Product ID
Product Name
Product Price
\n
\n";
while ($x < mysql_numrows($result)) :
$id = mysql_result($result. $x. 'prod_id');
$name = mysql_result($result, $x, 'prod_name');
$price = mysql_result($result. $x, 'prod_price');
print "
\n";
print "
$id
\n
$name
\n
$price
\n";
print "
\n";
$x++;
endwhile;
print "
";
mysql_close();
?>
В результате выполнения этого примера с данными, изображенными на рис. 11.1, будет получен следующий результат:
Листинг 11.2.
Результат выполнения листинга 11.1
Product ID
Product Name
Product Price
1000pr
apples
1.23
1001pr
oranges
2.34
1002pr
bananas
3.45
1003pr
pears
4.45
Функция mysql_result( ) удобна для работы с относительно небольшими наборами данных, однако существуют и другие функции, работающие намного эффективнее, — а именно, функции mysql_fetch_row( ) и mysql_fetch_array( ). Эти функции описаны в следующих разделах.
mysql_fetch_row()
Обычно гораздо удобнее сразу присвоить значения всех полей записи элементам индексируемого массива (начиная с индекса 0), нежели многократно вызывать mysql_result( ) для получения отдельных полей. Задача решается функцией mysql_fetch_row( ), имеющей следующий синтаксис:
array mysql_fetch_row (int результат)
Использование функции list( ) в сочетании с mysql_fetch_row( ) позволяет сэкономить несколько команд, необходимых при использовании mysql_result( ). В листинге 11.3 приведен код листинга 11.1, переписанный с использованием list( ) и mysql_fetch_row( ).
Листинг 11.3.
Выборка данных функцией mysql_fetch_row( )
@mysql_connect( "localhost", "web", "ffttss") or die("Could not connect to MySQL server!");
@mysql_select_db("company") or die("Could not select products database!");
$query = "SELECT * FROM products";
$result = mysql_query($query);
print "
\n";
print "
\n
Product ID
Product Name
Product Price
\n
\n";
while ($row = mysql_fetch_array($result)) :
print "
\n":
print "
".$row["prod_id"]."
\n
".$row["prod_name"]."
\n
" .$row["prod_price"]. "
\n";
print "
\n";
endwhile;
print "
";
mysql_close();
?>
Листинг 11. 3 выдает тот же результат, что и листинг 11.1, но использует при этом меньшее количество команд.
my sq l_f etch_array ( )
Функция mysql_fetch_array( ) аналогична mysql_fetch_row( ), однако по умолчанию значения полей записи сохраняются в ассоциативном массиве. Впрочем, вы можете выбрать тип индексации (ассоциативная, числовая или комбинированная). Синтаксис функции mysql_fetch_array( ):
В параметре идентификатор_результата передается значение, возвращенное функцией mysql_query( ). Необязательный параметр тип_индексации принимает одно из следующих значений:
MYSQL_ASSOC — функция mysql_fetch_array( ) возвращает ассоциативный массив. Если параметр не указан, это значение используется по умолчанию;
MYSQL_NUM — функция mysql_fetch_array( ) возвращает массив с числовой индексацией;
MYSQL_BOTH — к полям возвращаемой записи можно обращаться как по числовым, так и по ассоциативным индексам.
Листинг 11.4 содержит очередной вариант кода листингов 11.1 и 11.3. На этот раз используется функция mysql_fetch_array( ), возвращающая ассоциативный массив полей.
Листинг 11.4.
Выборка данных функцией mysql_fetch_array( )
@mysql_connect( "local host", "web", "ffttss")
or die("Could not connect to MySQL server!");
@mysql_select_db( "company" )
or die("Could not select products database!");
$query = "SELECT * FROM products";
$result = mysql_query($query);
"
\n";
print "
\n
Product ID
Product Name
Product Price
\n
\n";
while ($row = mysql_fetch_array($result)) ;
print "
\n";
print "
".$row["prod_id"]."
\n
".$row["prod_name"]."
\n
" . $row["prod_price"] . "
\n" ;
print "
\n";
endwhile;
print "
";
mysql_close();
?>
Листинг 11.4 выдает тот же результат, что и листинги 11.1 и 11.3.
Того, что сейчас вы знаете о функциональных возможностях MySQL в РНР, вполне достаточно, чтобы заняться созданием довольно интересных приложений. Первое приложение, которое мы рассмотрим, представляет собой простейшую поисковую систему. Этот пример демонстрирует применение форм HTML (см. предыдущую главу) для получения данных, которые в дальнейшем используются для выборки информации из базы.
Установка
Одна из причин популярности MySQL среди пользователей РНР заключается в том, что поддержка этого сервера автоматически включается в поставку РНР. Таким образом, вам остается лишь проследить за правильной установкой пакета MySQL СУБД MySQL совместима практически с любой серьезной операционной системой, включая FreeBSD, Solaris, UNIX, Linux и различные верии Windows. Хотя лицензионная политика MySQL отличается большей гибкостью в сравнении с другими серверами баз данных, я настоятельно рекомендую ознакомиться с лицензионной информацией, размещенной на сайте MySQL (http://www.mysql.com).
Последнюю версию MySQL можно принять с любого зеркального сайта. Полный список зеркальных сайтов приведен по адресу http://www.mysql.com/downloads/ mirrors.html. На момент написания книги последняя стабильная версия MySQL имела номер 3.22.32, а версия 3.32 находилась на стадии бета-тестирования. Конечно, всегда следует устанавливать последнюю стабильную версию, это в ваших интересах. Посетите ближайший «зеркальный» сайт и загрузите версию, соответствующую вашей операционной системе. В верхней части страницы расположены ссылки на новые версии для различных платформ. Обязательно прочитайте всю страницу, поскольку она завершается ссылками для некоторых специфических ОС.
Группа разработчиков MySQL подготовила подробную документацию с описанием процесса установки. Я советую внимательно изучить все общие аспекты установки, не ограничиваясь информацией, относящейся непосредственно к вашей операционной системе.
PHP 4 на практике
Недостатки системы шаблонов
Хотя рассмотренная система шаблонов справляется со своей главной задачей — полным разделением дизайна и программирования, она не лишена недостатков. Некоторые из этих недостатков перечислены ниже.
Необоснованные надежды на «идеальное решение»
Шаблоны помогают четко выделить в проекте аспекты программирования и дизайна, но они не заменяют нормального взаимодействия между этими аспектами. Более того, правильность их работы зависит от предварительного согласования списка переменных, заменяемых в процессе обработки шаблона. Как и в любом успешном проекте, переходить к написанию кода РНР следует лишь после тщательной проработки спецификации всего приложения. Это значительно уменьшает вероятность ошибок при последующей обработке, приводящих к непредвиденным последствиям при использовании шаблонов.
Нетривиальная система шаблонов
Как говорилось ранее, главной целью при разработке подобных систем шаблонов является фактическое отделение дизайна от функциональных возможностей. Собственно, эта система и создается для того, чтобы программисты и дизайнеры могли независимо трудиться над своими аспектами приложения, не мешая работе другой группы.
К счастью, сделать это проще, чем кажется на первый взгляд, — при условии, что до
начала разработки было проведено некоторое предварительное планирование. В листинге 12.1 представлен некий базовый шаблон, созданный на основе материала этой главы.
Листинг 12.1.
Пример шаблона
:::::{page_title}:::::
Welcome to your default home page. {user_name}!
You have 5 MB and 3 email addresses at your disposal.
Have fun!
Обратите внимание на три строки (page_title, bg_color и userjiame), заключенные в фигурные скобки ({ }). Фигурные скобки имеют особый смысл при обработке шаблонов — заключенная в них строка интерпретируется как имя переменной, вместо которого подставляется ее значение. Дизайнер строит страницу по своему усмотрению; все, что от него потребуется, — включать в соответствующие места документа эти ключевые строки. Конечно, программисты и дизайнеры должны заранее согласовать имена всех переменных!
Итак, как же работает эта схема? Прежде всего, возможно, нам придется одновременно работать с несколькими шаблонами, обладающими одними и теми же общими атрибутами. В таких ситуациях применение технологии объектно-ориентированного программирования (ООП) оказывается особенно эффективным. По этой причине все функции построения и выполнения операций с шаблонами будут оформлены в виде методов класса. Определение класса начинается так:
class template {
VAR $files = array( );
VAR $variables = array( );
VAR $openi ng_escape = '{';
VAR $closing_escape = '}';
В массиве $files хранятся идентификаторы файлов и содержимое каждого файла. Атрибут $variables представляет собой двухмерный массив для хранения файлового идентификатора (ключа) и всех соответствующих переменных, обрабатываемых в схеме шаблонов. Наконец, атрибуты $opening_escape и $closing_escape задают ограничители для частей шаблона, которые должны заменяться системой. Как было показано в листинге 12.1, в наших примерах в качестве ограничителей будут использоваться фигурные скобки ({ }). Впрочем, вы можете изменить два последних атрибута и выбрать ограничители по своему усмотрению. Главное — проследите за тем, чтобы эти символы не использовались для других целей.
Каждый метод класса решает конкретную задачу, соответствующую той или иной операции в процессе обработки шаблона. На простейшем уровне этот процесс можно разделить на четыре стадии.
Регистрация файлов
— регистрация всех файлов, обрабатываемых сценариями шаблонов.
Регистрация переменных
— регистрация всех переменных, которые должны заменяться своими значениями в зарегистрированных файлах.
Обработка файлов
— замена всех переменных, находящихся между ограничителями, в зарегистрированных файлах.
Вывод файла
— вывод обработанных зарегистрированных файлов в браузере.
Применение концепций ООП в РНР рассматривалось в главе 6. Если вы не знакомы с ООП, я рекомендую бегло просмотреть главу 6 перед тем, как читать дальше.
О чем говорилось выше
До настоящего момента я упоминал о двух разных подходах к созданию шаблонов РНР:
внедрение HTML в код РНР;
включение файлов в страницу.
Хотя первая схема более понятна и проще реализуется, она также в большей степени ограничивает вашу свободу действий. Главная проблема заключается в том, что код РНР смешивается с компонентами HTML, образующими макет страницы. Возникающие при этом проблемы связаны не только с необходимостью потенциальной поддержки одновременного доступа к странице и ее модификации, но и с повышенной вероятностью ошибок при непосредственном просмотре и редактировании страниц.
Вторая схема во многих ситуациях оказывается гораздо удобнее первой. Тем не менее, хотя структура «заголовок — основная часть — колонтитул» (см. главу 9)
хорошо подходит для структурирования относительно малых сайтов с четко определенным форматом, с увеличением объемов и сложности проекта эти ограничения проявляются все заметнее. Попытки решения этих проблем привели к разработке новой схемы применения шаблонов, более сложной по сравнению с двумя первыми, но и обладающей существенно большей гибкостью. В этой схеме разделяются два главных компонента web-приложения: дизайн и программирование. Подобное деление обеспечивает возможность параллельной разработки (web-дизайн и программирование) без необходимости постоянной координации на протяжении всего рабочего цикла. Более того, оно позволяет в будущем модифицировать один компонент, не влияя на работу другого. В следующем разделе я покажу, как устроена одна из таких схем «нетривиальных шаблонов». Следует помнить, что эта схема существует не только в РНР. Более того, она появилась задолго до РНР и в настоящее время используется в нескольких языках, включая РНР, Perl и Java Server Pages. To, что описано в этой главе, — не более чем адаптация этой схемы применительно к РНР.
Обработка файла
После того как файлы и переменные будут зарегистрированы в системе шаблонов, можно переходить к обработке зарегистрированных файлов и замене всех ссылок на переменные с соответствующими значениями. Метод file_parser( ) приведен в листинге 12.4.
Листинг 12.4. Метод обработки файла
function file_parser($file_id) {
// Сколько переменных зарегистрировано для данного файла?
$varcount = count($this->variables[$file_id]);
// Сколько файлов зарегистрировано?
$keys = array_keys($this->files):
// Если файл $file_id существует в массиве
$this->files
// и с ним связаны зарегистрированные переменные
If ( (in_array($file_id. $keys)) && ($varcount > 0) ) :
// Сбросить $x $x = 0:
// Пока остаются переменные для обработки...
while ($x < sizeof($this->variables[$file_id])) :
// Получить имя очередной переменной $string = $this->variables[$file_id][$x];
// Получить значение переменной. Обратите внимание:
// для получения значения используется конструкция $$.
// Полученное значение подставляется в файл вместо
// указанного имени переменной.GLOBAL $$string:
// Построить точный текст замены вместе с ограничителями
$needle = $this->opening_escape.$string.$this->closing_escape;
// Выполнить замену.
$this->files[$file_id] = str_replace( $needle.
$$string.
$this->files[$file_id]);
// Увеличить $х $x++;
endwhile;
endif;
}
Сначала мы проверяем, присутствует ли указанное имя файла в массиве $this->files. Если файл был зарегистрирован, мы также проверяем, были ли для него зарегистрированы переменные, и если были — значения этих переменных подставляются в содержимое $file_id. Пример:
// Включить класс шаблона include("template. class") ;
$page_title = "Welcome to your homepage!";
$bg_color = "white"; $user_name = "Chef Jacques";
// Создать новый экземпляр класса
$template = new template;
// Зарегистрировать файл "homepage.html",
II присвоив ему псевдоним "home"
$template->register_file( "home", "homepage.html");
// Зарегистрировать несолько переменных
$template->register_variables("home", "page_titie, bg_color, user_name");
$template->file_parser("home");
Поскольку переменные page_title, bg_color и user_name были зарегистрированы, значения каждой переменной (присвоенные в начале сценария) подставляются в страницу homepage.html, хранящуюся в массиве files (атрибуте объекта-шаблона). На этом предварительная подготовка завершается, остается лишь вывести полученный шаблон в браузере. Эта операция рассматривается в следующем разделе.
Ориентация дизайна на РНР
Одна из главных целей создания шаблонов заключается в том, чтобы по возможности изолировать дизайнера от программного кода при редактировании внешнего вида и поведения страницы. В идеальном случае дизайнер должен обладать некоторыми навыками программирования или, по крайней мере, быть знакомым с общими концепциями — переменными, циклами и условными командами. Дизайнеру, абсолютно не разбирающемуся в них, применение шаблонов практически ничего не даст, кроме относительно бесполезных сведений из области синтаксиса. В общем, независимо от того, захотите вы пользоваться этим типом шаблонов или нет, я настоятельно рекомендую потратить немного времени и обучить дизайнера азам языка РНР... а еще лучше — купить ему эту книгу! От этого выиграют обе стороны, поскольку дизайнер приобретет дополнительные навыки и станет более ценным членом рабочей группы, а у программиста появится новый источник идей. Может, дизайнер и не изобретет ничего выдающегося, но зато он взглянет на ситуацию под новым углом зрения, обычно недоступным для программиста.
Проект: адресная книга
Хотя системы шаблонов хорошо подходят для многих типов web-приложений, они приносят особенную пользу в приложениях, ориентированных на выборку и вывод данных, в которых особенно важно обеспечить правильное форматирование.
Примером такого приложения является адресная книга. Представьте себе обычную (бумажную) адресную книгу: все страницы выглядят практически одинаково, различаются разве что буквы, с которых начинаются имена на конкретной странице. Аналогичный подход можно применить и к адресной книге на базе Web. Форматирование в данном случае играет еще более важную роль, поскольку не исключено, что данные придется экспортировать в другое приложение в каком-нибудь специфическом формате. Подобные приложения прекрасно работают на базе шаблонов, поскольку дизайнеру остается лишь создать единый формат страницы, который будет использоваться для всех 26 букв алфавита.
Прежде всего, необходимо решить, какие данные и в каком формате будут храниться в адресной книге. Конечно, оптимальным носителем информации в данном случае является база данных, поскольку это упростит такие полезные операции, как поиск и сортировка данных. В своем примере я воспользуюсь СУБД MySQL. Определение таблицы выглядит следующим образом:
mysql>CREATE table addressbook (
last_name char(35) NOT NULL,
first_name char(20) MOT NULL,
tel char(20) NOT NULL,
email char(55) NOT NULL );
Разумеется, вы можете самостоятельно добавить поля для хранения адреса, города и т. д. Для наглядности я буду использовать сокращенную таблицу, приведенную ранее.
Теперь я возьму на себя роль дизайнера и займусь созданием шаблонов. Для этого проекта нужны два шаблона. Код первого, «родительского» шаблона book.html приведен в листинге 12.8.
Листинг 12.8. Основной шаблон адресной книги book.html
Как видите, файл в основном состоит из ссылок с разными буквами алфавита. Если щелкнуть на букве, в браузере отображается информация обо всех контактах в адресной книге, фамилии которых начинаются с указанной буквы.
В странице встречаются три имени переменных, заключенных в ограничители: page_title, letter и rows_addresses. Смысл первых двух переменных очевиден: текст в заголовке страницы и буква адресной книги, использованная для выборки текущих адресных данных. Третья переменная относится к дополнительному шаблону (листинг 12.9) и определяет файл конфигурации таблицы, включаемый в основной шаблон. Файлы конфигурации таблиц используются в связи с тем, что в сложных страницах может быть одновременно задействовано несколько шаблонов, в каждом из которых данные форматируются в виде таблиц HTML. Шаблон rows.addresses (листинг 12.9) выполняет вспомогательные функции и вставляется в основной шаблон book.html. Вскоре вы поймете, почему это необходимо.
В листинге 12.9 встречаются четыре переменных, заключенных в ограничители: last_name, first_name, telephone и emal. Смысл этих переменных очевиден (см. определение таблицы addressbook). Следует заметить, что этот файл состоит только из табличных тегов строк (
...
) и ячеек (
...
). Дело в том, что этот файл вставляется в шаблон многократно, по одному разу для каждого адреса, прочитанного из базы данных. Поскольку имя переменной rows.addresses в листинге 12.8 включается внутрь тегов
...
, форматирование HTML будет обработано правильно. Чтобы вы лучше поняли, как работает этот шаблон, взгляните на рис. 12.1 — на нем изображена копия страницы адресной книги. Затем проанализируйте листинг 12.10, содержащий исходный текст этой страницы. Вы увидите, что содержимое файла rows.addresses многократно встречается в странице.
Листинг 12.10.
Исходный текст страницы, изображенной на рис. 12.1
Как видно из приведенного листинга, в адресной книге хранятся записи двух лиц, фамилии которых начинаются с буквы F: Bobby Fries и Pierre Frenchy. Соответственно в таблицу вставляются данные двух записей.
Дизайнерская часть проекта адресной книги завершена, и я перехожу к роли программиста. Возможно, вас удивит тот факт, что класс tempiate. class (см. листинг 12.7) практически не изменился, если не считать появления одного нового метода — address_sql( ). Код этого метода приведен в листинге 12.11.
Листинг 12.11.
Обработка данных, полученных в результате запроса
class template {
VAR $files = array( );
VAR $variab!es = array( ):
VAR $sql = array();
VAR $opening_escape - '{';
VAR $closing_escape = '}';
VAR $host = "localhost";
VAR $user = "root";
VAR $pswd = "";
VAR $db = "book";
VAR $address table = "addressbook";
function address_sql($file_id, $vanable_name, $letter) {
// Подключиться к серверу MySQL и выбрать базу данных
// Включить ключ в массив variables для последующего поиска
$this->variables[$file_id][ ] = $variable_name;
// Закрыть файловый манипулятор fclose(lfh);
Комментариев, приведенных в листинге 12.11, вполне достаточно для того, чтобы вы разобрались в происходящем, однако я должен сделать несколько важных замечаний. Во-первых, обратите внимание на то, что файл rows.addresses открывается только один раз. Возможен и другой вариант — многократно открывать и закрывать файл rows.addresses, каждый раз производя замену и присоединяя его содержимое к переменной $complete_table. Впрочем, такое решение будет крайне неэффективным. Потратьте немного времени и разберитесь в том, как новые данные таблицы в цикле присоединяются к переменной $complete_table.
Второе, на что следует обратить внимание при просмотре листинга 12.11, — появление пяти новых атрибутов класса: $host, $user, $pswd, $db и $address_table. В этих атрибутах хранится информация, необходимая для сервера SQL. Полагаю, смысл каждого атрибута понятен без объяснений, а если нет — вернитесь и повторите материал главы 11.
Рис. 12.1.
Страница адресной книги
Все, что осталось сделать — написать файл index.php, инициирующий обработку шаблонов, Код этого файла приведен в листинге 12.12. Если щелкнуть на одной из ссылок (index.php?letter=буква) на странице book.html (см. листинг 12.8), загружается страница index.php, которая, в свою очередь, заново строит book.html с включением новой информации.
Перед вами практический пример, показывающий, как при помощи шаблонов организовать эффективное разделение труда между программистом и дизайнером. Подумайте, как бы вы использовали шаблоны для организации своих разработок. Готов поспорить, что вы найдете им полезное применение.
Расширения класса template
Конечно, класс tempi ate обладает весьма ограниченными возможностями, хотя для проектов, создаваемых на скорую руку, он вполне подходит. Объектно-ориентированные схемы хороши тем, что они позволяют легко наращивать функциональность, не беспокоясь о возможных нарушениях работы существующего кода. Допустим, вы решили создать новый метод, который будет загружать значения для последующей замены из базы данных. Хотя такой метод устроен чуть сложнее, чем метод file_parser( ), производящий простую замену глобальных переменных, его реализация на базе SQL состоит из нескольких строк и легко инкапсулируется в отдельном методе. Более того, мы создадим нечто подобное в проекте адресной книги, завершающем эту главу.
В класс tempi ate можно внести несколько очевидных усовершенствований. Первое — объединение функций register_file( ) и register_variables( ), обеспечивающее автоматическую регистрацию переменных для каждого регистрируемого файла. Конечно, при этом также необходимо реализовать проверку ошибок, чтобы предотвратить регистрацию неверных файлов и переменных.
Однако на этом возможности усовершенствования далеко не исчерпаны. Подумайте, как бы вы реализовали методы, работающие с целыми массивами? На самом деле это проще, чем кажется на первый взгляд. Проанализируйте решение, использованное в проекте адресной книги в конце главы. Общие принципы легко трансформируются под любую конкретную реализацию.
Общие схемы работы с шаблонами были реализованы на нескольких языках и ни в коем случае не являются чем-то принципиально новым. В Web можно найти немало информации о реализации шаблонов. Рекомендую два особенно интересных ресурса — сборники статей, написанных с ориентацией на JavaScript:
Кроме того, описанная схема построения шаблонов используется в нескольких библиотеках РНР, среди которых наибольший интерес представляют следующие:
PHPLib Base Library: http://phplib.netuse.de;
Richard Hayes's Template Class: http://www.heyes-computing.net;
Fast Template: http://www.thewebmasters.net/php.
На сайте ресурсов РНР, PHPBuilder (http://www.phpbuilder.com), также имеется несколько интересных учебников, посвященных обработке шаблонов. Кроме того, загляните на сайт РНР Classes Repository (http://phpclasses.UpperDesign.com), здесь также можно найти несколько реализаций.
Регистрация файлов
В процессе регистрации содержимое файла сохраняется в массиве с ключом, однозначно идентифицирующим этот файл. Метод register_file( ) открывает и читает содержимое файла, имя которого передается в качестве параметра. Код этого метода приведен в листинге 12.2.
Листинг 12.2. Метод регистрации файла
function register_file($file_id, $file_name) {
// Открыть $file_name для чтения или завершить программу
// с выдачей сообщения об ошибке.
$fh = fopen($file_name, "r") or die("Couldn't open $file_name!");
// Прочитать все содержимое файла $file_name в переменную.
$file_contents = fread($fh, filesize($file_name));
// Присвоить содержимое элементу массива
// с ключом $file_id. $this->files[$file_id] = $file_contents;
// Работа с файлом завершена, закрыть его.
fclose($fh);
}
Параметр $file_id содержит идентификатор — «псевдоним» для последующих операций с файлом, упрощающий последующие вызовы метода. Идентификатор используется в качестве ключа для индексирования массива $files. Пример регистрации файла:
// Включить класс шаблона
include("tempiate.class"):
// Создать новый экземпляр класса
$template = new template:
// Зарегистрировать файл "homepage.html",
// присвоив ему псевдоним "home"
$template->register_file("home", "homepage.html");
Регистрация переменных
После регистрации файлов необходимо зарегистрировать все переменные, которые будут интерпретироваться особым образом. Метод register_variables( ) (листинг 12.3) работает по тому же принципу, что и register_file( ), — он читает имена переменных и сохраняет их в массиве $variables.
Листинг 12.3. Метод регистрации переменнных
function register_vanables($file_id, $variable_name) {
// Попытаться создать массив,
// содержащий переданные имена переменных
$input_variables - explode(".", $variable_name);
// Перебрать имена переменных
while (Iist($value) = each($input_variables)) :
// Присвоить значение очередному элементу массива
$this->variables $this->variables[$file_id][] = $value:
endwhile;
}
В параметре $file_id передается ранее присвоенный псевдоним файла. Например, в предыдущем примере файлу homepage.html был присвоен псевдоним home. Обратите внимание — при регистрации имен переменных, которые должны особым образом обрабатываться в файле homepage.html, вы должны ссылаться на файл по псевдониму! В параметре $variable_name передаются имена одной или нескольких переменных, регистрируемых для указанного псевдонима. Пример:
// Включить класс шаблона include("tempiate.class");
// Создать новый экземпляр класса $template = new template;
// Зарегистрировать файл "homepage.html",
// присвоив ему псевдоним "home" $template->register_file("home", "homepage.html");
// Зарегистрировать несколько переменных
$template->register_variablest"home", "page_title.bg_color,user_name");
Снижение быстродействия
Затраты на обработку файлов приводят к некоторому замедлению работы программы. В какой мере замедляется работа, зависит от ряда факторов, в том числе от размера страницы, размера запроса SQL (если они задействован) и аппаратной конфигурации компьютера. Как правило, эти потери настолько малы, что ими можно пренебречь, но в некоторых ситуациях они оказываются довольно значительными (например, при одновременной обработке нескольких шаблонов в условиях высокого трафика).
Вывод файла
Вероятно, после обработки файла вы захотите отправить его в браузер, чтобы пользователь увидел результат обработки шаблона. В нашем примере для вывода
файла создается отдельный метод, приведенный в листинге 12.5, однако в зависимости от ситуации вывод также может интегрироваться с методом f i I e_parser().
Листинг 12.5. Метод вывода файла в браузере
function pnnt_file($file_id) {
// Вывести содержимое файла с идентификатором
$file_id print $this->files[$file id];
}
Все очень просто — при вызове print_file( ) содержимое файла, представленного ключом $file_id, передается в браузер.
В листинге 12.6 приведен пример использования класса template. Листинг 12.6. Пример использования класса template
// Включить класс шаблона, include("tempiate.class");
// Присвоить значения переменным
$page_title = "Welcome to your homepage!";
$bg_color = "white"; $user_name = "Chef Jacques":
// Создать новый экземпляр класса $template= new template;
// Зарегистрировать файл "homepage.html" с псевдонимом "home"
$template->register_file("home", "homepage.html");
// Зарегистрировать переменные
$template->register_variables("home", "page_title, bg_color.user_name");
$template->file_parser("home");
// Передать результат в браузер
$template->print_file("home");
Если бы шаблон, приведенный в листинге 12.1, хранился в файле homepage.html в одном каталоге со сценарием из листинга 12.6, то в браузер был бы направлен следующий код HTML:
:::::Welcome to your homepage!:::::
Welcome to your default home page, Chef Jacques!
You have 5 MB and 3 email addresses at your disposal.
Have fun!
Как видно из приведенного примера, все зарегистрированные переменные были заменены соответствующими значениями. При всей своей простоте класс tempi ate
обеспечивает стопроцентное разделение уровней программирования и дизайна. Полный код класса template приведен в листинге 12.7.
Листинг 12.7. Полный код класса template
class template {
VAR $files = array( );
VAR $variables = array( );
VAR $opening_escape = '{';
VAR $closing_escape = '}' ;
// Функция: register_file( )
// Назначение: сохранение в массиве содержимого файла.
// определяемого идентификатором $file_id
function register_file($file_id. $file_name) {
// Открыть $file_name для чтения или завершить программу
// с выдачей сообщения об ошибке.
$fh = fopen($file_name, "r") or die("Couldn't open $file_name!");
// Прочитать все содержимое файла $file_name в переменную.
$file_contents = fread($fh, filesize($file_name));
// Присвоить содержимое элементу массива
// с ключом $file_id. $this->files[$file_id] = $file_contents;
// Работа с файлом завершена, закрыть его.
fclose($fh):
} // Функция: register_variables( )
// Назначение: сохранение переменных, переданных
// в параметре $variable_name. в массиве с ключом $file_id.
function register_variables($file_id, $variable_name) {
// Попытаться создать массив.
// содержащий переданные имена переменных
$input_variables = explode(".", $vahable_name);
// Перебрать имена переменных
while (list(, $value) = each($input_variables)) :
// Присвоить значение очередному элементу массива $this->variables $this->variables[$file_id][] = $value:
endwhile;
} // Функция: file_parser( )
// Назначение: замена всех зарегистрированных переменных
// в файле с идентификатором $file_id
function file_parser($file_id) {
// Сколько переменных зарегистрировано для данного файла?
$varcount = count($this->variables[$file_id]):
// Сколько файлов зарегистрировано?
$keys = array_keys($this->files):
// Если файл $file_id существует в массиве $this->files
// и с ним связаны зарегистрированные переменные
if ( (in_array($file_id. $keys)) && ($varcount > 0) ) :
// Сбросить $х $x - 0;
// Пока остаются переменные для обработки...
while ($x < sizeof($this->variables[$file_id])) :
// Получить имя очередной переменной
$string = $this->variables[$file_id][$x];
// Получить значение переменной. Обратите внимание:
// для получения значения используется конструкция $$.
// Полученное значение подставляется в файл вместо
// указанного имени переменной.
GLOBAL $$string;
// Построить точный текст замены вместе с ограничителями
$needle = $this->opemng_escape.$string.$this->closing_escape;
// Выполнить замену.
$this->files[$file_id] = str_replace( $needle, $$string,
$this->files[$file_idj);
// Увеличить $х $x++;
endwhile;
endif;
}
// Функция: print_file()
// Назначение: вывод содержимого файла,
// определяемого параметром $file_id
function print_file($file_id) {
// Вывести содержимое файла с идентификатором $file_id
print $this->files[$file_id];
}
} //END template.class
PHP 4 на практике
Что такое cookie?
Cookie представляет собой небольшой пакет информации, переданный web-сервером и хранящийся на клиентском компьютере. В cookie можно сохранить полезные данные, описывающие состояние пользовательского сеанса, чтобы в будущем загрузить их и восстановить параметры сеансовой связи между сервером и клиентом. Cookie используются на многих сайтах Интернета для расширения возможностей пользователя и повышения эффективности сайта за счет отслеживания действий и личных предпочтений пользователя. Возможность хранения этих сведений играет ключевую роль на сайтах электронной коммерции, поддерживающих персональную настройку и целевую рекламу.
Вследствие того, что cookie обычно связываются с конкретным пользователем, в них часто сохраняется уникальный идентификатор пользователя (UIN). Этот идентификатор заносится в базу данных на сервере и используется в качестве ключа для выборки из базы всей информации, связанной с этим идентификатором. Конечно, сохранение UIN в cookie не является обязательным требованием; вы можете сохранить любую информацию при условии, что ее общий объем не превосходит 4 Кбайт (4096 байт).
Cookie и РНР
Хватит теории. Конечно, вам не терпится поскорее узнать, как задать значение cookie в РНР. Оказывается, очень просто — для этой цели используется стандартная функция setcookie( ).
Функция setcookie( ) сохраняет cookie на компьютере пользователя. Синтаксис функции setcookie( ):
int setcookie (string имя [string значение [, int дата [, string путь [, string домен [, int безопасность]]]]])
Если вы прочитали общие сведения о cookie, то смысл параметров setcookie( ) вам уже известен. Если вы пропустили этот раздел и не знакомы с компонентами cookie, я рекомендую вернуться к началу главы и перечитать его, поскольку все параметры setcookie( ) были описаны выше.
Прежде чем следовать дальше, я попрошу вас перечитать следующую фразу не один и не два, а целых три раза. Значение cookie должно устанавливаться до передачи в браузер любой другой информации, относящейся к странице. Напишите эту фразу 500 раз в тетрадке, сделайте татуировку, научите своего попугая произносить эти слова — короче, проявите фантазию. Другими словами, значение cookie не может устанавливаться в произвольном месте web-страницы. Оно должно быть задано до отправки любых данных в браузер; в противном случае cookie не будет работать.
Есть еще одно важное ограничение, о котором также необходимо помнить, — вы не сможете создать cookie и использовать его на той же странице. Либо пользователь должен вручную обновить страницу (хотя рассчитывать на это нельзя), либо вам придется подождать следующего запроса этой страницы — и только после этого можно будет использовать cookie.
В следующем примере функция setcookie( ) используется для создания cookie с идентификатором пользователя:
$userid = "4139b31b7bab052";
$cookie_set = setcookie ("uid", $value, time()+3600, "/", ".phprecipes.com", 0);
Последствия создания cookie:
После перезагрузки или перехода на другую страницу становится доступной переменная $userid, содержащая идентификатор 4139b31b7bab052.
Срок действия cookie истекает ровно через один час (3600 секунд) после отправки. После истечения этого срока cookie становится недействительным.
Доступ к cookie разрешен только из домена phprecipes.com.
Разрешен доступ к cookie через небезопасный протокол.
В следующем примере (листинг 13.1) cookie используется для хранения параметров форматирования страницы (в данном случае — цвета фона). Обратите внимание: значение cookie задается лишь в результате выполнения действия, установленного для формы.
Листинг 13.1.
Сохранение цвета фона, выбранного пользователем
// Если переменная $bgcolor существует
if (isset($bgcolor)) :
setcookie("bgcolor", $bgcolor, time()+3600);
?>
// Значение $bgcolor не задано, отобразить форму
else :
endif;
?>
При загрузке в браузер сценарий проверяет, было ли задано значение переменной $bgcolor. Если переменная существует, для страницы выбирается цвет фона, определяемый переменной $bgcolor. В противном случае в браузере выводится форма HTML с предложением выбрать цвет фона. После выбора цвета значение $bgcolor будет распознаваться при последующей перезагрузке той же страницы или при переходе к другой странице.
Кстати говоря, имена cookie могут выглядеть как элементы массива. Вы можете использовать имена вида uid[1], uid[2], uid[3] и т. д., а затем работать с ними, как с элементами обычного массива. Пример приведен в листинге 13.2.
while (list ($name, $value) = each ($phprecipes)) :
echo "$name = $value \n";
endwhile;
endif:
?>
В результате выполнения этого фрагмента будет выведен следующий результат (а на клиентском компьютере будут созданы три cookie):
uid = 4139b31b7bab052
color = black
preference = english
Хотя массивы cookie очень удобны для хранения всевозможной информации, следует помнить, что некоторые браузеры ограничивают количество создаваемых cookie (например, Netscape Communicator разрешает создавать до 20 cookie на домен).
Cookie чаще всего применяются для хранения числовых идентификаторов (UIN), по которым в дальнейшем на сервере производится выборка информации,
относящейся к данному пользователю. Этот процесс продемонстрирован в листинге 13.3, где UIN сохраняется в базе данных MySQL. Сохраненные данные впоследствии используются для настройки параметров форматирования страницы.
Допустим, у нас имеется таблица userjnfo в базе данных с именем user. В ней хранятся следующие атрибуты пользователя: идентификатор, имя и адрес электронной почты пользователя. Определение таблицы выглядит так:
mysql>create table user_info (
->user_id char (18),
->fname char(15),
->email char(35));
По сравнению с полноценными сценариями регистрации пользователя, работа листинга 13.3 начинается «на половине пути»: предполагается, что данные пользователя (идентификатор, имя и адрес электронной почты) уже хранятся в базе данных. Чтобы пользователю не приходилось вводить всю информацию заново, идентификатор (в листинге 13.3 для простоты он равен 15) загружается из cookie на клиентском компьютере.
Листинг 13.3.
Загрузка информации пользователя из базы данных
if (! isset($userid)) :
$id = 15;
setcookie ("userid", $id, time( )+3600);
print "A cookie containing your userID has been set on your machine.
Please refresh the page to retrieve your user information";
else:
@mysql_connect("localhost", "web", "4tf9zzzf") or die("Could not connect to MySQL server!");
@mysql_select_db("user") or die("Could not select user database!");
// Объявить запрос
$query = "SELECT * FROM users13 WHERE user_id = '$userid'";
// Выполнить запрос
$result = mysql_query($query)l;
// Если совпадение будет найдено, вывести данные пользователя.
if (mysql_num_rows($result) == 1) :
$row = mysql_fetch_array($result);
print "Hi ".$row["?name"].", ";
print "Your email address is ".$row[ "email"];
else:
print "Invalid User ID!";
endif;
mysql_close();
endif;
?>
Листинг 13.3 показывает, как удобно использовать cookie для идентификации пользователей. Этот прием может использоваться в разнообразных ситуациях, от автоматической регистрации пользователя на сайте до отслеживания пользовательских параметров настройки.
В следующем разделе приведен сценарии полной регистрации пользователя и последующего сохранения UIN в базе данных.
Функции MySQL, встречающиеся в листинге 13.3, были описаны в главе 11.
PHP 4 на практике
Cookie и отслеживание сеанса
Отслеживание пользователей и персональная настройка сайта относятся к числу самых популярных и вместе с тем неоднозначно воспринимаемых возможностей web-сайтов. Преимущества очевидны — вы можете предлагать пользователям именно ту информацию, которая их интересует. С другой стороны, возникает немало вопросов, связанных с конфиденциальностью, поскольку появляется возможность «следить» за тем, как пользователь перемещается от страницы к странице и даже от сайта к сайту.
Если отвлечься от проблем конфиденциальности, отслеживание пользовательских данных с применением cookie или других средств приносит огромную пользу как пользователю, так и сайту, обеспечивающему эти возможности. Пользователь выигрывает от того, что содержание сайта настраивается в соответствии с его личными предпочтениями, а из сайта исключается бесполезная или не представляющая интереса информация. Для администратора сайта отслеживание пользовательских предпочтений открывает совершенно новый уровень взаимодействия с пользователем, включая возможности целевого маркетинга и анализа популярности материалов сайта. В Web, где сейчас преобладает электронная коммерция, эти возможности стали практически стандартными.
Концепция «наблюдения» за пользователем в процессе перемещения по сайту обычно называется «отслеживанием сеанса» (session tracking). Принимая во внимание огромный объем полезной информации, получаемой в результате отслеживания сеанса на сайте, можно сказать, что преимущества отслеживания сеансов и персональной настройки содержания сайта значительно превышают любые недостатки. Вряд ли эту книгу можно было бы считать полноценным учебником по РНР, если бы я не посвятил в ней целую главу средствам отслеживания сеанса в РНР. В этой главе мы рассмотрим некоторые концепции, имеющие непосредственное отношение к отслеживанию сеансов, а именно — cookie и их применение, а также уникальные идентификаторы сеансов. Глава завершается сводкой стандартных функций РНР, предназначенных для отслеживания сеансов.
Компоненты cookie
В cookie хранятся и другие компоненты, при помощи которых разработчик может ограничивать использование cookie с позиций домена, пути, срока действия и безопасности. Ниже приведены описания различных компонентов cookie:
Имя
— имя cookie является обязательным параметром, по которому программа ссылается на cookie. Можно провести аналогию между именем cookie и именем переменной.
Значение
— фрагмент данных, связанный с именем cookie. В этих данных может храниться любая информация — идентификатор пользователя, цвет фона, текущая дата и т. д.
Срок действия
— дата, определяющая продолжительность существования cookie. Как только текущая дата и время превосходят заданный срок действия, cookie становится недействительным и перестает использоваться. В соответствии со спецификацией cookie устанавливать срок действия для cookie необязательно. Тем не менее, средства РНР для работы с cookie требуют, чтобы срок действия устанавливался. Согласно спецификации, если срок действия не указан, cookie становится недействительным в конце сеанса (то есть когда пользователь покидает сайт).
Домен
— домен, который создал cookie и может читать его значение. Если домен состоит из нескольких серверов и доступ к cookie должен быть разрешен всем серверам, то имя домена можно задать в форме .phprecipes.com. В этом случае все потенциальные домены третьего уровня, принадлежащие сайту PHPrecipes (например, wap.phprecipes.com или news.phprecipes.com), смогут работать с cookie. По соображениям безопасности cookie могут устанавливаться только для домена сервера, пытающегося создать cookie. Данный компонент необязателен; если он не указан, по умолчанию используется имя домена, из которого было полу; чено значение cookie.
Путь
— URL, с которого предоставляется доступ к cookie. Любые попытки получения доступа к cookie за пределами этого пути пресекаются. Данный компонент необязателен; если он не задан, по умолчанию используется путь к документу, создавшему cookie.
Безопасность
— параметр, показывающий, допускается ли чтение cookie в небезопасной среде. По умолчанию используется значение FALSE.
Хотя при создании cookie используются одни и те же синтаксические правила, формат хранения cookie зависит от браузера. Например, Netscape Communicator хранит cookie в формате следующего вида:
.phprecipes.com FALSE / FALSE 97728956 bgcolor blue
В Internet Explorer то же самое cookie выглядело бы иначе:
bgcolor
blue
localhost/php4/php.exe/book/13/
0
2154887040
29374385
522625408
29374377
*
Чтобы просмотреть cookie, сохраненные браузером Internet Explorer, достаточно открыть их в любом текстовом редакторе. Помните, что некоторые редакторы не обрабатывают завершающие символы новой строки и на месте этих символов в документе могут выводиться квадратики.
Internet Explorer сохраняет свои cookie в папке с именем «Cookies», a Netscape Communicator использует для этой цели один файл с именем cookies.
Назначение пользовательских функций для хранения сеансовых данных
Хранить сеансовые данные в файлах удобно, но вполне возможно, вы захотите воспользоваться другими средствами — например, базами данных. А может быть, вы хотите применить один и тот же сценарий на разных сайтах для разных баз данных. Существует и другая распространенная проблема — стандартная для РНР процедура хранения сеансовых данных в файлах затрудняет совместное использование данных на разных серверах. К счастью, все эти проблемы отслеживания сеансов в РНР решаются очень просто, поскольку РНР дает пользователю возможность установить собственную процедуру сохранения при помощи стандартной функции session_set_save_handler( ).
Функция session_set_save_handler( ) определяет процедуры сохранения и загрузки сеансовых данных пользовательского уровня.
Синтаксис функции session_set_save_handler():
void session_set_save_handler (string open, string close, string read, string write, string destroy, string go)
Шесть параметров session_set_save_handler( ) соответствуют шести функциям, вызываемым сеансовыми функциями РНР. Хотя имена этих функций могут быть произвольными, каждая функция должна получать жестко заданный набор параметров. Перед тем как переходить к рассмотрению примера, просмотрите таблицу 13.2 — в ней описаны назначение всех шести функций и их параметры. Чтобы использовать функцию session_set_save_handler( ), необходимо присвоить па-раметру session.save_handler в файле php.ini значение user.
Таблица 13.2. Шесть параметров функции session_set_save_handler( )
Параметр
Описание
sess_close( )
Вызывается при завершении сценария, в котором реализуются сеансовые функции. Не путайте эту функцию с функцией sess_destroy( ), предназначенной для уничтожения сеансовых переменных. Функция sess_close( ) вызывается без параметров
sess_destroy($идент_ceaнca)
Удаляет все сеансовые данные. Параметр определяет удаляемый сеанс
sess_gc($срок_действия)
Удаляет все сеансы с завершенным сроком действия. Срок определяется параметром $срок_действия, значение которого задается в секундах. Параметр читается из файла php.ini и соответствует значению session.gcjifetime
sess_open($путь, $имя)
Вызывается при инициализации нового сеанса функцией session_start( ) или session_register( ). Два параметра читаются из файла php.ini и соответствуют значениям session.save_path и session.name
sess_read($ключ)
Используется для выборки значения сеансовой переменной, определяемой заданным ключом
sess_write($ключ, $значение)
Используется для сохранения сеансовых данных. Любые данные, сохраненные функцией sess_write( ), позднее могут быть прочитаны функцией sess_read( ). Параметр $ключ соответствует имени сеансовой переменной, а параметр $значение — значению, связываемому с заданным ключом
Теперь, когда вы знаете все, что необходимо знать о параметрах session_set_save_handler( ), мы рассмотрим пример реализации сеансовых функций на базе MySQL (листинг 13.8).
После того как эти шесть функций будут зарегистрированы в программе, их можно вызывать по абстрактным именам (sess_close( ), sess_destroy( ), sess_gc( ), sess_open( ), sess_read( ) или sess_write( )). Такой подход удобен тем, что вы можете создать сколько угодно реализаций и переключаться между ними, вызывая ses-sion_set_save_handler( ) по мере необходимости.
Отслеживание сеанса
Сеансом (session) называется период времени, который начинается с момента прихода пользователя на сайт и завершается, когда пользователь покидает сайт. В течение сеанса часто возникает необходимость в сохранении различных переменных, которые бы «сопровождали» пользователя при перемещениях на сайте, чтобы вам не приходилось вручную кодировать многочисленные скрытые поля или переменные, присоединяемые к URL.
Рассмотрим следующую ситуацию. При входе на сайт пользователю присваивается уникальный идентификатор сеанса (SID), который сохраняется на компьютере пользователя в cookie с именем PHPSESSJD. Если использование cookie запрещено или cookie вообще не поддерживаются, SID автоматически присоединяется ко всем локальным URL на протяжении сеанса. В то же время на сервере сохраняется файл, имя которого совпадает с SID. По мере того как пользователь перемещается по сайту, значения некоторых параметров должны сохраняться в виде сеансовых переменных. Эти переменные сохраняются в файле пользователя. При последующем обращении к сеансовой переменной сервер открывает сеансовый файл пользователя и ищет в нем нужную переменную. В сущности, в этом и заключается суть отслеживания сеанса. Конечно, информация с таким же успехом может храниться в базе данных или в другом файле.
Интересно? Еще бы. После всего сказанного вы, несомненно, лучше поймете различные проблемы конфигурации, рассматриваемые ниже. Особенно важную роль играют три флага. Первый флаг, --enable-trans-id, включается в процесс конфигурации в том случае, если вы собираетесь использовать SID (см. ниже). Два других флага, track_vars и register_globals, включаются и отключаются по мере необходимости в файле php.ini. Последствия активизации этих флагов рассматриваются ниже.
--enable-trans-id
Если РНР компилируется с этим флагом, ко всем относительным URL автоматически присоединяется идентификатор сеанса (SID). Дополнение записывается в формате имя_сеанса=идентификатор_сеанса, где имя_сеанса определяется в файле php.ini (см. ниже). Если вы не захотите включать этот флаг, в качестве SID можно использовать константу.
track_vars
Установка флага track_vars позволяет использовать массивы $HTTP_*_VARS[], где * заменяется одним из значений EGPCS (Environment, Get, Post, Cookie, Server). Данный флаг необходим для того, чтобы значения SID передавались с одной страницы на другую. В РНР 4.03 этот флаг всегда находится в установленном состоянии.
register_globals
В результате установки этого флага все переменные EGPCS становятся доступными глобально. Если вы не хотите, чтобы массив глобальных переменных заполнялся данными, которые вам, возможно, и не понадобятся, флаг следует сбросить.
Если флаг register_globals сброшен, а флаг track_vars установлен, ко всем переменным GPC можно обращаться через массив $HTTP_*_VARS[]. Например, если сбросить флаг register_globals, к стандартной переменной $PHP_SELF придется обращаться в виде $HTTP_SERVER_VARS["PHP_SELF"].
Существует целый ряд других аспектов конфигурации, о которых следует позаботиться. Эти директивы перечислены в табл. 13.1 с указанием стандартных значений, задаваемых по умолчанию в файле php.ini. Перечисление производится в порядке появления директив в файле.
Таблица 13.1.
Сеансовые директивы в файле php.ini
Директива
Описание
session.save_handler = files
Определяет способ хранения сеансовых данных на сервере. Возможны три варианта: в файле (files), в общей памяти (mm) или с использованием функций, определяемых пользователем (User).
Последний вариант позволяет легко сохранить информацию в любом формате — например, в базе данных
session.save_path =/tmp
Определяет каталог для сеансовых файлов РНР. На платформе Linux обычно используется значение по умолчанию ('/tmp'). На платформе Windows следует указать путь к какому-нибудь каталогу, в противном случае произойдет ошибка
session_use_cookies =1
При установке этого флага для сохранения идентификатора сеанса на компьютере пользователя используются cookie
session.name =PHPRESSID.
Если флаг session.use_cookies установлен, то значение session.name
/p>
После внесения всех необходимых изменений в настройку сервера мы переходим к непосредственной реализации отслеживания сеанса на вашем сайте. Благодаря нескольким стандартным функциям РНР этот процесс не так уж сложен. Первое, что необходимо знать, — сеанс инициируется функцией session_start( ). Конечно, при включении директивы session.auto_start в файл php.ini (см. выше) необходимость в вызове этой функции отпадает. Тем не менее, в оставшейся части этого раздела я буду использовать эту функцию, чтобы примеры выглядели более последовательно. Функция session_start( ) имеет простой синтаксис, поскольку она не получает параметров и возвращает логическую величину.
Директива session.save_handler настолько важна, что я счел необходимым посвятить ей отдельный раздел. Он находится в конце главы под заголовком «Назначение пользовательских функций для хранения сеансовых данных».
session_start( )
Функция session_start( ) имеет двойное назначение. Сначала она проверяет, начал ли пользователь новый сеанс, и если нет — начинает его. Синтаксис функции
session_start( ): boolean session_start()
Если функция начинает новый сеанс, она выполняет три операции: назначение пользователю SID, отправку cookie (если в файле php.ini установлен флаг session_cookies) и создание файла сеанса на сервере. Второе назначение функции заключается в том, что она информирует ядро РНР о возможности использования в сценарии, в котором она была вызвана, сеансовых переменных.
Сеанс начинается простым вызовом session_start( ) следующего вида:
session_start( ):
Если сеанс можно создать, значит, его можно и уничтожить. Это делается функцией session_destroy( ).
Функция session_start( ) возвращает TRUE независимо от результата. Следовательно, проверять ее в условиях if или в команде die( ) бессмысленно.
session_destroy()
Функция session_destroy( ) уничтожает все хранимые данные, относящиеся к сеансу текущего пользователя. Синтаксис функции session_destroy( ):
boolean session_destroy( )
Следует помнить, что эта функция не уничтожает cookie на браузере пользователя. Впрочем, если вы не собираетесь использовать cookie после конца сеанса, просто присвойте параметру session.cookie_lifetime в файле php.ini значение ( ) (используемое по умолчанию). Пример использования функции:
session_start( );
// Выполнить некоторые действия для текущего сеанса
session_destroy( ):
?>
Теперь вы умеете уничтожать сеансы, и мы можем перейти к работе с сеансовыми переменными. Возможно, самой важной сеансовой переменной является SID (идентификатор сеанса). Его легко можно получить при помощи функции session_id( ).
session_id( )
Функция session_id( ) возвращает SID для сеанса, созданного функцией session_start( ). Синтаксис функции session_id( ):
string session_id ([string sfd])
Если в необязательном параметре передается идентификатор, то значение SID текущего сеанса изменяется. Однако следует учитывать, что cookie при этом заново не пересылаются. Пример:
session_start()
print "Your session identification number is ".sessionjd( ):
session_destroy( ):
?>
Результат, выводимый в браузере, выглядит примерно так:
Your session identification number is 067d992a949114ee9832flcllcafc640
Как же создать свою сеансовую переменную? С помощью функции session_register( ).
session_register( )
Функция session_register( ) регистрирует имена одной или нескольких переменных для текущего сеанса. Синтаксис функции session_register( ):
Следует помнить, что вы регистрируете не сами переменные, а их имена. Если сеанс не существует, функция session_register( ) также неявно вызывает session_start( ) для создания нового сеанса.
Прежде чем приводить примеры использования session_register( ), я хочу представить еще одну функцию, связанную с отслеживанием сеанса, — session_is_registered( ). Эта функция проверяет, была ли зарегистрирована переменная с заданным именем.
session_is_registered( )
Часто требуется определить, была ли ранее зарегистрирована переменная с заданным именем. Задача решается при помощи функции session_is_registered( ), имеющей следующий синтаксис:
Применение функций session_register( ) и session_is_registered( ) будет продемонстрировано на классическом примере использования сеансовых переменных — счетчике посещений (листинг 13.5).
Листинг 13.5.
Счетчик посещений сайта пользователем
session_start( ):
if (! sessionjs_registered('hits')) :
session_register( 'hits' ) ;
endif ;
$hits++:
print "You've seen this page $hits times.
?>
Сеансовые переменные можно не только создавать, но и уничтожать. Для этой цели применяется функция session_unregister( ).
При вызове функции передается имя сеансовой переменной, которую вы хотите уничтожить.
session_start()
session_register('username');
// Использовать переменную $username.
// Когда переменная становится ненужной - уничтожить ее.
session_unregister('username');
session_destroy();
?>
Как и в случае с функцией session_register, помните, что в параметре указывается не сама переменная (то есть имя с префиксом $). Вместо этого указывается имя переменной.
session_encode( )
Функция session_encode( ) обеспечивает чрезвычайно удобную возможность форматирования сеансовых переменных для хранения (например, в базе данных). Синтаксис функции session_encode( ):
boolean session_encode( )
В результате выполнения этой функции все сеансовые данные форматируются в одну длинную строку, которую можно сохранить в базе данных.
Пример использования session_encode( ) приведен в листинге 13.6. Предположим, что на компьютере «зарегистрированного» пользователя имеется cookie, в котором хранится уникальный идентификатор этого пользователя. Когда пользователь запрашивает страницу, содержащую листинг 13.6, UID читается из cookie и присваивается идентификатору сеанса. Мы создаем несколько сеансовых переменных и присваиваем им значения, после чего форматируем всю информацию функцией session_encode( ) и заносим в базу данных MySQL.
Листинг 13.6.
Использование функции session_encode( ) для сохранения данных в базе данных MySQL
// Инициировать сеанс и создать сеансовые переменные
session_register('bgcolor');
session_register('fontcolor');
// Предполагается, что переменная $usr_id (с уникальным
// идентификатором пользователя) хранится в cookie
// на компьютере пользователя.
// При помощи функции session_id( ) присвоить идентификатору
// хранящийся в cookie. $id = session_id($usr_id);
// Значения следующих переменных могут задаваться пользователем
// на форме HTML $bgcolor = "white"; $fontcolor = "blue";
// Преобразовать все сеансовые данные в одну строку
$usr_data = session_encode( );
// Подключиться к серверу MySQL и выбрать базу данных users
@mysql_pconnect("localhost", "web", "4tf9zzzf")
or die("Could not connect to MySQL server!");
@mysql_select_db("users")
or die("Could not select user database!");
// Обновить пользовательские параметры страницы
$query = "UPDATE user_info set page_data='$usr_data' WHERE user_id= '$id'";
$result - mysql_query($query) or die("Could not update user information!");
?>
Как видите, быстрое преобразование всех сеансовых переменных в одну строку избавляет нас от необходимости создавать несколько полей для хранения/загрузки данных, а также несколько уменьшает объем программы.
session_decode( )
Все сеансовые данные, ранее преобразованные в строку функцией sessi on_encode( ), восстанавливаются функцией session_decode( ). Синтаксис:
string session_decode (string сеансовые_данные)
В параметре сеансовые_данные передается преобразованная строка сеансовых переменных, возможно — прочитанная из файла или загруженная из базы данных. Строка восстанавливается, и все сеансовые переменные в строке преобразуются к исходному формату.
В листинге 13.7 продемонстрировано восстановление закодированных сеансовых переменных функцией session_decode( ). Предположим, таблица MySQL с именем user_info состоит из двух полей: user_id и page_data. Пользовательский UID, хранящийся в cookie на компьютере пользователя, применяется для загрузки сеансовых данных, хранящихся в поле page_data. В этом поле хранится закодированная строка переменных, одна из которых ($bgcolor) содержит цвет фона, выбранный пользователем.
Листинг 13.7.
Восстановление сеансовых данных, хранящихся в базе данных MySQL
// Предполагается, что переменная $usr_id (с уникальным
// идентификатором пользователя) хранится в cookie
// на компьютере пользователя.
$id = session_id($usr_id);
// Подключиться к серверу MySQL и выбрать базу данных users
@mysq]_pconnect("localhost", "web", "4tf9zzzf")
or die(" Could not connect to MySQL server!");
@mysql_select_db("users")
or die("Could not select company database!");
// Выбрать данные из таблицы MySQL
$query = "SELECT page_data FROM user_info WHERE user_id= '$id'",
// Восстановить данные session_decode($user_data):
// Вывести одну из восстановленных сеансовых переменных
print "BGCOLOR: $bgcolor";
?>
Как видно из двух приведенных листингов, функции session_ encode( ) и ses-sion_decode( ) обеспечивают очень удобные и эффективные сохранение и загрузку сеансовых данных.
Проект: журнал посещений сайта
Статистические сведения о посетителях сайта приносят немалую пользу. Как вы уже знаете, сохранение информации о посетителях широко практикуется на сайтах рекламных web-агентств и порталов, а также на многих других сайтах, желающих получить дополнительные сведения о своих посетителях. Хотя системы учета бывают невероятно сложными, даже относительно простая система ведения учета открывает немало интересных возможностей. Я покажу, как реализовать простейший журнал посещений на базе РНР, MySQL и cookie. В проекте использована методика идентификации браузера, описанная в главе 8. Если вы пропустили главу 8 или описание проекта, я настоятельно рекомендую вернуться и просмотреть код проекта.
Как было сказано ранее, наша система будет относительно простой — посещения будут отслеживаться только для индексной страницы сайта. При появлении нового посетителя сценарий РНР проверяет, существует ли на компьютере посетителя cookie. Если cookie находится, значит, пользователь посещал сайт в течение определенного интервала времени (который задается администратором сайта в инициализационном файле), и сценарий не учитывает новое посещение. Если cookie отсутствует или интервал между посещениями превысил заданную величину, информация сохраняется в таблице MySQL, а на компьютер посетителя создается cookie.
Как реализовать подобный сценарий на РНР? Прежде всего необходимо создать таблицу MySQL для хранения информации:
mysql>create table visitors (
->browser char(85) NOT NULL. ->ip char(30) NOT NULL.
->host char(85) NOT NULL.
->timeOfVisit datetime NOT NULL
->);
В поле browser хранится информация, непосредственно относящаяся к браузеру посетителя. Она берется из переменной РНР с именем $HTTP_USER_AGENT. В поле ip хранится IP-адрес посетителя. В поле host хранится информация о провайдере, от которого поступил IP-адрес. Наконец, поле timeOfVisit содержит дату и время посещения сайта. Полноценное приложение для ведения журнала посещений имеется на сайте ресурсов РНР phpinfo.net (http://www.phpinfo.net). Более того, вы сможете непосредственно
на сайте увидеть, как оно работает. К сожалению, перед посещением этого сайта вам
придется вспомнить школьный курс французского языка.
Затем мы создаем инициализационный файл приложения init.inc (листинг 13.9), содержащий определения глобальных переменных и основных функций. Обратите внимание: в функции viewStats( ) используется сценарий sniffer.php из главы 8. Этот сценарий включается в файл init.inc по мере необходимости. Рекомендую потратить немного времени на просмотр этого сценария и комментариев к нему.
Листинг 13.9.
Инициализационный файл приложения (init.inc)
// Файл: init.inc
// Назначение: инициализационный файл журнала посещений сайта
// Параметры соединения с сервером MySQL $host = "localhost";
$user = "root"; $pswd = "";
// Имя базы данных Sdatabase = "myTracker";
// Имя таблицы $visitors_table = "visitors":
@mysql_pconnect($host, $user, $pswd) or die("Couldn't connect to MySQL server!");
// Выбрать базу данных
@mysql_select_db($database) or die("Couldn't select $database database!");
// Максимальное количество посещений, отображаемое в таблице $maxNumVisitors = "5";
// Имя cookie
$cookieName = "visitorlog";
// Значение cookie $cookieValue="1";
// Срок, который должен пройти с момента последнего посещения сайта,
// чтобы информация о текущем посещении была сохранена в базе данных.
// Если переменная $timeLimit равна 0. сохраняются все посещения
// независимо от их частоты.
// Остальные целочисленные значения интерпретируются как интервал
// времени в секундах.
$timeLimit = 3600:
// Формат отображения данных в браузере
$header_color = "#cbda74";
$table_color = "#000080";
$row_color = "IcOcOcO";
$font_color = "#000000":
$font_face = "Arial. Times New Roman. Verdana";
$font_size = "-1";
function recordUser() {
GLOBAL $visitors_table, $HTTP_USER_AGENT, $REMOTE_AODR, $REMOTE_HOST; if ($REMOTE_HOST — "") :
$REMOTE_HOST - "localhost"; endif;
$timestamp - date("Y-m-d H:i:S");
$query - "INSERT INTO $visitors_table VALUES('$HTTP_USER_AGENT', '$REMOTE_ADDR', '$REMOTE_HOST', '$timestamp')";
Sresult = @mysql_query($query); }
// recordUser function viewStats() {
GLOBAL $visitors_table, $maxNumVisitors, $table_color, $header_color;
GLOBAL $row color. $font color, $font face, $font size:
$query = " SELECT browser, ip. host. TimeofVisit FROM $visitors_table ORDER BY TimeofVisit desc LIMIT 0, $maxNumVisitors";
$result = mysql_query($query);
print "
";
print "
Browser
IP
Host th>
TimeofVisit
";
while($row = mysql_fetch_array($result));
list ($browse_type, $browse_version) = browser_info ($row["browser"]); $op_sys = opsys_info ($row["browser"]);
print "
";
print "
$browse_type $browse_version = $op_sys
";
print "
".$row["ip"]."
";
print "
".$row["host"]."
";
print "
";
print $row["TimeofVisit"]."
";
print "
";
endwhile;
print "
"; }
// viewStats
?>
Фрагмент кода, приведенный в листинге 13.10, проверяет существование cookie и при необходимости вызывает функцию recordUser( ). Я привожу этот фрагмент в составе очень простого индексного файла index.php.
Листинг 13.10.
Проверка существования cookie (index.php)
include("Listing13-9.php"); if (! isset($$cookieName)) :
Как организовать просмотр информации, хранящейся в базе данных MySQL, в браузере? Задача решается простым вызовом функции viewStats( ) в отдельном файле visitors.php:
include("sniffer.inc"):
include("init.inc");
?>
Most recent =$maxNumVisitors:?> visitors
viewStats( );
?>
Возможно и другое решение — включить весь код HTML в функцию viewStats( ), а затем просто включить sniffer.inc, init.inc и вызов viewStats( ) в отдельный файл. Выбор зависит от того, до какой степени вы хотите интегрировать форматирование таблицы с процессом выборки данных.
На рис. 13.1 показан пример выходных данных viewStats( ) для атрибутов форматирования, заданных в файле init.inc.
Рис. 13.1.
Пример результата, сгенерированного функцией viewStats( )
Существует немало путей для расширения практических возможностей этого приложения. Например, для отслеживания посещений со страницами сайта часто связываются идентификаторы, по которым в дальнейшем можно следить за перемещением пользователей между страницами. В рассмотренном проекте для этого в таблицу MySQL следует включить дополнительное поле, в котором хранится идентификатор страницы, а затем переопределить функцию recordllser( ) с дополнительным параметром. Идентификатор страницы сохраняется в cookie. При поступлении очередного запроса сценарий проверяет существование cookie для конкретной страницы, информация о которой регистрируется в журнале.
Уникальные идентификаторы
Вероятно, у вас уже возник вопрос: как сгенерировать UIN, который действительно был бы уникальным? Отложите в сторону учебники — замысловатые алгоритмы вам не понадобятся. В РНР предусмотрено простое средство для создания уникальных UIN — встроенная функция uniqid( ).
Функция uniqid( ) генерирует уникальный идентификатор.из 13 символов, значение которого основано на текущем времени. Синтаксис функции uniqid( ): int uniqid(string префикс [б boolean дополнение])
В параметре префикс передается строка, с которой должен начинаться UIN. Поскольку этот параметр является обязательным, при вызове необходимо передать хотя бы пустую строку. Если необязательный параметр дополнение равен TRUE, функция uniqid( ) генерирует UIN из 23 символов. Чтобы быстро создать уникальный идентификатор, достаточно при вызове uniqid( ) передать один параметр — пустую строку:
$uniq_id = uniqid(" ");
// Генерируется строка из 13 символов - например. '39b3209ce8ef2'
В другом варианте сгенерированное значение присоединяется к строке, определяемой параметром префикс:
$uniq_id = uniqid("php", FALSE):
// Генерируется строка из 16 символов - например. 'php39b3209ce8ef2'
Поскольку uniqid( ) генерирует UIN на основании текущего времени, существует ничтожная вероятность того, что идентификатор удастся подобрать. Чтобы значение идентификатора было действительно случайным, можно предварительно сгенерировать префикс при помощи еще одной стандартной функции РНР, rand( ). Эта возможность продемонстрирована в следующем примере:
srand((double) microtime( ) * 1000000);
$uniq_id = uniqid(rand( ));
Функция srand( ) инициализирует («раскручивает») генератор случайных чисел. Если вы хотите, чтобы функция rand( ) генерировала действительно случайные числа, необходимо предварительно вызвать srand( ). Передача rand( ) в качестве параметра uniqid( ) приводит к тому, что функция uniqid( ) вызывается с заранее сгенерированным случайным префиксом, что усложняет подбор сгенерированного UIN.
Владея методикой создания уникальных идентификаторов, мы теперь можем реализовать вполне реальную схему регистрации пользователей. При первой загрузке сценария в листинге 13.4 пользователю предлагается заполнить короткую
форму с именем и адресом электронной почты. Эта информация вместе со сгенерированным уникальным идентификатором сохраняется в таблице user_info, определение которой приведено перед листингом 13.3. Cookie с этим идентификатором сохраняется на компьютере пользователя. При всех последующих посещениях сценарий ищет в базе данных уникальный идентификатор, взятый из cookie, и выводит в браузере найденную информацию о пользователе.
Листинг 13.4.
Процесс регистрации пользователя
// Построить форму
$form = "
// Если форма еще не отображалась
// и для данного пользователя еще не существует cookie...
Обилие команд i f позволяет организовать весь процесс регистрации и последующую идентификацию пользователя в одном сценарии. Принципиально возможны три ситуации:
Пользователь не заполнял форму и не имеет cookie. В этом случае он должен заполнить форму.
Пользователь заполнил форму, но cookie еще не создано. В этом случае информация пользователя сохраняется в базе данных и создается cookie со сроком действия один месяц.
Пользователь возвращается после предыдущих посещений. Если срок действия cookie еще не истек, сценарий читает идентификатор пользователя и загружает соответствующую информацию из базы данных.
Конечно, общий процесс, продемонстрированный в листинге 13.4, может применяться при работе с любыми базами данных. Листинг всего лишь на очень простом уровне показывает, как на крупных сайтах организуется сохранение пользовательских данных, благодаря которому сайт «подстраивается» под каждого посетителя.
На этом завершается наше знакомство с применением cookie в РНР. Если вы захотите побольше узнать о механизме cookie, обратитесь к ресурсам Интернета, перечисленным в следующем разделе.
Ссылки по теме
Дополнительную информацию о cookie и их использовании можно найти в специализированных web-pecypcax:
Как говорилось выше, в cookie очень удобно хранить параметры, специфические для данного пользователя, автоматически загружаемые при последующих посещениях сайта. Впрочем, на cookie нельзя полностью положиться, потому что пользователи могут запретить их использование на своем компьютере в настройках браузера. К счастью, в РНР для сохранения подобной информации существует и другой способ. Эта методика называется отслеживанием сеанса (session tracking) и рассматривается в следующем разделе.
PHP 4 на практике
Другие полезные функции
В РНР также существуют другие функции, упрощающие процесс обработки кода XML.
utf8_decode( )
Функция преобразует данные в кодировку ISO-8859-1. Предполагается, что преобразуемые данные находятся в кодировке UTF-8. Синтаксис:
string utf8_decode(string данные)
Параметр данные содержит преобразуемые данные в кодировке UTF-8.
utf8_encode( )
Функция преобразует данные из кодировки ISO-8859-1 в кодировку UTF-8. Синтаксис:
string utf8_decode(string данные)
Параметр данные содержит преобразуемые данные в кодировке ISO-8859-1.
xml_get_error_code( )
Функция xm1_get_error_code( ) получает код ошибки, возникшей в процессе обработки XML. Код ошибки передается функции xml_error_string( ) (см. ниже) для интерпретации. Синтаксис:
int xml_error_code(int анализатор)
Параметр функции определяет анализатор XML. Пример использования приведен ниже, в описании функции xml_get_current_line_number( ).
xml_error_string( )
Ошибкам, возникающим в процессе анализа кода XML, присваиваются числовые коды. Функция xml_error_string( ) возвращает текстовое описание ошибки по ее коду. Синтаксис:
string xml_error_string(int код)
В параметре функции передается код ошибки (вероятно, полученный при вызове функции xml_get_error_code( )). Пример использования функции приведен ниже, в описании функции xml_get_current_line_number( ).
xml_get_current_line_number( )
Функция возвращает номер текущей строки, обрабатываемой анализатором XML. Синтаксис:
int xml_get_current_line_number(int анализатор)
Параметр функции определяет анализатор XML. Пример использования функции:
while ($line - fread($fh. 4096)) :
if (! xml_parse($xml_parser. $line. feof($fh)));
$err_string - xml_error_string(xml_get_error_code($xml_parser));
$line_number - xml_get_current_line_number($xml_parser);
print "Error! [Line Sline_number]: $err_string";
endif;
endwhile;
Например, если ошибка была обнаружена в шестой строке файла, определяемого манипулятором $fh, сообщение будет выглядеть примерно так:
Error! [Line 6]:mi snatched tag
xml_get_current_column_number( )
Функция xml_get_current_colunin_number( ) может использоваться в сочетании с xml_get_current_line_number( ) для определения точного местонахождения ошибки в документе XML. Синтаксис:
int xml_get_current_column_number(int анализатор)
Параметр функции определяет анализатор XML. Давайте усовершенствуем предыдущий пример:
Например, если ошибка была обнаружена в шестой строке файла, определяемого манипулятором $fh, сообщение будет выглядеть примерно так:
Error! [Line 6 Column 2]:mismatched tag
Функции обработки кода XML
Хотя реализация всех функций-обработчиков не обязательна (документы XML не обязаны содержать элементы всех типов), по крайней мере три функции должны присутствовать во всех сценариях, работающих с XML.
xml_parser_create( )
Перед обработкой документа XML необходимо предварительно создать анализатор. Синтаксис:
int xml_parser_create([stnng кодировка])
Необязательный параметр определяет кодировку исходного текста. В настоящее время поддерживаются три варианта кодировки:
UTF-8;
US-ASCII;
ISO-8859-1 (используется по умолчанию).
По аналогии с тем, как функция fopen( ) возвращает манипулятор открытого файла, функция xml_parser_create( ) также возвращает манипулятор, используемый для вызова различных функций в процессе обработки XML. При одновременной обработке нескольких документов можно создать сразу несколько анализаторов.
xml_parse()
Функция xml_parse( ) выполняет обработку документа XML. Синтаксис:
int xml_parse(int анализатор, string данные [int завершение])
Первый параметр определяет анализатор XML (используется значение, возвращаемое при вызове xml_parser_create( )). Если необязательный параметр завершение равен TRUE, передаваемый фрагмент данных является последним. Как правило, это происходит при достижении конца обрабатываемого файла.
xml_parser_free( )
Функция освобождает ресурсы, выделенные для работы анализатора. Синтаксис:
int xml_parser_free(int анализатор)
Параметр функции определяет анализатор XML.
PHP 4 на практике
РНР и XML
Бесспорно, развитие World Wide Web оказало заметное влияние на способы обмена информацией. Вследствие огромных размеров этой электронной сети соблюдение стандартов превратилось из простого удобства в обязательное требование — конечно, если ваша организация собирается в полной мере использовать потенциал Web. Одним из таких стандартов является язык XML (extensible Markup Language) — удобное средство обмена данными между организациями и приложениями.
В начале этой главы приведены общие сведения о XML, при этом особое внимание уделяется общему синтаксису языка. Второй раздел посвящен средствам РНР для работы с XML. Мы рассмотрим стандартные функции поддержки XML, а также схему общего процесса обработки файлов в формате XML. Этот материал позволит вам лучше понять, чем же так ценен XML и как РНР может применяться для разработки полезных и интересных приложений на базе XML.
Но прежде чем переходить к непосредственному описанию XML, я расскажу о том, как же развивались концепции, в конечном счете приведшие к возникновению формата XML.
Язык SGML
SGML представляет собой международный стандарт обмена электронной информацией между различными аппаратными и программными компонентами. По названию можно предположить, что SGML — это язык. На самом деле это не совсем так, поскольку SGML в действительности определяет формализованный набор правил для создания языков. На базе SGML были созданы два самых популярных языка разметки — HTML и XML. Как вы уже знаете, HTML — плат-форменно- и аппаратно-независимый язык, предназначенный для форматирования и отображения текста. То же самое можно сказать и о XML.
Появление стандарта SGML было обусловлено необходимостью совместного использования данных разными приложениями и операционными системами. Даже в далеких 60-х годах у пользователей компьютеров возникало немало проблем с совместимостью. Проанализировав недостатки многих нестандартных языков разметки, трое ученых из IBM — Чарльз Гольдфарб (Charles Goldfarb), Эд Мо-шер (Ed Mosher) и Рэй Лори (Ray Lorie) — сформулировали три общих принципа, обеспечивающих возможность совместной работы с документами в разных операционных системах:
Использование единых принципов форматирования во всех программах, выполняющих обработку документов.
Вполне логичное требование — всем нам хорошо известно, как трудно договориться между собой людям, говорящим на разных языках. Наличие единого набора синтаксических конструкций и общей семантики заметно упрощает взаимодействие между программами.
Специализация языков форматирования.
Благодаря возможности построения специализированного языка на базе набора стандартных правил программист
перестает зависеть от внешних реализаций и их представлений о потребностях конечного пользователя
Четкое определение формата документа.
Правила, определяющие формат документа, задают количество и маркировку языковых конструкций, используемых в документе. Применение стандартного формата гарантирует, что пользователь будет точно знать структуру содержимого документа. Обратите внимание: речь идет не о формате отображения документа, а о его структурном формате. Набор правил, описывающих этот формат, называется «определением типа документа» (document type definition, DTD).
Эти три правила были заложены в основу предшественника SGML — GML (Generalized Markup Language). Исследования и разработка GML продолжались около десяти лет, пока в результате соглашения, заключенного международной группой разработчиков, не появился стандарт SGML.
В 1980-х годах необходимость в общих средствах обмена информацией непрерывно возрастала, и SGML вскоре превратился в отраслевой стандарт (в 1986 году он был принят в качестве стандарта ISO). Даже в наши дни этот стандарт занимает достаточно сильные позиции, поскольку многие организации, работающие с огромными объемами информации, полагаются на SGML как на удобное и надежное средство хранения данных. Чтобы подкрепить сказанное, замечу, что Бюро патентов и товарных знаков США (http://www.uspto.gov), Служба внутренних сборов США (http://www.irs.gov) и Библиотека Конгресса (http://lcweb.loc.gov) используют SGML в своих основных приложениях. Только представьте, какой объем документации проходит через эти организации за год!
Одним из лучших ресурсов Интернета, посвященных SGML, XML и другим языкам раз-метки, является сайт Robin Cover/OASIS XML Cover Pages (http://www.oasis-open.org/cover).
Идея передачи гипертекстовых документов через web-браузер, предложенная Тимом Бернерсом-Ли (Tim Berners-Lee), не требовала многих возможностей, поддерживаемых полной реализацией SGML. В результате появился известный язык разметки HTML.
Несколько слов о РНР и XML
В этой главе мы познакомились с XML и различными функциями РНР, предназначенными для обработки документов в формате XML. Поскольку основной темой книги является РНР, я описал лишь одну из трех спецификаций стандарта XML и не упомянул о том, как работают XSL и XLL. Конечно, полноценное отделение содержания от представления требует использования всех трех компонентов или по меньшей мере XML и XSL.
К сожалению, на момент написания книги РНР еще не обладал возможностями, которые бы позволяли работать с XML исключительно средствами РНР. Конечно,
возможности РНР продолжают расширяться, и в будущем эта проблема обязательно будет решена. Особого внимания в этой области заслуживает XSL-процессор Sablotron, разработанный компанией Ginger Alliance Lts. (http://www.gingerall.com). 12 октября 2000 года было объявлено о том, что РНР 4.03 отныне распространяется с модулем расширения Sablotron для платформ Linux и Windows. Обязательно проследите за дальнейшим развитием событий.
Определение типа документа (DTD)
DTD представляет собой совокупность синтаксических правил, на основе которых проверяется структура документа XML. В DTD явно определяется структура документа XML, указываются элементы и их атрибуты, а также приводится другая информация, распространяющаяся на все документы XML, созданные на основе данного DTD.
Учтите, что наличие DTD не является обязательным. Если DTD существует, система XML руководствуется им при интерпретации документа XML. Если DTD отсутствует, предполагается, что система XML должна интерпретировать документ по собственным правилам. Впрочем, для документов XML все же рекомендуется создавать DTD, поскольку это упрощает их интерпретацию и проверку структуры.
DTD можно включить непосредственно в документ XML, сослаться на него по URL или использовать комбинацию этих двух способов. При непосредственном включении DTD в документ XML определение DTD располагается сразу же после пролога:
...прочие объявления...
] >
Атрибут имя_корневого_элемента соответствует имени корневого элемента в тегах, содержащих весь документ XML. В секции «прочих объявлений» находятся определения элементов, атрибутов и т. д.
Возможно, вы предпочитаете разместить DTD в отдельном файле, чтобы обеспечить модульную структуру программы. Давайте посмотрим, как выглядит ссылка на внешний DTD в документе XML. Задача решается одной простой командой:
Как и в случае с внутренним объявлением DTD, имя_корневого_элемента должно соответствовать имени корневого элемента в тегах, содержащих весь документ XML. Атрибут SYSTEM указывает на то, что some_dtd.dtd находится на локальном сервере. Впрочем, на файл some_dtd.dtd также можно сослаться по его абсолютному URL. Наконец, в кавычках указывается URL внешнего DTD, расположенного на локальном или на удаленном сервере.
Как же создать DTD для листинга 14.1? Во-первых, мы собираемся создать в документе XML ссылку на внешний DTD. Как упоминалось в предыдущем разделе, ссылка на DTD выглядит так:
Возвращаясь к листингу 14.1, мы видим, что cookbook является именем корневого элемента, a cookbook.dtd — именем DTD-файла. Содержимое DTD показано в листинге 14.2, а ниже приведены подробные описания всех строк.
Листинг 14.2.
DTD для листинга 14.1 (cookbook.dtd)
] >
Что же означает этот загадочный документ? Несмотря на внешнюю сложность, в действительности он довольно прост. Давайте переберем все содержимое листинга 14.2:
Перед нами пролог XML, о котором уже говорилось выше.
Вторая строка сообщает, что далее следует DTD с именем cookbook.
Третья строка описывает элемент XML, в данном случае — корневой элемент cookbook. После него следует слово recipe, заключенное в круглые скобки. Это означает, что в теги cookbook заключается вложенный тег с именем recipe. Знак + говорит о том, что в родительских тегах cookbook находится одна или несколько пар тегов recipe.
Четвертая строка описывает тег recipe. В ней сообщается, что в тег recipe входят четыре вложенных тега: title, description, ingredients и process. Поскольку после имен тегов не указываются признаки повторения (см. следующий раздел), внутри тегов recipe должна быть заключена ровно одна пара каждого из перечисленных тегов.
Перед нами первое определение тега, который не содержит вложенных тегов. В соответствии с определением он содержит #PCDATA, то есть произвольные символьные данные, не считающиеся частью разметки.
В соответствии с определением элемент ingredients содержит один или несколько тегов с именем ingredient. Обратитесь к листингу 14.1, и вы все поймете.
Поскольку элемент ingredient соответствует отдельному ингредиенту, вполне логично, что этот элемент содержит простые символьные данные.
Элемент process содержит один или несколько экземпляров элемента step.
Элемент step, как и элемент ingredient, соответствует отдельному пункту в списке более высокого уровня. Следовательно, он должен содержать символьные данные.
Обратите внимание: элемент recipe в листинге 14.1 содержит атрибут. Этот атрибут, category, определяет общую категорию, к которой относится рецепт — в приведенном примере это категория «итальянская кухня» (Italian). В определении ATTLIST указывается как имя элемента, так и имя атрибута. Кроме того, отнесение каждого рецепта к определенной категории упрощает классификацию, поэтому атрибут объявляется обязательным (#REQUIRED).
]>
Последняя строка просто завершает определение DTD. Определение всегда должно быть должным образом завершено, иначе произойдет ошибка.
В завершение этого раздела я приведу сводку основных компонентов типичного DTD-файла:
объявления типов элементов;
объявления атрибутов;
ID, IDREF и IDREFS;
объявления сущностей.
Некоторые из этих компонентов уже встречались нам в описании листинга 14.2. Далее каждый компонент будет описан более подробно.
Объявления элементов
Все элементы, используемые в документе XML, должны быть определены в DTD, прилагаемом к документу. Мы уже встречались с двумя распространенными разновидностями определений: для элемента, содержащего другие элементы, и элемента, содержащего символьные данные. Данное определение свидетельствует, что элемент содержит только символьные данные:
Следующее определение элемента process говорит о том, что он содержит ровно один вложенный элемент с именем step:
Впрочем, процессы (process) из одного шага (step) встречаются довольно редко — скорее всего, шагов будет несколько. Чтобы указать, что элемент содержит один или несколько экземпляров вложенного элемента step, следует воспользоваться признаком
повторения:
Количество вложенных элементов можно задать несколькими способами. Полный список операторов элементов приведен в табл. 14.1.
Таблица 14.1.
Операторы элементов
Признак
Значение
?
Ноль или ровно один экземпляр
*
Ноль или несколько экземпляров
+
Один или несколько экземпляров
Ровно один экземпляр
|
Один из элементов
,
Перечисление элементов
Если элемент будет содержать несколько вложенных элементов, их следует перечислить через запятую в определении родительского элемента:
Поскольку признаки повторения не указаны, каждый тег должен встречаться ровно один раз.
Определение элемента уточняется при помощи логических операторов. Предположим, вы работаете с рецептами, в которые всегда входят макароны (pasta) с одним или несколькими типами сыра (cheese) или мяса (meat). В этом случае элемент ingredient определяется следующим образом:
Поскольку элемент pasta обязательно должен присутствовать в элементе ingredient, он указывается с признаком повторения +. Затем следует либо элемент cheese, либо элемент meat; мы разделяем альтернативы вертикальной чертой и заключаем их в круглые скобки со знаком +, поскольку в рецепт всегда входит либо одно, либо другое.
Существуют и другие разновидности определений элементов. Мы рассмотрели лишь простейшие случаи. Тем не менее, приведенного материала вполне достаточно для понимания примеров, приведенных в оставшейся части этой главы.
Объявления атрибутов
Атрибуты элементов
описывают значения, связываемые с элементами. Элементы XML, как и элементы HTML, могут иметь ноль, один или несколько атрибутов. Общий синтаксис объявления атрибутов выглядит следующим образом:
Имя_элемента определяет имя элемента, включаемое в тег. Затем перечисляются атрибуты, связанные с данным элементом. Объявление каждого атрибута состоит из трех основных компонентов: имени, типа данных и флага, определяющего особенности данного атрибута. Вместо многоточия (...) могут быть расположены объявления других атрибутов.
Простое объявление атрибута уже встречалось нам в листинге 14.2:
Тем не менее, как видно из приведенного общего определения, допускается одновременное объявление нескольких атрибутов. Допустим, в дополнение к атрибуту category вы хотите связать с элементом recipe дополнительный атрибут difficulty (сложность приготовления). Оба атрибута объявляются в одном списке:
Форматировать объявления подобным образом необязательно; тем не менее, многострочные объявления нагляднее однострочных. Кроме того, поскольку оба атрибута являются обязательными, тег reci ре не может ограничиться каким-нибудь одним атрибутом, он должен включать в себя оба атрибута сразу. Например, следующий тег будет считаться неверным:
Почему? Потому что в нем отсутствует атрибут category. Правильный тег должен содержать оба атрибута:
Особые условия обработки атрибута описываются тремя флагами, перечисленными в табл. 14.2.
Таблица 14.2.
Флаги атрибутов
Флаг
Описание
#FIXED
Во всех экземплярах элемента в документе атрибуту может присваиваться только одно конкретное значение
#IMPLIED
Если атрибут не указан в элементе, используется значение по умолчанию
#REQUIRED
Атрибут является обязательным и должен присутствовать во всех экземплярах элемента в документе
<
/p>
Типы атрибутов
Атрибут элемента может объявляться с определенным типом. Типы атрибутов описаны далее.
Атрибуты CDATA
Очень часто атрибуты содержат общие символьные данные. Такие атрибуты называются атрибутами CDATA. Следующий пример уже встречался в начале этого раздела:
Атрибуты ID, IDREF и IDREFS
Идея однозначного представления данных (например, информации о пользователе или товаре, хранящейся в базе данных) посредством идентификаторов неоднократно встречалась в предыдущих главах книги. Идентификаторы также часто используются в XML, поскольку перекрестные ссылки между документами применяются не только в общих задачах обработки данных, но и в World Wide Web (гиперссылки).
Идентификаторы элементов присваиваются атрибуту ID. Допустим, вы хотите связать с каждым рецептом уникальный идентификатор. Соответствующий фрагмент DTD может выглядеть так:
После этого объявление элемента recipe в документе может выглядеть так:
Spaghetti alla Carbonara
Рецепт однозначно определяется идентификатором ital003. Следует помнить, что атрибут redpe-id относится к типу ID, поэтому ital003 не может использоваться в качестве значения атрибута recipe-id другого элемента, в противном случае документ будет считаться синтаксически неверным. Теперь допустим, что позднее вы захотели сослаться на этот рецепт из другого документа — скажем, из списка любимых рецептов пользователя. Именно здесь в игру вступают перекрестные ссылки и атрибут IDREF. Атрибуту IDREF присваивается идентификатор, используемый для ссылок на элемент, — по аналогии с тем, как URL используется для идентификации страницы в гиперссылке. Рассмотрим следующий фрагмент кода XML:
В процессе обработки документа XML элемент заменяется более наглядной ссылкой на рецепт с указанным идентификатором (например, названием рецепта). Вероятно, он будет отформатирован в виде гиперссылки, чтобы упростить переход к указанному рецепту.
Перечисляемые атрибуты
При объявлении атрибута можно перечислить все допустимые значения, принимаемые атрибутом. В нашем примере это было бы удобно, поскольку вы можете сразу определить список допустимых категорий. Приведенное выше объявление записывается в следующем виде:
Обратите внимание: при использовании списков допустимых значений включать в объявление тип CDATA не нужно, поскольку все перечисленные значения относятся к формату CDATA.
Перечисляемые атрибуты со значением по умолчанию
Иногда бывает удобно объявить для атрибута значение по умолчанию. Скорее всего, вам уже приходилось делать это раньше при построении форм с раскрывающимися списками. Например, если большинство рецептов в вашей поваренной книге относится к итальянской кухне, атрибут recipe будет часто относиться к категории Italian. В этом случае категорию Italian можно назначить по умолчанию:
Если атрибут category не задан явно, по умолчанию ему присваивается значение Italian.
Атрибуты ENTITY и ENTITIES
Данные в документах XML не всегда являются текстовыми — документ может содержать и двоичную информацию (например, графику). На такие данные можно ссылаться при помощи атрибута entity. Например, в описании элемента description можно указать атрибут recipePicture с графическим изображением:
Также можно объявить сразу несколько сущностей, заменив ENTITY на ENTITIES. Значения разделяются пробелами.
Атрибуты NMTOKEN и NMTOKENS
Атрибуты NMTOKEN представляют собой строки из символов, входящих в ограниченный набор. Объявление атрибута с типом NMTOKEN предполагает, что значение атрибута соответствует установленным ограничениям. Как правило, значение атрибута NMTOKEN состоит из одного слова:
Можно объявить сразу несколько атрибутов, заменив NMTOKEN на NMTOKENS. Значения разделяются пробелами.
Объявления сущностей
Объявление сущности напоминает команду define в некоторых языках программирования, включая РНР. Ссылки на сущности кратко упоминались в предыдущем разделе «Знакомство с синтаксисом XML». На всякий случай напомню, что ссылка на сущность используется в качестве замены для другого фрагмента содержания. В процессе обработки документа XML все вхождения сущности заменяются содержанием, которое она представляет. Существует два вида сущностей: внутренние и внешние.
Внутренние сущности
Внутренние сущности напоминают строковые переменные, связывающие имя с фрагментом текста. Например, если вы хотите определить имя для ссылки на информацию об авторских правах, можно объявить сущность следующего вида:
В процессе обработки документа все экземпляры &Соруright заменяются текстом «Copyright 2000 YourCompanyName. All Rights Reserved». Весь код XML в заменяющем тексте обрабатывается так, словно он присутствовал в исходном документе.
Внутренние сущности удобны в ситуациях, когда вы планируете использовать сущность в относительно небольшом количестве документов XML. При большом количестве документов лучше воспользоваться внешними сущностями.
Внешние сущности
Внешние сущности используются для ссылок на содержание, находящееся в другом файле. Сущности этого типа могут содержать текстовую информацию, но также могут ссылаться и на двоичные данные (например, графику). Возвращаясь к предыдущему примеру, допустим, что вы решили сохранить информацию об авторских правах в отдельном файле, чтобы упростить ее редактирование в будущем. Ссылка на созданный файл выглядит следующим образом:
При последующей обработке документа XML все ссылки &Соруright заменяются содержимым документа copyright.xml. Весь код XML в заменяющем тексте обрабатывается так, словно он присутствовал в исходном документе.
Внешние сущности также удобно использовать для ссылок на графические изображения. Например, если вы хотите включить в документ XML графический логотип, создайте внешнюю сущность:
Как и в предыдущем примере, все ссылки &food_picture заменяются графическим изображением, на которое указывает ссылка. Поскольку данные являются двоичными, а не текстовыми, они не интерпретируются.
Ресурсы, посвященные XML
Хотя приведенного выше материала вполне достаточно для понимания базовой структуры документов XML, данное описание не является полным. Ниже приведены ссылки на ресурсы Интернета, содержащие более подробную информацию:
http://www.w3.org/XML;
http://www.xml.com/pub/ArticlesByTopic;
http://ww.ibm.com/developer/xml;
http://www.oasis-open.org.cover.
В оставшейся части главы рассказано о том, как использовать РНР для обработки документов XML. На первый взгляд задача кажется очень сложной (лексический анализ любых документов любого типа вызывает немало затруднений).
Но стоит познакомиться с базовой стратегией работы с XML в РНР, и все оказывается на удивление просто.
Параметры анализатора XML
В настоящее время в РНР поддерживаются два параметра, влияющих на работу анализатора XML:
XML_OPTION_CASE_FOLDING — автоматическое преобразование имен тегов к верхнему регистру;
XML_OPTION_TARGET_ENCODING — кодировка документа на выходе анализатора XML. В настоящее время поддерживаются кодировки UTF-8, ISO-8859-1 и US-ASCII.
Для получения текущих значений и модификации этих параметров применяются, соответственно, функции xml_parser_get_option( ) и xml_parser_set_option( ).
xml_parser_get_option( )
Функция xml_parser_get_option( ) получает текущее значение параметра анализатора XML. Синтаксис:
int xml_parser_get_option(int анализатор, int параметр)
Первый параметр функции определяет анализатор XML, а второй — имя интересующего вас параметра. Пример:
$setting = xml_parser_get_option($xml_parser, XML_OPTION_CASE_FOLDING);
print "Case Folding: $setting";
Если параметру XML_OPTION_CASE_FOLDING не присваивалось другое значение, функция вернет значение по умолчанию. В этом случае будет выведен следующий результат:
Case Folding: 1
xml_parser_set_option( )
Функция xml_parser_set_option() задает значение параметра анализатора XML. Синтаксис:
int xml_parser_set_option(int анализатор, int параметр, mixed значение)
Первый параметр функции определяет анализатор XML, второй — имя интересующего вас параметра, а третий — его новое значение. Пример:
$setting = xml_parser_set_option($xml_parser, XML_OPTION_TARGER_ENCODING."UTF-8"):
В результате выполнения этой команды выходная кодировка документа изменяется с ISO-8859-1 на UTF-8.
Подключение пользовательских функций к обработке XML
В РНР существует восемь стандартных функций для регистрации пользовательских функций, обрабатывающих различные компоненты документов XML.
Следует помнить, что вы обязательно должны определить все пользовательские функции; в противном случае произойдет ошибка. В этом разделе перечислены все стандартные функции регистрации и приведены спецификации всех пользовательских функций.
xml_set_character_data_handler()
Функция регистрирует пользовательскую функцию для работы с символьными данными. Синтаксис:
int xml_set_character data_handler(int анализатор, string обработчик_символьных_данных)
Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой при обработке символьных данных. Определение функции-обработчика должно выглядеть так:
function обработчик_символьных_данных (int анализатор, string данные) {
...
}
Первый параметр определяет анализатор XML, а второй — символьные данные, подлежащие обработке.
xml_set_default_handler( )
Функция регистрирует пользовательскую функцию для всех незарегистрированных компонентов документа XML. В частности, к числу таких компонентов относятся пролог XML и комментарии. Синтаксис:
int xml_set_default_handler(int анализатор, string обработчик_по_умолчанию)
Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой по умолчанию. Определение функции-обработчика должно выглядеть так:
function обработчик_по_умолчанию (int анализатор, string данные) {
...
}
Первый параметр определяет анализатор XML, а второй — символьные данные, подлежащие обработке.
xml_set_element_handler( )
Функция регистрирует пользовательские функции для обработки открывающих и закрывающих тегов элементов. Синтаксис:
int xml_set_element_handler(int анализатор, string обработчик_открывающих_тегов, string обработчик_закрывающих_тегов)
Первый параметр определяет анализатор XML. Второй и третий параметры определяют имена функций, используемых для обработки, соответственно, открывающих и закрывающих тегов. Определение обработчика открывающих тегов должно выглядеть так:
function обработчик_открывающих_тегов (int анализатор, string имя_тега,
string атрибуты[ ]) {
...
}
Первый параметр определяет анализатор XML, второй — имя открывающего тега для анализируемого элемента, а третий содержит массив атрибутов соответствующего тега.
Обработчик закрывающих тегов определяется следующим образом:
function обработчик_закрывающих_тегов (int анализатор, string имя_тега) {
...
}
Первый параметр определяет анализатор XML, второй — имя закрывающего тега для анализируемого элемента.
xml_set_external_entity_ref_handler( )
Функция регистрирует пользовательскую функцию для обработки внешних ссылок на сущности. Синтаксис:
int xml_set_external_entity_ref_handler(int анализатор, string обработчик_внешних_ссылок)
Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой при обработке внешних ссылок. Определение функции-обработчика должно выглядеть так:
Первый параметр определяет анализатор XML. Второй параметр определяет имя ссылки, четвертый — системный идентификатор ссылки на сущность, а пятый — открытый идентификатор ссылки. Третий параметр, база, в настоящее время не используется, однако его объявление все равно обязательно.
xml_set_notation_decl_handler ( )
Функция регистрирует пользовательскую функцию для обработки синтаксических объявлений. Синтаксис:
Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой при обработке синтаксических объявлений. Определение функции-обработчика должно выглядеть так:
Первый параметр определяет анализатор XML. Второй параметр определяет имя объявления, четвертый — системный идентификатор, а пятый — открытый идентификатор объявления. Третий параметр, база, в настоящее время не используется, однако его объявление все равно обязательно.
xml_set_object( )
Функция ассоциирует анализатор XML с некоторым объектом. Синтаксис:
Первый параметр определяет анализатор XML, а второй содержит ссылку на объект, методы которого будут использоваться для обработки компонентов XML. Таким образом, функция xml_set_object связывает анализатор с объектом. Как правило, она вызывается в конструкторе объекта перед определениями функций-обработчиков:
... Определения функций-обработчиков startTag. endTag. characterData и т.д. ...
} // class xmlDB
В порядке эксперимента попробуйте закомментировать вызов xml_set_object( ). Вы увидите, что при выполнении этого фрагмента выводятся сообщения об ошибках, в которых говорится о невозможности обращения к методам объекта.
xml_set_processing_instruction_handler( )
Функция регистрирует пользовательскую функцию для работы с Pi-инструкциями.
Синтаксис:
int xml_set_processing_instruction_handler(int анализатор, string обработчик_инструкций)
Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой при обработке Pi-инструкций. Определение функции-обработчика должно выглядеть так:
function обработчик_инструкций (int анализатор, string приложение, string инструкция) {
...
}
Первый параметр определяет анализатор XML, второй — имя приложения, выполняющего инструкции, а третий — инструкцию, передаваемую приложению.
xml_set_unparsed_entity_decl_handler( )
Функция регистрирует пользовательскую функцию для необработанных внешних ссылок на сущности. Синтаксис:
int xml_set_external_entity_ref_handler(int анализатор, string обработчик_внешних_ссылок)
Первый параметр определяет анализатор XML, а второй — имя пользовательской функции, используемой для обработки необработанных внешних ссылок. Определение функции-обработчика должно выглядеть так:
Первый параметр определяет анализатор XML. Второй параметр определяет имя ссылки, четвертый — системный идентификатор ссылки на сущность, а пятый — открытый идентификатор ссылки. Третий параметр, база, в настоящее время не используется, однако его объявление все равно обязательно. Наконец, последний параметр определяет имя синтаксического объявления.
На этом завершается наше краткое знакомство с обработчиками и функциями регистрации. Впрочем, для эффективной обработки документов XML вам понадобятся и другие функции. В следующем разделе представлены остальные функции РНР, связанные с обработкой кода XML.
Преобразование XML в HTML
Предположим, у вас имеется документ XML bookmarks.xml, содержащий список ссылок. Он выглядит примерно так:
Epicurious http://www.epicurious.com
Epicurious is a great online cooking resource, providing tutorials.
recipes, forums and more.
Допустим, вы хотите преобразовать bookmarks.xml и вывести его содержимое в формате, совместимом с форматом браузера вашего компьютера. Программа, приведенная в листинге 14.3, преобразует файл к нужному формату.
Листинг 14.3. Преобразование XML в HTML
Class XMLHTML {
VAR $xmlparser: VAR $tagcolor ="#800000";
VAR $datacolor ="#0000ff";
function XMLHTML( ) {
$this->xmlparser = xml_parser_create();
xml_set_object($this->xmlparser. &$this);
xml_set_element_handler($this->xmlparser, "startTag", "endTag");
xml_set_character_data_handler($this->xmlparser. "characterData");
}
// Функция отвечает за обработку всех открывающих тегов.
function startTag($parser, $tagname, $attributes) {
GLOBAL $tagcolor;
print "tagcolor\" face=\"arial,
verdana\ ">&1 t ; $tagname> ; " ;
// Функция отвечает за обработку всех символьных данных.
function characterData($parser. $characterData) {
GLOBAL $datacplor;
print "datacolor\" face=\"arial,
verdana\ "> $characterData ";
// Функция отвечает за обработку всех закрывающих тегов.
function endTag(Sparser, $tagname) {
GLOBAL Stagcolor;
print "tagcolor\" face=\"arial,
verdana\"></
$tagname> ";
}
function.parse($fp) {
// xml_parse($this->xm1parser,$data);
// Обработать файл XML
while ( $line = fread($fp. 4096) ) :
// При возникновении ошибки прервать обработку // и вывести сообщение об ошибке.
if ( ! xml_parse($this->xmlparser, $line, feof($fp))) :
В результате преобразования файл bookmarks.xml выводится в браузере в следующем виде:
Epicurious
http : //www.epicurious.com
Epicurious is a great online cooking resource,
providing tutorials, recipes, forums and more.
Конечно, результат не такой уж впечатляющий — мы всего лишь добились, чтобы файл XML отображался в браузере. Внеся небольшие изменения в листинг 14.3, можно преобразовать URL в работающие гиперссылки, оформить данные между парой тегов ... жирным шрифтом и т. д. Как видно из листинга 14.3, я использую шрифт двух разных цветов, чтобы продемонстрировать возможность форматирования текста в браузере.
Пришествие HTML
Концепция World Wide Web идеально соответствовала идее применения обобщенного языка разметки для упрощения обмена информацией в среде, содержащей множество разных аппаратных конфигураций, операционных систем и программных реализаций. Несомненно, Бернерс-Ли учитывал это обстоятельство, поскольку он смоделировал первую версию HTML на основе стандарта SGML. HTML унаследовал некоторые характеристики SGML, в том числе простой обобщенный набор тегов и особую роль угловых скобок. Простые документы в формате HTML можно прочитать в любой компьютерной системе, в которой предусмотрены средства для просмотра текстовых документов. Все остальное — история.
Тем не менее, у HTML имеется существенный недостаток: он не позволяет разработчику создавать собственные типы документов. Результатом стала «война браузеров», в ходе которой разработчики браузеров начали создавать свои собственные усовершенствования языка HTML. Эти модификации существенно отклонялись от идеи работы с единым стандартом HTML и вызвали настоящий хаос среди разработчиков, которые хотели создавать web-сайты, не зависящие от браузера. Более того, долгий период неопределенности в области стандартов привел к тому, что разработчики вывели язык из первоначально задуманных границ. Думаю, подавляющее большинство web-страниц современного Интернета вообще не соответствуют текущей спецификации HTML.
Реакцией консорциума W3 (http://www.w3.org) на быстро ухудшающуюся ситуацию стала попытка вернуть развитие HTML на правильный путь — другими словами, вернуться к истокам SGML. Результатом этих усилий стал XML.
Разметка текста
Как нетрудно предположить по его названию, язык HTML (HyperText MarkUp Language) относится к числу так называемых языков разметки текста (markup languages). Под термином «разметка» понимается общая служебная информация, которая не выводится вместе с документом, но определяет; как должны выглядеть те или иные фрагменты документа. Например, вы можете потребовать, чтобы какое-либо слово выводилось жирным или курсивным шрифтом, вывести отдельный абзац особым шрифтом или оформлять заголовки увеличенным шрифтом. Текстовый редактор, в котором я ввожу этот абзац, тоже использует особую форму разметки для представления тех атрибутов форматирования, которые я выбираю. Таким образом, в нем тоже используется особая разновидность языка разметки. Короче говоря, язык разметки, используемый моим текстовым редактором, представляет собой средство для описания визуального оформления текста в моих документах.
В наши дни существует множество разных языков разметки. Например, в коммуникационных программах особая форма разметки определяет смысл каждого пакета из нулей и единиц, пересылаемого в Интернете. Когда мы подчеркиваем слова в книге, это тоже можно считать своего рода разметкой. Впрочем, любой язык разметки должен решать две важные задачи:
Язык определяет синтаксис разметки.
Например, в соответствии со спецификацией HTML конструкция text определяет синтаксически правильную разметку текста, а конструкция text считается неправильной из-за несовпадения открывающего и закрывающего тегов.
Язык определяет смысл разметки.
Конечно, вы знаете, что команда text выводит слово text жирным шрифтом. В данном случае определяется смысл, связанный с объявлением некоторого компонента документа.
Стремительное развитие Web за последние несколько лет наглядно показывает, что самым популярным языком разметки текста является HTML. Но как появился этот язык? Кто закрепил за тегами и определенный смысл в документе? Чтобы ответить на этот вопрос, необходимо познакомиться с предшественником HTML — SGML (Standard Generalized Markup Language).
РНР и ХМL
Для работы с XML в РНР используется пакет Джеймса Кларка (James Clark) Expat (XML Parser Toolkit) — cm. http://www.jclark.com/xml. Expat включается в поставку Apache 1.3.7 и более поздних версий, поэтому вам не придется специально загружать его, если вы используете свежую версию Apache. Чтобы воспользоваться функциональными возможностями XML в РНР, необходимо настроить РНР с ключом -with-xml. Разработку Expat 2.0 в настоящее время ведет Кларк Купер (Clark Cooper). За дополни- тельной информацией обращайтесь по адресу http://expat.sourceforge.net.
На первый взгляд задача обработки данных XML на РHР (или на любом другом языке) выглядит устрашающе, но на самом деле большая часть работы выполняется за вас стандартными средствами РНР. Вам остается лишь определить новые функции для своих DTD и затем применить их в несложном процессе обработки кода XML.
Прежде чем переходить к рассмотрению функций РНР, предназначенных для работы с XML, необходимо познакомиться с основными компонентами документа XML. Это поможет вам лучше понять, почему эти функции являются незаменимой частью любой программы обработки XML-кода. На самом общем уровне документ XML содержит компоненты девяти видов:
открывающие теги;
атрибуты;
символьные данные;
закрывающие теги;
инструкции по обработке;
синтаксические объявления;
внешние ссылки на сущности;
необработанные сущности;
прочие компоненты (комментарии, объявления XML и т. д.).
Для эффективной обработки документов XML необходимо определить пользовательские функции-обработчики (handlers), обрабатывающие каждый из перечисленных компонентов. Определенные функции подключаются к процессу обработки XML стандартными средствами РНР. Общий процесс обработки кода XML в РНР состоит из пяти этапов:
Определите пользовательские функции. Разумеется, если вы собираетесь постоянно работать с документами XML, эти функции достаточно написать всего один раз и в дальнейшем лишь вносить в них необходимые изменения.
Создайте анализатор (parser) кода XML, который будет использоваться для обработки документа. Анализатор создается вызовом функции xml_parser_create( ).
При помощи стандартных функций зарегистрируйте свои функции в анализаторе XML.
Откройте файл XML, прочитайте содержащиеся в нем данные и передайте их анализатору XML. Обработка данных выполняется простым вызовом xml_parse( )! В процессе своей работы эта функция обеспечивает косвенный вызов всех определенных вами обработчиков.
Уничтожьте анализатор XML, чтобы освободить задействованные им ресурсы.
Задача решается функцией xml_parser_free( ). Смысл всех перечисленных этапов разъясняется в следующем разделе.
XML как неопровержимое свидетельство эволюции
XML воплощает все усилия, предпринятые W3 в области выработки Интернет-стандарта, который бы соответствовал трем главным принципам SGML (см. предыдущий раздел). XML, как и SGML, не является языком; он также представляет собой набор рекомендаций, на базе которых создаются другие языки. Точнее говоря, XML является конгломератом из трех отдельных спецификаций:
— спецификация, направленная на отделение визуального оформления страницы от ее содержимого за счет применения к документу стилей (style sheets), определяющих конкретные атрибуты форматирования;
XLL (Extensible Linking Language)
— спецификация, определяющая представление ссылок на другие ресурсы.
XML не только позволяет разработчикам создавать специализированные языки для Интернет-приложений; он также обеспечивает возможность проверки этих документов на соответствие спецификации XML. Более того, XML действительно реализует концепцию данных, не зависящих от реализации, поскольку формат отображаемого документа можно точно описать при помощи XSL. Допустим, вы переформатировали свой web-сайт, чтобы он хранился в формате XML. После этого' вы сможете использовать один стиль для форматирования исходного текста XML на портативном компьютере типа Palm Pilot, а другой — для форматирования на мониторе обычного компьютера. В обоих случаях код XML остается одним и тем же, изменяется только его форматирование в соответствии с используемым устройством. Примером популярного языка, созданного на базе XML, является WML (Wireless Markup Language).
Знакомство с синтаксисом XML
Для большинства читателей, знакомых с SGML или HTML, структура документов XML не содержит ничего нового. Пример простого документа XML приведен в листинге 14.1.
Листинг 14.1.
Пример документа XML
Spaghetti alla Carbonara This traditional Italian dish is sure to please even the most discriminating
critic. 2 large eggs 4 strips of bacon l clove garlic 12 ounces spaghetti 3 tablespoons olive oil
Combine oil and bacon in large skillet over medium heat. Cook until bacon is
brown and crisp. whisk eggs in bowl. Set aside. Cook pasta in large pot of boiling water to taste, stirring occasionally.
Add salt as necessary. Drain pasta and return to pot. adding whisked eggs. Stir over medium-low
heat for 2-3 minutes. Mix in bacon. Season with salt and pepper to taste.
Обратите внимание на основные компоненты, из которых состоит документ XML:
пролог XML;
теги;
атрибуты;
ссылки на сущности;
инструкции по обработке;
комментарии.
Пролог XML
Все документы XML начинаются с пролога (prolog). Пролог сообщает, что документ написан на XML, а также указывает, какая версия XML при этом использовалась.
Поскольку текущая версия XML имеет номер 1.0, все ваши документы XML должны начинаться со строки
Следующая строка в листинге 14.1 указывает на внешний DTD. Пока не обращайте на нее внимания — DTD подробно рассматриваются в следующем разделе «Определение типа документа (DTD)»:
Оставшаяся часть листинга 14. 1 состоит из элементов, очень похожих на элементы документов HTML. Первый элемент, cookbook, называется корневым элементом (root element), поскольку в эту пару тегов заключены все остальные теги документа. Конечно, вы можете присвоить корневому элементу любое имя по своему усмотрению. Главное, о чем следует помнить, — все остальные элементы должны находиться внутри пары корневых тегов.
Пролог может содержать другие инструкции. Например, объявление можно расширить, указав, что документ является автономным:
Присваивание yes атрибуту standalone сообщает механизму обработки XML-кода о том, что документ не импортирует других файлов (например, DTD).
Хотя это расширение, как и многие другие, приносит несомненную пользу, я сокращаю описание синтаксиса до минимума, чтобы лучше выделить основную тему этой главы — совместное использование РНР и XML.
Элементы
Оставшаяся часть документа состоит в основном из различных служебных элементов и соответствующих данных. Служебные элементы легко узнать по угловым скобкам (как в разметке HTML). Элемент может быть пустым или содержащим информацию; в этом случае элемент содержит открывающий и закрывающий теги. Если элемент не пуст, то в теги включаются имена, описывающие природу данных. Как видно из листинга 14.1, эти теги очень похожи на теги документов HTML. Впрочем, следует помнить о некоторых важных различиях:
Непустые элементы должны содержать как открывающий, так и закрывающий тег. В элементах, которые логически не могут иметь закрывающего тега, используется альтернативная форма синтаксиса <элемент />. Возникает вопрос — у каких элементов нет закрывающего тега? Достаточно вспомнить некоторые теги форматирования HTML — например, , и , у них нет парных тегов. Теги этого формата могут создаваться и в документах XML
Элементы XML должны находиться на правильном уровне вложенности. Документ XML, приведенный в листинге 14.1, синтаксически правилен; другими словами, теги элементов не встречаются там, где их быть не должно. Например, следующий фрагмент недопустим:
Spaghetti alia Carbonara
В элементах XML различается регистр символов. Некоторым читателям это наверняка не понравится. Например, в XML теги , и считаются разными тегами. Привыкайте поскорее — с непривычки это может свести вас с ума.
Атрибуты
Теги XML, по аналогии с тегами HTML, могут обладать атрибутами. Атрибуты содержат дополнительную информацию о содержании, которая в дальнейшем используется при форматировании или обработке XML. Значения атрибутов присваиваются в формате «имя=значение», и, в отличие от HTML, атрибуты XML должны быть заключены в апострофы или кавычки. В листинге 14.1 встречается пример использования атрибута:
Атрибут сообщает, что данный рецепт (recipe) относится к категории «итальянской кухни» (italian). Наличие такой информации упрощает дальнейшую группировку и обработку данных.
Ссылки на сущности
Концепция сущности (entity) упрощает сопровождение документа, обеспечивая возможность ссылки на некоторое содержание по ключевым словам. Ключевое слово может относиться как к простейшему фрагменту вроде расширения аббревиатуры, так и к совершенно новому фрагменту кода XML. Сущности удобны тем, что они могут многократно использоваться в документах XML. При последующей обработке документа все ссылки на сущность заменяются конкретным содержанием, указанным при объявлении сущности. Объявление сущности включается в DTD документа XML.
Чтобы сослаться на некоторую сущность в документе HTML, следует указать ее имя с префиксом «амперсанд» (&) и суффиксом «точка с запятой» (;). Допустим, вы объявили сущность с информацией об авторских правах. После этого на данную сущность можно ссылаться следующим образом:
&Соруright:
При этом строка документа XML может выглядеть так:
Сущности, как и переменные и шаблоны, часто применяются в ситуациях, когда некоторая информация может измениться в будущем или документ содержит множество повторяющихся ссылок. Мы вернемся к проблемам объявления ссылок в разделе «Определение типа документа (DTD)».
Инструкции по обработке
Инструкции по обработке
( processing instructions, PI) представляют собой внешние команды, которые выполняются приложением, работающим с документом XML.
В общем случае синтаксис PI выглядит так:
Атрибут приложение указывает, какой программе адресованы последующие инструкции. Например, для выполнения команды РНР в документе XML можно воспользоваться следующей конструкцией:
Инструкции по обработке удобны тем, что они позволяют нескольким приложениям совместно работать с одним документом.
Комментарии
Комментарии принадлежат к числу основных возможностей любого языка. В XML используется тот же синтаксис комментариев, что и в HTML:
Итак, мы проанализировали структуру типичного документа XML. Но у документов XML существует еще один важный аспект — определение типа документа (DTD).
PHP 4 на практике
Динамическое создание временных окон
В JavaScript предусмотрены простые и удобные средства для работы с окнами браузера. В частности, JavaScript позволяет отображать временные окна с вспомогательной информацией, не оправдывающей создания и загрузки отдельной страницы. Напрашивается очевидная идея — построить универсальный шаблон, который будет использоваться для всех временных окон. Все, что для этого потребуется, — РНР. В листинге 15.4 показано, как файл РНР window.php вызывается из JavaScript. В этом файле реализован очень простой шаблон с директивой INCLUDE для включения файла, идентификатор которого передается window.php при вызове из JavaScript.
Для читателей, не имеющих опыта программирования на JavaScript, я включил в программу подробные комментарии. Значение переменной winld, передаваемой сценарию РНР window.php, задается внутри ссылки в основном коде HTML. Когда пользователь щелкает на ссылке, вызывается функция newWindow( ), определенная в JavaScript. Чтобы вы лучше поняли, как это происходит, рассмотрим следующую ссылку:
<а href="#" onClick="newWindow(1):">Contact us
Как видите, я просто включаю в href значение "#", поскольку ссылка генерируется обработчиком события onClick в JavaScript. Установка обработчика приводит к тому, что при щелчке на ссылке вызывается функция newWindow( ). Обратите внимание на параметр, передаваемый при вызове этой функции (в приведенном примере — 1). Содержащийся в нем идентификатор используется сценарием РНР для выбора отображаемой информации. Вы можете передать любое число — при условии, что оно соответствует имени файла, отображаемого в сценарии РНР. Внимательно просмотрите листинг 15.4. Чтобы вам было легче ориентироваться, я создал три простых файла *.inc, соответствующих ссылкам в этом листинге.
Листинг 15.4.
Динамическое построение временных окон
Когда пользователь щелкает на одной из ссылок в листинге 15.4, программа создает временное окно и загружает в него содержимое, полученное в результате вызова window.php. Сценарию window.php передается переменная winID, по которой определяется файл, включаемый в сценарий РНР. Сценарий window.php приведен в листинге 15.5.
Остается лишь создать файлы для ссылок в листинге 15.4. Поскольку в ссылках передаются три уникальных идентификатора (1, 2 и 3), мы должны создать три файла. Первый файл, содержащий контактную информацию, сохраняется с именем Line:
Следующий файл (местонахождение) сохраняется с именем 2.inс.
Driving Directions
Turn left on 1st avenue.
Enter the old Grant building.
Take elevator to 4th floor.
We're in room 444.
Последний файл (сводка погоды) сохраняется с именем 3.inc. Обратите внимание на вызов функции РНР, возвращающей текущую дату, — этот пример наглядно показывает, как легко РНР интегрируется с JavaScript: ,
Weather Report =date("m-d-Y");?>
Today: Birr... Brisk, with blowing and drifting snow.
На рис. 15.1 показано, как выглядит временное окно, открываемое по третьей ссылке.
Рис. 15.1.
Сводка погоды во временном окне
Наше короткое знакомство с интеграцией PHP/JavaScript подходит к концу. Мы рассмотрели несколько простых, но вполне реальных примеров, которые при желании легко адаптируются для более сложных целей. При объединении РНР с JavaScript или любой другой технологией, ориентированной на работу на стороне сервера, необходимо правильно определить возможности браузера, чтобы предотвратить случайные ошибки. Всегда полезно поэкспериментировать с другими технологиями, интегрируемыми с кодом РНР; только проследите за тем, чтобы не отпугнуть пользователей от сайта недоступными возможностями или содержанием, которое невозможно просмотреть.
Следующий раздел посвящен СОМ — еще одной технологии, с которой легко работать средствами РНР.
Дополнительная информация
Ниже перечислены ссылки на некоторые полезные ресурсы, посвященные СОМ и найденные мной в Интернете:
JavaScript и COM
Как неоднократно упоминалось в книге, одной из самых замечательных особенностей РНР является простота его интеграции с другими технологиями. Примеры такой интеграции уже встречались при описании работы с базами данных, ODBC и XML. В этой главе будет показано, как просто организуется работа РНР в комбинации с JavaScript и приложениями на базе СОМ. Ниже приводятся общие сведения о JavaScript и СОМ, подкрепленные примерами их использования в РНР. К концу главы вы узнаете немало полезного об этих замечательных технологиях и о том, как они применяются в РНР.
JavaScript
Сценарный язык JavaScript обладает чрезвычайно богатыми возможностями для разработки Интернет-приложений, работающих как на клиентской, так и на серверной стороне. У этого языка есть немало интересных особенностей, и одна из них — возможность обработки не только данных, но и событий. Событие определяется как некоторое действие, выполненное в контексте браузера, — например, щелчок мышью или загрузка страницы.
Любой программист с опытом работы на РНР, Pascal или C++ освоит JavaScript без особого труда. Если вы не программировали на этих языках, не огорчайтесь — JavaScript изучается легко. Разработчики JavaScript (как, впрочем, и разработчики РНР) в первую очередь ориентировались на решение реальных, практических задач.
Если вы хотите воспользоваться средствами обработки,событий JavaScript, сохранив при этом многочисленные преимущества РНР, могу вас обрадовать — РНР интегрируется с JavaScript так же легко, как и с HTML. В сущности, JavaScript неплохо дополняет РНР — на нем удобно делать то, что неудобно делать в РНР, и наоборот.
Но прежде чем интегрировать РНР с JavaScript, следует учесть, что некоторые пользователи отключают поддержку JavaScript в своих браузерах или работают в браузерах, вообще не поддерживающих JavaScript (представьте, такое тоже бывает!). В РНР предусмотрены простые средства для распознавания таких ситуаций.
Поддержка СОМ в РНР
Стандартные функции РНР, предназначенные для работы с СОМ, создают объекты СОМ и используют их свойства и методы. Пожалуйста, не забывайте о том, что эта поддержка присутствует только в версии РНР для Windows. Следующие примеры были протестированы для Microsoft Word 2000. За информацией об объектах, методах и событиях, используемых в программе, обращайтесь на web-сайт MSDN (http://msdn.microsoft.com/library/officedev/off2000/ wotocobjectmodelapplication.htm).
Создание экземпляров объектов СОМ
Экземпляры объектов СОМ создаются вызовом new, как при обычном объектно-ориентированном программировании. Синтаксис:
object new СОМ("обьекг.класс" [, string удаленный_адрес])
Параметр объект.класс определяет модуль СОМ, присутствующий на сервере. Необязательный параметр удаленный_адрес используется в том случае, если объект СОМ создается на удаленном компьютере. Допустим, вы хотите создать экземпляр объекта для приложения MS Word. При этом приложение Microsoft Word запускается так, словно вы запустили его вручную (разумеется, для этого MS Word должен быть установлен на компьютере). Команда имеет следующий синтаксис:
$word=new COM("word.application") or die("Couldn't start Word!");
После того как экземпляр объекта СОМ будет создан, можно приступать к работе с различными методами и свойствами этого объекта. Допустим, вы захотели активизировать окно Word. Следующая команда изменяет атрибут видимости объекта, в результате чего графический интерфейс приложения отображается на экране:
$word->visible = 1:
He огорчайтесь, если эта команда выглядит непонятной. Вызов методов объектов СОМ рассматривается в следующем разделе.
Вызов методов объекта СОМ
Методы объектов СОМ вызываются в типичном для ООП формате, с использованием ссылки из объектной переменной. Синтаксис:
объект->имя_метода([значение, ...])
Объект соответствует экземпляру объекта СОМ, созданному описанным выше способом. Параметр имя_метода определяет имя метода, определенного в классе объект. Необязательный параметр значение позволяет передавать параметры при вызове методов, допускающих (или требующих) дополнительных данных. Как и при вызове обычных функций, параметры разделяются запятыми. Если после создания экземпляра объекта СОМ, представляющего MS Word, вы захотите создать в приложении новый документ, просто вызовите соответствующий метод. Задача решается методом add( ) субкласса Documents экземпляра $word:
$word->Documents->Add( );
Обратите внимание: для вызова методов используется очень логичный синтаксис в стиле ООП. В результате выполнения этой команды в окне приложения MS Word открывается новый документ.
com_get( )
Функция com_get( ) возвращает значение свойства объектов СОМ. Синтаксис:
mixed com_get(resource объект, string свойство)
Первый параметр определяет экземпляр объекта СОМ, а второй — атрибут класса, к которому относится данный экземпляр.
// Создать экземпляр объекта для приложения MS Word
$word=new COM("word.application") or die("Couldn't start Word!");
// Режим CapsLock либо включен (свойство CapsLock = 0),
// либо выключен (свойство CapsLock = 1).
$flag = com_get(Sword->Application.CapsLock)
// Преобразовать значение Sflag (0 или 1) в логическое значение
if ($flag == 1) :
$flag = "YES";
else :
$flag = "NO";
endif;
// Вывести сообщение
print "CAPS Lock activated: $flag";
$word->Quit();
?>
Существует и другое решение — значение атрибута CapsLock можно получить при помощи стандартного для ООП синтаксиса обращения к атрибутам. В предыдущем примере для этого следует заменить строку
$flag = com_get($word->Application,CapsLock)
следующей строкой:
$flag = $word->Application->CapsLock:
Атрибуты объекта позволяют получать разнообразную информацию о характеристиках приложения. Более того, многим атрибутам можно присваивать новые значения. Это делается при помощи функции com_set( ).
com_set( )
Функция com_set( ) присваивает атрибуту объекта новое значение:
Первый параметр определяет экземпляр объекта СОМ, а второй — атрибут класса, к которому относится данный экземпляр. Третий параметр определяет новое значение свойства.
Следующая программа (листинг 15.6) запускает Microsoft Word и активизирует окно приложения. Затем она создает новый документ, добавляет в него строку текста и выбирает режим сохранения документа (атрибут DefaultSaveFormat) в текстовом формате. Результат виден при открытии окна Сохранить как (Save As) — в списке Тип файла (Save As Type) автоматически выбирается строка Только текст (Text Only). После сохранения документа приложение Microsoft Word закрывается.
Листинг 15.6.
Выбор типа документа по умолчанию
// Создать экземпляр объекта для приложения MS Word
$word-new COMC'word.application") or die("Couldn't start Word!");
// Активизировать окно MS Word $word->visible = 1;
// Создать новый документ $word->Documents->Add();
// Вставить в документ фрагмент текста
$word->Selection->Typetext("php's com functionality is cool\n");
// Запросить у пользователя имя и сохранить документ.
// Обратите внимание: по умолчанию документ сохраняется
// в текстовом формате! $word->Documents[l]->Save;
// Выйти из MS Word
$word->Quit();
?>
Существует и другое решение — новое значение атрибута DefaultSaveFormat можно присвоить непосредственно, как обычной переменной. В листинге 15.6 для этого следует заменить строку
Итак, вы получили общее представление об управлении приложениями Windows через поддержку СОМ в РНР. Мы переходим к занимательному примеру, кото-
рый наглядно показывает, каких полезных и впечатляющих результатов можно добиться при помощи СОМ.
Проверка поддержки JavaScript
Правильное определение возможностей браузера избавит пользователей от неприятностей при посещении вашего сайта. Ничто так не действует на нервы, как град раздражающих сообщений «JavaScript Error» или недоступность каких-то средств сайта из-за того, что использованные вами технологии не поддерживаются браузером. К счастью, в РНР предусмотрено простое средство для проверки возможностей браузера — стандартная функция get_browser( ).
get_browser( )
Функция get_browser( ) возвращает информацию о возможностях браузера в виде объекта. Синтаксис:
object get_browser([string агент])
Необязательный параметр агент используется для получения характеристик конкретного браузера. Как правило, функция get_browser( ) вызывается без параметров, поскольку по умолчанию она использует глобальную переменную РНР $HTTP_USER_AGENT.
Стандартный список возможностей браузера хранится в файле browcap, путь к которому определяется параметром browcap в файле php.ini. По умолчанию эта строка выглядит следующим образом:
;browcap = extra/browcap.ini
Файл browser.ini был разработан компанией cyScape, Inc. Последняя версия этого файла находится по адресу http://www.cyscape.com/browcap. Загрузите и распакуйте этот файл в каталог на сервере. Запомните имя каталога, оно понадобится вам для обновления параметра browcap в файле php.ini.
В принципе, после загрузки browcap.ini и редактирования файла php.ini вы можете включать в свои программы проверку возможностей браузера. Впрочем, я рекомендую сначала открыть файл browser.ini и ознакомиться с его структурой, а затем просмотреть листинги 15.1 и 15.2. В листинге 15.1 приведен очень простой пример отображения всех возможностей браузера в самом браузере. Листинг 15.2 ограничивается лишь одной возможностью — поддержкой JavaScript.
Листинг 15.1. Отображение всех атрибутов браузера
// Получить информацию о браузере
$browser = get_browser();
// Преобразовать $browser в массив
Sbrowser = (array) Sbrowser;
while (list ($key, $value) = each ($browser)) :
// Присвоить нули пустым элементам массива
if ($value == "") : $value = 0;
endif;
print "$key : $value ";
endwhile;
?>
Для браузера Microsoft Internet Explorer 5.0 листинг 15.1 выводит следующий результат:
В листинге 15.2 приведен простой, но эффективный сценарий, который при помощи файла browcap.ini определяет, включена ли поддержка JavaScript в браузере.
Листинг 15.2.
Проверка поддержки JavaScript
$browser = get_browser( );
// Преобразовать $browser в массив $browser = (array) $browser;
if ($browser["javascript"] == 1) :
print "Javascript enabled!";
else :
print "No javascript allowed!";
endif;
?>
Листинг 15.2 проверяет, присутствует ли ключ javascript для заданного браузера. Если ключ присутствует и равен 1, в браузере выводится сообщение о поддержке JavaScript. В противном случае выводится сообщение об ошибке. Конечно, в реальной программе вместо выдачи сообщения следует выполнить какие-нибудь полезные действия.
Следующие два примера показывают, как легко РНР,интегрируется с JavaScript. Листинг 15.3 определяет параметры экрана (разрешение и цветовую глубину) средствами JavaScript и затем выводит их средствами РНР. Листинг 15.4 (см. следующий раздел) показывает, как при помощи шаблона РНР во временном (pop-up) окне, вызванном из кода JavaScript, выводится информация о ссылке, на которой щелкнул пользователь.
Листинг 15.3.
Определение цветовой глубины и разрешения экрана
Browser Information
echo "Browser: $type Version: $version ";
echo "Screen Resolution: $screenWidth x $screenHeight pixels. ";
if ($browserWidth != 0) :
echo "Browser resolution: $browserWidth x $browserHeight pixels.";
else :
echo " No javascript browser resolution support for Internet Explorer";
endif;
?>
СОМ
Технология СОМ (сокращение от «Component Object Model», то есть «модель составного объекта») обеспечивает взаимодействие между приложениями, работающими на разных языках и платформах. Такое взаимодействие в значительной мере способствует идее построения многократно используемых, легко сопровождаемых и адаптируемых программных компонентов (в последнее время к этим трем принципам проявляется повышенное внимание в области компьютерных технологий). Хотя СОМ обычно рассматривается как спецификация, ориентированная в первую очередь на продукты Microsoft, поддержка СОМ уже реализована во многих языках (например, в РНР, Java, C++ и Delphi) и существует на многих платформах, включая Windows, Linux и Macintosh.
Что же вам даст объединение СОМ с РНР? Во-первых, средства СОМ позволяют напрямую взаимодействовать со многими приложениями Microsoft. Ниже рассмотрен интересный пример — форматирование и вывод в Microsoft Word записей базы данных, полученных из Web. В следующем разделе вы увидите, как легко решается эта задача. На нескольких страницах, посвященных технологии СОМ, эту огромную тему нельзя осветить даже поверхностно. Ситуация осложняется тем, что возможности использования СОМ в языке РНР почти не документируются. За дополнительной информацией о механизме работы СОМ обращайтесь к ресурсам, перечисленным в конце этой главы.
РНР содержит несколько стандартных функций для работы с СОМ. Учтите, эти функции поддерживаются только в версии РНР для Windows! Прежде чем переходить к примерам, мы рассмотрим все эти функции.
Запись информации в документ Microsoft Word
Допустим, вам потребовалось отформатировать информацию, загруженную из базы данных, в документе Word для построения отчета. Весь процесс автоматизируется всего в нескольких строках кода РНР. Для демонстрационных целей я воспользуюсь таблицей addressbook из проекта адресной книги, приведенного в конце главы 12. Алгоритм работы сценария выглядит следующим образом:
Подключиться к серверу MySQL и выбрать нужную базу данных.
Выбрать все данные из таблицы с сортировкой по фамилиям.
Открыть приложение Microsoft Word и создать новый документ.
Отформатировать и вывести все записи в документе.
Запросить у пользователя имя для сохранения документа.
Закрыть Microsoft Word.
Программный код приведен в листинге 15.7.
Листинг 15.7. Запись информации в документ Microsoft Word
// Создать соединение с сервером MySQL
$host = "localhost";
$user = "root";
$pswd = "";
$db = "book";
$address_table = "addressbook";
mysql_connect($host. $user, $pswd)
or die("Couldn't connect to MySQL server!");
mysql_select_db($db) or die("Couldn't select database!");
// Выбрать из базы данных все записи
$query = "SELECT * FROM $address_table ORDER BY lastjiame";
Sresult = mysql_query($query):
// Создать новый объект COM для приложения MS Word
$word=new COM("word.application") or die("Couldn't start Word!");
// Активизировать окно MS Word $word->visible = 1;
// Открыть пустой документ. $word->Documents->Add( );
// Перебрать записи из таблицы адресов
while($row = mysql_fetch_array($result));
$last_name = $row["last_name"];
$first_name = $row["first_name"];
$tel = $row["tel"];
$email = $row["email"];
// Вывести данные таблицы в открытый документ Word.
$word->Selection->Typetext("$last_name. $first_name\n"); $word->Selection->Typetext("tel. $tel\n"): $word->Selection->Typetext("email. $email:\n");
endwhile;
// Запросить у пользователя имя документа.
$word->Documents[l]->Save;
// Выйти из MS Word
$word->Quit();
?>
При всей простоте рассмотренный пример наглядно показывает, как писать приложения РНР для пересылки содержимого базы данных в приложения Windows. Можно написать и более сложное приложение, обеспечивающее синхронизацию данных, полученных из Web, из Microsoft Outlook. Все, что для этого нужно — получить ссылку на объекты, свойства и методы Outlook, после чего можно переходить к экспериментам (обзор объектной модели всех приложений семейства Office приведен по адресу http://www.microsoft.com/officedev/articles/Opg/toc/PGTOC.htm).
PHP 4 на практике
Аутентификация пользователя
Правильно введенные имя и пароль открывают пользователю доступ к каталогам сервера, недоступным для анонимного доступа. Этот принцип аутентификации обычно называется схемой «запрос/ответ» (challenge/response). Запросом является приглашение к вводу имени и пароля, а ответом — введенные данные. Если введенная комбинация верна, пользователю предоставляется доступ к защищенным каталогам; в противном случае попытка получения доступа отклоняется с выводом соответствующего сообщения.
Как правило, для ввода имени и пароля применяются диалоговые окна, активизируемые вызовом функции header( ) (листинг 16.2).
Листинг 16.2. Запрос данных для аутентификации пользователя
header( 'WWW-Authenticate: Basic realm="Secret Family Recipes"');
header ('HTTP/1.0 401 Unauthorized');
exit;
?>
Выполнение фрагмента из листинга 16.2 всего лишь активизирует окно для ввода данных. Примерный вид этого окна изображен на рис. 16.1.
Рис. 16.1. Окно аутентификации пользователя
Следующим шагом после подготовки интерфейса для ввода является обработка имени пользователя и пароля. В РНР имя и пароль хранятся в двух глобальных переменных, $PHP_AUTH_USER (имя) и $PHP_AUTH_PW (пароль). В листинге 16.3 показано, как проверяются значения этих переменных. Если данные не были введены, окно аутентификации отображается заново. Экспериментируя со сценариями этого раздела, вы увидите, что окно аутентификации не всегда появляется после обновления страницы. Проблема кроется не в программе, а в том, как окна аутентификации реализованы в браузере. Чтобы вызвать окно, вам придется закрыть и перезапустить браузер.
Листинг 16.3. Проверка глобальных переменных аутентификации в РНР
If ( (! isset ($PHP_AUTH_USER)) || (! isset ($PHP_AUTH_PW)) ):
headert 'WWW-Authenticate: Basic realm="Secret Family Recipes'");
header(''HTTP/1.0 401 Unauthorized');
print "You are attempting to enter a restricted area. Authorization is required.";
exit;
endif;
?>
В простейшей, но недостаточно гибкой схеме ограничения доступа к странице имя пользователя и пароль жестко кодируются в сценарии. В листинге 16.4 приведен вариант предыдущего примера, построенный с использованием этой схемы.
Листинг 16.4. Жесткое кодирование имени и пароля в сценарии
if ( (! isset ($PHP_AUTH_USER)) | (! isset ($PHP_AUTH_PW)) || ($РНР AUTH USER != 'secret') | ($PHP_AUTH_PW !- 'recipes') ) :
header('WWW-Authenticate: Basic realm="Secret Family Recipes'"); header('HTTP/1.0 401 Unauthorized');
print "You are attempting to enter a restricted area. Authorization is required,
exit;
endif;
?>
Аутентификация с несколькими пользователями
Хотя вариант, приведенный в листинге 16.4, неплохо подходит для небольших статических групп пользователей, для ограничения доступа к некоторым областям web-сайта обычно выбираются более гибкие и надежные решения. Вероятно, вы предпочтете создать отдельные имя и пароль для каждого пользователя, которому предоставляется расширенный доступ. Существует несколько реализаций этой схемы, из которых чаще всего встречается чтение аутентификационных данных из текстового файла или базы данных.
Хранение информации в текстовом файле
Существует очень простое, но эффективное решение — хранить аутентификацион-ные данные в текстовом файле. В каждой строке файла содержится отдельная пара «имя:пароль»; в,процессе проверки программа последовательно читает и проверяет все строки файла. Примерный вид текстового файла приведен в листинге 16.5.
Листинг 16.5. Типичный текстовый файл с параметрами аутентификации (authenticate.txt)
brian:snaidni00
alessia:aiggaips
gary:9avaj9
chris:poghsawcd
matt:tsoptaes
Как видно из листинга, каждая строка приведенного файла состоит из имени пользователя и пароля, разделенных двоеточием (:). Таким образом, при использовании этого файла существует пять комбинаций «имя/пароль», обеспечивающих доступ к ограниченным ресурсам. Каждый раз, когда пользователь вводит имя и пароль в окне, сценарий открывает текстовый файл и последовательно ищет в нем совпадающую пару. Если совпадение находится, запрашиваемый доступ пользователю предоставляется, а если нет — запрос отклоняется. Процедура аутентификации продемонстрирована в листинге 16.6.
Листинг 16.6. Аутентификация на основе текстового файла
$file = "Listing16-5.txt":
$fp = fopen($file, "r"):
$auth_file = fread ($fp, filesize($fp)):
fclose($fp);
$authorized = 0;
// Сохранить строки файла в виде элементов массива
$elements = explode ("\n", $auth_file);
foreach ($elements as $element) {
list ($user, $pw) = split (":", $element);
if (($user == $PHP_AUTH_U$ER) && ($pw = $PHP_AUTH_PW)) :
Листинг 16.7.
Аутентификация пользователя посредством поиска в базе данных
if (!isset($PHP_AUTH_USER)) :
header( 'WWW-Authenticate: Basic realm="Secret Family Recipes'");
header('HTTP/1.0 401 Unauthorized');
exit;
else :
// Создать содинение с базой данных MySQL
mysql_connect ("host", "user", "password")
or die ("Can't connect to database!");
mysql_select_db ("useMnfo")
or die ("Can't select database!");
// Обратиться к таблице user_authenticate
// для поиска совпадающей строки
$query = "select userid from user_authenticate where
username = '$PHP_AUTH_USER' and
password = '$PHP_AUTH_PW'";
$result = mysql_query (Squery):
// Если совпадение не найдено, вывести окно аутентификации
if (mysql_numrows($result) != 1) :
header('WWW-Authenticate: Basic realm="Secret Family Recipes'");
header ('HTTP/ 1.0 401 Unauthorized');
exit;
// Если проверка пройдена, получить идентификатор пользователя
Даже после того, как вы обеспечили надежную конфигурацию сервера, необходимо постоянно помнить о потенциальной угрозе для системы безопасности, исходящей из программного кода РНР. Не подумайте, что язык РНР недостаточно надежен — теоретическую брешь в системе безопасности можно создать на любом языке программирования. Тем не менее, учитывая широкое распространение РНР для программирования в распределенных средах с большим количеством пользователей (то есть в Web), вероятность попыток «взлома» ваших программ со стороны пользователей существенно возрастает. Вы должны сами позаботиться о том, чтобы этого не произошло.
Безопасный режим и работа РНР в режиме модуля Apache
Следует помнить, что при работе РНР в режиме модуля Apache безопасный режим недоступен. Это объясняется тем, что модуль РНР работает в составе сервера Apache, поэтому все сценарии РНР работают под тем же UID, что и сам сервер Apache. Поскольку ограничения вызова функций в безопасном режиме основаны на сравнении UID, этот режим полноценно работает только при использовании CGI-версии РНР в сочетании с suExec (http://www.apache.org/docs/ suexec.html). Дело в том, что CGI-версия РНР работает как отдельный процесс, что позволяет динамически изменять UID средствами suExec. Если вас интересует использование РНР в безопасном режиме, вероятно, вам следует остановить свой выбор на комбинации CGI/suExec, хотя за это приходится расплачиваться быстродействием.
Другой важный аспект конфигурации — запрет на просмотр некоторых файлов в браузере. Конечно, секретные пароли или другие конфигурационные данные должны оставаться недоступными для внешних пользователей. Эта тема рассматривается в следующем разделе.
CCVS
Технология CCVS (Credit Card Verification System) была разработана RedHat (http://www.redhat.com) для независимой обработки сделок по кредитным картам. Она позволяет напрямую обращаться к агентствам кредитных карт вместо того, чтобы пользоваться услугами третьих сторон (например, Cybercash). Технология CCVS совместима со многими платформами Linux/UNIX и легко адаптируется, поскольку RedHat предоставляет исходные тексты. Для использования средств CCVS РНР необходимо откомпилировать с ключом
-with-ccvs[-DIR].
За дополнительной информацией о CCVS обращайтесь по адресам:
Компания Cybercash, Inc. (http://www.cybercash.com) предлагает разнообразные услуги по проверке кредитных карт и проведению сделок, а также программное обеспечение для тех, кто желает использовать эти услуги в своих web-приложениях.
Сценарии, написанные на Perl, могут взаимодействовать со службой проведения сделок Cybercash. Учитывая это обстоятельство, пользователи РНР обычно интегрируют Cybercash со своими сайтами одним из следующих способов:
Используя библиотеку cyberlib.php, включенную в поставку РНР. Этот вариант предоставляет в ваше распоряжение все необходимое для проведения транзакций (рекомендуется).
Взаимодействуя со службой Cybercash при помощи готовых сценариев Perl и С и вызывая их из сценариев РНР (рекомендуется).
Переписывая готовые сценарии Perl и С на РНР (не рекомендуется).
Для использования средств Cybercash РНР необходимо откомпилировать с ключом -with-cybercash[=DIR].
Как и в случае с Verisign, помните, что включение поддержки Cybercash при компиляции РНР еще не означает, что вы можете пользоваться этой службой! Услуги Cybercash не бесплатны и могут обойтись довольно дорого (подключение к службе Cybercash Commerce Cash Register в настоящее время стоит $495, ежемесячная оплата составляет $20, а каждая сделка стоит $0,20). Тем не менее, невзирая на все расходы, многие разработчики РНР считают, что Cybercash является одним из лучших решений.
Прежде чем оплачивать услуги Cybercash, вы можете протестировать свой сценарий при помощи тестового входа (эту услугу Cybercash предлагает бесплатно). Бесплатное тестирование сценариев избавит вас от лишних расходов в процессе отладки программ. Дополнительную информацию можно получить на сайте Cybercash.
Ресурсы Интернета, посвященные Cybercash:
http://www.cybercash.com;
http://www.php.net/manual/ref.cybercash.php.
Disable_functions
В этом параметре через запятую перечисляются имена функций, выполнение которых требуется запретить. Обратите внимание — этот параметр никак не связан с safe_mode. Например, чтобы запретить вызовы функций fopen( ), popen( ) и file( ), достаточно включить в конфигурационный файл следующую строку:
disable_functions = fopen, popen.file
Doc_root
Параметру присваивается путь к корневому каталогу для файлов РНР. Если значение doc_root представляет собой пустую строку, оно игнорируется и сценарии РНР выполняются в полном соответствии с URL. Если безопасный режим включен, а параметр doc_root содержит непустое значение, то сценарии РНР за пределами этого каталога выполняться не будут.
Дополнительная информация
В этом разделе описаны лишь те средства, которые в той или иной степени интегрируются в РНР. Впрочем, этим ваши возможности не ограничиваются. Помните о том, что при помощи функций рореn( ) или ехес( ) можно работать с любыми технологиями шифрования, разработанными независимыми фирмами, — например, PGP (http://www.pgpi.org) или GPG (http://www.gnupg.org).
Ниже перечислены некоторые ресурсы Интернета, посвященные криптографии и информационной безопасности:
В завершение этого раздела я хочу лишний раз напомнить об осторожности. Прежде чем включать поддержку шифрования данных в критические приложения, потратьте немного времени на изучение механики шифрования. В мире безопасности данных неведение всегда приводит к печальным результатам. Если вы слабо разбираетесь в этой теме, обязательно ознакомьтесь с приведенными ссылками — они дают превосходное представление о многих аспектах шифрования и безопасности данных.
PHP 4 на практике
Безопасность
Non sum qualis eram
(Я не такой, каким был раньше).
Гораций
Когда-то, наткнувшись на эту цитату из Горация, я подумал, что она очень точно отражает суть сетевой безопасности, и сохранил ее где-то в недрах своего жесткого диска на будущее.
Конечно, читатель недоумевает — какое отношение древнеримский поэт Гораций имеет к сетевой безопасности? Безопасность — одна из тем, порождающих нескончаемый поток информации и вечно меняющихся в соответствии с новыми технологическими веяниями. Короче говоря, она в любой момент «не такая, какой была раньше». Вы никогда не можете полагаться на свои познания в этой области, поскольку в момент выхода на широкий рынок любая технология либо устаревает, либо обречена на устаревание в ближайшем будущем. Чтобы добиться относительной безопасности при построении серверных приложений, вам придется постоянно следить за последними достижениями в этой области или нанять кого-нибудь, кто это будет делать за вас.
Применительно к РНР тема безопасности выглядит многогранной, причем некоторые ее аспекты связаны с безопасностью самого сервера. Ведь безопасность сервера во многих отношениях определяет безопасность данных, обрабатываемых сценариям РНР. Я настоятельно рекомендую собрать как можно больше информации о вашем web-сервере и постоянно следить за всеми обновлениями и исправлениями. Вероятно, большинство читателей работает с сервером Apache, поэтому я советую почаще посещать сайт Apache (http://www.apache.org) и замечательный сайт Apache Week (http://www.apacheweek.org). Впрочем, безопасностью сервера дело не ограничивается — РНР также в определенной степени влияет на безопасность системы за счет правильного выбора параметров конфигурации и защищенного программирования.
Последняя глава этой книги состоит из пяти разделов:
Проблемы конфигурации.
Проблемы программирования.
Шифрование данных.
Электронная коммерция.
Аутентификация пользователей.
Хотя ни один из этих разделов не содержит ответов на все вопросы, относящиеся к построению защищенных приложений на базе РНР, по крайней мере, они закладывают основу для дальнейших самостоятельных исследований.
Электронная коммерция
Появление электронной коммерции вызвало настоящий ажиотаж во всем мире. Бесспорно, она обладает многочисленными достоинствами и открывает массу новых возможностей. К счастью, читатели, занимающиеся разработкой собственных коммерческих сайтов, могут воспользоваться надежными коммерческими технологиями, легко интегрируемыми в сценарии РНР. В этом разделе кратко описаны самые популярные из этих технологий.
Маскировка файлов данных и конфигурационных файлов
Этот раздел посвящен процедуре, которая играет очень важную роль независимо от используемого языка программирования. На примере сервера Apache я покажу, как легко нарушить вашу систему безопасности, если вы не предпримете необходимых шагов для «маскировки» файлов, не предназначенных для внешних пользователей.
В конфигурационном файле Apache httpd.conf присутствует параметр DocumentRoot. С его помощью устанавливается путь к каталогу, который рассматривается сервером как общедоступный каталог HTML. Считается, что любой файл в этом каталоге может быть передан в пользовательский браузер, даже если расширение этого файла не опознано. Пользователи не могут просматривать файлы, находящиеся за пределами этого каталога. Следовательно, конфигурационные файлы никогда
не следует хранить в каталоге DocumentRoot!
Для примера создайте файл и введите в нем какой-нибудь «секретный» текст. Сохраните этот файл в общедоступном каталоге HTML с именем secrets и каким-нибудь экзотическим расширением типа .zkgjg. Разумеется, сервер не распознает это расширение, но все равно попытается передать запрошенные данные. Теперь запустите браузер и введите URL со ссылкой на этот файл. Интересно, правда? К счастью, у этой проблемы существует два простых решения.
Хранение файлов за пределами корневого каталога документов
В первом варианте вы просто сохраняете все файлы, которые не должны просматриваться пользователями, вне корневого каталога документов и в дальнейшем включаете их в сценарии РНР директивой include( ). Допустим, параметр DocumentRoot настроен следующим образом:
DocumentRoot C:\Program Files\Apache Group\Apache\htdocs # Windows
DocumentRoot /www/apache/home # Другие системы
Предположим, у вас имеется файл с атрибутами доступа (хост, имя пользователя, пароль) к базе данных MySQL. Конечно, этот файл не должен попадаться на глаза посторонним, поэтому вы сохраняете его вне корневого каталога документов. Например, в системе Windows можно воспользоваться каталогом C:/Program FHes/mysecretdata, а в UNIX — каталогом /usr/local/mysecretdata.
Чтобы воспользоваться атрибутами доступа в сценарии, достаточно включить эти файлы с указанием полного пути. Пример для Windows:
Конечно, при отключении безопасного режима (см. предыдущий раздел) это не помешает другим пользователям, которые могут выполнять сценарии РНР, включить этот файл в свои сценарии. Следовательно, в многопользовательской среде эту меру безопасности желательно сочетать с включением безопасного режима.
Настройка файла httpd.conf
Во втором варианте доступ к файлам с конфиденциальной информацией ограничивается по расширению файла, при этом используется параметр FILES файла httpd.conf. Допустим, вы хотите запретить пользователям доступ к файлам с расширением .inc. Для этого достаточно включить в файл httpd.conf следующий фрагмент:
Order allow, deny
Deny from all
После редактирования файла перезапустите сервер Apache, после этого все попытки запросить любой файл с расширением .inc в браузере отклоняются сервером. Впрочем, эти файлы все равно могут включаться в сценарии РНР. Кстати говоря, при просмотре файла httpd.conf вы увидите, что этот способ применяется для ограничения доступа к файлам .htaccess. Эти файлы используются для парольной защиты каталогов и рассматриваются в конце главы.
Max_execution_time
Параметр указывает максимальную продолжительность выполнения сценария (в секундах). По истечении указанного срока сценарий автоматически завершается, что помогает бороться с чрезмерными затратами процессорного времени на выполнение пользовательских сценариев. По умолчанию параметр равен 30 секундам. Если присвоить ему 0, время выполнения сценариев не ограничивается.
Memory_limit
Параметр определяет максимальный объем памяти (в байтах), используемой сценарием. По умолчанию параметр равен 8 Мбайт (8 388 608 байт).
Обработка пользовательского ввода
Хотя обработка данных, введенных пользователем, является важной частью практически любого нормального приложения, необходимо постоянно помнить о возможности передачи неправильных данных (злонамеренной или случайной). В web-приложениях эта опасность выражена еще сильнее, поскольку пользователи могут выполнять системные команды при помощи таких функций, как system( ) или ехес( ).
Простейший способ борьбы с потенциально опасным пользовательским вводом — обработка полученных данных стандартной функцией escapeshellcmd( ).
escapeshellcmd( )
Функция escapeshellcmd( ) экранирует все сомнительные символы в строке, которые могут привести к выполнению потенциально опасной системной команды:
string escapeshellcmd(string команда)
Чтобы вы лучше представили, к каким последствиям может привести бездумное использование полученных данных, представьте, что вы предоставили пользователям возможность выполнения системных команд — например, `ls -l`. Но если пользователь введет команду `rm -rf *` и вы используете ее для эхо-вывода или вставите в вызов ехес( ) или system( ), это приведет к рекурсивному удалению файлов и каталогов на сервере! Проблемы можно решить предварительной «очисткой» команды при помощи функции escapeshel lcmd( ). В примере `rm -rf *` после предварительной обработки функцией escapeshellcmd( ) строка превращается в \ `rm -rf *\`. Обратные апострофы (backticks) представляют собой оператор РНР, который пытается выполнить строку, заключенную между апострофами. Результаты выполнения направляются прямо на экран или присваиваются переменной.
При обработке пользовательского ввода возникает и другая проблема — возможное внедрение тегов HTML. Особенно серьезные проблемы возникают при отображении введенной информации в браузере (как, например, на форуме). Присутствие тегов HTML в отображаемом сообщении может нарушить структуру страницы, исказить ее внешний вид или вообще помешать загрузке. Проблема решается обработкой пользовательского ввода функцией strip_tags( ).
strip_tags( )
Функция strip_tags( ) удаляет из строки все теги HTML. Синтаксис:
string strip_tags (string строка [, string разрешенные_теги])
Первый параметр определяет строку, из которой удаляются теги, а второй необязательный параметр определяет теги, остающиеся в строке. Например, теги курсивного начертания (<1 >... 1>) не причинят особого вреда, но лишние табличные теги (например,
...
) вызовут настоящий хаос. Пример использования функции strip_tags( ):
$input = "I really love РНР!";
$input = strip_tags($input);
// Результат: $input = "I really love PHP!";
На этом завершается краткое знакомство с двумя функциями, часто используемыми при обработке пользовательского ввода. Следующий раздел посвящен шифрованию данных, причем особое внимание, как обычно, уделяется стандартным функциям РНР.
Общие функции шифрования
Шифрование данных в Web имеет смысл только в том случае, если сценарии, в которых используются средства шифрования, работают на защищенном сервере. Почему? Поскольку РНР является сценарным языком, работающим на стороне сервера, перед шифрованием данные должны быть отправлены на сервер в простом текстовом формате. Если данные передаются через незащищенное соединение, существует немало способов перехвата этой информации в процессе ее пересылки от пользователя на сервер. За дополнительными сведениями о защите сервера Apache обращайтесь на сайт http://www.apache-ssl.org. Читателям, работающим с другими web-серверами, следует обращаться к документации. Скорее всего, для этих серверов существует хотя бы одно (а может, и больше) решение области безопасности.
md5( )
Хэширующий алгоритм MD5 используется (в частности) для создания цифровых подписей, позволяющих однозначно идентифицировать отправителя. В РНР для этого алгоритма существует специальная функция
string md5(string строка)
MD5 является алгоритмом «одностороннего» хэширования; это означает, что данные, хэшируемые функцией md5(), восстановить уже невозможно.
Алгоритм MD5 также применяется при проверке паролей. Поскольку теоретически невозможно восстановить исходную строку, обработанную алгоритмом MD5, можно хэшировать пароль функцией md5( ) и затем сравнивать зашифрованный пароль с результатом обработки пароля, введенного пользователем при попытке получения доступа к конфиденциальной информации.
Допустим, у нас имеется некоторый секретный пароль toystore с хэш-кодом 745e2abd7c52eeldd7cl4aeOd71b9d76. Хэшированное значение сохраняется на сервере и сравнивается с хэш-эквивалентом пароля, введенного пользователем. Даже если злоумышленник получит доступ к зашифрованному паролю, это ни на что не повлияет, поскольку он (теоретически) не сможет восстановить по нему оригинал. Пример хэширования строки:
$val = "secret";
$hash_val = md5 ($val);
// $hash_val = "Clab6fb9182fl6eed935bal9aa830788";
В следующем раздеяе я представлю другой способ шифрования данных, в котором используется одна из стандартных функций РНР.
crypt( )
Функция crypt( ) является удобным средством для одностороннего шифрования данных. Под «односторонним шифрованием» я подразумеваю, что данные могут только шифроваться — алгоритмы для расшифровки данных, обработанных функцией crypt( ), пока неизвестны. Синтаксис:
string cкypt(string строке [, детерминант])
Первый параметр определяет строку, шифруемую функцией crypt( ). Необязательный второй параметр определяет алгоритм, используемый при шифровании. Точнее, тип алгоритма определяется длиной детерминанта. Различные типы алгоритмов и длины их детерминантов перечислены в табл. 16.2.
Таблица 16.2.
Алгоритмы шифрования и длины их детерминантов
Алгоритм
Длина
CRYPT_STD_DES
2
CRYPT_EXT_OES
9
CRYPT_MD5
12
CRYPT BLOWFISH
16
В листинге 16.1 продемонстрировано использование функции crypt( ) для создания и сравнения зашифрованных паролей.
Листинг 16.1.
Применение функции crypt (STD_DES) для хранения и сравнения паролей
$user_pass = "123456";
// Выделить первые два символа $user_pass
// и использовать их в качестве детерминанта.
$salt = substr($user_pass. 0, 2);
// Зашифровать и сохранить пароль.
$crypt1 = crypt($user_pass, ;salt);
// $crypt1 = "12tir.zIbWQ3c"
//... пользователь вводит пароль
$entered_pass = "123456";
// Получить первые два символа хранящегося пароля
$salt1 = substr($crypt, 0, 2);
// Зашифровать $entered_pass, используя $saltl в качестве детерминанта.
$crypt2 = crypt($entered_pass, $salt1);
// $crypt2 = "12tir.zIbWQ3c";
// Следовательно. $cryptl = $crypt2
?>
Если вы выбираете между crypt( ) и md5() для шифрования данных на сайте, рекомендую остановиться на md5( ) — эта функция обеспечивает лучшую защиту.
Как видно из листинга 16.1, $crypt совпадает с $crypt2, но только потому, что мы правильно использовали первые два символа $crypt1 в качестве детерминанта для шифрования $entered_pass. Поэкспериментируйте с этим примером, попробуйте использовать различные значения, и вы убедитесь, что $crypt1 совпадает с $crypt2 лишь при использовании этой процедуры.
mhash( )
Функция mhash( ) поддерживает несколько алгоритмов хэширования, которые позволяют разработчикам использовать контрольные суммы и разнообразные цифровые подписи в приложениях РНР. Хэши также используются для хранения паролей. Подключение модуля mhash в РНР выполняется очень просто:
Зайдите на сайт http://mhash.sourceforge.net
и загрузите пакет.
Распакуйте содержимое архива и выполните инструкции, приведенные в документе INSTALL.
Откомпилируйте РНР с ключом -with-mhash.
Как видите, ничего сложного. Впрочем, имеется одно обстоятельство, которое часто вызывает проблемы при компиляции mhash для комбинации РНР/Apache, — многим пользователям приходится конфигурировать mhash следующим образом: " ./configure -disable -pthreads" (вы поймете, о чем идет речь, если прочитаете документ INSTALL). Помните об этом в процессе компиляции.
После завершения установки в вашем распоряжении оказываются все функциональные возможности mhash. Алгоритмы хэширования, поддерживаемые в настоящее время mhash, перечислены в табл. 16.3.
Таблица 16.3.
Алгоритмы хэширования, поддерживаемые mhash( )
SHA1
RIPEMD160
MD5
GOST
TIGER
SNEFRU
HAVAL
CRC32
RIPEMD128
CRC32B
mcrypt( )
Mcrypt — популярный пакет шифрования данных в РНР, обеспечивающий возможность двустороннего шифрования (то есть собственно шифрование и расшифровку данных). Четыре режима шифрования, поддерживаемых модулем mcrypt, перечислены ниже.
CBC
Режим СВС (Cipher Block Chaining) является самым распространенным из всех четырех режимов mcrypt. В отличие от режима ЕСВ (см. ниже), СВС обеспечивает разное шифрование идентичных блоков текста, что затрудняет поиск закономерностей при попытке несанкционированной расшифровки. Если вы не знаете, какой из четырех режимов следует использовать, выбирайте СВС. Впрочем, перед принятием окончательного решения стоит ознакомиться со всеми четырьмя режимами.
СFВ
Режим СFВ (Cipher Feedback) обладает некоторыми характеристиками потоковых шифров, что избавляет от необходимости накопления блоков данных перед шифрованием. Данный режим используется очень редко.
ЕСВ
Режим ЕСВ (Electronic Code Book) шифрует каждый текстовый блок независимым блочным шифром, что несколько снижает его защищенность при шифровании относительно малых блоков обычного текста. Поскольку ЕСВ шифрует два блока простого текста одинаковым шифром, у злоумышленника появляется основа для расшифровки. Следовательно, если у вас нет веских доводов в пользу ЕСВ, вероятно, лучше воспользоваться режимом СВС.
OFB
По многим характеристикам режим OFB (Output Feedback) похож на режим СFВ. Как и СFВ, он используется относительно редко.
Чтобы воспользоваться средствами mcrypt необходимо предварительно принять па-кет по адресу ftp://argeas.cs-net.gr/pub/unix/mcrypt.
Проблемы конфигурации
Сразу же после установки РНР следует уделить внимание некоторым аспектам конфигурации, влияющим на безопасность вашей системы. Конечно, выбор зависит от конкретной ситуации. Например, если программированием на РНР будете заниматься только вы и ваши коллеги, то конфигурация системы безопасности будет выглядеть совсем не так, как если бы программирование сценариев РНР, работающих на сервере, разрешалось всем клиентам. Независимо от ситуации, следует внимательно проанализировать все параметры конфигурации и разрешить только то, что действительно необходимо. Параметры конфигурации РНР определяются в файле php.ini.
Safe_mode_exec_dir
Параметр определяет каталог для размещения системных программ, запускаемых такими функциями, как system( ), exec( ) или passthru( ). Параметр используется лишь при включенном безопасном режиме.
Safe_mode
При включении безопасного режима (safe_mode) ограничивается использование некоторых потенциально опасных возможностей РНР. Для включения или выключения безопасного режима параметру safe_mode присваивается значение on или off. Механизм ограничения основан на сравнении идентификатора пользователя (UID) выполняющегося сценария с идентификатором пользователя того файла, к которому этот сценарий пытается обратиться. Если идентификаторы совпадают, функция выполняется; в противном случае попытка завершается неудачей.
Безопасный режим не может использоваться в том случае, если РНР откомпилирован в виде модуля Apache. Дело в том, что при работе РНР в режиме модуля Apache все сценарии РНР работают под тем же идентификатором, что и Apache, что не позволяет различать владельцев разных сценариев. За дополнительной информацией обращайтесь к разделу «Безопасный режим и работа РНР в режиме модуля Apache».
В частности, при включении безопасного режима действуют следующие ограничения:
Функции ввода/вывода (в частности, fopen ( ), filе( ) и include ( )) работают только с файлами, принадлежащими владельцу сценария. Предположим, в безопасном режиме сценарий, принадлежащий пользователю Мэри, вызывает функцию fopen( ). Если функция попытается открыть файл, принадлежащий Джону, ее вызов завершится неудачей. Но если Мэри принадлежит как сценарий, вызывающий fopen( ), так и открываемый файл, все будет нормально.
Запуск внешних сценариев функциями popen( ), system( ) или ехес( ) разрешается лишь в том случае, если запускаемый сценарий находится в каталоге, определяемом параметром safe_mode_exec_dir (см. далее).
Новые файлы создаются только в каталогах, принадлежащих владельцу сценария.
Аутентификация HTTP становится более жесткой, поскольку в ней также учитывается UID аутентифицирующего сценария. Механизм аутентификации пользователей рассматривается в одном из разделов этой главы.
Имя пользователя, использованное при подключении к серверу MySQL, должно совпадать с именем владельца файла, вызывающего mysql_connect( ).
В табл. 16. 1 приведен полный список функций, на которые распространяется безопасный режим.
Таблица 16.1.
Функции, выполнение которых ограничивается в безопасном режиме
chgrp
include
require
chmod
link
rmdir
chown
passthru
symlink
exec
popen
system
fopen
readfile
unlink
file
rename
К сожалению, документация РНР по безопасному режиму не обновлялась с версии 2.0, хотя функциональность безопасного режима практически не изменилась. Документация находится по адресу http://www.php.net/manual/phpfi2.html.
Шифрование данных
Шифрованием называется процесс преобразования данных в формат, в котором они могут быть прочитаны (во всяком случае, теоретически) только предполагаемым
получателем сообщения. Получатель расшифровывает данные при помощи ключа или секретного пароля. РНР поддерживает несколько алгоритмов шифрования. Наиболее важные из этих алгоритмов описаны ниже.
Sql.safe_mode
При включении параметра sql .safejnode игнорируется вся информация, передаваемая функциям mysql_connect( ) и mysql_pconnect( ), а подключения разрешаются только для UID, под которым работает web-сервер.
User_dir
Параметр определяет имя подкаталога в домашнем каталоге пользователя, в котором должны находиться исполняемые сценарии РНР. Например, если параметру user_dir присвоено значение scripts и пользователь с именем Alessia хочет выполнить сценарий somescript.php, он должен создать в своем домашнем каталоге подкаталог с именем scripts и поместить сценарий в этот каталог. К сценарию можно обратиться по URL http://www.yoursite.com/~alessia/somescript.php. Обратите внимание — каталог scripts в URL не включается. Как правило, этот параметр используется в сочетании с параметром конфигурации Apache UserDi г.
Verisign
Компания Verisign, Inc. (http://www.verisign.com) предоставляет широкий ассортимент коммерческих продуктов и услуг. В РНР предусмотрена поддержка взаимодействия со службой Verisign Payflow Pro. Для использования средств Verisign РНР необходимо откомпилировать с ключом -with-pfproC-DIR]. Кроме того, в файле php.ini имеется несколько конфигурационных параметров, относящихся к Payflow Pro.
Поддержка Payflow Pro в РНР очень проста в использовании, а непосредственное проведение сделок требует минимальных времени и знаний. Однако простое включение поддержки Verisign при компиляции РНР вовсе не означает, что вы можете пользоваться услугами Verisign! Для этого необходимо предварительно зарегистрироваться на сайте Verisign и принять пакет Verisign SDK. На момент написания книги за подключение к Payflow Pro взимался разовый взнос $249, а также ежемесячная оплата $59.95 (если ежемесячное количество сделок не превышает 5000) или $995 (при неограниченном количестве сделок).
И еще одно замечание: прежде чем оплачивать услуги Verisign, вы можете протестировать свой сценарий при помощи тестового входа (эту услугу Verisign предлагает бесплатно). Бесплатное тестирование сценариев избавит вас от лишних расходов в процессе отладки программ. Дополнительную информацию можно получить на сайте Verisign.
Ресурсы Интернета, посвященные Verisign: