|
||||
|
Язык программирования PHPPHP, в настоящее время, – один из наиболее популярных языков для реализации веб-приложений. Данный курс посвящен изучению его основ. Акцент делается на практическое применение полученных навыков. Язык PHP был создан для решения конкретной практической задачи в среде интернет (какой именно можно узнать, прочитав первую лекцию курса). Мы тоже постараемся не отвлекаться слишком сильно на теоретические рассуждения, и будем стремиться к решению какой-нибудь конкретной задачи в каждой из лекций. Большинство примеров взяты из реально существующей системы: виртуального музея истории информатики. Первая часть курса посвящена изучению основ синтаксиса и управляющим конструкциям. После этого рассматривается технология клиент-сервер, как основная сфера приложения языка PHP. Затем переходим к изучению наиболее полезных на наш взгляд встроенных функций и решению с их помощью практических задач. Хотя объектная модель в языке PHP не самая богатая, ее принципиальное наличие позволяет естественным образом описывать объектные модели данных. В качестве базовой модели будет рассмотрена модель документа виртуального музея информатики. После этого будет рассмотрен ряд прикладных аспектов: работа с файловой системой, с БД, строками, сессиями, DOM XML - все это позволит рассмотреть ключевые задачи практического использования языка. Цель Знакомство с языком PHP, развитие навыков проектирования и программирования веб-приложений. Предварительные знания Общие представления о языках программирования и клиент-серверных технологиях, язык HTML. Предварительные курсы Применение каскадных таблиц стилей (CSS) Введение в HTML Дополнительные курсы Введение в JavaScript Язык программирования Perl Язык программирования Python Введение в CGI Спецификация CSS2 Спецификация языка HTML Введение в PHPИстория PHP Язык PHP был разработан как инструмент для решения чисто практических задач. Его создатель, Расмус Лердорф, хотел знать, сколько человек читают его online-резюме, и написал для этого простенькую CGI-оболочку на языке Perl, т.е. это был набор Perl-скриптов, предназначенных исключительно для определенной цели – сбора статистики посещений. Для справки. CGI (Common Gateway Interface – общий интерфейс шлюзов) является стандартом, который предназначен для создания серверных приложений, работающих по протоколу HTTP. Такие приложения (их называют шлюзами или CGI-программами) запускаются сервером в режиме реального времени. Сервер передает запросы пользователя CGI-программе, которая их обрабатывает и возвращает результат своей работы на экран пользователя. Таким образом, посетитель получает динамическую информацию, которая может изменяться в результате влияния различных факторов. Сам шлюз (скрипт CGI) может быть написан на различных языках программирования – Cи/C++, Fortran, Perl, TCL, UNIX Shell, Visual Basic, Python и др. Вскоре выяснилось, что оболочка обладает небольшой производительностью, и пришлось переписать ее заново, но уже на языке Си. После этого исходники были выложены на всеобщее обозрение для исправления ошибок и дополнения. Пользователи сервера, где располагался сайт с первой версией PHP, заинтересовались инструментом, появились желающие его использовать. Так что скоро PHP превратился в самостоятельный проект, и в начале 1995 года вышла первая известная версия продукта, называвшаяся Personal Home Page Tools (средства для персональной домашней страницы). Средства эти были более чем скромными: анализатор кода, понимающий всего лишь несколько специальных команд, и набор утилит, полезных для создания гостевой книги, счетчика посещений, чата и т.п. К середине 1995 года после основательной переработки появилась вторая версия продукта, названная PHP/FI (Personal Home Page / Forms Interpreter – персональная домашняя страница/ интерпретатор форм). Она включала набор базовых возможностей сегодняшнего PHP, возможность автоматически обрабатывать html-формы и встраиваться в html-коды. Синтаксис PHP/FI сильно напоминал синтаксис Perl, но был более простым. В 1997 вышла вторая версия Cи-реализации PHP – PHP/FI 2.0. К тому моменту PHP использовали уже несколько тысяч людей по всему миру, примерно с 50 тыс. доменов, что составляло около 1% всего числа доменов Internet. Число разработчиков PHP увеличилось до нескольких человек, но, несмотря на это, PHP/FI 2.0 все еще оставался крупным проектом одного человека. Официально PHP/FI 2.0 вышел только в ноябре 1997 года, просуществовав до этого в основном в бета-версиях. Вскоре после выхода его заменили альфа-версии PHP 3.0. PHP 3.0 была первой версией, напоминающей PHP, каким мы знаем его сегодня. Он очень сильно отличался от PHP/FI 2.0 и появился опять же как инструмент для решения конкретной прикладной задачи. Его создатели, Энди Гутманс (Andi Gutmans) и Зив Сураски (Zeev Suraski), в 1997 году переписали заново код PHP/FI, поскольку он показался им непригодным для разработки приложения электронной коммерции, над которым они работали. Для того чтобы получить помощь в реализации проекта от разработчиков PHP/FI, Гутманс и Сураски решили объединиться с ними и объявить PHP3 официальным преемником PHP/FI. После объединения разработка PHP/FI была полностью прекращена. Одной из сильных сторон PHP 3.0 была возможность расширения ядра. Именно свойство расширяемости PHP 3.0 привлекло внимание множества разработчиков, желающих добавить свой модуль расширения. Кроме того, PHP 3.0 предоставляла широкие возможности для взаимодействия с базами данных, различными протоколами и API. Немаловажным шагом к успеху оказалась разработка нового, намного более мощного и полного синтаксиса с поддержкой ООП. С момента появления PHP 3.0 изменилась не только функциональность и внутреннее устройство языка, но и его название. В аббревиатуре PHP больше не было упоминания о персональном использовании, PHP стало сокращением (рекурсивным акронимом) от PHP: Hypertext Preprocessor, что значит «PHP: препроцессор гипертекста». К концу 1998 года число пользователей PHP возросло до десятков тысяч. Сотни тысяч web-сайтов сообщали о том, что они работают с использованием этого языка. Почти на 10% серверов Internet был установлен PHP 3.0. Официально PHP 3.0 вышел в июне 1998 года, после 9 месяцев публичного тестирования. А уже к зиме Энди Гутманс и Зив Сураски начали переработку ядра PHP. В их задачи входило увеличение производительности работы сложных приложений и улучшение модульности кода, лежащего в основе PHP. Новое ядро было названо «Zend Engine» (от имен создателей: Zeev и Andi) и впервые представлено в середине 1999 года. PHP 4.0, основанный на этом ядре и принесший с собой набор дополнительных функций, официально вышел в мае 2000 года, почти через два года после своего предшественника, PHP 3.0. Помимо улучшения производительности, PHP 4.0 имел еще несколько ключевых нововведений, таких как поддержка сессий, буферизация вывода, более безопасные способы обработки вводимой пользователем информации и несколько новых языковых конструкций. В настоящее время ведутся работы по улучшению Zend Engine и внедрению нововведений в PHP 5.0, первые бета-версии которого уже вышли в свет. Одно из существенных изменений произошло в объектной модели языка, ее основательно подлатали и добавили много новых возможностей. Сегодня PHP используется сотнями тысяч разработчиков. Несколько миллионов сайтов написаны на PHP, что составляет более 20% доменов Internet. Возможности PHP «PHP может все», – заявляют его создатели. В первую очередь PHP используется для создания скриптов, работающих на стороне сервера, для этого его, собственно, и придумали. PHP способен решать те же задачи, что и любые другие CGI-скрипты, в том числе обрабатывать данные html-форм, динамически генерировать html страницы и т.п. Но есть и другие области, где может использоваться PHP. Всего выделяют три основные области применения PHP. - Первая область, как уже говорилось, – это создание приложений (скриптов), которые исполняются на стороне сервера. PHP наиболее широко используется именно для создания такого рода скриптов. Для того чтобы работать таким образом, понадобится PHP-парсер (т.е. обработчик php-скриптов) и web-сервер для обработки скрипта, браузер для просмотра результатов работы скрипта, ну, и, конечно, какой-либо текстовый редактор для написания самого php-кода. Парсер PHP распространяется в виде CGI-программы или серверного модуля. Как установить его и web-сервер на свой компьютер, мы рассмотрим немного позднее. В этом курсе мы будем обсуждать, как правило, создание именно серверных приложений, как пример использования языка PHP. - Вторая область – это создание скриптов, выполняющихся в командной строке. То есть с помощью PHP можно создавать такие скрипты, которые будут исполняться, вне зависимости от web-сервера и браузера, на конкретной машине. Для такой работы потребуется лишь парсер PHP (в этом случае его называют интерпретатором командной строки (cli, command line interpreter)). Этот способ работы подходит, например, для скриптов, которые должны выполняться регулярно с помощью различных планировщиков задач или для решения задач простой обработки текста. - И последняя область – это создание GUI-приложений (графических интерфейсов), выполняющихся на стороне клиента. В принципе это не самый лучший способ использовать PHP, особенно для начинающих, но если вы уже досконально изучили PHP, то такие возможности языка могут оказаться весьма полезны. Для применения PHP в этой области потребуется специальный инструмент – PHP-GTK, который является расширением PHP. Итак, область применения PHP достаточно обширна и разнообразна. Тем не менее существует множество других языков программирования, способных решать похожие задачи. Почему стоит изучать PHP? Что это нам дает? Во-первых, PHP очень прост в изучении. Достаточно ознакомиться лишь с основными правилами синтаксиса и принципами его работы, и можно начинать писать собственные программы, причем браться за такие задачи, решение которых на другом языке требовало бы серьезной подготовки. Во-вторых, PHP поддерживается почти на всех известных платформах, почти во всех операционных системах и на самых разных серверах. Это тоже очень важно. Вряд ли кому-то захочется переходить, например, от работы под Windows к работе под Linux или от сервера IIS к серверу Apache только для того, чтобы изучить еще один язык программирования. В PHP сочетаются две самые популярные парадигмы программирования – объектная и процедурная. В PHP4 более полно поддерживается процедурное программирование, но есть возможность писать программы и в объектном стиле. Уже в первых пробных версиях PHP5 большинство недочетов в реализации объектно-ориентированной модели языка, существующих в PHP4, устранены. Таким образом, можно выбрать наиболее привычный стиль работы. Если говорить о возможностях сегодняшнего PHP, то они выходят далеко за рамки тех, что были реализованы в его первых версиях. С помощью PHP можно создавать изображения, PDF-файлы, флэш-ролики, в него включена поддержка большого числа современных баз данных, встроены функции для работы с текстовыми данными любых форматов, включая XML, и функции для работы с файловой системой. PHP поддерживает взаимодействие с различными сервисами посредством соответствующих протоколов, таких как протокол управления доступом к директориям LDAP, протокол работы с сетевым оборудованием SNMP, протоколы передачи сообщений IMAP, NNTP и POP3, протокол передачи гипертекста HTTP и т.д. Обращая внимание на взаимодействие между различными языками, следует упомянуть о поддержке объектов Java и возможности их использования в качестве объектов PHP. Для доступа к удаленным объектам можно использовать расширение CORBA. Для работы с текстовой информацией PHP унаследовал (с небольшими изменениями) механизмы работы с регулярными выражениями из языка Perl и UNIX-систем. Для обработки XML-документов можно использовать как стандарты DOM и SAX, так и API для XSLT-трансформаций. Для создания приложений электронной коммерции существует ряд полезных функций, таких как функции осуществления платежей Cybercash, CyberMUT, VeriSign Payflow Pro и CCVS. Установка и настройка ПО Возможности языка мы обсудили, области применения рассмотрели, историю изучили. Теперь можно приступать к установке необходимого инструментария. Поскольку в качестве практической основы курса мы будем рассматривать задачи, решаемые с помощью технологии клиент-сервер, и PHP соответственно будет использоваться для создания скриптов, обрабатываемых сервером, нам нужно установить web-сервер и интерпретатор PHP. В качестве web-сервера выберем, например, Apache, как наиболее популярный среди web-разработчиков. Для просмотра результатов работы программ нам понадобится web-браузер, например Internet Explorer. Установка Apache 1.3.29 под Windows XP Чтобы что-нибудь установить, нужно для начала иметь соответствующее программное обеспечение (ПО). Скачать ПО для установки Apache можно, например, с его официального сайта http://www.apache.org. Мы скачали файл apache_1.3.29-win3x86-no_src.exe. Это автоматический установщик (иначе – wizard) сервера Apache под Windows. Эта программа попытается почти самостоятельно (а точнее, с минимальными усилиями с вашей стороны) установить на компьютер какое-либо программное обеспечение, а в данном случае сервер. После запуска файла установщика появляется следующее окошко (рис. 1.1). Рис. 1.1. Автоматическая установка сервера Apache Чтобы установить HTTP-сервер Apache версии 1.3.29 на свой компьютер, нужно нажать на кнопку Next. Кстати говоря, эта же программа позволит изменить или удалить уже установленный web-сервер. После нажатия кнопки Next программа предложит согласиться с условиями лицензии (рис. 1.2). Рис. 1.2. Лицензионное соглашение Следующий экран будет содержать информацию о сервере Apache, и в частности о его Windows-реализации (его изображение не приводим). На следующем шаге нужно ввести имя сетевого домена, имя сервера и e-mail администратора. Программа попытается автоматически определить ваш домен и хост по настройкам компьютера (рис. 1.3). Рис. 1.3. Основная информация о сервере После того как вы ввели данные в вышеприведенную форму, нужно выбрать тип установки: полная (устанавливаются все компоненты сервера) или определяемая пользователем (можно выбрать компоненты для установки) (рис. 1.4.). Рис. 1.4. Тип установки Выбор компонентов сервера не очень большой – это инструменты, необходимые для работы сервера и документация к нему (рис. 1.5). Рис. 1.5. Выбор компонент пользовательской установки Мы выберем полную установку. Тогда на следующем шаге программа предложит выбрать папку, в которую будет установлен сервер. По умолчанию сервер устанавливается в папку c:\Program Files\Apache Group\ (рис. 1.6). Рис. 1.6. Папка, в которую будет установлен сервер На следующем экране (рис. 1.7) потребуется подтвердить правильность введенных данных и начать установку. Из любого окна установки, включая и это, можно вернуться назад и изменить введенные ранее данные. Рис. 1.7. Начало установки После нажатия кнопки Install начнется непосредственная установка сервера. От пользователя никаких дополнительных действий больше не требуется. С одной стороны, это хорошо, но есть у такой автоматизированной установки и некоторые недостатки. Например, домашние директории пользователей оказываются там же, где и файлы настроек сервера (c:\Program Files\Apache Group\Apache\users\). Это небезопасно, если на компьютере работает несколько пользователей, не являющихся администраторами сервера. Но для начала можно ничего не менять. Допустим, мы запустили установщик, ввели все необходимые данные, он выполнил все операции успешно и говорит, что сервер установлен. Как проверить, действительно ли сервер установлен? Набираем в окне браузера http://localhost/. Если все установилось правильно, мы получим страничку приветствия сервера Apache (см. рис. 1.8). Рис. 1.8. Страница приветствия Итак, сервер установлен. Как теперь с ним работать? Откуда можно запускать скрипты и где должны находиться файлы пользователей? Файлы, которые должны быть обработаны сервером, можно сохранять либо в корне сервера (в нашем случае это c:\Program Files\Apache Group\Apache\htdocs ), либо в директориях пользователей (в нашем случае это c:\Program Files\Apache Group\Apache\users\). Местоположение корня сервера и директорий пользователей прописано в настройках сервера, а точнее, в файле конфигурации httpd.conf (найти его можно в c:\Program Files\Apache Group\Apache\conf ). Для изменения этих путей нужно изменить соответствующие переменные в файле конфигурации сервера. Переменная в файле конфигурации ServerRoot отвечает за корневую директорию сервера, а переменная UserDir – за директорию, где будут располагаться файлы пользователей сервера (для более безопасной работы советуем изменить переменную UserDir на что-нибудь типа c:\users\). Чтобы получить доступ к файлу test.html, находящемуся в корне сервера, нужно набрать в браузере http://localhost/test.html (т. е. имя хоста, имя файла). Если же файл test.html находится в директории пользователя user, то для его просмотра нужно набрать в браузере http://localhost/~user/test.html. Установка PHP 4.3.4 под Windows Перейдем к установке PHP. Скачать его дистрибутив можно с официального сайта PHP – http://www.php.net. Для удобства лучше выбрать автоматический инсталлятор, как и в случае с сервером. Самое первое окошко при такой установке PHP содержит приветствие и предупреждение о существовании авторских прав на этот продукт (рис. 1.9). Рис. 1.9. Программа автоматической установки PHP Далее нужно прочитать и согласиться (или не согласиться) с лицензией (рис. 1.10). Рис. 1.10. Лицензионное соглашение Затем выбираем тип установки: стандартный или для специалистов. Мы выберем стандартную установку (рис. 1.11). Рис. 1.11. Тип установки В следующем окне программа предложит выбрать директорию для установки PHP. Туда будут скопированы файлы библиотек, расширений, интерпретатор командной строки, модули и т.п. (рис. 1.12). Рис. 1.12. Выбор папки, в которую будет установлен PHP В следующем окошке предлагается ввести адрес SMTP-сервера, т.е. сервера отправки почтовых сообщений, и обратный адрес отправителя почты. Это окошко мы приводить не будем. Далее предлагается выбрать из списка сервер, с которым будет работать PHP. Поскольку мы установили Apache, то выберем из списка именно его (рис. 1.13). Рис. 1.13. Выбор сервера, на котором будет работать PHP Следующее окошко начинает инсталляцию (рис. 1.14). Рис. 1.14. Начало автоматической установки PHP После копирования необходимых файлов мы получили следующее сообщение (см. рис. 1.15): Рис. 1.15. Сообщение о невозможности автоматически настроить Apache для работы с PHP Это значит, что нужно настраивать сервер Apache для работы с PHP вручную. Сначала следует выбрать, как мы хотим установить PHP, поскольку он поставляется в двух видах: CGI-скрипт (php.exe) или набор SAPI-модулей (например, php4isapi.dll), используемых сервером. Последний вариант обладает новыми возможностями, однако из-за недостаточной проработанности может функционировать недостаточно надежно, особенно на платформах старше Windows 2000 (может появляться куча ошибок 500, могут возникать сбои в других серверных модулях, таких как ASP). Так что, если нужна абсолютная стабильность – надо выбирать установку PHP в виде CGI выполнимого приложения. Если мы хотим установить PHP как серверный модуль, то в файле конфигурации сервера (httpd.conf) нужно написать: LoadModule php4_module c:/php/sapi/php4apache.dll AddType application/x-httpd-php .php .phtml AddModule mod_php4.c Если мы устанавливаем PHP как cgi-программу, то в httpd.conf нужно написать: ScriptAlias /php/ "c:/php/index.html" AddType application/x-httpd-php .php .phtml Action application/x-httpd-php "/php/php.exe" В этом случае могут возникнуть проблемы с безопасностью. Рекомендуется исправить директорию, где лежит исполняемый файл интерпретатора (c:\php\), на что-нибудь менее очевидное (например, на c:\abc_php\). Мы советуем устанавливать PHP как серверный модуль. Еще нужно отредактировать файл php.ini (в папке c:\Windows), чтобы PHP «знал», где находится корневая директория сервера, где пользовательские директории, а где его собственные библиотеки расширений. За это в файле php.ini отвечают соответственно переменные doc_root, user_dir и extension_dir. Зададим их таким образом: doc_root = "c:\Program Files\Apache Group\Apache\htdocs" user_dir = "c:\users" extension_dir = "c:\php\extensions" Кроме того, можно выбрать расширения, которые будут загружаться при запуске PHP. В реализацию PHP под Windows изначально входит очень мало расширений. Чтобы загрузить расширение, нужно раскомментировать в php.ini соответствующую ему строчку 'extension=php_*.dll'. Например, чтобы загрузить расширение для работы с MSSQL, нужно раскомментировать строку 'extension=php_mssql.dll'. Некоторые расширения требуют дополнительных библиотек. Поэтому рекомендуется скопировать дополнительные библиотеки в системную директорию (из папки c:\php\dlls). При первой установке следует настроить и протестировать PHP без расширений. Для того чтобы настройки, выполненные в конфигурационных файлах сервера и PHP вступили в силу, нужно перезапустить сервер. Проверим, работает ли PHP. Для этого создадим тестовый файл (1.php) в директории пользователя (c:\users\nina) со следующим содержанием: <?php echo"<h1>Привет всем!</h1>"; ?> Запустим этот файл через браузер, набрав http://localhost/~nina/1.php. Если что-то не так, то на экран будет выведен текст этого файла. Если все хорошо, то наш скрипт должен обработаться сервером и вывести большими буквами строку «Привет всем!». Установка PHP под Linux Как и в случае с Windows, для Linux существует два способа установки PHP: используя пакеты и непосредственно компилируя исходный код PHP, который можно скачать с http://www.php.net. Мы остановимся на первом варианте и рассмотрим установку PHP+Apache на примере дистрибутива AltMaster2.2 (http://altlinux.ru). Для того чтобы установить apache, необходимо выбрать одноименный пакет. Это можно сделать, например, с помощью менеджера пакетов Synaptic. Воспользовавшись меню «Пакет -> Установить» (рис. 1.16) нужно выбрать интересующие пакеты. После этого необходимо перейти к установке выбранных пакетов с помощью меню «Действия -> Выполнить». После установки Apache имеет смысл перезагрузиться или выполнить следующую команду: /etc/init.d/httpd start # /etc/init.d/httpd start Starting httpd: [ OK ] Это приведет к запуску сервера и можно будет увидеть по адресу http://localhost почти такую же страницу, как и при установке Apache под Windows. Возможным отличием будет присутствие логотипа AltLinux. Рис. 1.16. Выбор пакета для установки Apache под Linux После установки Apache можно приступить к установке непосредственно PHP. Опять же можно использовать PHP с помощью CGI и через модуль Apache. В первом случае достаточно пакета php и необходимых для его установки пакетов. Во втором случае необходимо дополнительно установить пакет mod_php. (рис. 1.17). Рис. 1.17. Выбор пакетов для установки PHP под Linux Менеджер пакетов Synaptic – это оболочка к программе apt-get, более подробно о ней можно узнать с помощью команды man apt-get. Воспользуемся утилитой apt-get для установки пакета mod_php. Команда apt-get build-dep mod_php установит пакеты, которые нам необходимы для инсталляции mod_php. Команда apt-get install mod_php завершит установку: Как видно из приведенного листинга, скрипт установки самостоятельно перезагрузил сервер Apache. Также он самостоятельно внес изменения в конфигурационный файл Apache, полный путь до которого: /etc/httpd/conf/httpd.conf В httpd.conf добавлена строка: Include conf/addon-modules/mod_php4.conf Содержимое файла mod_php4.conf LoadModule php4_module usr/lib/apache/libphp4.so AddModule mod_php4.c AddType application/x-httpd-php .php .php4 .php3 .phtml AddType application/x-httpd-php-source .phps AddHandler application/x-httpd-php .php .php4 .php3 .phtml Как видно, изменения в httpd.conf подобны изменениям, которые мы вносили при установке под Windows. Теперь можно воспользоваться тестовым файлом для проверки работоспособности PHP+Apache. Создадим test.php со следующим содержанием: <?php echo"<h1>Привет всем!</h1>"; ?> Корень сервера находится /var/www/html/. Можно создать каталог test и разместить там файл test.php. Вызвав этот файл из браузера по его адресу (http://localhost/test/test.php), получим страничку с текстом «Привет всем». В заключение отметим, что процесс установки PHP с помощью пакетов в других дистрибутивах Linux принципиально не отличается от рассмотренного нами. Процесс установки из исходного кода в них же подробно описан. Стоит отметить, что «ручная» компоновка позволит настроить PHP под конкретные нужды, также возможен выигрыш в производительности. Дистрибутив Денвер Мы достаточно подробно рассмотрели установку и настройку PHP для платформ Linux и Windows. Для тех, кто не желает вникать в устройство PHP и требуемых для его работы компонентов, существуют готовые дистрибутивы, содержащие все наиболее распространенные расширения PHP. Один из самых известных – дистрибутив Денвер (http://dklab.ru/chicken/web/). Инструкции по его установке можно прочитать на сайте разработчиков. Отметим только, что его установка достаточно проста и не требует особых навыков. Этот дистрибутив можно рекомендовать для начинающих программистов, желающих научиться работать с PHP. Для более серьезных задач лучше все же установить и настроить PHP самостоятельно. Первая PHP-программа По большому счету, первую PHP-программу вы уже создали, когда проверяли, работает ли интерпретатор PHP (в предыдущей главе). Сейчас мы хотим более подробно рассказать о том, что представляет собой PHP-программа и чем она отличается от программ на языках Cи, Perl и JavaScript. Рассмотрим пример. <html> <head> <title>Пример</title> </head> <body> <?php echo "<p>Привет, я – скрипт PHP!</p>"; ?> </body> </html> Это простой HTML-файл, в который встроен с помощью специальных тегов код, написанный на языке PHP. Как мы уже отмечали выше, PHP похож на Cи и Perl. Однако приведенная здесь программа сильно отличается от аналогичных по смыслу программ на языках Cи и Perl. Здесь не нужно писать кучу специальных команд для вывода HTML. Пишется непосредственно HTML-скрипт, в который можно встраивать PHP-код, осуществляющий какие-либо действия (например, выводящий текст на экран, как в нашем примере). Недостатком PHP по сравнению с Cи и Perl, несмотря на все усилия разработчиков, все еще является недостаточная быстрота выполнения сложных скриптов. PHP-скрипты – это программы, которые выполняются и обрабатываются сервером. Так что сравнивать его со скриптовыми языками типа JavaScript невозможно, потому что написанные на них скрипты выполняются на машине клиента. В чем отличие скриптов, выполняемых на сервере и на клиенте? Если скрипт обрабатывается сервером, клиенту посылаются только результаты работы скрипта. Например, если на сервере выполнялся скрипт, подобный приведенному выше, клиент получит сгенерированную HTML-страницу вида: <html> <head> <title>Пример</title> </head> <body> <p>Привет, я – скрипт PHP!</p> </body> </html> В этом случае клиент не знает, какой код выполняется. Можно даже сконфигурировать свой сервер таким образом, чтобы HTML-файлы обрабатывались процессором PHP, так что клиенты даже не смогут узнать, получают ли они обычный HTML-файл или результат выполнения скрипта. Если же скрипт обрабатывается клиентом (например, это программа на языке JavaScript), то клиент получает страницу, содержащую код скрипта. Мы отмечали выше, что PHP-скрипты встраиваются в HTML-код. Возникает вопрос, каким образом? Есть несколько способов. Один из них приведен в самом первом примере – с помощью открывающего тега <?php и закрывающего тега ?>. Такого вида специальные теги позволяют переключаться между режимами HTML и PHP. Этот синтаксис наиболее предпочтителен, поскольку позволяет задействовать PHP в XML-совместимых программах (например, написанных на языке XHTML), но тем не менее можно использовать следующие альтернативные варианты (команда echo "Some text"; выводит на экран текст «Some text».): 1. <? echo "Это простейшая инструкция для обработки PHP"; ?> 2. <script language="php"> echo "Некоторые редакторы (FrontPage) предпочитают делать так"; </script> 3. <% echo "Можно использовать теги в стиле ASP "; %> Первый из этих способов не всегда доступен. Чтобы им пользоваться, нужно включить короткие теги либо с помощью функции short_tags() для PHP 3, либо включив установку short_open_tag в конфигурационном файле PHP, либо скомпилировав PHP с параметром --enable-short-tags. Даже если это включено по умолчанию в php.ini-dist, использование коротких тегов не рекомендуется. Второй способ аналогичен вставке, например, JavaScript-кода и использует для этого соответствующий html тег. Поэтому использовать его можно всегда, но это делается редко из-за его громоздкости. Третий способ можно применить, только если теги в стиле ASP были включены, используя конфигурационную установку asp_tags. Когда PHP обрабатывает файл, он просто передает его текст, пока не встретит один из перечисленных специальных тегов, который сообщает ему о необходимости начать интерпретацию текста как кода PHP. Затем он выполняет весь найденный код до закрывающего тега, говорящего интерпретатору, что далее снова идет просто текст. Этот механизм позволяет внедрять PHP-код в HTML – все за пределами тегов PHP остается неизменным, тогда как внутри интерпретируется как код. Заметим также, что php-файл не похож на CGI-скрипт. Php файл не должен быть исполнимым или еще каким-либо образом помеченным. Для того чтобы отправить php-файл на обработку серверу, нужно в строке браузера набрать путь к этому файлу на сервере. Скрипты php должны располагаться там, где разрешен доступ через www, например там же, где лежит домашняя страничка. Если php-файл лежит на локальной машине, то его можно обработать с помощью интерпретатора командной строки. Заключение Итак, мы получили представление о том, что такое язык PHP, как он появился и развивался, узнали, как и где его можно использовать, установили программное обеспечение и сделали все необходимые настройки для работы с ним, а также выяснили, что представляет собой php-программа. В следующих лекциях мы рассмотрим основы синтаксиса PHP и решим несколько полезных практических задач. Основы синтаксисаМы приступаем к изучению основных элементов синтаксиса языка PHP. Рассмотрим способы разделения инструкций и создания комментариев, переменные, константы, типы данных и операторы. В качестве примера решим задачу создания заготовки электронного письма. Ее смысл заключается в следующем. Допустим, у вас есть какое-то объявление и несколько разных людей, которым нужно это объявление отправить. Для этого вы делаете заготовку с содержанием объявления, внутри которого есть ряд изменяющихся (в зависимости от потенциального получателя) параметров. Основной синтаксис Первое, что нужно знать относительно синтаксиса PHP, – это то, как он встраивается в HTML-код, как интерпретатор узнает, что это код на языке PHP. В предыдущей лекции мы уже говорили об этом. Повторяться не будем, отметим только, что в примерах мы чаще всего будем использовать вариант <?php ?>, и иногда сокращенный вариант <? ?>. Разделение инструкций Программа на PHP (да и на любом другом языке программирования) – это набор команд (инструкций). Обработчику программы (парсеру) необходимо как-то отличать одну команду от другой. Для этого используются специальные символы – разделители. В PHP инструкции разделяются так же, как и в Cи или Perl, – каждое выражение заканчивается точкой с запятой. Закрывающий тег «?>» также подразумевает конец инструкции, поэтому перед ним точку с запятой не ставят. Например, два следующих фрагмента кода эквивалентны: <?php echo "Hello, world!"; // точка с запятой // в конце команды // обязательна ?> <?php echo "Hello, world!" ?> <!-- точка с запятой опускается из-за "?>" --> Комментарии Часто при написании программ возникает необходимость делать какие-либо комментарии к коду, которые никак не влияют на сам код, а только поясняют его. Это важно при создании больших программ и в случае, если несколько человек работают над одной программой. При наличии комментариев в программе в ее коде разобраться гораздо проще. Кроме того, если решать задачу по частям, недоделанные части решения также удобно комментировать, чтобы не забыть о них в дальнейшем. Во всех языках программирования предусмотрена возможность включать комментарии в код программы. PHP поддерживает несколько видов комментариев: в стиле Cи, C++ и оболочки Unix. Символы // и # обозначают начало однострочных комментариев, /* и */ – соответственно начало и конец многострочных комментариев. <?php echo "Меня зовут Вася"; // Это однострочный комментарий // в стиле С++ echo "Фамилия моя Петров"; /* Это многострочный комментарий. Здесь можно написать несколько строк. При исполнении программы все, что находится здесь (закомментировано), будет игнорировано. */ echo "Я изучаю PHP в INTUIT.ru"; # Это комментарий в стиле # оболочки Unix ?> Переменные, константы и операторы Важным элементом каждого языка являются переменные, константы и операторы, применяемые к этим переменным и константам. Рассмотрим, как выделяются и обрабатываются эти элементы в PHP. Переменные Переменная в PHP обозначается знаком доллара, за которым следует ее имя. Например: $my_var Имя переменной чувствительно к регистру, т.е. переменные $my_var и $My_var различны. Имена переменных соответствуют тем же правилам, что и остальные наименования в PHP: правильное имя переменной должно начинаться с буквы или символа подчеркивания с последующими в любом количестве буквами, цифрами или символами подчеркивания. В PHP 3 переменные всегда присваивались по значению. То есть когда вы присваиваете выражение переменной, все значения оригинального выражения копируются в эту переменную. Это означает, к примеру, что после присвоения одной переменной значения другой изменение одной из них не влияет на значение другой. <?php $first = ' Text '; // Присваиваем $first // значение // ' Text ' $second = $first; // Присваиваем $second // значение // переменной $first $first = ' New text '; // Изменяем // значение // $first // на ' New text ' echo "Переменая с именем first " . "равна $first <br>"; // выводим значение $first echo "Переменая с именем second " . "равна $second"; // выводим значение $second ?> Результат работы этого скрипта будет следующим: Переменная с именем first равна New text Переменная с именем second равна Text PHP 4, кроме этого, предлагает еще один способ присвоения значений переменным: присвоение по ссылке. Для того чтобы присвоить значение переменной по ссылке, это значение должно иметь имя, т.е. оно должно быть представлено какой-либо переменной. Чтобы указать, что значение одной переменной присваивается другой переменной по ссылке, нужно перед именем первой переменной поставить знак амперсанд &. Рассмотрим тот же пример, что и выше, только будем присваивать значение переменной first переменной second по ссылке: <?php $first = ' Text '; // Присваиваем $first // значение ' Text ' $second = &$first; /* Делаем ссылку на $first через $second. Теперь значения этих переменных будут всегда совпадать */ // Изменим значение $first // на ' New text ' $first = ' New text '; echo "Переменная с именем first " . "равна $first <br>"; // выведем значения обеих переменных echo "Переменная с именем second " . "равна $second"; ?> Этот скрипт выведет следующее: Переменная с именем first равна New text. Переменная с именем second равна New text. То есть вместе с переменной $first изменилась и переменная $second. Константы Для хранения постоянных величин, т.е. таких величин, значение которых не меняется в ходе выполнения скрипта, используются константы. Такими величинами могут быть математические константы, пароли, пути к файлам и т.п. Основное отличие константы от переменной состоит в том, что ей нельзя присвоить значение больше одного раза и ее значение нельзя аннулировать после ее объявления. Кроме того, у константы нет приставки в виде знака доллара и ее нельзя определить простым присваиванием значения. Как же тогда можно определить константу? Для этого существует специальная функция define(). Ее синтаксис таков: define("Имя_константы", "Значение_константы", [Нечувствительность_к_регистру]) По умолчанию имена констант чувствительны к регистру. Для каждой константы это можно изменить, указав в качестве значения аргумента Нечувствительность_к_регистру значение True. Существует соглашение, по которому имена констант всегда пишутся в верхнем регистре. Получить значение константы можно, указав ее имя. В отличие от переменных, не нужно предварять имя константы символом $. Кроме того, для получения значения константы можно использовать функцию constant() с именем константы в качестве параметра. <?php // определяем константу // PASSWORD define("PASSWORD","qwerty"); // определяем регистронезависимую // константу PI со значением 3.14 define("PI","3.14", True); // выведет значение константы PASSWORD, // т.е. qwerty echo (PASSWORD); // тоже выведет qwerty echo constant("PASSWORD"); echo (password); /* выведет password и предупреждение, поскольку мы ввели регистрозависимую константу PASSWORD */ echo pi; // выведет 3.14, поскольку константа PI // регистронезависима по определению ?> Кроме переменных, объявляемых пользователем, о которых мы только что рассказали, в PHP существует ряд констант, определяемых самим интерпретатором. Например, константа __FILE__ хранит имя файла программы (и путь к нему), которая выполняется в данный момент, __FUNCTION__ содержит имя функции, __CLASS__ – имя класса, PHP_VERSION – версия интерпретатора PHP. Полный список предопределенных констант можно получить, прочитав руководство по PHP. Операторы Операторы позволяют выполнять различные действия с переменными, константами и выражениями. Мы еще не упоминали о том, что такое выражение. Выражение можно определить как все, что угодно, что имеет значение. Переменные и константы – это основные и наиболее простые формы выражений. Существует множество операций (и соответствующих им операторов), которые можно производить с выражениями. Рассмотрим некоторые из них подробнее. + Сложение $a + $b - Вычитание $a - $b * Умножение $a * $b / Деление $a / $b % Остаток от деления $a % $b . Конкатенация (сложение строк) $c = $a . $b (это строка, состоящая из $a и $b) = Присваивание Переменной слева от оператора будет присвоено значение, полученное в результате выполнения каких-либо операций или переменной/константы с правой стороны $a = ($b = 4) +5; ($a будет равна 9, $b будет равна 4) += Сокращение. Прибавляет к переменной число и затем присваивает ей полученное значение $a += 5; (эквивалентно $a = $a + 5;) .= Сокращенно обозначает комбинацию операций конкатенации и присваивания (сначала добавляется строка, потом полученная строка записывается в переменную) $b = "Привет "; $b .= "всем"; В результате: $b="Привет всем" and И $a и $b истинны (True) $a and $b && И $a && $b or Или Хотя бы одна из переменных $a или $b истинна (возможно, что и обе) $a or $b || Или $a || $b xor Исключающее или Одна из переменных истинна. Случай, когда они обе истинны, исключается $a xor $b ! Инверсия (NOT) Если $a=True, то !$a=False и наоборот ! $a == Равенство Значения переменных равны $a == $b === Эквивалентность Равны значения и типы переменных $a === $b != Неравенство Значения переменных не равны $a != $b <> Неравенство $a <> $b !== Неэквивалентность Переменные не эквивалентны $a !== $b < Меньше $a < $b > Больше $a > $b <= Меньше или равно $a <= $b >= Больше или равно $a >= $b ++$a Пре-инкремент Увеличивает $a на единицу и возвращает $a <? $a=4; echo "Должно быть 4:" .$a++; echo "Должно быть 6:" .++$a; ?> $a++ Пост-инкремент Возвращает $a, затем увеличивает $a на единицу --$a Пре-декремент Уменьшает $a на единицу и возвращает $a $a-- Пост-декремент Возвращает $a, затем уменьшает $a на единицу Типы данных PHP поддерживает восемь простых типов данных. Четыре скалярных типа: - boolean (логический); - integer (целый); - float (с плавающей точкой); - string (строковый). Два смешанных типа: - array (массив); - object (объект). И два специальных типа: - resource (ресурс); - NULL. В PHP не принято явное объявление типов переменных. Предпочтительнее, чтобы это делал сам интерпретатор во время выполнения программы в зависимости от контекста, в котором используется переменная. Рассмотрим по порядку все перечисленные типы данных. Тип boolean (булев или логический тип) Этот простейший тип выражает истинность значения, то есть переменная этого типа может иметь только два значения – истина TRUE или ложь FALSE. Чтобы определить булев тип, используют ключевое слово TRUE или FALSE. Оба регистронезависимы. <?php $test = True; ?> Логические переменные используются в различных управляющих конструкциях (циклах, условиях и т.п., более подробно речь о них пойдет в одной из следующих лекций). Иметь логический тип, т.е. принимать только два значения, истину или ложь, могут также и некоторые операторы (например, оператор равенства). Они также используются в управляющих конструкциях для проверки каких-либо условий. Например, в условной конструкции проверяется истинность значения оператора или переменной и в зависимости от результата проверки выполняются те или иные действия. Здесь условие может быть истинно или ложно, что как раз и отражает переменная и оператор логического типа. <?php // Оператор '==' проверяет равенство // и возвращает // булево значение if ($know == False) { // если $know // имеет значение // false echo "Изучай PHP!"; } if (!$know) { // то же самое, что // и выше, т.е. проверка // имеет ли $know значение // false echo "Изучай PHP!"; } /* оператор == проверяет, совпадает ли значение переменной $action со строкой "Изучить PHP". Если совпадает, то возвращает true, иначе – false. Если возвращено true, то выполняется то, что внутри фигурных скобок */ if ($action == "Изучить PHP") { echo "Начал изучать"; } ?> Тип integer (целые) Этот тип задает число из множества целых чисел Z = {..., -2, -1, 0, 1, 2, ...}. Целые могут быть указаны в десятичной, шестнадцатеричной или восьмеричной системе счисления, по желанию с предшествующим знаком «-» или «+». Если вы используете восьмеричную систему счисления, вы должны предварить число 0 (нулем), для использования шестнадцатеричной системы нужно поставить перед числом 0x. <?php # десятичное число $a = 1234; # отрицательное число $a = -123; # восьмеричное число (эквивалентно # 83 в десятичной системе) $a = 0123; # шестнадцатеричное число (эквивалентно # 26 в десятичной системе) $a = 0x1A; ?> Размер целого зависит от платформы, хотя, как правило, максимальное значение около двух миллиардов (это 32-битное знаковое). Беззнаковые целые PHP не поддерживает. Если вы определите число, превышающее пределы целого типа, оно будет интерпретировано как число с плавающей точкой. Также если вы используете оператор, результатом работы которого будет число, превышающее пределы целого, вместо него будет возвращено число с плавающей точкой. В PHP не существует оператора деления целых. Результатом 1/2 будет число с плавающей точкой 0.5. Вы можете привести значение к целому, что всегда округляет его в меньшую сторону, либо использовать функцию round(), округляющую значение по стандартным правилам. Для преобразования переменной к конкретному типу нужно перед переменной указать в скобках нужный тип. Например, для преобразования переменной $a=0.5 к целому типу необходимо написать (integer)(0.5) или (integer) $a или использовать сокращенную запись (int)(0.5). Возможность явного приведения типов по такому принципу существует для всех типов данных (конечно, не всегда значение одного типа можно перевести в другой тип). Мы не будем углубляться во все тонкости приведения типов, поскольку PHP делает это автоматически в зависимости от контекста. Тип float (числа с плавающей точкой) Числа с плавающей точкой (они же числа двойной точности или действительные числа) могут быть определены при помощи любого из следующих синтаксисов: <?php $a = 1.234; $b = 1.2e3; $c = 7E-10; ?> Размер числа с плавающей точкой зависит от платформы, хотя максимум, как правило, ~1.8e308 с точностью около 14 десятичных цифр. Тип string (строки) Строка – это набор символов. В PHP символ – это то же самое, что байт, это значит, что существует ровно 256 различных символов. Это также означает, что PHP не имеет встроенной поддержки Unicode. В PHP практически не существует ограничений на размер строк, поэтому нет абсолютно никаких причин беспокоиться об их длине. Строка в PHP может быть определена тремя различными способами: - с помощью одинарных кавычек; - с помощью двойных кавычек; - heredoc-синтаксисом. Одинарные кавычки Простейший способ определить строку – это заключить ее в одинарные кавычки «'». Чтобы использовать одинарную кавычку внутри строки, как и во многих других языках, перед ней необходимо поставить символ обратной косой черты «\», т. е. экранировать ее. Если обратная косая черта должна идти перед одинарной кавычкой либо быть в конце строки, необходимо продублировать ее «\\'». Если внутри строки, заключенной в одинарные кавычки, обратный слэш «\» встречается перед любым другим символом (отличным от «\» и «'» ), то он рассматривается как обычный символ и выводится, как и все остальные. Поэтому обратную косую черту необходимо экранировать, только если она находится в конце строки, перед закрывающей кавычкой. В PHP существует ряд комбинаций символов, начинающихся с символа обратной косой черты. Их называют управляющими последовательностями, и они имеют специальные значения, о которых мы расскажем немного позднее. Так вот, в отличие от двух других синтаксисов, переменные и управляющие последовательности для специальных символов, встречающиеся в строках, заключенных в одинарные кавычки, не обрабатываются. <?php echo 'Также вы можете вставлять в строки символ новой строки таким образом, поскольку это нормально'; // Выведет: Чтобы вывести ' надо // перед ней поставить \ echo 'Чтобы вывести \' надо перед ' 'ней поставить \\'; // Выведет: Вы хотите удалить C:\*.*? echo 'Вы хотите удалить C:\\*.*?'; // Выведет: Это не вставит: \n // новую строку echo 'Это не вставит: \n новую строку'; // Выведет: Переменные $expand также // $either не подставляются echo 'Переменные $expand также $either' . 'не подставляются'; ?> Двойные кавычки Если строка заключена в двойные кавычки «"», PHP распознает большее количество управляющих последовательностей для специальных символов. Некоторые из них приведены в таблице 2.7. \n Новая строка (LF или 0x0A (10) в ASCII) \r Возврат каретки (CR или 0x0D (13) в ASCII) \t Горизонтальная табуляция (HT или 0x09 (9) в ASCII) \\ Обратная косая черта \$ Знак доллара \" Двойная кавычка Повторяем, если вы захотите экранировать любой другой символ, обратная косая черта также будет напечатана! Самым важным свойством строк в двойных кавычках является обработка переменных. Heredoc Другой способ определения строк – это использование heredoc-синтаксиса. В этом случае строка должна начинаться с символа <<<, после которого идет идентификатор. Заканчивается строка этим же идентификатором. Закрывающий идентификатор должен начинаться в первом столбце строки. Кроме того, идентификатор должен соответствовать тем же правилам именования, что и все остальные метки в PHP: содержать только буквенно-цифровые символы и знак подчеркивания и начинаться не с цифры или знака подчеркивания. Heredoc-текст ведет себя так же, как и строка в двойных кавычках, при этом их не имея. Это означает, что вам нет необходимости экранировать кавычки в heredoc, но вы по-прежнему можете использовать перечисленные выше управляющие последовательности. Переменные внутри heredoc тоже обрабатываются. <?php $str = <<<EOD Пример строки,охватывающей несколько строчек, с использованием heredoc-синтаксиса EOD; // Здесь идентификатор – EOD. Ниже // идентификатор EOT $name = 'Вася'; echo <<<EOT Меня зовут "$name". EOT; // это выведет "Меня зовут "Вася"." ?> Замечание: Поддержка heredoc была добавлена в PHP 4. Тип array (массив) Массив в PHP представляет собой упорядоченную карту – тип, который преобразует значения в ключи. Этот тип оптимизирован в нескольких направлениях, поэтому вы можете использовать его как собственно массив, список (вектор), хеш-таблицу (являющуюся реализацией карты), стэк, очередь и т.д. Поскольку вы можете иметь в качестве значения другой массив PHP, можно также легко эмулировать деревья. Определить массив можно с помощью конструкции array() или непосредственно задавая значения его элементам. Определение при помощи array() array ([key] => value, [key1] => value1, ... ) Языковая конструкция array() принимает в качестве параметров пары ключ => значение, разделенные запятыми. Символ => устанавливает соответствие между значением и его ключом. Ключ может быть как целым числом, так и строкой, а значение может быть любого имеющегося в PHP типа. Числовой ключ массива часто называют индексом. Индексирование массива в PHP начинается с нуля. Значение элемента массива можно получить, указав после имени массива в квадратных скобках ключ искомого элемента. Если ключ массива представляет собой стандартную запись целого числа, то он рассматривается как число, в противном случае – как строка. Поэтому запись $a["1"] равносильна записи $a[1], так же как и $a["-1"] равносильно $a[-1]. <?php $books = array ("php" => "PHP users guide", 12 => true); echo $books["php"]; //выведет "PHP users guide" echo $books[12]; //выведет 1 ?> Если для элемента ключ не задан, то в качестве ключа берется максимальный числовой ключ, увеличенный на единицу. Если указать ключ, которому уже было присвоено какое-то значение, то оно будет перезаписано. Начиная с PHP 4.3.0, если максимальный ключ – отрицательное число, то следующим ключом массива будет ноль (0). <?php // массивы $arr и $arr1 эквиваленты $arr = array(5 => 43, 32, 56, "b" => 12); $arr1 = array(5 => 43, 6 => 32, 7 => 56, "b" => 12); ?> Если использовать в качестве ключа TRUE или FALSE, то его значение переводится соответственно в единицу и ноль типа integer. Если использовать NULL, то вместо ключа получим пустую строку. Можно использовать и саму пустую строку в качестве ключа, при этом ее надо брать в кавычки. Так что это не то же самое, что использование пустых квадратных скобок. Нельзя использовать в качестве ключа массивы и объекты. Определение с помощью синтаксиса квадратных скобок Создать массив можно, просто записывая в него значения. Как мы уже говорили, значение элемента массива можно получить с помощью квадратных скобок, внутри которых нужно указать его ключ например, $book["php"]. Если указать новый ключ и новое значение например, $book["new_key"]="new_value", то в массив добавится новый элемент. Если мы не укажем ключ, а только присвоим значение $book[]="new_value", то новый элемент массива будет иметь числовой ключ, на единицу больший максимального существующего. Если массив, в который мы добавляем значения, еще не существует, то он будет создан. <? $books["key"]= value; // добавили в массив // $books значение // value с ключом key $books[] = value1; /* добавили в массив значение value1 с ключом 13, поскольку максимальный ключ у нас был 12 */ ?> Для того чтобы изменить конкретный элемент массива, нужно просто присвоить ему с его ключом новое значение. Изменить ключ элемента нельзя, можно только удалить элемент (пару ключ/значение) и добавить новую. Чтобы удалить элемент массива, нужно использовать функцию unset(). <?php $books = array ("php" => "PHP users guide", 12 => true); $books[] = "Book about Perl"; // добавили элемент // с ключом (индексом) // 13 это эквивалентно // $books[13] = // "Book about Perl"; $books["lisp"] = 123456; /* Это добавляет к массиву новый элемент с ключом "lisp" и значением 123456 */ unset($books[12]); // Это удаляет элемент // c ключом 12 из массива unset ($books); // удаляет массив полностью ?> Заметим, что, когда используются пустые квадратные скобки, максимальный числовой ключ ищется среди ключей, существующих в массиве с момента последнего переиндексирования. Переиндексировать массив можно с помощью функции array_values(). <?php $arr = array ("a","b","c"); /* Создаем массив со значениями "a", "b" и "c". Поскольку ключи не указаны, они будут 0,1,2 соответственно */ print_r($arr); // выводим массив (и ключи, // и значения) unset($arr[0]); unset($arr[1]); unset($arr[2]); // удаляем из него все значения print_r($arr); // выводим массив (и ключи, // и значения) $arr[] = "aa"; // добавляем новый элемент // в массив. // Его индексом (ключом) // будет 3, а не 0 print_r($arr); $arr = array_values($arr); // переиндексируем // массив $arr[] = "bb"; // ключом этого элемента // будет 1 print_r($arr); ?> Результатом работы этого скрипта будет: Array ( [0] => a [1] => b [2] => c ) Array ( ) Array ( [3] => aa ) Array ( [0] => aa [1] => bb ) Тип object (объекты) Объекты – тип данных, пришедший из объектно-ориентированного программирования (ООП). Согласно принципам ООП, класс – это набор объектов, обладающих определенными свойствами и методами работы с ним, а объект соответственно – экземпляр класса. Например, программисты – это класс людей, которые пишут программы, изучают компьютерную литературу и, кроме того, как все люди, имеют имя и фамилию. Теперь, если взять одного конкретного программиста, Васю Иванова, то можно сказать, что он является объектом класса программистов, обладает теми же свойствами, что и другие программисты, тоже имеет имя, пишет программы и т.п. В PHP для доступа к методам объекта используется оператор ->. Для инициализации объекта используется выражение new, создающее в переменной экземпляр объекта. <?php //создаем класс людей class Person { // метод, который обучает человека PHP function know_php() { echo "Теперь я знаю PHP"; } } $bob = new Person; // создаем объект // класса человек $bob -> know_php(); // обучаем его PHP ?> Более подробно реализацию принципов ООП в языке PHP мы рассмотрим в одной из следующих лекций. Тип resource (ресурсы) Ресурс – это специальная переменная, содержащая ссылку на внешний ресурс (например, соединение с базой данных). Ресурсы создаются и используются специальными функциями (например, mysql_connect(), pdf_new() и т.п.). Тип Null Специальное значение NULL говорит о том, что переменная не имеет значения. Переменная считается NULL, если: - ей была присвоена константа NULL ($var = NULL); - ей еще не было присвоено какое-либо значение; - она была удалена с помощью unset(). Существует только одно значение типа NULL – регистронезависимое ключевое слово NULL. Решение задачи Теперь вернемся к задаче, которую мы поставили в самом начале лекции. Напомним, что она состояла в составлении письма разным людям по поводу разных событий. Попытаемся использовать для решения этой задачи изученные средства – переменные, операторы, константы, строки и массивы. В зависимости от получателя изменяется событие и обращение, указанные в письме, поэтому естественно вынести эти величины в переменные. Более того, поскольку событий и людей много, удобно использовать переменные типа массив. Подпись в письме остается постоянной всегда, поэтому логично задать ее как константу. Чтобы не писать слишком длинные и громоздкие строки, используем оператор конкатенации. Итак, вот что получилось: <? // пусть наша подпись // будет константой define("SIGN","С уважением, Вася"); // зададим массивы людей и событий $names = array("Иван Иванович", "Петр Петрович", "Семен Семенович"); $events = array( "f" => "день открытых дверей", "o" => "открытие выставки", "p" => "бал выпускников"); // составим текст приглашения $str = "Уважаемый (ая), $names[0]"; $str .= "<br>Приглашаем Вас на ". $events["f"]; $str .= "<br>" . SIGN; echo $str; // выведем текст на экран ?> Заключение Итак, в этой лекции мы познакомились с основами синтаксиса языка PHP, научились работать с переменными различных типов, константами, операторами, познакомились со всеми существующими в PHP типами данных. Говоря о таких типах данных, как строки и массивы, мы разобрали только самые основы. Эти конструкции настолько удобны и просты в использовании, что заслуживают отдельного разговора. Подробности будут изложены в последующих лекциях. Решая задачу, мы старались использовать только имеющиеся знания и не забегать вперед, поэтому решение не слишком подходит для практического применения. В следующей лекции мы исправим этот недостаток и создадим более универсальный шаблон электронного письма. Управляющие конструкцииУсловные операторы Оператор if Это один из самых важных операторов многих языков, включая PHP. Он позволяет выполнять фрагменты кода в зависимости от условия. Структуру оператора if можно представить следующим образом: if (выражение) блок_выполнения Здесь выражение есть любое правильное PHP-выражение (т.е. все, что имеет значение). В процессе обработки скрипта выражение преобразуется к логическому типу. Если в результате преобразования значение выражения истинно (True), то выполняется блок_выполнения. В противном случае блок_выполнения игнорируется. Если блок_выполнения содержит несколько команд, то он должен быть заключен в фигурные скобки { }. Правила преобразования выражения к логическому типу: - логическое False - целый ноль (0) - действительный ноль (0.0) - пустая строка и строка "0" - массив без элементов - объект без переменных (подробно об объектах будет рассказано в одной из следующих лекций) - специальный тип NULL - Все остальные значения преобразуются в TRUE. <? $names = array("Иван","Петр","Семен"); if ($names[0]=="Иван") { echo "Привет, Ваня!"; $num = 1; $account = 2000; } if ($num) echo "Иван первый в списке!"; $bax = 30; if ($account > 100*$bax+3) echo "Эта строчка не появится на экране, так как условие не выполнено"; ?> Оператор else Мы рассмотрели только одну, основную часть оператора if. Существует несколько расширений этого оператора. Оператор else расширяет if на случай, если проверяемое в if выражение является неверным, и позволяет выполнить какие-либо действия при таких условиях. Структуру оператора if, расширенного с помощью оператора else, можно представить следующим образом: if (выражение) блок_выполнения else блок_выполнения1 Эту конструкцию if...else можно интерпретировать примерно так: если выполнено условие (т.е. выражение=true), то выполняем действия из блока_выполнения, иначе – действия из блока_выполнения1. Использовать оператор else не обязательно. Посмотрим, как можно изменить предыдущий пример, учитывая необходимость совершения действий и в случае невыполнения условия. <? $names = array("Иван","Петр","Семен"); if ($names[0]=="Иван") { echo "Привет, Ваня!"; $num = 1; $account = 2000; } else { echo "Привет, $names[0]. А мы ждали Ваню :("; } if ($num) echo "Иван первый в списке!"; else echo "Иван НЕ первый в списке?!"; $bax = 30; if ($account > 100*$bax+3) echo "Эта строка не появится на экране, так как условие не выполнено"; else echo "Зато появится эта строка!"; ?> Оператор elseif Еще один способ расширения условного оператора if – использование оператора elseif. elseif – это комбинация else и if. Как и else, он расширяет if для выполнения различных действий в том случае, если условие, проверяемое в if, неверно. Но в отличие от else, альтернативные действия будут выполнены, только если elseif-условие является верным. Структуру оператора if, расширенного с помощью операторов else и elseif, можно представить следующим образом: if (выражение) блок_выполнения elseif(выражение1) блок_выполнения1 ... else блок_выполненияN Операторов elseif может быть сразу несколько в одном if-блоке. Elseif-утверждение будет выполнено, только если предшествующее if-условие является False, все предшествующие elseif-условия являются False, а данное elseif-условие – True. <? $names = array("Иван","Петр","Семен"); if ($names[0]=="Иван") { // если первое имя в массиве Иван echo "Привет, Ваня!"; }elseif ($names[0] == "Петр"){ // если первое имя // не Иван, а Петр echo "Привет, Петя!"; }elseif ($names[0] == "Семен"){ // если первое имя не // Иван, не Петр, а Семен echo "Привет, Сеня!"; }else { // если первое имя не Иван, // не Петр и не Семен echo "Привет, $names[0]. А ты кто такой?"; } ?> Альтернативный синтаксис PHP предлагает альтернативный синтаксис для некоторых своих управляющих структур, а именно для if, while, for, foreach и switch. В каждом случае открывающую скобку нужно заменить на двоеточие (:), а закрывающую – на endif;, endwhile; и т.д. соответственно. Например, синтаксис оператора if можно записать таким образом: if(выражение): блок_выполнения endif; Смысл остается тем же: если условие, записанное в круглых скобках оператора if, оказалось истиной, будет выполняться весь код, от двоеточия «:» до команды endif;. Использование такого синтаксиса полезно при встраивании php в html-код. <?php $names = array("Иван","Петр","Семен"); if ($names[0]=="Иван"): ?> Привет, Ваня! <?php endif ?> Если используются конструкции else и elseif, то также можно задействовать альтернативный синтаксис: <?php if ($a == 5): print "a равно 5"; print "..."; elseif ($a == 6): print "a равно 6"; print "!!!"; else: print "a не равно ни 5, ни 6"; endif; ?> Оператор switch Еще одна конструкция, позволяющая проверять условие и выполнять в зависимости от этого различные действия, – это switch. На русский язык название данного оператора можно перевести как «переключатель». И смысл у него именно такой. В зависимости от того, какое значение имеет переменная, он переключается между различными блоками действия. switch очень похож на оператор if...elseif...else или набор операторов if. Структуру switch можно записать следующим образом: switch (выражение или переменная){ case значение1: блок_действий1 break; case значение2: блок_действий2 break; ... default: блок_действий_по_умолчанию } В отличие от if, здесь значение выражения не приводится к логическому типу, а просто сравнивается со значениями, перечисленными после ключевых слов case (значение1, значение 2 и т.д.). Если значение выражения совпало с каким-то вариантом, то выполняется соответствующий блок_действий – от двоеточия после совпавшего значения до конца switch или до первого оператора break, если таковой найдется. Если значение выражения не совпало ни с одним из вариантов, то выполняются действия по умолчанию (блок_действий_по_умолчанию), находящиеся после ключевого слова default. Выражение в switch вычисляется только один раз, а в операторе elseif – каждый раз, поэтому, если выражение достаточно сложное, то switch работает быстрее. Пример 3.3 можно переписать с использованием switch следующим образом: <? $names = array("Иван","Петр","Семен"); switch ($names[0]){ case "Иван": echo "Привет, Ваня!"; break; case "Петр": echo "Привет, Петя!"; break; case "Семен": echo "Привет, Сеня!"; break; default: echo "Привет, $names[0]. А как Вас зовут?"; } ?> Если в этом примере опустить оператор break, например, в case "Петр":, то, если переменная окажется равной строке "Петр", после вывода на экран сообщения "Привет, Петя!" программа пойдет дальше и выведет также сообщение "Привет, Сеня!" и только потом, встретив break, продолжит свое выполнение за пределами switch. Для конструкции switch, как и для if, возможен альтернативный синтаксис, где открывающая switch фигурная скобка заменяется двоеточием, а закрывающая – endswitch; соответственно. Циклы В PHP существует несколько конструкций, позволяющих выполнять повторяющиеся действия в зависимости от условия. Это циклы while, do..while, foreach и for. Рассмотрим их более подробно. while Структура: while (выражение) { блок_выполнения } либо while (выражение): блок_выполнения endwhile; while – простой цикл. Он предписывает PHP выполнять команды блока_выполнения до тех пор, пока выражение вычисляется как True (здесь, как и в if, происходит приведение выражения к логическому типу). Значение выражения проверяется каждый раз в начале цикла, так что, даже если его значение изменилось в процессе выполнения блока_выполнения, цикл не будет остановлен до конца итерации (т.е. пока все команды блока_выполнения не будут исполнены). <? //эта программа напечатает все четные цифры $i = 1; while ($i < 10) { if ($i % 2 == 0) print $i; // печатаем цифру, если она четная $i++; // и увеличиваем $i на единицу } ?> do... while Циклы do..while очень похожи на циклы while, с той лишь разницей, что истинность выражения проверяется в конце цикла, а не в начале. Благодаря этому блок_выполнения цикл do...while гарантированно выполняется хотя бы один раз. Структура: do {блок_выполнения} while (выражение); <? // эта программа напечатает число 12, несмотря на то // что условие цикла не выполнено $i = 12; do{ if ($i % 2 == 0) print $i; // если число четное, то печатаем его $i++; // увеличиваем число на единицу }while ($i<10) ?> for Это самые сложные циклы в PHP. Они напоминают соответствующие циклы C. Структура: for (выражение1; выражение2; выражение3) {блок_выполнения} либо for (выражение1; выражение2; выражение3): блок_выполнения endfor; Здесь, как мы видим, условие состоит сразу из трех выражений. Первое выражение выражение1 вычисляется безусловно один раз в начале цикла. В начале каждой итерации вычисляется выражение2. Если оно является True, то цикл продолжается и выполняются все команды блока_выполнения. Если выражение2 вычисляется как False, то исполнение цикла останавливается. В конце каждой итерации (т.е. после выполнения всех команд блока_выполнения) вычисляется выражение3. Каждое из выражений 1, 2, 3 может быть пустым. Если выражение2 является пустым, то это значит, что цикл должен выполняться неопределенное время (в этом случае PHP считает это выражение всегда истинным). Это не так бесполезно, как кажется, ведь цикл можно останавливать, используя оператор break. Например, все четные цифры можно вывести с использованием цикла for таким образом: <?php for ($i=0; $i<10; $i++){ if ($i % 2 == 0) print $i; // печатаем четные числа } ?> Если опустить второе выражение (условие $i<10), то такую же задачу можно решить, останавливая цикл оператором break. <?php for ($i=0; ; $i++){ if ($i>=10) break; // если $i больше или равно 10, // то прекращаем работу цикла if ($i % 2 == 0) print $i; // если число четное, // то печатаем его } ?> Можно опустить все три выражения. В этом случае просто не будет задано начальное значение счетчика $i и оно не будет изменяться каждый раз в конце цикла. Все эти действия можно записать в виде отдельных команд либо в блоке_выполнения, либо перед циклом: <?php $i=0; // задаем начальное значение счетчика for ( ; ; ){ if ($i>=10) break; // если $i больше или равно 10, // то прекращаем работу цикла if ($i % 2 == 0) print $i; // если число четное, // то печатаем его $i++; // увеличиваем счетчик на единицу } ?> В третье выражение конструкции for можно записывать через запятую сразу несколько простейших команд. Например, если мы хотим просто вывести все цифры, то программу можно записать совсем просто: <?php for ($i=0; $i<10; print $i, $i++) /* Если блок_выполнения не содержит команд или содержит только одну команду, фигурные скобки, в которые он заключен, можно опускать*/ ?> foreach Еще одна полезная конструкция. Она появилась только в PHP4 и предназначена исключительно для работы с массивами. Синтаксис: foreach ($array as $value) {блок_выполнения} либо foreach ($array as $key => $value) {блок_выполнения} В первом случае формируется цикл по всем элементам массива, заданного переменной $array. На каждом шаге цикла значение текущего элемента массива записывается в переменную $value, и внутренний счетчик массива передвигается на единицу (так что на следующем шаге будет виден следующий элемент массива). Внутри блока_выполнения значение текущего элемента массива может быть получено с помощью переменной $value. Выполнение блока_выполнения происходит столько раз, сколько элементов в массиве $array. Вторая форма записи в дополнение к перечисленному выше на каждом шаге цикла записывает ключ текущего элемента массива в переменную $key, которую тоже можно использовать в блоке_выполнения. Когда foreach начинает исполнение, внутренний указатель массива автоматически устанавливается на первый элемент. <?php $names = array("Иван","Петр","Семен"); foreach ($names as $val) { echo "Привет, $val <br>"; // выведет всем приветствие } foreach ($names as $k => $val) { // кроме приветствия, // выведем номера в списке, т.е. ключи echo "Привет, $val ! Ты в списке под номером $k <br>"; } ?> Операторы передачи управления Иногда в случае особых обстоятельств требуется немедленно завершить работу цикла и передать управление первой инструкции программы, расположенной за последней инструкцией цикла. Для этого используют операторы break и continue. Break Оператор break заканчивает выполнение текущего цикла, будь то for, foreach, while, do..while или switch. break может использоваться с числовым аргументом, который говорит, работу скольких управляющих структур, содержащих его, нужно завершить. <?php $i=1; while ($i) { $n = rand(1,10); // генерируем произвольное число // от 1 до 10 echo "$i:$n "; // выводим номер итерации и // сгенерированное число if ($n==5) break; /* Если было сгенерировано число 5, то прекращаем работу цикла. В этом случае все, что находится после этой строчки внутри цикла, не будет выполнено */ echo "Цикл работает <br>"; $i++; } echo "<br>Число итераций цикла $i "; ?> Результатом работы этого скрипта будет примерно следующее: 1:7 Цикл работает 2:2 Цикл работает 3:5 Число итераций цикла 3 Если после оператора break указать число, то прервется именно такое количество содержащих этот оператор циклов. В приведенном выше примере это неактуально, поскольку вложенных циклов нет. Немного изменим наш скрипт: <?php $i=1; while ($i) { $n = rand(1,10); // генерируем произвольное число // от 1 до 10 switch ($n){ case 5: echo "<font color=blue> Выход из switch (n=$n)</font>"; break 1; // прекращаем работу switch // (первого содержащего break цикла) case 10: echo "<font color=red> Выход из switch и while (n=$n)</font>"; break 2; // прекращаем работу switch и while // (двух содержащих break циклов) default: echo "switch работает (n=$n), "; } echo " while работает – шаг $i <br>"; $i++; } echo "<br>Число итераций цикла $i "; ?> continue Иногда нужно не полностью прекратить работу цикла, а только начать его новую итерацию. Оператор continue позволяет пропустить дальнейшие инструкции из блока_выполнения любого цикла и продолжить выполнение с нового круга. continue можно использовать с числовым аргументом, который указывает, сколько содержащих его управляющих конструкций должны завершить работу. Заменим в примере предыдущего параграфа оператор break на continue. Кроме того, ограничим число шагов цикла четырьмя. <?php $i=1; while ($i<4) { $n = rand(1,10); // генерируем произвольное число // от 1 до 10 echo "$i:$n "; // выводим номер итерации и // сгенерированное число if ($n==5) { echo "Новая итерация "; continue; /* Если было сгенерировано число 5, то начинаем новую итерацию цикла, $i не увеличивается */ } echo "Цикл работает <br>"; $i++; } echo "<br>Число итераций цикла $i "; ?> Результатом работы этого скрипта будет 1:10 Цикл работает 2:5 Новая итерация 2:1 Цикл работает 3:1 Цикл работает Число итераций цикла 4 Заметим, что после выполнения оператора continue работа цикла не заканчивается. В примере счетчик цикла не меняется в случае получения числа 5, поскольку он находится после оператора continue. Фактически с помощью continue мы пытаемся избежать ситуации, когда будет сгенерировано число 5. Поэтому можно было просто написать, заменив оператор continue на проверку истинности выражения: <?php $i=1; while ($i<4) { $n = rand(1,10); // генерируем произвольное число // от 1 до 10 if ($n!==5) { echo "$i:$n <br>"; // выводим номер итерации // и сгенерированное число $i++; } } ?> В PHP существует одна особенность использования оператора continue – в конструкциях switch он работает так же, как и break. Если switch находится внутри цикла и нужно начать новую итерацию цикла, следует использовать continue 2. Операторы включения include Оператор include позволяет включать код, содержащийся в указанном файле, и выполнять его столько раз, сколько программа встречает этот оператор. Включение может производиться любым из перечисленных способов: include 'имя_файла'; include $file_name; include ("имя_файла"); Пример 3.9. Пусть в файле params.inc у нас хранится набор каких-то параметров и функций. Каждый раз, когда нам нужно будет использовать эти параметры (функции), мы будем вставлять в текст нашей основной программы команду include 'params.inc'. params.inc <?php $user = "Вася"; $today = date("d.m.y"); /* функция date() возвращает дату и время (здесь – дату в формате день.месяц.год) */ ?> include.php <?php include ("params.inc"); /* переменные $user и $today заданы в файле params.inc. Здесь мы тоже можем ими пользоваться благодаря команде include("params.inc") */ echo "Привет, $user!<br>"; // выведет "Привет, Вася!" echo "Сегодня $today"; // выведет, например, "Сегодня 7.07.05" ?> Заметим, что использование оператора include эквивалентно простой вставке содержательной части файла params.inc в код программы include.php. Может быть, тогда можно было в params.inc записать простой текст без всяких тегов, указывающих на то, что это php-код? Нельзя! Дело в том, что в момент вставки файла происходит переключение из режима обработки PHP в режим HTML. Поэтому код внутри включаемого файла, который нужно обработать как PHP-скрипт, должен быть заключен в соответствующие теги. Поиск файла для вставки происходит по следующим правилам. - Сначала ведется поиск файла в include_path относительно текущей рабочей директории. - Если файл не найден, то поиск производится в include_path относительно директории текущего скрипта. - Параметр include_path, определяемый в файле настроек PHP, задает имена директорий, в которых нужно искать включаемые файлы. Например, ваш include_path это . (то есть текущая директория), текущая рабочая директория это /www/. В основной файл include.php вы включаете файл my_dir/a.php, который в свою очередь включает b.php. Тогда парсер первым делом ищет файл b.php в директории /www/, и если такового нет, то в директории /www/my_dir/. Если файл включен с помощью include, то содержащийся в нем код наследует область видимости переменных строки, где появился include. Любые переменные вызванного файла будут доступны в вызывающем файле с этой строки и далее. Соответственно, если include появляется внутри функции вызывающего файла, то код, содержащийся в вызываемом файле, будет вести себя так, как будто он был определен внутри функции. Таким образом, он унаследует область видимости этой функции. Хотя мы и не знакомились еще с понятием функции, все же приводим здесь эти сведения в расчете на интуитивное его понимание. Пример 3.10. Пусть файл для вставки params.inc останется таким же, а include.php будет следующим: <?php function Footer(){ // объявляем функцию с именем Footer include ("params.inc"); /* включаем файл params.inc. Теперь его переменными можно пользоваться, но только внутри функции */ $str = "Сегодня: $today <br>"; $str .= "<a href='mailto:help@intuit.ru'>Страницу создал $user</a>"; echo "$str"; } Footer(); // вызываем функцию Footer(). Получим: //Сегодня: 08.07.05 //Страницу создал Вася echo "$user, $today"; // выведет запятую, так как // эти переменные видны только // внутри функции ?> Кроме локальных файлов, с помощью include можно включать и внешние файлы, указывая их url-адреса. Данная возможность контролируется директивой url_fopen_wrappers в файле настроек PHP и по умолчанию, как правило, включена. Но в версиях PHP для Windows до PHP 4.3.0 эта возможность не поддерживается совсем, вне зависимости от url_fopen_wrappers. include() – это специальная языковая конструкция, поэтому при использовании внутри условных блоков ее нужно заключать в фигурные скобки. <?php /* Это неверная запись. Получим ошибку. Мы же вставляем не одну команду, а несколько, они только записаны в другом файле */ if ($condition) include("first.php"); else include("second.php"); // А вот так правильно. if ($condition){ include("first.php"); } else { include("second.php"); } ?> При использовании include возможны два вида ошибок – ошибка вставки (например, нельзя найти указанный файл, неверно написана сама команда вставки и т.п.) или ошибка исполнения (если ошибка содержится во вставляемом файле). В любом случае при ошибке в команде include исполнение скрипта не завершается. require Этот оператор действует примерно так же, как и #include в C++. Все, что мы говорили о include, лишь за некоторыми исключениями, справедливо и для require. Require также позволяет включать в программу и исполнять какой-либо файл. Основное отличие require и include заключается в том, как они реагируют на возникновение ошибки. Как уже говорилось, include выдает предупреждение, и работа скрипта продолжается. Ошибка в require вызывает фатальную ошибку работы скрипта и прекращает его выполнение. Условные операторы на require() не влияют. Хотя, если строка, в которой появляется этот оператор, не исполняется, то ни одна строка кода из вставляемого файла тоже не исполняется. Циклы также не влияют на require(). Хотя код, содержащийся во вставляемом файле, является объектом цикла, но вставка сама по себе происходит только однажды. В реализациях PHP до версии 4.0.2 использование require() означало, что интерпретатор обязательно попытается прочесть вставляемый файл. require, как и include, при использовании внутри условных блоков нужно заключать в фигурные скобки. Решение задачи И наконец, вернемся к задаче, сформулированной в начале лекции. Мы хотим создать программу, которую можно было бы использовать для отправки писем (или просто для их генерации) с приглашениями на различные мероприятия множеству пользователей. В предыдущей лекции уже рассматривался подобный случай. Сейчас мы вынесем всю информацию о людях и событиях в отдельный файл data.php и напишем программу, не зависящую (ну, может, совсем чуть-чуть зависящую) от этой информации и ее структуры. В этом случае для того, чтобы, например, расширить список адресатов, не нужно будет изменять скрипт, генерирующий приглашения. Кроме того, можно будет использовать информацию о людях и событиях в других скриптах. В самом скрипте, генерирующем приглашения letters.php, мы использовали условные операторы, циклы, require и другие изученные ранее конструкции. <?php define("SIGN","С уважением, Вася"); // пусть наша подпись // будет константой // информация о событиях $events = array( "f" => "день открытых дверей", "o" => "открытие выставки", "p" => "бал выпускников"); // имеющаяся информация о людях // (имя и электронный адрес) $people = array( "ivan" => array( "name" => "Иван Иванович", "email"=>"user_ivan@intuit.ru"), "pit" => array( "name" => "Петр Петрович", "email" => "user_petr@intuit.ru"), "semen" => array( "name" => "Семен Семенович")); // кто куда приглашается $who_where["ivan"] = "o" ; // Иван – на выставку $who_where["pit"] = "p"; // Петр – на бал ?> <?php require("data.php"); // включаем файл с данными о событиях foreach($people as $key => $man_info){ // для каждого человека делаем следующее: $event_key = $who_where[$key]; // получаем событие, // на которое он приглашается if ($event_key<>""){ foreach($man_info as $key1 => $info){ // получаем имя и email // конкретного человека if ($key1=="name") $str = "Уважаемый (ая), $info"; if ($key1=="email") $email = $info; } // составляем приглашение $str .= "<br>Приглашаем Вас на ". $events[$event_key]; switch ($event_key){ // в зависимости от события // добавляем какую-нибудь строчку case "f": $str .= "<br>Подтвердите Ваше участие по телефону!"; break; case "o": $str .= "<br>Приходите за 15 минут до открытия!"; break; case "p": $str .= "<br>Не забудьте подарок :-)"; break; } $str .= "<br>" . SIGN . "<hr>"; // добавляем подпись echo $str; // вводим приглашение на экран /* если у вас настроена отправка почты с помощью PHP, то письмо можно отправить командой mail($email,"Letter",$str); */ } } ?> Заключение Итак, мы завершили изучение управляющих конструкций языка PHP. Мы старались привести самое основное и не углубляться в различные нюансы тех или иных конструкций. Подробную информацию можно найти в руководстве по PHP. Обработка запросов с помощью PHPОсновы клиент-серверных технологий В самом начале курса мы уже говорили о том, что PHP – это скриптовый язык, обрабатываемый сервером. Сейчас мы хотим уточнить, что же такое сервер, какие функции он выполняет и какие вообще бывают серверы. Если речь идет о сервере, невольно всплывает в памяти понятие клиента. Все потому, что эти два понятия неразрывно связаны. Объединяет их компьютерная архитектура клиент-сервер. Обычно, когда говорят «сервер», имеют в виду сервер в архитектуре клиент-сервер, а когда говорят «клиент» – имеют в виду клиент в этой же архитектуре. Так что же это за архитектура? Суть ее в том, чтобы разделить функции между двумя подсистемами: клиентом, который отправляет запрос на выполнение каких-либо действий, и сервером, который выполняет этот запрос. Взаимодействие между клиентом и сервером происходит посредством стандартных специальных протоколов, таких как TCP/IP и z39.50. На самом деле протоколов очень много, они различаются по уровням. Мы рассмотрим только протокол прикладного уровня HTTP (чуть позднее), поскольку для решения наших программистских задач нужен только он. А пока вернемся к клиент-серверной архитектуре и разберемся, что же такое клиент и что такое сервер. Сервер представляет собой набор программ, которые контролируют выполнение различных процессов. Соответственно, этот набор программ установлен на каком-то компьютере. Часто компьютер, на котором установлен сервер, и называют сервером. Основная функция компьютера-сервера – по запросу клиента запустить какой-либо определенный процесс и отправить клиенту результаты его работы. Клиентом называют любой процесс, который пользуется услугами сервера. Клиентом может быть как пользователь, так и программа. Основная задача клиента – выполнение приложения и осуществление связи с сервером, когда этого требует приложение. То есть клиент должен предоставлять пользователю интерфейс для работы с приложением, реализовывать логику его работы и при необходимости отправлять задания серверу. Взаимодействие между клиентом и сервером начинается по инициативе клиента. Клиент запрашивает вид обслуживания, устанавливает сеанс, получает нужные ему результаты и сообщает об окончании работы. Услугами одного сервера чаще всего пользуется несколько клиентов одновременно. Поэтому каждый сервер должен иметь достаточно большую производительность и обеспечивать безопасность данных. Логичнее всего устанавливать сервер на компьютере, входящем в какую-либо сеть, локальную или глобальную. Однако можно устанавливать сервер и на отдельно стоящий компьютер (тогда он будет являться одновременно и клиентом и сервером). Существует множество типов серверов. Вот лишь некоторые из них. Такой сервер специально приспособлен к обработке изображений, хранению видеоматериалов, видеоигр и т.п. В связи с этим компьютер, на котором установлен видеосервер, должен иметь высокую производительность и большую память. - Поисковый сервер предназначен для поиска информации в Internet. - Почтовый сервер предоставляет услуги в ответ на запросы, присланные по электронной почте. - Сервер WWW предназначен для работы в Internet. - Сервер баз данных выполняет обработку запросов к базам данных. - Сервер защиты данных предназначен для обеспечения безопасности данных (содержит, например, средства для идентификации паролей). - Сервер приложений предназначен для выполнения прикладных процессов. С одной стороны взаимодействует с клиентами, получая задания, а с другой – работает с базами данных, подбирая необходимые для обработки данные. - Сервер удаленного доступа обеспечивает коллективный удаленный доступ к данным. - Файловый сервер обеспечивает функционирование распределенных ресурсов, предоставляет услуги поиска, хранения, архивирования данных и возможность одновременного доступа к ним нескольких пользователей. Обычно на компьютере-сервере работает сразу несколько программ-серверов. Одна занимается электронной почтой, другая распределением файлов, третья предоставляет web-страницы. Из всех типов серверов нас в основном интересует сервер WWW. Часто его называют web-сервером, http-сервером или даже просто сервером. Что представляет собой web-сервер? Во-первых, это хранилище информационных ресурсов. Во-вторых, эти ресурсы хранятся и предоставляются пользователям в соответствии со стандартами Internet (такими, как протокол передачи данных HTTP). Как предоставляются данные в соответствии с этим протоколом, мы рассмотрим чуть позже. Работа с документами web-сервера осуществляется при помощи браузера (например, IE, Opera или Mozilla), который отсылает серверу запросы, созданные в соответствии с протоколом HTTP. В процессе выполнения задания сервер может связываться с другими серверами. Далее в ходе лекции, говоря «сервер», мы будем подразумевать web-сервер. В качестве примеров web-серверов можно привести сервер Apache группы Apache, Internet Information Server (IIS) компании Microsoft, SunOne фирмы Sun Microsystems,WebLogic фирмы BEA Systems, IAS (Inprise Application Server) фирмы Borland, WebSphere фирмы IBM, OAS (Oracle Application Server). На рис. 4.1 и в таблице 4.1 приведена статистика использования различных серверов среди всех доменов Internet от NetCraft http://news.netcraft.com/. Рис. 4.1. Статистика использования ведущих web-серверов Apache 31703884 67.21 32280582 67.20 -0.01 Microsoft 9849971 20.88 10099760 21.02 0.14 SunONE 1657295 3.51 1651575 3.44 -0.07 Zeus 755227 1.60 762716 1.59 -0.01 Как видно из приведенной таблицы, сервер Apache занимает лидирующие позиции. Все, что мы когда-либо будем говорить о web-серверах, ориентировано на Apache, если не указано иное. О том, как установить его на свой компьютер, мы уже рассказывали в самой первой лекции. А теперь, как было обещано, обратимся к протоколу HTTP. Протокол HTTP и способы передачи данных на сервер Internet построен по многоуровневому принципу, от физического уровня, связанного с физическими аспектами передачи двоичной информации, и до прикладного уровня, обеспечивающего интерфейс между пользователем и сетью. HTTP (HyperText Transfer Protocol, протокол передачи гипертекста) – это протокол прикладного уровня, разработанный для обмена гипертекстовой информацией в Internet. HTTP предоставляет набор методов для указания целей запроса, отправляемого серверу. Эти методы основаны на дисциплине ссылок, где для указания ресурса, к которому должен быть применен данный метод, используется универсальный идентификатор ресурсов (Universal Resource Identifier) в виде местонахождения ресурса (Universal Resource Locator, URL) или в виде его универсального имени (Universal Resource Name, URN). Сообщения по сети при использовании протокола HTTP передаются в формате, схожем с форматом почтового сообщения Internet (RFC-822) или с форматом сообщений MIME (Multipurpose Internet Mail Exchange). HTTP используется для коммуникаций между различными пользовательскими программами и программами-шлюзами, предоставляющими доступ к существующим Internet-протоколам, таким как SMTP (протокол электронной почты), NNTP (протокол передачи новостей), FTP (протокол передачи файлов), Gopher и WAIS. HTTP разработан для того, чтобы позволять таким шлюзам через промежуточные программы-серверы (proxy) передавать данные без потерь. Протокол реализует принцип запрос/ответ. Запрашивающая программа – клиент инициирует взаимодействие с отвечающей программой – сервером и посылает запрос, содержащий: - метод доступа; - адрес URI; - версию протокола; - сообщение (похожее по форме на MIME) с информацией о типе передаваемых данных, информацией о клиенте, пославшем запрос, и, возможно, с содержательной частью (телом) сообщения. Ответ сервера содержит: - строку состояния, в которую входит версия протокола и код возврата (успех или ошибка); - сообщение (в форме, похожей на MIME), в которое входит информация сервера, метаинформация (т.е. информация о содержании сообщения) и тело сообщения. В протоколе не указывается, кто должен открывать и закрывать соединение между клиентом и сервером. На практике соединение, как правило, открывает клиент, а сервер после отправки ответа инициирует его разрыв. Давайте рассмотрим более подробно, в какой форме отправляются запросы на сервер. Форма запроса клиента Клиент отсылает серверу запрос в одной из двух форм: в полной или сокращенной. Запрос в первой форме называется соответственно полным запросом, а во второй форме – простым запросом. Простой запрос содержит метод доступа и адрес ресурса. Формально это можно записать так: <Простой-Запрос> := <Метод> <символ пробел> <Запрашиваемый-URI> <символ новой строки> В качестве метода могут быть указаны GET, POST, HEAD, PUT, DELETE и другие. О наиболее распространенных из них мы поговорим немного позже. В качестве запрашиваемого URI чаще всего используется URL-адрес ресурса. Пример простого запроса: GET http://phpbook.info/ Здесь GET – это метод доступа, т.е. метод, который должен быть применен к запрашиваемому ресурсу, а http://phpbook.info/ – это URL-адрес запрашиваемого ресурса. Полный запрос содержит строку состояния, несколько заголовков (заголовок запроса, общий заголовок или заголовок содержания) и, возможно, тело запроса. Формально общий вид полного запроса можно записать так: <Полный запрос> := <Строка Состояния> (<Общий заголовок>|<Заголовок запроса>| <Заголовок содержания>) <символ новой строки> [<содержание запроса>] Квадратные скобки здесь обозначают необязательные элементы заголовка, через вертикальную черту перечислены альтернативные варианты. Элемент <Строка состояния> содержит метод запроса и URI ресурса (как и простой запрос) и, кроме того, используемую версию протокола HTTP. Например, для вызова внешней программы можно задействовать следующую строку состояния: POST http://phpbook.info/cgi-bin/test HTTP/1.0 В данном случае используется метод POST и протокол HTTP версии 1.0. В обеих формах запроса важное место занимает URI запрашиваемого ресурса. Чаще всего URI используется в виде URL-адреса ресурса. При обращении к серверу можно применять как полную форму URL, так и упрощенную. Полная форма содержит тип протокола доступа, адрес сервера ресурса и адрес ресурса на сервере (рисунок 4.2). В сокращенной форме опускают протокол и адрес сервера, указывая только местоположение ресурса от корня сервера. Полную форму используют, если возможна пересылка запроса другому серверу. Если же работа происходит только с одним сервером, то чаще применяют сокращенную форму. Рис. 4.2. Полная форма URL Далее мы рассмотрим наиболее распространенные методы отправки запросов. Методы Как уже говорилось, любой запрос клиента к серверу должен начинаться с указания метода. Метод сообщает о цели запроса клиента. Протокол HTTP поддерживает достаточно много методов, но реально используются только три: POST, GET и HEAD. Метод GET позволяет получить любые данные, идентифицированные с помощью URI в запросе ресурса. Если URI указывает на программу, то возвращается результат работы программы, а не ее текст (если, конечно, текст не есть результат ее работы). Дополнительная информация, необходимая для обработки запроса, встраивается в сам запрос (в строку статуса). При использовании метода GET в поле тела ресурса возвращается собственно затребованная информация (текст HTML-документа, например). Существует разновидность метода GET – условный GET. Этот метод сообщает серверу о том, что на запрос нужно ответить, только если выполнено условие, содержащееся в поле if-Modified-Since заголовка запроса. Если говорить более точно, то тело ресурса передается в ответ на запрос, если этот ресурс изменялся после даты, указанной в if-Modified-Since. Метод HEAD аналогичен методу GET, только не возвращает тело ресурса и не имеет условного аналога. Метод HEAD используют для получения информации о ресурсе. Это может пригодиться, например, при решении задачи тестирования гипертекстовых ссылок. Метод POST разработан для передачи на сервер такой информации, как аннотации ресурсов, новостные и почтовые сообщения, данные для добавления в базу данных, т.е. для передачи информации большого объема и достаточно важной. В отличие от методов GET и HEAD, в POST передается тело ресурса, которое и является информацией, получаемой из полей форм или других источников ввода. До сих пор мы только теоретизировали, знакомились с основными понятиями. Теперь пора научиться использовать все это на практике. Далее в лекции мы рассмотрим, как посылать запросы серверу и как обрабатывать его ответы. Использование HTML-форм для передачи данных на сервер Как передавать данные серверу? Для этого в языке HTML есть специальная конструкция – формы. Формы предназначены для того чтобы получать от пользователя информацию. Например, вам нужно знать логин и пароль пользователя для того, чтобы определить, на какие страницы сайта его можно допускать. Или вам необходимы личные данные пользователя, чтобы была возможность с ним связаться. Формы как раз и применяются для ввода такой информации. В них можно вводить текст или выбирать подходящие варианты из списка. Данные, записанные в форму, отправляются для обработки специальной программе (например, скрипту на PHP) на сервере. В зависимости от введенных пользователем данных эта программа может формировать различные web-страницы, отправлять запросы к базе данных, запускать различные приложения и т.п. Разберемся с синтаксисом HTML-форм. Возможно, многие с ним знакомы, но мы все же повторим основные моменты, поскольку это важно. Итак, для создания формы в языке HTML используется тег FORM. Внутри него находится одна или несколько команд INPUT. С помощью атрибутов action и method тега FORM задаются имя программы, которая будет обрабатывать данные формы, и метод запроса, соответственно. Команда INPUT определяет тип и различные характеристики запрашиваемой информации. Отправка данных формы происходит после нажатия кнопки input типа submit. Создадим форму для регистрации участников заочной школы программирования. После обработки браузером этот файл будет выглядеть примерно так: Рис. 4.3. Пример html-формы Вот так создаются и выглядят HTML-формы. Будем считать, что мы научились или вспомнили, как их создавать. Как мы видим, в форме можно указывать метод передачи данных. Посмотрим, что будет происходить, если указать метод GET или POST, и в чем будет разница. Для метода GET При отправке данных формы с помощью метода GET содержимое формы добавляется к URL после знака вопроса в виде пар имя=значения, объединенных с помощью амперсанта &: action?name1=value1&name2=value2&name3=value3 Здесь action – это URL-адрес программы, которая должна обрабатывать форму (это либо программа, заданная в атрибуте action тега form, либо сама текущая программа, если этот атрибут опущен). Имена name1, name2, name3 соответствуют именам элементов формы, а value1, value2, value3 – значениям этих элементов. Все специальные символы, включая = и &, в именах или значениях этих параметров будут опущены. Поэтому не стоит использовать в названиях или значениях элементов формы эти символы и символы кириллицы в идентификаторах. Если в поле для ввода ввести какой-нибудь служебный символ, то он будет передан в его шестнадцатеричном коде, например, символ $ заменится на %24. Так же передаются и русские буквы. Для полей ввода текста и пароля (это элементы input с атрибутом type=text и type=password), значением будет то, что введет пользователь. Если пользователь ничего не вводит в такое поле, то в строке запроса будет присутствовать элемент name=, где name соответствует имени этого элемента формы. Для кнопок типа checkbox и radio button значение value определяется атрибутом VALUE в том случае, когда кнопка отмечена. Не отмеченные кнопки при составлении строки запроса игнорируются целиком. Несколько кнопок типа checkbox могут иметь один атрибут NAME (и различные VALUE), если это необходимо. Кнопки типа radio button предназначены для одного из всех предложенных вариантов и поэтому должны иметь одинаковый атрибут NAME и различные атрибуты VALUE. В принципе создавать HTML-форму для передачи данных методом GET не обязательно. Можно просто добавить в строку URL нужные переменные и их значения. http://phpbook.info/test.php?id=10&user=pit В связи с этим у передачи данных методом GET есть один существенный недостаток – любой может подделать значения параметров. Поэтому не советуем использовать этот метод для доступа к защищенным паролем страницам, для передачи информации, влияющей на безопасность работы программы или сервера. Кроме того, не стоит применять метод GET для передачи информации, которую не разрешено изменять пользователю. Несмотря на все эти недостатки, использовать метод GET достаточно удобно при отладке скриптов (тогда можно видеть значения и имена передаваемых переменных) и для передачи параметров, не влияющих на безопасность. Для метода POST Содержимое формы кодируется точно так же, как для метода GET (см. выше), но вместо добавления строки к URL содержимое запроса посылается блоком данных как часть операции POST. Если присутствует атрибут ACTION, то значение URL, которое там находится, определяет, куда посылать этот блок данных. Этот метод, как уже отмечалось, рекомендуется для передачи больших по объему блоков данных. Информация, введенная пользователем и отправленная серверу с помощью метода POST, подается на стандартный ввод программе, указанной в атрибуте action, или текущему скрипту, если этот атрибут опущен. Длина посылаемого файла передается в переменной окружения CONTENT_LENGTH, а тип данных – в переменной CONTENT_TYPE. Передать данные методом POST можно только с помощью HTML-формы, поскольку данные передаются в теле запроса, а не в заголовке, как в GET. Соответственно и изменить значение параметров можно, только изменив значение, введенное в форму. При использовании POST пользователь не видит передаваемые серверу данные. Основное преимущество POST запросов – это их большая безопасность и функциональность по сравнению с GET-запросами. Поэтому метод POST чаще используют для передачи важной информации, а также информации большого объема. Тем не менее не стоит целиком полагаться на безопасность этого механизма, поскольку данные POST запроса также можно подделать, например создав html-файл на своей машине и заполнив его нужными данными. Кроме того, не все клиенты могут применять метод POST, что ограничивает варианты его использования. При отправке данных на сервер любым методом передаются не только сами данные, введенные пользователем, но и ряд переменных, называемых переменными окружения, характеризующих клиента, историю его работы, пути к файлам и т.п. Вот некоторые из переменных окружения: - REMOTE_ADDR – IP-адрес хоста (компьютера), отправляющего запрос; - REMOTE_HOST – имя хоста, с которого отправлен запрос; - HTTP_REFERER – адрес страницы, ссылающейся на текущий скрипт; - REQUEST_METHOD – метод, который был использован при отправке запроса; - QUERY_STRING – информация, находящаяся в URL после знака вопроса; - SCRIPT_NAME – виртуальный путь к программе, которая должна выполняться; - HTTP_USER_AGENT – информация о браузере, который использует клиент Обработка запросов с помощью PHP До сих пор мы упоминали только, что запросы клиента обрабатываются на сервере с помощью специальной программы. На самом деле эту программу мы можем написать сами, в том числе и на языке PHP, и она будет делать с полученными данными все, что мы захотим. Для того чтобы написать эту программу, необходимо познакомиться с некоторыми правилами и инструментами, предлагаемыми для этих целей PHP. Внутри PHP-скрипта имеется несколько способов получения доступа к данным, переданным клиентом по протоколу HTTP. До версии PHP 4.1.0 доступ к таким данным осуществлялся по именам переданных переменных (напомним, что данные передаются в виде пар «имя переменной, символ «=», значение переменной»). Таким образом, если, например, было передано first_name=Nina, то внутри скрипта появлялась переменная $first_name со значением Nina. Если требовалось различать, каким методом были переданы данные, то использовались ассоциативные массивы $HTTP_POST_VARS и $HTTP_GET_VARS, ключами которых являлись имена переданных переменных, а значениями – соответственно значения этих переменных. Таким образом, если пара first_name=Nina передана методом GET, то $HTTP_GET_VARS["first_name"]="Nina". Использовать в программе имена переданных переменных напрямую небезопасно. Поэтому было решено начиная с PHP 4.1.0 задействовать для обращения к переменным, переданным с помощью HTTP-запросов, специальный массив – $_REQUEST. Этот массив содержит данные, переданные методами POST и GET, а также с помощью HTTP cookies. Это суперглобальный ассоциативный массив, т.е. его значения можно получить в любом месте программы, используя в качестве ключа имя соответствующей переменной (элемента формы). Пример 4.2. Допустим, мы создали форму для регистрации участников заочной школы программирования, как в приведенном выше примере. Тогда в файле 1.php, обрабатывающем эту форму, можно написать следующее: <?php $str = "Здравствуйте, ".$_REQUEST["first_name"]. " ".$_REQUEST["last_name"]."! <br>"; $str .="Вы выбрали для изучения курс по ".$_REQUEST["kurs"]; echo $str; ?> Тогда, если в форму мы ввели имя «Вася», фамилию «Петров» и выбрали среди всех курсов курс по PHP, на экране браузера получим такое сообщение: Здравствуйте, Вася Петров! Вы выбрали для изучения курс по PHP После введения массива $_REQUEST массивы $HTTP_POST_VARS и $HTTP_GET_VARS для однородности были переименованы в $_POST и $_GET соответственно, но сами они из обихода не исчезли из соображений совместимости с предыдущими версиями PHP. В отличие от своих предшественников, массивы $_POST и $_GET стали суперглобальными, т.е. доступными напрямую и внутри функций и методов. Приведем пример использования этих массивов. Допустим, нам нужно обработать форму, содержащую элементы ввода с именами first_name, last_name, kurs (например, форму form.html, приведенную выше). Данные были переданы методом POST, и данные, переданные другими методами, мы обрабатывать не хотим. Это можно сделать следующим образом: <?php $str = "Здравствуйте, ".$_POST ["first_name"]." ".$_POST ["last_name"] ."! <br>"; $str .= "Вы выбрали для изучения курс по ". $_POST["kurs"]; echo $str; ?> Тогда на экране браузера, если мы ввели имя «Вася», фамилию «Петров» и выбрали среди всех курсов курс по PHP, увидим сообщение, как в предыдущем примере: Здравствуйте, Вася Петров! Вы выбрали для изучения курс по PHP Для того чтобы сохранить возможность обработки скриптов более ранних версий, чем PHP 4.1.0, была введена директива register_globals, разрешающая или запрещающая доступ к переменным непосредственно по их именам. Если в файле настроек PHP параметр register_globals=On, то к переменным, переданным серверу методами GET и POST, можно обращаться просто по их именам (т.е. можно писать $first_name). Если же register_globals=Off, то нужно писать $_REQUEST["first_name"] или $_POST["first_name"], $_GET["first_name"], $HTTP_POST_VARS["first_name"], $HTTP_GET_VARS["first_name"]. С точки зрения безопасности эту директиву лучше отключать (т.е. register_globals=Off). При включенной директиве register_globals перечисленные выше массивы также будут содержать данные, переданные клиентом. Иногда возникает необходимость узнать значение какой-либо переменной окружения, например метод, использовавшийся при передаче запроса или IP-адрес компьютера, отправившего запрос. Получить такую информацию можно с помощью функции getenv(). Она возвращает значение переменной окружения, имя которой передано ей в качестве параметра. <? getenv("REQUEST_METHOD"); // возвратит использованный метод echo getenv ("REMOTE_ADDR"); // выведет IP-адрес пользователя, // пославшего запрос ?> Как мы уже говорили, если используется метод GET, то данные передаются добавлением строки запроса в виде пар «имя_переменной=значение к URL-адресу ресурса». Все, что записано в URL после знака вопроса, можно получить с помощью команды getenv("QUERY_STRING"); Благодаря этому можно по методу GET передавать данные в каком-нибудь другом виде. Например, указывать только значения нескольких параметров через знак плюс, а в скрипте разбирать строку запроса на части или можно передавать значение всего одного параметра. В этом случае в массиве $_GET появится пустой элемент с ключом, равным этому значению (всей строке запроса), причем символ «+», встретившийся в строке запроса, будет заменен на подчеркивание «_». Методом POST данные передаются только с помощью форм, и пользователь (клиент) не видит, какие именно данные отправляются серверу. Чтобы их увидеть, хакер должен подменить нашу форму своей. Тогда сервер отправит результаты обработки неправильной формы не туда, куда нужно. Чтобы этого избежать, можно проверять адрес страницы, с которой были посланы данные. Это можно сделать опять же с помощью функции getenv(): getenv("HTTP_REFERER"); Теперь самое время решить задачу, сформулированную в начале лекции. Пример обработки запроса с помощью PHP Напомним, в чем состояла задача, и уточним ее формулировку. Нужно написать форму для регистрации участников заочной школы программирования и после регистрации отправить участнику сообщение. Мы назвали это сообщение универсальным письмом, но оно будет немного отличаться от того письма, которое мы составили на предыдущей лекции. Здесь мы также не будем отправлять что-либо по электронной почте, дабы не уподобляться спамерам, а просто сгенерируем это сообщение и выведем его на экран браузера. Начальный вариант формы регистрации мы уже приводили выше. Изменим его таким образом, чтобы каждый регистрирующийся мог выбрать сколько угодно курсов для посещения, и не будем подтверждать получение регистрационной формы. Здесь все достаточно просто и понятно. Единственное, что можно отметить, – это способ передачи значений элемента checkbox. Когда мы пишем в имени элемента kurs[], это значит, что первый отмеченный элемент checkbox будет записан в первый элемент массива kurs, второй отмеченный checkbox – во второй элемент массива и т.д. Можно, конечно, просто дать разные имена элементам checkbox, но это усложнит обработку данных, если курсов будет много. Скрипт, который все это будет разбирать и обрабатывать, называется 1.php (форма ссылается именно на этот файл, что записано в ее атрибуте action). По умолчанию используется для передачи метод GET, но мы указали POST. По полученным сведениям от зарегистрировавшегося человека, скрипт генерирует соответствующее сообщение. Если человек выбрал какие-то курсы, то ему выводится сообщение о времени их проведения и о лекторах, которые их читают. Если человек ничего не выбрал, то выводится сообщение о следующем собрании заочной школы программистов (ЗШП). Заключение Подведем итоги. Мы научились отличать клиента от сервера и компьютер-сервер от программы-сервера, познакомились с основными методами, используемыми для передачи данных на сервер, изучили средства, предлагаемые языком PHP для обработки клиентских запросов, и испробовали их на практике. В принципе этого достаточно для того, чтобы создавать клиент-серверные приложения. Функции в PHPВ этой лекции будут рассматриваться вопросы создания и использования функций в PHP. Говоря «функции», мы не имеем в виду все существующие в PHP функции, а лишь функции, определяемые пользователем. Мы рассмотрим способы задания таких функций, методы передачи аргументов, использование аргументов со значением по умолчанию и значения, возвращаемые функцией. В качестве примера создадим web-интерфейс для генерации html-формы. То есть пользователь выбирает, не прибегая к программированию, какие элементы формы нужно создать, и характеристики этих элементов, а наша программа генерирует нужную форму. Функции, определяемые пользователем Для чего нужны функции? Чтобы ответить на этот вопрос, нужно понять, что вообще представляют собой функции. В программировании, как и в математике, функция есть отображение множества ее аргументов на множество ее значений. То есть функция для каждого набора значений аргумента возвращает какие-то значения, являющиеся результатом ее работы. Зачем нужны функции, попытаемся объяснить на примере. Классический пример функции в программировании – это функция, вычисляющая значение факториала числа. То есть мы задаем ей число, а она возвращает нам его факториал. При этом не нужно для каждого числа, факториал которого мы хотим получить, повторять один и тот же код – достаточно просто вызвать функцию с аргументом, равным этому числу. Функция вычисления факториала натурального числа <?php function fact($n){ if ($n==0) return 1; else return $fact = $n * fact($n-1); } echo fact(3); // можно было бы написать echo (3*2); // но если число большое, echo fact(50); // то удобнее пользоваться функцией, // чем писать echo (50*49*48*...*3*2); ?> Таким образом, когда мы осуществляем действия, в которых прослеживается зависимость от каких-то данных, и при этом, возможно, нам понадобится выполнять такие же действия, но с другими исходными данными, удобно использовать механизм функций – оформить блок действий в виде тела функции, а меняющиеся данные – в качестве ее параметров. Посмотрим, как в общем виде выглядит задание (объявление) функции. Функция может быть определена с помощью следующего синтаксиса: function Имя_функции (параметр1, параметр2, ... параметрN){ Блок_действий return "значение возвращаемое функцией"; } Если прямо так написать в php-программе, то работать ничего не будет. Во-первых, Имя_функции и имена параметров функции (параметр1, параметр2 и т.д.) должны соответствовать правилам наименования в PHP (и русских символов в них лучше не использовать). Имена функций нечувствительны к регистру. Во-вторых, параметры функции – это переменные языка, поэтому перед названием каждого из них должен стоять знак $. Никаких троеточий ставить в списке параметров нельзя. В-третьих, вместо слов блок_действий в теле функции должен находиться любой правильный PHP-код (не обязательно зависящий от параметров). И наконец, после ключевого слова return должно идти корректное php-выражение (что-либо, что имеет значение). Кроме того, у функции может и не быть параметров, как и возвращаемого значения. Пример правильного объявления функции – функция вычисления факториала, приведенная выше. Как происходит вызов функции? Указывается имя функции и в круглых скобках список значений ее параметров, если таковые имеются: <?php Имя_функции ("значение_для_параметра1", "значение_для_параметра2",...); // пример вызова функции – вызов функции // вычисления факториала приведен выше, // там для вычисления факториала числа 3 // мы писали: fact(3); // где fact – имя вызываемой функции, // а 3 – значение ее параметра с именем $n ?> Когда можно вызывать функцию? Казалось бы, странный вопрос. Функцию можно вызвать после ее определения, т.е. в любой строке программы ниже блока function f_name(){...}. В PHP3 это было действительно так. Но уже в PHP4 такого требования нет. Все дело в том, как интерпретатор обрабатывает получаемый код. Единственное исключение составляют функции, определяемые условно (внутри условных операторов или других функций). Когда функция определяется таким образом, ее определение должно предшествовать ее вызову. <? $make = true; /* здесь нельзя вызвать Make_event(); потому что она еще не существует, но можно вызвать Save_info() */ Save_info("Вася","Иванов", "Я выбрал курс по PHP"); if ($make){ // определение функции Make_event() function Make_event(){ echo "<p>Хочу изучать Python<br>"; } } // теперь можно вызывать Make_event() Make_event(); // определение функции Save_info function Save_info($first, $last, $message){ echo "<br>$message<br>"; echo "Имя: ". $first . " ". $last . "<br>"; } Save_info("Федя","Федоров", "А я выбрал Lisp"); // Save_info можно вызывать и здесь ?> Если функция однажды определена в программе, то переопределить или удалить ее позже нельзя. Несмотря на то, что имена функций нечувствительны к регистру, лучше вызывать функцию по тому же имени, каким она была задана в определении. <?php /* нельзя сохранить данные, т.е. вызвать функцию DataSave() до того, как выполнена проверка их правильности, т.е. вызвана функция DataCheck() */ DataCheck(); DataSave(); function DataCheck(){ // проверка правильности данных function DataSave(){ // сохраняем данные } } ?> Рассмотрим подробнее аргументы функций, их назначение и использование. Аргументы функций У каждой функции может быть, как мы уже говорили, список аргументов. С помощью этих аргументов в функцию передается различная информация (например, значение числа, факториал которого надо подсчитать). Каждый аргумент представляет собой переменную или константу. С помощью аргументов данные в функцию можно передавать тремя различными способами. Это передача аргументов по значению (используется по умолчанию), по ссылке и задание значения аргументов по умолчанию. Рассмотрим эти способы подробнее. Когда аргумент передается в функцию по значению, изменение значения аргумента внутри функции не влияет на его значение вне функции. Чтобы позволить функции изменять ее аргументы, их нужно передавать по ссылке. Для этого в определении функции перед именем аргумента следует написать знак амперсанд «&». <?php // напишем функцию, которая бы добавляла // к строке слово checked function add_label(&$data_str){ $data_str .= "checked"; } $str = "<input type=radio name=article "; // пусть имеется такая строка echo $str ."><br>"; // выведет элемент формы – // не отмеченную радио кнопку add_label($str); // вызовем функцию echo $str ."><br>"; // это выведет уже отмеченную // радио кнопку ?> В функции можно определять значения аргументов, используемые по умолчанию. Само значение по умолчанию должно быть константным выражением, а не переменной и не представителем класса или вызовом другой функции. У нас есть функция, создающая информационное сообщение, подпись к которому меняется в зависимости от значения переданного ей параметра. Если значение параметра не задано, то используется подпись "Оргкомитет". <?php function Message($sign="Оргкомитет."){ // здесь параметр sign имеет по умолчанию значение "Оргкомитет" echo "Следующее собрание состоится завтра.<br>"; echo "$sign<br>"; } Message(); // вызываем функцию без параметра. // В этом случае подпись – это Оргкомитет Message("С уважением, Вася"); // В этом случае подпись // будет "С уважением, Вася." ?> Результатом работы этого скрипта будет: Следующее собрание состоится завтра. Оргкомитет. Следующее собрание состоится завтра. С уважением, Вася. Если у функции несколько параметров, то те аргументы, для которых задаются значения по умолчанию, должны быть записаны после всех остальных аргументов в определении функции. В противном случае появится ошибка, если эти аргументы будут опущены при вызове функции. Например, мы хотим внести описание статьи в каталог. Пользователь должен ввести такие характеристики статьи, как ее название, автор и краткое описание. Если пользователь не вводит имя автора статьи, считаем, что это Иванов Иван. <?php function Add_article($title, $description, $author="Иванов Иван"){ echo "Заносим в каталог статью: $title,"; echo "автор $author"; echo "<br>Краткое описание: "; echo "$description <hr>"; } Add_article("Информатика и мы", "Это статья про информатику ...", "Петров Петр"); Add_article("Кто такие хакеры", "Это статья про хакеров ..."); ?> В результате работы скрипта получим следующее Заносим в каталог статью: Информатика и мы, автор Петров Петр. Краткое описание: Это статья про информатику... Заносим в каталог статью: Кто такие хакеры, автор Иванов Иван. Краткое описание: Это статья про хакеров... Если же мы напишем вот так: <?php function Add_article($author="Иванов Иван", $title, $description){ // ...действия как в предыдущем примере } Add_article("Кто такие хакеры", "Это статья про хакеров..."); ?> То в результате получим: Warning: Missing argument 3 for add_article() in c:\users\nina\tasks\func\def_bad.php on line 2 Списки аргументов переменной длины В PHP4 можно создавать функции с переменным числом аргументов. То есть мы создаем функцию, не зная заранее, со сколькими аргументами ее вызовут. Для написания такой функции никакого специального синтаксиса не требуется. Все делается с помощью встроенных функций func_num_args(), func_get_arg(), func_get_args(). Функция func_num_args() возвращает число аргументов, переданных в текущую функцию. Эта функция может использоваться только внутри определения пользовательской функции. Если она появится вне функции, то интерпретатор выдаст предупреждение. <?php function DataCheck(){ $n = func_num_args(); echo "Число аргументов функции $n"; } DataCheck(); // выведет строку // "Число аргументов функции 0" DataCheck(1,2,3); // выведет строку // "Число аргументов функции 3" ?> Функция func_get_arg (целое номер_аргумента ) возвращает аргумент из списка переданных в функцию аргументов, порядковый номер которого задан параметром номер_аргумента. Аргументы функции считаются начиная с нуля. Как и func_num_args(), эта функция может использоваться только внутри определения какой-либо функции. Номер_аргумента не может превышать число аргументов, переданных в функцию. Иначе будет сгенерировано предупреждение, и функция func_get_arg() возвратит False. Создадим функцию для проверки типа данных, ее аргументов. Считаем, что проверка прошла успешно, если первый аргумент функции – целое число, второй – строка. <? function DataCheck(){ $check =true; $n = func_num_args(); // число аргументов, // переданных в функцию /* проверяем, является ли первый переданный аргумент целым числом */ if ($n>=1) if (!is_int(func_get_arg(0))) $check = false; /* проверяем, является ли второй переданный аргумент строкой */ if ($n>=2) if (!is_string(func_get_arg(1))) $check = false; return $check; } if (DataCheck(a123,"text")) echo "Проверка прошла успешно<br>"; else echo "Данные не удовлетворяют условиям<br>"; if (DataCheck(324)) echo "Проверка прошла успешно<br>"; else echo "Данные не удовлетворяют условиям<br>"; ?> Результатом работы будет следующее. Данные не удовлетворяют условиям Проверка прошла успешно Функция func_get_args() возвращает массив, состоящий из списка аргументов, переданных функции. Каждый элемент массива соответствует аргументу, переданному функции. Если функция используется вне определения пользовательской функции, то генерируется предупреждение. Перепишем предыдущий пример, используя эту функцию. Будем проверять, является ли целым числом каждый четный аргумент, передаваемый функции: <? function DataCheck(){ $check =true; $n = func_num_args(); // число аргументов, // переданных в функцию $args = func_get_args(); // массив аргументов функции for ($i=0;$i<$n;$i++){ $v = $args[$i]; if ($i % 2 == 0){ if (!is_int($v)) $check = false; // проверяем, // является ли четный аргумент целым } } return $check; } if (DataCheck("text", 324)) echo "Проверка прошла успешно<br>"; else echo "Данные не удовлетворяют условиям<br>"; ?> Как видим, комбинации функций func_num_args(), func_get_arg() и func_get_args() используется для того, чтобы функции могли иметь переменный список аргументов. Эти функции были добавлены только в PHP 4. В PHP3 для того, чтобы добиться подобного эффекта, можно использовать в качестве аргумента функции массив. Например, вот так можно написать скрипт, проверяющий, является ли каждый нечетный параметр функции целым числом: <? function DataCheck($params){ $check =true; $n = count($params); // число аргументов, // переданных в функцию for ($i=0;$i<$n;$i++){ $v = $params[$i]; if ($i % 2 !== 0){ // проверяем, является ли нечетный // аргумент целым if (!is_int($v)) $check = false; } } return $check; } if (DataCheck("text", 324)) echo "Проверка прошла успешно<br>"; else echo "Данные не удовлетворяют условиям<br>"; ?> Использование переменных внутри функции Глобальные переменные Чтобы использовать внутри функции переменные, заданные вне нее, эти переменные нужно объявить как глобальные. Для этого в теле функции следует перечислить их имена после ключевого слова global: global $var1, $var2; <? $a=1; function Test_g(){ global $a; $a = $a*2; echo 'в результате работы функции $a=',$a; } echo 'вне функции $a=',$a,', '; Test_g(); echo "<br>"; echo 'вне функции $a=',$a,', '; Test_g(); ?> В результате работы этого скрипта получим: вне функции $a=1, в результате работы функции $a=2 вне функции $a=2, в результате работы функции $a=4 Когда переменная объявляется как глобальная, фактически создается ссылка на глобальную переменную. Поэтому такая запись эквивалентна следующей (массив GLOBALS содержит все переменные, глобальные относительно текущей области видимости): $var1 = & $GLOBALS["var1"]; $var2 = & $GLOBALS["var2"]; Это значит, например, что удаление переменной $var1 не удаляет глобальной переменной $_GLOBALS["var1"]. Статические переменные Чтобы использовать переменные только внутри функции, при этом сохраняя их значения и после выхода из функции, нужно объявить эти переменные как статические. Статические переменные видны только внутри функции и не теряют своего значения, если выполнение программы выходит за пределы функции. Объявление таких переменных производится с помощью ключевого слова static: static $var1, $var2; Статической переменной может быть присвоено любое значение, но не ссылка. <? function Test_s(){ static $a = 1; // нельзя присваивать выражение или ссылку $a = $a*2; echo $a; } Test_s(); // выведет 2 echo $a; // ничего не выведет, так как // $a доступна только // внутри функции Test_s(); // внутри функции $a=2, поэтому // результатом работы функции // будет число 4 ?> Возвращаемые значения Все функции, приведенные выше в качестве примеров, выполняли какие-либо действия. Кроме подобных действий, любая функция может возвращать как результат своей работы какое-нибудь значение. Это делается с помощью утверждения return. Возвращаемое значение может быть любого типа, включая списки и объекты. Когда интерпретатор встречает команду return в теле функции, он немедленно прекращает ее исполнение и переходит на ту строку, из которой была вызвана функция. Например, составим функцию, которая возвращает возраст человека. Если человек не умер, то возраст считается относительно текущего года. <?php /* если второй параметр вычисляется как true, то он рассматривается как дата смерти, */ function Age($birth, $is_dead){ if ($is_dead) return $is_dead-$birth; else return date("Y")-$birth; } echo Age(1971, false); // выведет 33 echo Age(1971, 2001); // выведет 30 ?> В этом примере можно было и не использовать функцию return, а просто заменить ее функцией вывода echo. Однако если мы все же делаем так, что функция возвращает какое-то значение (в данном случае возраст человека), то в программе мы можем присвоить любой переменной значение этой функции: $my_age = Age(1981, 2004); В результате работы функции может быть возвращено только одно значение. Несколько значений можно получить, если возвращать список значений (одномерный массив). Допустим, мы хотим получить полный возраст человека с точностью до дня. <?php function Full_age($b_day, $b_month, $b_year) { $y = date("y"); $m = intval(date("m")); $d = intval(date("d")); $b_month = intval($b_month); $b_day = intval($b_day); $b_year = intval($b_year); $day = ($b_day > $d ? $d : $d - $b_day); $tmpMonth = ($b_day > $d ? -1 : 0); $month = ($b_month > $m + $tmpMonth ? $m : $m + $tmpMonth - $b_month); $tmpYear = ($b_month > $m + $tmpMonth ? -1 : 0); if ($b_year > $y + $tmpYear) { $year = 0; $month = 0; $day = 0; } else { $year = $y + $tmpYear - $b_year; } return array ($day,$month,$year); } $age = Full_age("29","06","1986"); echo "Вам $age[2] лет, $age[1] месяцев и $age[0] дней"; ?> Когда функция возвращает несколько значений для их обработки в программе, удобно использовать языковую конструкцию list(), которая позволяет одним действием присвоить значения сразу нескольким переменным. Например, в предыдущем примере, оставив без изменения функцию, обработать возвращаемые ей значения можно было так: <? // задание функции Full_age() list($day,$month,$year) = Full_age("07", "08","1974"); echo "Вам $year лет, $month месяцев и $day дней"; ?> Вообще конструкцию list() можно использовать для присвоения переменным значений элементов любого массива. <? $arr = array("first","second"); list($a,$b) = $arr; // переменной $a присваивается первое // значение массива, $b – второе echo $a," ",$b; // выведет строку «first second» ?> Возвращение ссылки В результате своей работы функция также может возвращать ссылку на какую-либо переменную. Это может пригодиться, если требуется использовать функцию для того, чтобы определить, какой переменной должна быть присвоена ссылка. Чтобы получить из функции ссылку, нужно при объявлении перед ее именем написать знак амперсанд (&) и каждый раз при вызове функции перед ее именем тоже писать амперсанд (&). Обычно функция возвращает ссылку на какую-либо глобальную переменную (или ее часть – ссылку на элемент глобального массива), ссылку на статическую переменную (или ее часть) или ссылку на один из аргументов, если он был также передан по ссылке. <? $a = 3; $b = 2; function & ref($par){ global $a, $b; if ($par % 2 == 0) return $b; else return $a; } $var =& ref(4); echo $var, " и ", $b, "<br>"; //выведет 2 и 2 $b = 10; echo $var, " и ", $b, "<br>"; // выведет 10 и 10 ?> При использовании синтаксиса ссылок в переменную $var нашего примера не копируется значение переменной $b возвращенной функцией $ref, а создается ссылка на эту переменную. То есть теперь переменные $var и $b идентичны и будут изменяться одновременно. Переменные функции PHP поддерживает концепцию переменных функций. Это значит, что если имя переменной заканчивается круглыми скобками, то PHP ищет функцию с таким же именем и пытается ее выполнить. <? /* создадим две простые функции: Add_sign – добавляет подпись к строке и Show_text – выводит строку текста */ function Add_sign($string, $sign="С уважением, Петр"){ echo $string ." ".$sign; } function Show_text(){ echo "Отправить сообщение по почте<br>"; } $func = "Show_text"; // создаем переменную со значением, // равным имени функции Show_text $func(); // это вызовет функцию Show_text $func = "Add_sign"; // создаем переменную со значением, // равным имени функции Add_sign $func("Привет всем <br>"); // это вызовет функцию // Add_sign с параметром "Привет всем" ?> В этом примере функция Show_text просто выводит строку текста. Казалось бы, зачем для этого создавать отдельную функцию, если существует специальная функция echo(). Дело в том, что такие функции, как echo(), print(), unset(), include() и т.п. нельзя использовать в качестве переменных функций. То есть если мы напишем: <? $func = "echo "; $func("TEXT"); ?> то интерпретатор выведет ошибку: Fatal error: Call to undefined function: echo() in c:\users\nina\tasks\func\var_f.php on line 2 Поэтому для того, чтобы использовать любую из перечисленных выше функций как переменную функцию, нужно создать собственную функцию, что мы и сделали в предыдущем примере. Внутренние (встроенные) функции Говоря о функциях, определяемых пользователем, все же нельзя не сказать пару слов о встроенных функциях. С некоторыми из встроенных функций, такими как echo(), print(), date(), include(), мы уже познакомились. На самом деле все перечисленные функции, кроме date(), являются языковыми конструкциями. Они входят в ядро PHP и не требуют никаких дополнительных настроек и модулей. Функция date() тоже входит в состав ядра PHP и не требует настроек. Но есть и функции, для работы с которыми нужно установить различные библиотеки и подключить соответствующий модуль. Например, для использования функций работы с базой данных MySql следует скомпилировать PHP с поддержкой этого расширения. В последнее время наиболее распространенные расширения и соответственно их функции изначально включают в состав PHP так, чтобы с ними можно работать без каких бы то ни было дополнительных настроек интерпретатора. Решение задачи Напомним, в чем состоит задача. Мы хотим написать интерфейс, который позволял бы создавать html-формы. Пользователь выбирает, какие элементы и в каком количестве нужно создать, придумывает им названия, а наша программа сама генерирует требуемую форму. Разобьем задачу на несколько подзадач: выбор типов элементов ввода и их количества, создание названий элементов ввода и обработка полученных данных, т.е. непосредственно генерация формы. Первая задача достаточно проста: нужно написать соответствующую форму, например подобную приведенной ниже (task_form.html): <form action="ask_names.php"> Создать элемент "строка ввода текста": <input type=checkbox name=types[] value=string><br> Количество элементов: <input type=text name=numbers[string] size=3><br> <br> Создать элемент "текстовая область": <input type=checkbox name=types[] value=text><br> Количество элементов: <input type=text name=numbers[text] size=3><br> <input type=submit value="Создать"> </form> Когда мы пишем в имени элемента формы, например types[], это значит, что его имя – следующий элемент массива types. То есть у нас первый элемент формы ("строка ввода текста") будет иметь имя types[0], а второй (текстовая область) – types[1]. В браузере task_form.html будет выглядеть примерно так: Рис. 5.1. Форма для выбора создаваемых элементов и их количества После отправки данных этой формы мы получим информацию о том, какие элементы и сколько элементов каждого типа нужно создать. Следующий скрипт запрашивает названия для этих элементов: Допустим, нужно создать два элемента типа «текстовая строка» и один элемент типа «текстовая область», как и отмечено в форме выше. Тогда скрипт ask_names.php обработает ее таким образом, что мы получим такую форму: Рис. 5.2. Форма для ввода названий создаваемых элементов Введем в эту форму, например, строки «Название», «Автор» и «Краткое содержание». Эти данные будет обрабатывать скрипт task.php. Результатом работы этого скрипта с входными данными, приведенными выше, будет следующая форма: Рис. 5.3. Пример формы, сгенерированной нашей программой Заключение Подведем итоги. В этой лекции мы изучили функции, определяемые пользователем, их синтаксис и семантику, способы передачи их аргументов и возвращаемых значений. Кроме того, обсуждались способы задания и работы с функциями, имеющими переменное число аргументов и альтернативный способ вызова функции (с помощью переменной, значение которой есть имя функции). В следующей лекции будет рассмотрена объектная модель языка PHP. Объекты и классы в PHPВ этой лекции мы рассмотрим объектную модель, предлагаемую языком PHP. Будут представлены понятия класса и объекта, способы их задания и использования, способы расширения классов, конструкторы классов, механизмы наследования и т.п. Кроме того, мы затронем некоторые нововведения, касающиеся объектной модели, появившиеся в PHP5. В качестве примера решим задачу автоматической генерации по желанию пользователя представителей классов статей или личностей, а также их отображения на странице браузера. Классы и объекты Начнем с основных понятий объектно-ориентированного программирования – класса и объекта. Существует множество определений этих понятий. Мы дадим следующее: объект – это структурированная переменная, содержащая всю информацию о некотором физическом предмете или реализуемом в программе понятии, класс – это описание таких объектов и действий, которые можно с ними выполнять. В PHP класс определяется с помощью следующего синтаксиса: class Имя_класса{ var $имя_свойства; /*список свойств*/ function имя_метода( ){ /* определение метода */ } /*список методов*/ } Имена свойств объектов класса объявляются с помощью ключевого слова var, методы, применимые к объектам данного класса, описываются функциями. Внутри определения класса можно использовать ключевое слово this для обращения к текущему представителю класса. Например, нам нужно создать класс, описывающий категорию статей. У каждой статьи имеются такие свойства, как название, автор и краткое содержание. Какие действия мы хотим совершать со статьями? Возможно, нам понадобится задавать значения перечисленным свойствами статьи, отображать статью в браузере. Тогда определение этого класса может выглядеть следующим образом: <? class Articles { // Создаем класс Статей var $title; var $author; var $description; // метод, который присваивает значения // атрибутам класса function make_article($t, $a, $d){ $this->title = $t; $this->author = $a; $this->description = $d; } //метод для отображения экземпляров класса function show_article(){ $art = $this->title . "<br>" . $this->description . "<br>Автор: " . $this->author; echo $art; } } ?> Итак, для описания физических объектов типа «статья» мы создали класс с именем Articles, состоящий из трех переменных, содержащих характеристики статьи, и двух функций для создания конкретной статьи и для ее отображения. Как известно, работая с PHP, можно периодически переключаться в режим HTML. В этом случае программа состоит из нескольких кусков (блоков) кода. Определение класса нельзя разносить по разным блокам php-кода и тем более по разным файлам. То есть если написать: <?php class Articles { // Начало описания класса var $title; ?> <?php // продолжение описания класса function show_article(){ // содержание метода } } // конец описания класса ?> то программа не будет работать корректно. Несколько замечаний по поводу имен классов. Имя класса должно удовлетворять правилам именования объектов в языке PHP, но есть ряд имен, которые зарезервированы разработчиками для своих целей. В первую очередь это имена, начинающиеся с символа подчеркивания «_». Для создания классов и функций нельзя использовать такие имена. Кроме того, зарезервировано имя stdClass, поскольку оно используется внутри движка PHP. Инициализация переменных Часто некоторым атрибутам класса бывает необходимо присваивать значения сразу после создания представителя класса. Когда мы создавали класс статей, для присваивания значений атрибутам (свойствам) класса мы использовали специальную функцию make_article(). Вообще говоря, мы поступили не совсем верно, потому что занялись изобретением велосипеда. Специально для задания начальных значений атрибутам класса существует два стандартных метода. В PHP4 можно инициализировать значения с помощью оператора var или с помощью функции конструктора. С помощью var можно инициализировать только константные значения. Для задания не константных значений используют функцию конструктор, которая вызывается автоматически, когда объект конструируется из класса. Функция-конструктор должна иметь имя, совпадающее с именем всего класса, в котором она определена. Приведем пример. Допустим, при создании объекта «статья» мы хотим установить его свойства следующим образом: автора – равным строке «Иванов», название и краткое содержание – соответствующим элементам глобального массива $_POST, а дату публикации статьи – текущей дате. Тогда следующее описание класса не является корректным в PHP4: <? class Articles { // Создаем класс Статей var $title= $_POST["title"]; var $author = "Иванов"; var $description = $_POST["description"]; var $published = date("Y-m-d"); // метод, который присваивает значения // атрибутам класса } ?> А вот такое описание класса в PHP4 будет работать так, как нужно: <? class Articles { // Создаем класс Статей var $title; var $author = "Иванов"; var $description; var $published; // метод, который присваивает значения // атрибутам класса function Articles(){ $this->title = $_POST["title"]; $this->description = $_POST["description"]; $this ->published = date("Y-m-d"); } } ?> Отметим, что в PHP3 и PHP4 конструкторы работают по-разному. В PHP3 функция становилась конструктором, если она имела то же имя, что и класс, а в PHP4 – если она имеет то же имя, что и класс, в котором она определена. Разница в подходах видна, когда один класс расширяет другой и происходит наследование свойств и методов базового класса. Но об этом мы поговорим чуть позже. В PHP5 конструктор класса именуется _construct. Кроме того, в PHP5 появились и деструкторы – функции, которые вызываются автоматически перед уничтожением объекта. В PHP5 функция-деструктор должна быть названа _destruct. Объекты В одной из первых лекций мы упоминали о существовании в PHP такого типа данных, как объект. Класс – это описание данных одного типа, данных типа объект. Классы являются как бы шаблонами для реальных переменных. Переменная нужного типа создается из класса с помощью оператора new. Создав объект, мы можем применять к нему все методы и получать все свойства, определенные в описании класса. Для этого используют такой синтаксис: $имя_объекта->название_свойства или $имя_объекта->название_метода(список аргументов). Заметим, что перед названием свойства или метода знака $ не ставят. <?php $art = new Articles; // создаем объект $art echo ($art ->title); // выводим название объекта $art $another_art = new Articles; // создаем объект $another_art $another_art->show_article(); // вызываем метод для // отображения объекта в браузер ?> Каждый из объектов класса имеет одни и те же свойства и методы. Так, у объекта $art и у объекта $another_art есть свойства title, description, author и методы Articles(), show_article(). Но это два разных объекта. Представим себе объект как директорию в файловой системе, а его характеристики – как файлы в этой директории. Очевидно, что в каждой директории могут лежать одинаковые файлы, но тем не менее они считаются различными, поскольку хранятся в разных директориях. Точно так же свойства и методы считаются различными, если они применяются к разным объектам. Чтобы получить нужный файл из директории верхнего уровня, мы пишем полный путь к этому файлу. При работе с классами нужно указывать полное имя функции, которую мы хотим вызвать. Директорией верхнего уровня в PHP будет пространство глобальных переменных, а путь указывается с помощью разделителя ->. Таким образом, имена $art->title и $another_art->title обозначают две разные переменные. Переменная в PHP имеет только один знак доллара перед именем, поэтому нельзя писать $art->$title. Эта конструкция будет рассмотрена не как обращение к свойству title объекта $art, а как обращение к свойству, имя которого задано переменной $title (например, $art->""). <?php $art->title = "Введение в Internet"; // так можно установить // значение свойства объекта $art->$title = "Введение в Internet"; // так нельзя установить // значение свойства объекта $property = "title"; $art->$property = "Введение в Internet"; // так можно установить значение // свойства объекта ?> Создавая класс, мы не можем знать, какое имя будет иметь объект этого класса, тем более что объектов может быть много и все могут иметь разные имена. Соответственно мы не знаем, как обращаться к объекту внутри определения класса. Для того чтобы иметь доступ к функциям и переменным внутри определения класса, нужно использовать псевдопеременную $this. Например, $this->title возвращает значение свойства title у текущего объекта данного класса. Иногда эту переменную предлагают читать как «мое собственное» (к примеру, по отношению к свойству). Наследование extends Механизм наследования – очень важная часть всего объектно-ориентированного подхода. Попытаемся объяснить его суть на примере. Допустим, мы создаем описание человека. Очевидно, что сделать это мы можем по-разному, в зависимости от того, для чего нужно это описание. Можно описать человека как программиста: он знает такие-то языки программирования, операционные системы, участвовал в стольких-то проектах. Однако если человек программист, то он не перестает быть человеком вообще, т.е. он имеет имя, фамилию, место жительства и т.п. Если перевести наши рассуждения в термины объектно-ориентированного программирования, то можно сказать, что мы описали два класса – класс людей и класс программистов, каждый со своими свойствами и методами. Причем класс программистов, очевидно, обладает всеми свойствами класса людей и при этом имеет свои специфические характеристики, т.е. класс программистов является подклассом класса людей. Так, если у человека вообще есть имя, то у программиста оно тоже должно быть, но не наоборот. Кроме программистов можно выделить еще множество классов по профессиональной принадлежности людей. И все они будут подклассами класса людей. Часто на практике удобно определять общий класс, который может использоваться сразу в нескольких проектах (например, класс людей или личностей), и адаптировать его для специфических нужд каждого проекта (например, как класс программистов). Как это можно реализовать? С помощью механизма расширений. Любой класс может быть расширением другого класса. Расширяющий (или производный) класс, кроме тех свойств и методов, которые описаны в его определении, имеет все функции и свойства основного (базового класса). В нашем примере класс программистов – расширяющий, а класс всех людей – базовый. Из класса нельзя удалить никакие существующие свойства и функции, класс можно только расширить. Расширяющий класс в PHP4 всегда зависит только от одного базового класса, поскольку множественное наследование в PHP не поддерживается. Расширяются классы в PHP с помощью ключевого слова extends. <?php class Person { // определяем класс Личности var $first_name; // имя личности var $last_name; // фамилия личности function make_person($t,$a){ // метод устанавливает // значения имени и фамилии объекта $this->first_name = $t; $this->last_name = $a; } function show_person(){ // метод отображает информацию о личности echo ("<h2>" . $this->first_name . " " . $this->last_name . "</h2>"); } } class Programmer extends Person{ // определяем класс // Programmer, расширяющий Person var $langs = array ("Lisp"); // константным массивом // задать переменную в var можно function set_lang($new_lang){ // метод добавляет еще // один язык к списку известных $this->langs[] = $new_lang; } } ?> Класс Programmer имеет те же переменные и функции, что и класс Person, плюс переменную $langs, в которой содержится список изученных программистом языков, и функцию set_lang для добавления еще одного языка к списку изученных. Создать представителя класса программистов можно обычным способом с помощью конструкции new. После этого можно устанавливать и получать список языков, которые знает программист, и в то же время можно использовать функции, заданные для класса Person, т.е. устанавливать и получать имя и фамилию программиста и отображать сведения о нем в браузере: <?php $progr = new Programmer; $progr -> set_lang("PHP"); // методы, определенные для // класса Programmer print_r ($progr->langs); // методы, определенные для класса Person $progr->make_person("Bill","Gates"); $progr->show_person(); ?> Отношения, в которых состоят созданные нами классы Person и Programmer, называют также отношениями родитель–потомок. Класс Person – родитель, а его потомки, такие как класс Programmer, создаются, основываясь на нем, с помощью расширений. Любой класс может стать родительским и соответственно породить потомков. Порядок определения классов имеет значение. Нельзя сначала определить класс Programmer, расширяющий класс Person, а уже потом сам класс Person. Класс должен быть определен перед тем, как он будет использоваться (расширяться). Конструкторы Теперь, после знакомства с механизмом наследования в PHP, мы можем прокомментировать различие между конструкторами PHP4 и PHP3 и более подробно рассказать о конструкторах вообще. Напомним, что в PHP3 конструктор – это функция, имя которой совпадает с именем класса. А в PHP4 – функция, имя которой совпадает с именем класса, в котором она определена. <?php class Programmer extends Person{ // определяем класс // Programmer, расширяющий Person var $langs = array ("Lisp"); function Programmer(){ // этот конструктор будет // работать и в PHP3, и в PHP4 $this->make_person("Иван","Петров"); } } ?> Здесь функция Programmer() является конструктором, т.е. выполняется сразу после создания любого представителя класса Programmer, задавая ему имя «Иван» и фамилию «Петров». Конструкторы, как и любые другие функции, могут иметь аргументы. В этом случае, создавая представителя класса, нужно указать значения этих параметров. Аргументы конструктора могут иметь и значения по умолчанию. Если все аргументы имеют значения по умолчанию, тогда можно создавать экземпляр класса без параметров. <?php class Programmer extends Person{ // определяем класс // Programmer, расширяющий Person var $langs = array ("Lisp"); function Programmer($n = "Иван", $f = "Петров"){ // это конструктор $this->make_person($n,$f); } } $default_progr = new Programmer(); // создаст программиста Ивана Петрова $new_progr = new Programmer("Вася", "Сидоров"); // создаст программиста Васю Сидорова print_r($new_progr); /* выведет информацию о переменной $new_progr, т.е. свойства объекта и их значения */ ?> Приведенные примеры будут работать и в PHP3, и в PHP4, конечно если дописать в них определение базового класса Person. Допустим, ситуация немного другая: конструктор имеется только у базового класса Person: <?php class Person { // определяем класс Личности var $first_name; var $last_name; function Person($t,$a){ // конструктор $this->first_name = $t; $this->last_name = $a; } /* ... */ } class Programmer extends Person{ // определяем класс // Programmer, расширяющий Person var $langs = array ("Lisp"); function set_lang($new_lang){ $this->langs[] = $new_lang; } } $new_progr = new Programmer("Вася", "Сидоров"); ?> Что произойдет в этом случае при создании объекта класса Programmer, будет ли автоматически вызвана какая-либо функция? В PHP3 ничего не произойдет, поскольку в этом классе нет функции с именем Programmer() (здесь конструктор – это функция, имя которой совпадает с именем класса). В PHP4 будет вызван конструктор базового класса, если он существует, т.е. вызовется функция Person() из класса Person (здесь конструктор – функция, имя которой совпадает с именем класса, в котором она определена). Еще одна ситуация – в базовом классе есть функция, имя которой совпадает с именем расширяющего класса, а в расширяющем классе нет конструктора. <?php class Person { // определяем класс Личности var $first_name; var $last_name; function Person($t,$a){ // конструктор $this->first_name = $t; $this->last_name = $a; } function Programmer($new_lang){ echo "Я – программист"; } } class Programmer extends Person{ // определяем класс // Programmer, расширяющий Person var $langs = array ("Lisp"); function set_lang($new_lang){ $this->langs[] = $new_lang; } } $new_progr = new Programmer("Вася", "Сидоров"); ?> В этом случае PHP3 вызовет в качестве конструктора функцию Programmer() из описания класса Person. Поскольку конструктор – это функция, у которой то же имя, что и у класса. И неважно, определена ли эта функция в самом классе или она наследуется из базового класса. В PHP4 класс Programmer не будет иметь своего конструктора, поэтому вызовется конструктор базового класса. Ни в PHP 3, ни в PHP 4 конструктор базового класса не вызывается автоматически из конструктора порожденного класса. Оператор :: Иногда внутри описания класса возникает необходимость сослаться на функции или переменные из базового класса. Бывает, что нужно ссылаться на функции в классе, ни один представитель которого еще не создан. Как быть в таком случае? В PHP4 для этого существует специальный оператор «::» Например, вот так можно вызвать в описании класса Programmer функцию show_name() из базового класса Person и функцию say_hello(), заданную в описании класса Programmer, когда ни один объект этого класса еще не был создан: <?php class Person { // определяем класс Личности var $first_name; var $last_name; function Person($t,$a){ // конструктор $this->first_name = $t; $this->last_name = $a; } function show_name(){ // метод отображает информацию о личности echo ("Меня зовут, " . $this->first_name . " " . $this->last_name . "!<br>"); } } class Programmer extends Person{ // определяем класс // Programmer, расширяющий Person function set_lang($new_lang){ // метод добавляет еще // один язык к списку известных $this->langs[] = $new_lang; Person::show_name(); // вызываем функцию из базового класса echo "И я знаю теперь еще и " . $new_lang; } function show_name(){ echo ("Я программист, " . $this->first_name . " " . $this->last_name . "!<br>"); } function say_hello(){ echo "Привет!<br>"; } } Programmer::say_hello(); // вызываем функцию, когда ни // один объект ее класса еще не создан $new_progr = new Programmer("Вася","Сидоров"); $new_progr->set_lang("PHP"); ?> В результате работы этой программы получим следующее: Привет! Меня зовут Вася Сидоров! И я знаю теперь еще и PHP С помощью команды Programmer::say_hello(); мы вызываем функцию say_hello класса Programmer как таковую, а не как метод, применяемый к объекту данного класса. В этот момент переменных класса нет. Поэтому функции, вызываемые до создания объекта, не могут пользоваться переменными класса и конструкцией this, но могут пользоваться локальными и глобальными переменными. В определении класса Programmer мы переопределили функцию show_name(), поэтому вызвать функцию show_name() из базового класса Person можно только с помощью оператора «::» Вообще говоря, внутри определения класса мы можем вызывать любые методы и свойства, заданные в его базовом классе с помощью обычного $this, если только порожденный класс не переопределяет эти свойства и методы, как в нашем примере. Оператор parent В приведенном выше примере, обращаясь в базовому классу, мы использовали его имя (мы писали Person::show_name()). Это не совсем удобно, потому что имя класса или иерархия классов может измениться, и тогда придется переписывать код описаний всех классов с тем, чтобы привести используемые в них имена в соответствие с новой иерархией. Чтобы избежать подобной ситуации, вместо имени базового класса нужно использовать ключевое слово parent (например, parent::show_name()). Parent ссылается на класс, прописанный после extends в объявлении вашего класса. Поэтому если вдруг иерархия классов изменится, то достаточно будет внести изменения в имена, указанные после extends в описаниях классов. Объектная модель PHP5 Кроме нового названия для конструкторов и появления деструкторов в PHP5 произошло еще достаточно много изменений. Мы не будем обсуждать их подробно, только опишем в общих чертах. Основное изменение – это передача значений параметров класса по ссылке и присвоение объектов по ссылке, а не по значению, как это было в PHP4. В PHP5 если создаются две равные переменные типа объект, то они указывают на одно значение и изменяются одновременно (мы приводили похожий пример с переменными строкового типа). В связи с этим появился новый механизм для создания копий объектов – так называемое клонирование. В PHP4 все методы и переменные класса доступны извне, т.е. они всегда являются открытыми. В PHP5 переменные и методы можно делать открытыми (доступными отовсюду), закрытыми (доступными только внутри класса) и защищенными (доступными внутри класса и в его производных классах). Кроме того, появилась возможность создавать интерфейсы и абстрактные классы и многое другое. В целом объектная модель в PHP5 значительно усовершенствована для более точного соответствия объектно-ориентированной парадигме программирования. Решение задачи Итак, мы хотели по выбору пользователя генерировать форму для ввода описания статьи или человека и отображать данные, введенные в эту форму. Попробуем решить эту задачу, используя объектно-ориентированный подход. Для начала создадим форму, где пользователь выбирает, что он хочет создать, – описание статьи или человека (точнее, это будут две формы): <form action="task1.php"> Создать описание статьи: <input type=submit name=art_create value="Create Article"> </form> <form action="task1.php"> Создать описание личности: <input type=submit name=pers_create value="Create Person"> </form> Теперь напишем файл для обработки этих форм. В нем создадим два класса – статьи и личности. У каждого класса имеется метод для инициализации его переменных и метод для отображения объектов данного класса. При решении задачи будут использованы две функции, встроенные в PHP для работы с классами и объектами. Это функция get_class(объект), возвращающая имя класса, экземпляром которого является объект, переданный ей в качестве параметра. И функция get_class_vars(имя класса), которая возвращает массив всех свойств класса и их значений по умолчанию. Аналогично можно получить массив имен всех методов класса: get_class_methods (имя класса) Заключение Итак, мы изучили основные понятия объектной модели, используемой в языке PHP. Были описаны правила создания классов и их представителей-объектов, способы задания начальных значений переменным класса, способы получения значений свойств и вызов методов классов. Работа с массивами данныхЯзык PHP предоставляет множество функций для работы с массивами данных. Как правило, эти функции решают наиболее часто встречающиеся задачи, связанные с обработкой массивов. В этой лекции мы рассмотрим некоторые из таких функций и с их помощью решим несколько прикладных задач. В частности, будут рассмотрены функции для поиска элементов в массиве, для сортировки элементов массива, применение созданных пользователем функций ко всем элементам массива и разбивка массива на подмассивы. Массивы В одной из первых лекций мы рассказывали о том, как можно создать массив данных. Напомним, что массив можно создать двумя способами: С помощью конструкции array $array_name = array("key1"=>"value1", "key2"=>"value2"); - Непосредственно задавая значения элементам массива $array_name["key1"] = value1; - Например, нам нужно хранить список документов, которые будут удалены из базы данных. Естественно хранить его в виде массива, ключом в котором будет идентификатор документа (его уникальный номер), а значением – название документа. Этот массив можно создать таким образом: <? $del_items = array("10"=>"Наука и жизнь", "12"=>"Информатика"); $del_items["13"] = "Программирование на Php"; // добавляем элемент в массив ?>
Операции с массивами Массив – это тип данных, с данными этого типа должны быть определены операции. Какие же операции можно производить с массивами? Массивы можно складывать и сравнивать. Складывают массивы с помощью стандартного оператора «+». Вообще говоря, эту операцию по отношению к массивам точнее назвать объединением. Если у нас есть два массива, $a и $b, то результатом их сложения (объединения) будет массив $c, состоящий из элементов $a, к которым справа дописаны элементы массива $b. Причем, если встречаются совпадающие ключи, то в результирующий массив включается элемент из первого массива, т.е. из $a. Таким образом, если складываются массивы в языке PHP, от перемены мест слагаемых сумма меняется. <? $a = array("и"=>"Информатика", "м"=>"Математика"); $b = array("и"=>"История","м"=>"Биология", "ф"=>"Физика"); $c = $a + $b; $d = $b +$a; print_r($c); /* получим: Array([и]=>Информатика [м]=>Математика [ф]=>Физика) */ print_r($d); /* получим: Array([и]=>История [м]=>Биология [ф]=>Физика) */ ?>
Сравнивать массивы можно, проверяя их равенство или неравенство либо эквивалентность или неэквивалентность. Равенство массивов – это когда совпадают все пары ключ/значение элементов массивов. Эквивалентность – когда кроме равенства значений и ключей элементов требуется еще, чтобы элементы в обоих массивах были записаны в одном и том же порядке. Равенство значений в PHP обозначается символом «==», а эквивалентность – символом «===». <? $a = array("и"=>"Информатика", "м"=>"Математика"); $b = array("м"=>"Математика", "и"=>"Информатика"); if ($a == $b) echo "Массивы равны и"; else echo "Массивы НЕ равны и "; if ($a === $b) echo " эквивалентны"; else echo " НЕ эквивалентны"; // получим echo "Массивы равны и НЕ эквивалентны" ?>
Далее рассмотрим еще одну важную операцию с массивом – подсчет количества его элементов. Для ее реализации в PHP есть специальная функция. Функция count Не раз уже мы использовали функцию count(), чтобы вычислить количество элементов массива. На самом деле эта функция вычисляет число элементов в переменной вообще. Если применить ее к любой другой переменной, она возвратит 1. Исключение составляет переменная типа NULL – count(NULL) есть 0. Кроме того, применяя эту функцию к многомерному массиву, чтобы получить число его элементов, нужно использовать дополнительный параметр COUNT_RECURSIVE. <? $del_items = array("langs" => array( "10"=>"Python", "12"=>"Lisp"), "other"=>"Информатика"); echo count($del_items) . "<br>"; // выведет 2 echo count($del_items,COUNT_RECURSIVE); // выведет 4 ?>
Мы не будем повторять все, что было сказано о массивах в предыдущих лекциях. В этой лекции мы рассмотрим некоторые встроенные функции для работы с массивами. И начнем мы с функций для поиска значений в массиве. Функция in_array in_array("искомое значение","массив", ["ограничение на тип"]); позволяет установить, содержится ли в заданном массиве искомое значение. Если третий аргумент задан как true, то в массиве нужно найти элемент, совпадающий с искомым не только по значению, но и по типу. Если искомое значение – строка, то сравнение чувствительно к регистру. Например, имеется массив не изученных нами языков программирования. Мы хотим узнать, содержится ли в этом массиве язык PHP. Напишем следующую программу: <?php $langs = array("Lisp","Python","Java", "PHP","Perl"); if (in_array("PHP",$langs)) echo "Надо бы изучить PHP<br>"; // выведет сообщение "Надо бы изучить PHP" if (in_array("php",$langs)) echo "Надо бы изучить php<br>"; // ничего не выведет, поскольку в массиве // есть строка "PHP", а не "php" ?>
В качестве искомого значения этой функции может выступать и массив. Правда, это свойство было добавлено только начиная с PHP 4.2.0. Например: <?php $langs = array("Lisp","Python",array("PHP","Java"),"Perl"); if (in_array(array("PHP","Java"),$langs)) echo "Надо бы изучить PHP и Java<br>"; ?>
Функция array_search Это еще одна функция для поиска значения в массиве. В отличие от in_array в результате работы array_search возвращает значение ключа, если элемент найден, и ложь – в противном случае. А вот синтаксис у этих функций одинаковый: array_search("искомое значение","массив", ["ограничение на тип"]); Сравнение строк чувствительно к регистру, а если указан опциональный аргумент, то сравниваются еще и типы значений. До PHP 4.2.0, если искомое значение не было найдено, эта функция возвращала ошибку или пустое значение NULL. Пример 7.4. Теперь, наоборот, пусть у нас есть массив языков программирования, которые мы знаем. Причем ключом каждого элемента является номер, указывающий, каким по счету был изучен этот язык. <?php $langs = array("Lisp","Python","Java", "PHP","Perl"); if (!array_search("PHP",$langs)) echo "Надо бы изучить PHP<br>"; else { $k = array_search("PHP",$langs); echo "PHP я изучила $k – м"; } ?>
В результате мы получим строчку: PHP я изучила 3 – м Очевидно, что эта функция более функциональна, чем in_array, поскольку мы не только получаем информацию о том, что искомый элемент в массиве есть, но и узнаем, где именно в массиве он находится. А что будет, если искомых элементов в массиве несколько? В таком случае функция array_search() вернет ключ первого из найденных элементов. Чтобы получить ключи всех элементов, нужно воспользоваться функцией array_keys(). Функция array_keys Функция array_keys() выбирает все ключи массива. Но у нее имеется дополнительный аргумент, с помощью которого можно получить список ключей элементов с конкретным значением. Синтаксис этой функции таков: array_keys ("массив" [,"значение для поиска"]) Функция array_keys() возвращает как строковые, так и числовые ключи массива, организуя все значения в виде нового массива с числовыми индексами. Пример 7.5. Мы записали массив языков, которые изучили. Список был длинным, и некоторые языки были записаны несколько раз. У нас возникло подозрение, что один из таких языков – Lisp. Давайте это проверим: <?php $langs = array("Lisp","Python","Java","PHP", "Perl","Lisp"); $lisp_keys = array_keys($langs,"Lisp"); echo "Lisp входит в массив ". count($lisp_keys) ." раза:<br>"; foreach ($lisp_keys as $val){ echo "под номером $val <br>"; } ?>
В результате получим: Lisp входит в массив 2 раза: под номером 0 под номером 5
Функция array_keys(), как и две предыдущие, зависит от регистра, т.е. элементов LISP в массиве она не обнаружит. array_keys() появилась только в PHP4. В PHP3 для реализации ее функциональности нужно придумывать свою функцию. Если есть функция для получения всех ключей массива, то можно предположить, что существует и функция для получения всех значений массива. Действительно, она существует. Это функция array_values(массив). Все значения переданного ей массива записываются в новый массив, проиндексированный целыми числами, т.е. все ключи массива теряются, остаются только значения. Но вернемся к нашему примеру. Итак, мы выяснили, что язык Lisp случайно упомянут в нашем массиве дважды. Поскольку изучить один язык дважды нельзя («учил, но забыл» не считается), то нужно как-то избавиться от повторяющихся языков. Сделать это довольно просто с помощью функции array_unique(). Функция array_unique Функция array_unique(массив) удаляет повторяющиеся значения из массива и возвращает новый массив. Таким образом, вместо нескольких одинаковых значений и их ключей мы имеем одно значение. Какой у него будет ключ? Как из нескольких ключей одинаковых элементов выбирается тот, который будет сохранен в новом массиве? Происходит следующее. Все элементы массива преобразуются в строки и сортируются. Затем обработчик запоминает первый ключ для каждого значения, а остальные ключи игнорирует. Попробуем избавиться от повторяющихся языков в списке изученных. <?php $langs = array("Lisp","Java","Python","Java", "PHP","Perl","Lisp"); print_r(array_unique($langs)); ?>
Получим следующее: Array ( [0] => Lisp [1] => Java [2] => Python [4] => PHP [5] => Perl )
Далее рассмотрим задачу сортировки массива. Сортировка массивов Необходимость сортировки данных, в том числе и данных, хранящихся в виде массивов, очень часто возникает при решении самых разнообразных задач. Если в языке Си для того, чтобы решить эту задачу, нужно написать десятки строк кода, то в PHP это делается одной простой командой. Функция sort Функция sort имеет следующий синтаксис sort (массив [, флаги]) и сортирует массив, т.е. упорядочивает его значения по возрастанию. Эта функция удаляет все существовавшие в массиве ключи, заменяя их числовыми индексами, соответствующими новому порядку элементов. В случае успешного завершения работы она возвращает true, иначе – false. Пример 7.6. Пусть у нас есть два массива: цены товаров – их названия и, наоборот, названия товаров – их цены. Упорядочим эти массивы по возрастанию: $items = array(10 => "хлеб", 20 => "молоко", 30 => "бутерброд"); sort($items); // строки сортируются в алфавитном // порядке, ключи теряются print_r($items); $rev_items = array("хлеб" => 10, "бутерброд" => 30, "молоко" => 20); sort($rev_items); // числа сортируются по возрастанию, // ключи теряются print_r($rev_items); ?>
Получим: Array ( [0] => бутерброд [1] => молоко [2] => хлеб ) Array ( [0] => 10 [1] => 20 [2] => 30 )
В качестве дополнительного аргумента флаги может использоваться одна из следующих констант: - SORT_REGULAR – сравнивать элементы массива обычным образом; - SORT_NUMERIC – сравнивать элементы массива как числа; - SORT_STRING – сравнивать элементы массива как строки. Функции asort, rsort, arsort Если требуется сохранять индексы элементов массива после сортировки, то нужно использовать функцию asort (массив [, флаги]). Если необходимо отсортировать массив в обратном порядке, т.е. от наибольшего значения к наименьшему, то можно задействовать функцию rsort (массив [, флаги]). А если при этом нужно еще и сохранить значения ключей, то следует использовать функцию arsort(массив [, флаги]). Как вы, наверное, заметили синтаксис у этих функций абсолютно такой же, как у функции sort. Соответственно и значения флагов могут быть такими же, как у sort: SORT_REGULAR, SORT_NUMERIC, SORT_STRING. Кстати говоря, флаг SORT_NUMERIC появился только в PHP4. <?php $books = array("Пушкин"=>"Руслан и Людмила", "Толстой"=>"Война и мир", "Лермонтов"=>"Герой нашего времени"); asort($books); // сортируем массив, // сохраняя значения ключей print_r($books); echo "<br>"; rsort($books); // сортируем массив в обратном порядке, // ключи будут заменены print_r($books); ?>
В результате работы этого скрипта получим: Array ( [Толстой] => Война и мир [Лермонтов] => Герой нашего времени [Пушкин] => Руслан и Людмила ) Array ( [0] => Руслан и Людмила [1] => Герой нашего времени [2] => Война и мир )
Пример 7.8. Допустим, мы создаем каталог описаний документов. У каждого документа есть автор, название, дата публикации и краткое содержание. Мы уже не раз отображали описания, составленные из этих характеристик. Каждый раз порядок отображения этих элементов зависел от созданной нами программы. Теперь же мы хотим иметь возможность изменять порядок отображения элементов по желанию пользователя. Составим для этого следующую форму: <form action=task.php> <table border=1> <tr><td>Название </td><td><input type=text name=title size=5> </td></tr> <tr><td>Краткое содержание </td><td><input type=text name=description size=5> </td></tr> <tr><td>Автор </td><td><input type=text name=author size=5> </td></tr> <tr><td>Дата публикации </td><td><input type=text name=published size=5></td></tr> </table> <input type=submit value="Отправить"> </form>
Будем упорядочивать данные, переданные этой формой, по убыванию их значений, сохраняя при этом значения ключей. Для этого удобно воспользоваться функцией arsort(). Поскольку нам важен только новый порядок элементов, сохраним в новом массиве ключи исходного массива в нужном порядке. Мы сохраняем ключи исходного массива, поскольку они являются именами элементов, из которых конструируется описание документа, а помнить их важно. Итак, получаем такой скрипт: <?php print_r($_GET); echo "<br>"; arsort ($_GET); // сортируем массив в обратном порядке, // сохраняя ключи print_r($_GET); echo "<br>"; $ordered_names = array_keys($_GET); // составляем новый массив foreach($ordered_names as $key => $val) echo "$key :$val <br>"; // выводим элементы нового массива ?>
Сортировка массива по ключам Очевидно, что может возникнуть необходимость в сортировке массива по значениям ключей. Например, если у нас есть массив данных о книгах, как в приведенном выше примере, то вполне вероятно, что мы захотим отсортировать книги по именам авторов. Для этого в PHP также не нужно писать много строк кода – можно просто воспользоваться функцией ksort() для сортировки по возрастанию (прямой порядок сортировки) или krsort() – для сортировки по убыванию (обратный порядок сортировки). Синтаксис этих функций опять же аналогичен синтаксису функции sort(). <?php $books = array("Пушкин"=>"Руслан и Людмила", "Толстой"=>"Война и мир", "Лермонтов"=>"Герой нашего времени"); ksort($books); // сортируем массив, // сохраняя значения ключей print_r($books); ?>
Получим: Array ( [Лермонтов] => Герой нашего времени [Пушкин] => Руслан и Людмила [Толстой] => Война и мир )
Сортировка с помощью функции, заданной пользователем Кроме двух простых способов сортировки значений массива (по убыванию или по возрастанию) PHP предлагает пользователю возможность самому задавать критерии для сортировки данных. Критерий задается с помощью функции, имя которой указывается в качестве аргумента для специальных функций сортировки usort() или uksort(). По названиям этих функций можно догадаться, что usort() сортирует значения элементов массива, а uksort() – значения ключей массива с помощью определенной пользователем функции. Обе функции возвращают true, если сортировка прошла успешно, и false – в противном случае. Их синтаксис выглядит следующим образом: usort (массив , сортирующая функция) uksort (массив , сортирующая функция) Конечно же, нельзя сортировать массив с помощью любой пользовательской функции. Эта функция должна удовлетворять определенным критериям, позволяющим сравнивать элементы массива. Как должна быть устроена сортирующая функция? Во-первых, она должна иметь два аргумента. В них интерпретатор будет передавать пары значений элементов для функции usort() или ключей массива для функции uksort(). Во-вторых, сортирующая функция должна возвращать: - целое число, меньшее нуля, если первый аргумент меньше второго; - число, равное нулю, если два аргумента равны; - число большее нуля, если первый аргумент больше второго. Как и для других функций сортировки, для функции usort() существует аналог, не изменяющий значения ключей, – функция uasort(). Пример 7.10. Допустим, у нас есть массив, содержащий такие сведения о литературных произведениях, как название, автор и год создания. Мы хотим упорядочить книги по дате создания. <?php // массив выглядит таким образом: $books = array("Герой нашего времени" => array ("Лермонтов", 1840), "Руслан и Людмила" => array("Пушкин",1820), "Война и мир" => array ("Толстой",1863), "Идиот" => array("Достоевский",1868)); /* можно, конечно переписать этот массив по-другому, сделав год издания, например, индексом, но гораздо удобнее написать свою функцию для сортировки */ uasort($books,"cmp"); // сортируем массив с помощью функции cmp foreach ($books as $key => $book) { echo "$book[0]: \"$key\"<br>"; } function cmp($a,$b){ // функция, определяющая способ сортировки if ($a[1] < $b[1]) return -1; elseif ($a[1]==$b[1]) return 0; else return 1; } ?>
В результате получим: Пушкин: "Руслан и Людмила" Лермонтов: "Герой нашего времени" Толстой: "Война и мир" Достоевский: "Идиот"
Мы применили нашу собственную функцию сортировки ко всем элементам массива. Далее рассмотрим, как применить к элементам массива любую другую пользовательскую функцию. Применение функции ко всем элементам массива Функция array_walk(массив, функция [, данные]) применяет созданную пользователем функцию функция ко всем элементам массива массив и возвращает true в случае успешного выполнения операции и false – в противном случае. Пользовательская функция, как правило, имеет два аргумента, в которые поочередно передаются значение и ключ каждого элемента массива. Но если при вызове функции array_walk() указан третий аргумент, то он будет рассмотрен как значение третьего аргумента пользовательской функции, смысл которого определяет сам пользователь. Если функция пользователя требует больше аргументов, чем в нее передано, то при каждом вызове array_walk() будет выдаваться предупреждение. Если необходимо работать с реальными значениями массива, а не с их копиями, следует передавать аргумент в функцию по ссылке. Однако нужно иметь в виду, что нельзя добавлять или удалять элементы массива и производить действия, изменяющие сам массив, поскольку в этом случае результат работы array_walk() считается неопределенным. <?php $books1 = array( "А.С. Пушкин"=>"Руслан и Людмила", "Л.Н. Толстой"=>"Война и мир", "М.Ю. Лермонтов"=>"Герой нашего времени"); // создаем функцию, которую хотим // применить к элементам массива function try_walk($val,$key,$data){ echo "$data \"$val\" написал $key<br>"; } // применяем ко всем элементам массива // $book1 функцию try_walk array_walk($books1,"try_walk","Роман"); ?>
В результате работы скрипта получим: Роман "Руслан и Людмила" написал А.С. Пушкин Роман "Война и мир" написал Л.Н. Толстой Роман "Герой нашего времени" написал М.Ю. Лермонтов
Заметим, что мы не изменили значений у элементов массива. Чтобы их изменить, надо было передавать значения в переменную $val функции try_walk по ссылке. <?php $books1 = array( "А.С. Пушкин"=>"Руслан и Людмила", "Л.Н. Толстой"=>"Война и мир", "М.Ю. Лермонтов"=>"Герой нашего времени"); // создаем функцию, которую хотим // применить к элементам массива function try_walk(&$val,$key){ $key = "<p>Автор: " .$key ."<br>"; $val = "Название: \"" . $val ."\"</p>"; echo $key.$val; } // применяем ко всем элементам массива // $book1 функцию try_walk array_walk($books1,"try_walk"); print_r($books1); ?>
В результате работы скрипта получим: Автор: А.С. Пушкин Название: "Руслан и Людмила" Автор: Л.Н. Толстой Название: "Война и мир" Автор: М.Ю. Лермонтов Название: "Герой нашего времени" Array ( [А.С. Пушкин] => Название: "Руслан и Людмила" [Л.Н. Толстой] => Название: "Война и мир" [М.Ю. Лермонтов] => Название: "Герой нашего времени")
Выделение подмассива Функция array_slice Поскольку массив – это набор элементов, вполне вероятно, потребуется выделить из него какой-нибудь поднабор. В PHP для этих целей есть функция array_slice. Ее синтаксис таков: array_slice (массив, номер_элемента [, длина]) Эта функция выделяет подмассив длины длина в массиве массив, начиная с элемента, номер которого задан параметром номер_элемента. Положительный номер_элемента указывает на порядковый номер элемента относительно начала массива, отрицательный – на номер элемента с конца массива. <?php $arr = array(1,2,3,4,5); $sub_arr = array_slice($arr,2); print_r($sub_arr); /* выведет Array ( [0] => 3 [1] =>4 [2] => 5 ), т.е. подмассив, состоящий из элементов 3, 4, 5 */ $sub_arr = array_slice($arr,-2); print_r($sub_arr); // выведет Array ( [0] => 4 [1] => 5 ), // т.е. подмассив, из элементов 4, 5 ?>
Если задать параметр длина при использовании array_slice, то будет выделен подмассив, имеющий ровно столько элементов, сколько задано этим параметром. Длину можно указывать и отрицательную. В этом случае интерпретатор удалит с конца массива число элементов, равное модулю параметра длина. <?php $arr = array(1,2,3,4,5); $sub_arr = array_slice($arr, 2, 2); // содержит массив из элементов 3, 4 $sub = array_slice($arr,-3, 2); // тоже содержит массив из элементов 3, 4 $sub1 = array_slice($arr,0, -1); // содержит массив из // элементов 1, 2, 3, 4 $sub2 = array_slice($arr,-4, -2); // содержит массив из элементов 2, 3 ?>
Функция array_chunk Есть еще одна функция, похожая на array_slice() – это array_chunk(). Она разбивает массив на несколько подмассивов заданной длины. Синтаксис ее такой: array_chunk ( массив, размер [, сохранять_ключи]) В результате работы array_chunk() возвращает многомерный массив, элементы которого представляют собой полученные подмассивы. Если задать параметр сохранять ключи как true, то при разбиении будут сохранены ключи исходного массива. В противном случае ключи элементов заменяются числовыми индексами, которые начинаются с нуля. Пример 7.15. У нас есть список приглашенных, оформленный в виде массива их фамилий. У нас имеются столики на три персоны. Поэтому нужно распределить всех приглашенных по трое. <?php $persons = array("Иванов", "Петров", "Сидорова","Зайцева", "Волкова"); $triples = array_chunk($persons,3); // делим массив на подмассивы // по три элемента foreach ($triples as $k => $table){ // выводим полученные тройки echo "За столиком номер $k сидят: <ul>"; foreach ($table as $pers) echo "<li>$pers"; echo "</ul>"; } ?>
В результате получим: за столиком номер 0 сидят: • Иванов • Петров • Сидорова за столиком номер 1 сидят: • Зайцева • Волкова
Сумма элементов массива В этом разделе мы познакомимся с функцией, вычисляющей сумму всех элементов массива. Сама задача вычисления суммы значений массива предельно проста. Но зачем писать лишний раз один и тот же код, если можно воспользоваться специально созданной и всегда доступной функцией. Функция эта называется, как можно догадаться, array_sum(). И в качестве параметра ей передается только имя массива, сумму значений элементов которого нужно вычислить. В качестве примера использования этой функции приведем решение более сложной задачи, чем просто вычисление суммы элементов. Этот пример также иллюстрирует применение функции array_slice(), которую мы обсуждали чуть раньше. Пример 7.16. Пусть дан массив натуральных чисел. Нужно найти в нем такое число, что сумма элементов справа от него равна сумме элементов слева от него. <?php //массив задается функцией array $arr = array(2,1,3,4,5,6,4); // перебираем каждый элемент массива $arr. // Внутри цикла текущий ключ массива // содержится в переменной $k, // текущее значение – в переменной $val foreach ($arr as $k => $val){ $p = $k + 1; // синтаксис array array_slice ( array array,int offset [,int length]) // array_slice выделяет подмассив // длины length в массиве array, // начиная с элемента offset. $out_next = array_slice($arr,$p); // получаем массив элементов, // идущих после текущего $out_prev = array_slice($arr,0,$k); // получаем массив элементов, // идущих перед текущим // функция mixed array_sum (array array) // подсчитывает сумму элементов массива array $next_sum = array_sum($out_next); $prev_sum = array_sum($out_prev); // если сумма элементов до текущего равна // сумме элементов после, то выводим // значение текущего элемента if ($next_sum==$prev_sum) echo "value:$val"; // можно посмотреть, что представляют собой // рассмотренные массивы на каждом шаге // print_r($out_next); echo "<br>"; // print_r($out_prev); // echo "$next_sum, $prev_sum<br>"; echo "<hr>"; } ?>
Заключение Итак, подведем итоги. В этой лекции мы изучили ряд функций, упрощающих работу с массивами данных. Мы рассмотрели функции для поиска значения среди элементов массива; функции для сортировки элементов массива, как по их значениям, так и по ключам; функции, позволяющие применять ко всем элементам массива функцию, созданную пользователем. Кроме того, мы изучили функцию, выделяющую подмассивы из элементов массива, и функцию, вычисляющую сумму всех элементов массива. Использование всех этих функций было продемонстрировано на примерах. Все функции для работы с массивами доступны без каких-либо конфигурационных настроек PHP, и пользоваться ими можно абсолютно свободно. Так что, прежде чем изобретать велосипед, загляните в руководство по языку PHP: – вполне возможно, что кто-то сделал это до вас. Работа со строкамиСтроки Вероятно, читатели примерно представляют, что такое тип данных «строка» и как создать переменную такого типа. В одной из первых лекций мы приводили три способа задания строк: с помощью одинарных кавычек, двойных кавычек и с помощью heredoc–синтаксиса. Отмечали мы и основные различия между этими способами. В основном они касаются обработки переменных и управляющих последовательностей внутри строки. <?php echo 'В такой строке НЕ обрабатываются переменные и большинство последовательностей'; echo "Здесь переменные и последовательности обрабатываются"; echo <<<EOT Здесь тоже обрабатываются как переменные, так и управляющие последовательности. И кроме того, можно вводить символы кавычек без их экранирования обратным слэшем. EOT; ?>
Уже не раз, начиная с самой первой лекции, мы использовали функцию echo. На самом деле, echo – не функция, а языковая конструкция, поэтому использовать при ее вызове круглые скобки не обязательно. Echo позволяет выводить на экран строки, переданные ей в качестве параметров. Параметров у echo может быть сколько угодно. Их разделяют запятыми или объединяют с помощью оператора конкатенации и никогда не заключают в круглые скобки. <? echo "Пришел ", "увидел ", "победил "; // выведет строку "Пришел увидел победил" // многие предпочитают передавать несколько // параметров в echo с помощью конкатенации echo "Пришел " . "увидел " . "победил "; // тоже выведет строку // "Пришел увидел победил" echo ("Пришел ", "увидел ", "победил "); // выдаст ошибку: unexpected ',' ?>
Существует сокращенный синтаксис для команды echo: <?=строка_для_вывода?> Здесь параметр строка_для вывода содержит строку, заданную любым из известных способов, которая должна быть выведена на экран. Например, такой скрипт выведет на экран красным цветом "Меня зовут Вася": <? $name="Вася" ?> <font color=red>Меня зовут <?=$name?></font>
Кроме языковой конструкции echo существует ряд функций для вывода строк. Это в первую очередь функция print и ее разновидности printf, sprintf и т.п. Функция print позволяет выводить на экран только одну строку и, как и echo, не может быть вызвана с помощью переменных функций, поскольку является языковой конструкцией. Функция print_r не относится к строковым функциям, как можно было бы подумать. Она отображает информацию о переменной в форме, понятной пользователю. Функции sprintf и printf обрабатывают переданную им строку в соответствии с заданным форматом. Но о них мы говорить не будем. А поговорим о том, как можно осуществлять поиск в тексте, представленном в виде строки. Поиск элемента в строке Для того чтобы определить, входит ли данная подстрока в состав строки, используется функция strpos(). Синтаксис strpos() такой: strpos (исходная строка,строка для поиска [,с какого символа искать]) Она возвращает позицию появления искомой строки в исходной строке или возвращает логическое false, если вхождение не найдено. Дополнительный аргумент позволяет задавать символ, начиная с которого будет производиться поиск. Кроме логического false эта функция может возвращать и другие значения, которые приводятся к false (например, 0 или ""). Поэтому для того, чтобы проверить, найдена ли искомая строка, рекомендуют использовать оператор эквивалентности «===». <? $str = "Идея наносить данные на перфокарты и затем считывать и обрабатывать их автоматически принадлежала Джону Биллингсу, а ее техническое решение осуществил Герман Холлерит. Перфокарта Холлерита оказалась настолько удачной, что без малейших изменений просуществовала до наших дней."; $pos = strpos($str,"Холлерит"); if ($pos !== false) echo "Искомая строка встречена в позиции номер $pos "; else echo "Искомая строка не найдена"; /* заметим, что мы проверяем значение $pos на эквивалентность с false. Иначе строка, находящаяся в первой позиции, не была бы найдена, так как 0 интерпретируется как false. */ ?>
Если значение параметра строка_для_поиска не является строкой, то оно преобразуется к целому типу и рассматривается как ASCII-код символа. Чтобы получить ASCII-код любого символа в PHP, можно воспользоваться функцией ord("символ") Например, если мы напишем $pos = strpos($str,228); то интерпретатор будет считать, что мы ищем символ «д». Если добавить эту строчку в приведенный выше пример и вывести результат, то получим сообщение, что искомая строка найдена в позиции 1. Функция, обратная по смыслу ord, – это chr (код символа). Она по ASCII-коду выводит символ, соответствующий этому коду. С помощью функции strpos можно найти номер только первого появления строки в исходной строке. Естественно, есть функции, которые позволяют вычислить номер последнего появления строки в исходной строке. Это функция strrpos(). Ее синтаксис таков: strrpos (исходная строка, символ для поиска) В отличие от strpos() эта функция позволяет найти позицию последнего появления в строке указанного символа. Нельзя искать позицию строки, только символа. Бывают ситуации, когда знать позицию, где находится искомая строка, необязательно, а нужно просто получить все символы, которые расположены после вхождения этой строки. Можно, конечно, воспользоваться и приведенными выше функциями strpos() и strrpos(), но можно сделать и проще – выделить подстроку с помощью предназначенных именно для этого функций. Выделение подстроки Функция strstr Говоря о выделении подстроки из искомой строки в языке PHP, в первую очередь стоит отметить функцию strstr(): strstr (исходная строка, строка для поиска) Она находит первое появление искомой строки и возвращает подстроку, начиная с этой искомой строки до конца исходной строки. Если строка для поиска не найдена, то функция вернет false. Если строка для поиска не принадлежит строковому типу данных, то она переводится в целое число и рассматривается как код символа. Кроме того, эта функция чувствительна к регистру, т.е. если мы будем параллельно искать вхождения слов «Идея» и «идея», то результаты будут разными. Вместо strstr() можно использовать абсолютно идентичную ей функцию strchr(). Пример 8.4. Выделим из строки, содержащей название и автора исследования, подстроку, начинающуюся со слова «Название»: <? $str = "Автор: Иванов Иван (<a href=mailto:van@mail.ru>написать письмо</a>), Название: 'Исследование языков программирования' "; echo "<b>Исходная строка: </b>",$str; if (!strstr($str, "Название")) echo "Строка не найдена<br>"; else echo "<p><b>Полученная подстрока: </b>", strstr($str, "Название"); ?>
В результате получим: Исходная строка: Автор: Иванов Иван (написать письмо), Название: 'Исследование языков программирования' Полученная подстрока: Название: 'Исследование языков программирования'
Для реализации регистронезависимого поиска подстроки существует соответствующий аналог этой функции – функция stristr (исходная строка, искомая строка). Действует и используется она точно так же, как и strstr(), за исключением того, что регистр, в котором записаны символы искомой строки, не играет роли при поиске. Очевидно, что функция strstr() не слишком часто используется – на практике редко бывает нужно получить подстроку, начинающуюся с определенного слова или строки. Но в некоторых случаях и она может пригодиться. Кроме того, в PHP есть и более удобные функции для поиска вхождений. Наиболее мощные из них, конечно, связаны с регулярными выражениями. Их мы рассмотрим в одной из последующих лекций. Функция substr Иногда мы не знаем, с каких символов начинается искомая строка, но знаем, например, что начинается она с пятого символа и заканчивается за два символа до конца исходной строки. Как выделить подстроку по такому описанию? Очень просто, с помощью функции substr(). Ее синтаксис можно записать следующим образом: substr (исходная строка, позиция начального символа [, длина]) Эта функция возвращает часть строки длиной, заданной параметром длина, начиная с символа, указанного параметром позиция начального символа. Позиция, с которой начинается выделяемая подстрока, может быть как положительным целым числом, так и отрицательным. В последнем случае отсчет элементов производится с конца строки. Если параметр длина опущен, то substr() возвращает подстроку от указанного символа и до конца исходной строки. Длина выделяемой подстроки тоже может быть задана отрицательным числом. Это означает, что указанное число символов отбрасывается с конца строки. Пример 8.5. Допустим, у нас есть фраза, выделенная жирным шрифтом с помощью тега <b> языка HTML. Мы хотим получить эту же фразу, но в обычном стиле. Напишем такую программу: <?php $word = "<b>Hello, world!</b>"; echo $word , "<br>"; $pure_str = substr($word, 3, -4); /* выделяем подстроку, начиная с 3-го символа, не включая 4 символа с конца строки */ echo $pure_str; ?>
В результате работы этого скрипта получим: Hello, world! Hello, world!
На самом деле решить такую задачу можно гораздо проще, с помощью функции strip_tags: strip_tags (строка [, допустимые теги]) Эта функция возвращает строку, из которой удалены все html и php-теги. С помощью дополнительного аргумента можно задать теги, которые не будут удалены из строки. Список из нескольких тегов вводится без каких-либо знаков разделителей. Функция выдает предупреждение, если встречает неправильные или неполные теги. <?php $string = "<b>Bold text</b> <i>Italic text</i>"; $str = strip_tags($string); // удаляем все теги из строки $str1 = strip_tags($string, '<i>'); // удаляем все теги кроме тега <i> $str2 = strip_tags($string, '<i><b>'); // удаляем все теги кроме тегов <i> и <b> echo $str,"<br>",$str1,"<br>", $str2; ?>
В результате получим: Bold text Italic text Bold text Italic text Bold text Italic text
Приведем другой пример использования функции substr(). Допустим, у нас есть какое-то сообщение с приветствием и подписью автора. Мы хотим удалить сначала приветствие, а потом и подпись, оставив только содержательную часть сообщения. <?php $text = "Привет! Сегодня мы изучаем работу со строками. Автор."; $no_hello = substr($text, 8); // убираем приветствие $content = substr($text, 8, 39); // то же самое, что substr($text, 8, -6). // Убираем подпись. echo $text, "<br>", $no_hello, "<br>", $content; ?>
В результате получим: Привет! Сегодня мы изучаем работу со строками. Автор. Сегодня мы изучаем работу со строками. Автор. Сегодня мы изучаем работу со строками.
Если нам нужно получить один конкретный символ из строки, зная его порядковый номер, то не следует задействовать функции типа substr. Можно воспользоваться более простым синтаксисом – записывая номер символа в фигурных скобках после имени строковой переменной. В контексте предыдущего примера букву «р», расположенную второй по счету, можно получить так: echo $text{1}; // выведет символ "р" Заметим, что номером этого символа является число один, а не два, так как нумерация символов строки производится начиная с нуля. Раз уж мы начали говорить о символах в строке и их нумерации, то невольно возникает вопрос, сколько всего символов в строке и как это вычислить. Число символов в строке – это длина строки. Вычислить длину строки можно с помощью функции strlen (строка). Например, длина строки «Разработка информационной модели» вычисляется с помощью команды: strlen("Разработка информационной модели"); и равна 32 символам. Итак, как выделять и находить подстроки, мы рассмотрели. Теперь научимся заменять строку, входящую в состав исходной строки, на другую строку по нашему выбору. Замена вхождения подстроки Функция str_replace Для замены вхождения подстроки можно использовать функцию str_replace(). Это простая и удобная функция, позволяющая решать множество задач, не требующих особых тонкостей при выборе заменяемой подстроки. Для того чтобы производить замены с более сложными условиями, используют механизм регулярных выражений и соответствующие функции ereg_replace() и preg_replace(). Синтаксис функции str_replace() такой: str_replace(искомое значение, значение для замены, объект) Функция str_replace() ищет в рассматриваемом объекте значение и заменяет его значением, предназначенным для замены. Почему мы говорим здесь не про строки для поиска и замены и исходную строку, а про значения и объект, в котором происходит замена? Дело в том, что начиная с PHP 4.0.5 любой аргумент этой функции может быть массивом. Если объект, в котором производится поиск и замена, является массивом, то эти действия выполняются для каждого элемента массива и в результате возвращается новый массив. <?php $greeting = array("Привет", "Привет всем!", "Привет, дорогая!"); // объект $new_greet = str_replace("Привет", "Доброе утро", $greeting); // делаем замену print_r($new_greet); /* получим: Array ([0]=>Доброе утро [1]=>Доброе утро всем! [2]=>Доброе утро, дорогая!) */ ?>
Если искомое значение и значение для замены – массивы, то берется по одному значению из каждого массива и производится их поиск и замена в объекте. Если значений для замены меньше, чем значений для поиска, то в качестве новых значений используется пустая строка. <?php $greeting = array("Привет", "Привет всем!", "Привет, дорогая!","Здравствуйте", "Здравствуйте, товарищи", "Hi"); // объект $search = array ("Привет", "Здравствуйте", "Hi"); // значения, которые будем заменять $replace = array ("Доброе утро", "День добрый"); // значения, которыми будем заменять $new_greet = str_replace($search, $replace, $greeting); // делаем замену print_r($new_greet); //выводим полученный массив ?>
В результате получим такой массив: Array ( [0] => Доброе утро [1] => Доброе утро всем! [2] => Доброе утро, дорогая! [3] => День добрый [4] => День добрый, товарищи [5] => )
Если значения для поиска – массив, а значение для замены – строка, то эта строка будет использована для замены всех найденных значений. <?php $greeting = array("Привет", "Привет всем!", "Привет, дорогая!", "Здравствуйте", "Здравствуйте, товарищи"); // объект $search = array ("Привет","Здравствуйте"); // значения, которые будем заменять $replace = "День добрый"; // значение, которым будем заменять $new_greet = str_replace($search, $replace, $greeting); // делаем замену print_r($new_greet); //выводим полученный массив ?>
Получим: Array ( [0] => День добрый [1] => День добрый всем! [2] => День добрый, дорогая! [3] => День добрый [4] => День добрый, товарищи )
Функция str_replace() чувствительна к регистру, но существует ее регистронезависимый аналог – функция str_ireplace(). Однако эта функция поддерживается не во всех версиях PHP. Еще один пример использования функции str_replace() – обработка шаблонов. Обратимся в очередной раз к описанию какого-либо документа, например статьи. Много раз мы уже создавали форму для ввода подобного описания и даже отображали данные, введенные пользователем в такого рода форму. Но как отображать эти данные, мы описывали непосредственно в коде нашей программы. Теперь мы хотим, чтобы способ отображения данных задавал сам пользователь. Для этого добавим в нашу форму еще один элемент для ввода шаблона. <h2>Введите описание статьи</h2> <form action=sbl.php> <table border=0> <tr><td>Название </td><td><input type=text name=title > </td></tr> <tr><td>Краткое содержание </td><td><input type=text name=description > </td></tr> <tr><td>Автор </td><td><input type=text name=author > </td></tr> <tr><td>Дата публикации </td><td><input type=text name=published ></td></tr> <tr><td>Шаблон документа </td><td><textarea name=shablon ></textarea></td></tr> </table> <input type=submit value="Отправить"> </form>
Однако просто поля для ввода шаблона недостаточно. Один человек введет в него одно, другой – другое. Нужно договориться о том, как создавать шаблоны, что можно в них использовать, т.е. нужно придумать язык шаблонов. Например, мы договариваемся, что при создании шаблона можно задействовать любые html-теги, а набор спецсимволов вида <!имя_элемента> определяет значение элемента с именем имя_элемента. Далее, как обрабатывать такого рода шаблоны? Можно использовать функцию str_replace(): <?php $tmpl = $_GET["shablon"]; /* шаблон, введенный пользователем. Например, это может быть такая строка: "<h1><!title></h1> <p><font size=-1><!description></font></p><p align=right><!author><br><!published></p>" */ function Show(){ // функция, которая производит замену // элемента шаблона на его значение global $tmpl; foreach($_GET as $k => $v) { $tmpl = str_replace("<!$k>",$v,$tmpl); } echo $tmpl; } Show(); ?>
Как эти файлы выглядят для обычного пользователя? Если мы введем в форму такие данные как показано на рисунке 8.1, то в результате получим: Первая машина для переписи населения Идея наносить данные на перфокарты и затем считывать и обрабатывать их автоматически принадлежала Джону Биллингсу, а ее техническое решение осуществил Герман Холлерит. Перфокарта Холлерита оказалась настолько удачной, что без малейших изменений просуществовала до наших дней. А. М. Федотов 12.02.03
Рис. 8.1. Форма для ввода описания документа «статья» и шаблона для его отображения Функция substr_replace Эта функция сочетает в себе свойства двух уже рассмотренных нами функций – функции str_replace() и substr(). Ее синтаксис таков: substr_replace (исходная строка, строка для замены, позиция начального символа [, длина]) Эта функция заменяет часть строки строкой, предназначенной для замены. Заменяется та часть строки (т.е. подстрока), которая начинается с позиции, указанной параметром позиция начального символа. С помощью дополнительного аргумента длина можно ограничить число заменяемых символов. То есть, фактически, мы не указываем конкретно строку, которую нужно заменить, мы только описываем, где она находится и, возможно, какую длину имеет. В этом отличие функции substr_replace() от str_replace(). Как и в случае с функцией substr() аргументы позиция начального символа и длина могут быть отрицательными. Если позиция начального символа отрицательна, то замена производится, начиная с этой позиции относительно конца строки. Отрицательная длина задает, сколько символов от конца строки не должно быть заменено. Если длина не указывается, то замена происходит до конца строки. <?php $text = "Меня зовут Вася."; echo "Исходная строка: $text<hr>\n"; /* Следующие две строки заменят всю исходную строку строкой 'А меня – Петя' */ echo substr_replace($text, 'А меня – Петя', 0) . "<br>\n"; echo substr_replace($text, 'А меня – Петя', 0, strlen($text)) . "<br>\n"; // Следующая строка добавит слово 'Привет! ' // в начало исходной строки echo substr_replace($text, 'Привет! ', 0, 0) . "<br>\n"; // Следующие две строки заменят имя Вася // на имя Иван в исходной строке echo substr_replace($text, 'Иван', 11, -1) . "<br>\n"; echo substr_replace($text, 'Иван', -5, -1) . "<br>\n"; ?>
В результате работы этого скрипта получим: Исходная строка: Меня зовут Вася. ------------------------------------------ А меня – Петя А меня – Петя Привет! Меня зовут Вася. Меня зовут Иван. Меня зовут Иван.
Разделение и соединение строки Очень полезные функции – функция разделения строки на части и обратная ей функция объединения строк в одну строку. Почему очень полезные? Например, если вы динамически генерируете форму по желанию пользователя, можно предложить ему вводить элементы для создания списка выбора, разделяя их каким-нибудь символом. И для того чтобы обработать полученный список значений, как раз и пригодится умение разбивать строку на кусочки. Для реализации такого разбиения в PHP можно использовать несколько функций: explode(разделитель,исходная строка [,максимальное число элементов]) split (шаблон, исходная строка [, максимальное число элементов]) preg_split (шаблон, исходная строка [, максимальное число элементов [,флаги]]) Последние две функции работают с регулярными выражениями, поэтому в данной лекции мы их рассматривать не будем. Рассмотрим более простую функцию – explode(). Функция explode() делит исходную строку на подстроки, каждая из которых отделена от соседней с помощью указанного разделителя, и возвращает массив полученных строк. Если задан дополнительный параметр максимальное число элементов, то число элементов в массиве будет не больше этого параметра, в последний элемент записывается весь остаток строки. Если в качестве разделителя указана пустая строка «""», то функция explode() вернет false. Если символа разделителя в исходной строке нет, то возвращается исходная строка без изменений. Пример 8.11. мы хотим создать элемент формы – выпадающий список и значения для этого списка должен ввести пользователь, не знакомый с языком html. Создадим такую форму: <form action=exp.php> Введите варианты для выбора автора статьи через двоеточие (":"):<br> <input type=text name=author size=40> <br> <input type=submit value=Создать элемент> </form>
Скрипт, который будет ее обрабатывать (exp.php), может быть таким: <?php $str = $_GET["author"]; $names = explode(":",$str); // разбиваем строку введенную, // пользователем с помощью ":" $s = "<select name=author>"; // создаем выпадающий список foreach ($names as $k => $name) { $s .= "<option value=$k>$name"; // добавляем элементы к списку } $s .= "</select>"; echo $s; ?>
В итоге, если мы введем такую строчку в форму: Рис. 8.2. Ввод значений для создания выпадающего списка то получим следующий выпадающий список: Рис. 8.3. Выпадающий список, полученный в результате обработки формы Кроме разделения строки на части иногда, наоборот, возникает необходимость объединения нескольких строк в одно целое. Функция, предлагаемая для этого языком PHP, называется implode(): implode (массив строк, объединяющий элемент) Эта функция объединяет элементы массива с помощью переданного ей объединяющего элемента (например, запятой). В отличие от функции explode(), порядок аргументов в функции implode() не имеет значения. Пример 8.12. Допустим, мы храним имя, фамилию и отчество человека по отдельности, а выводить их на странице нужно вместе. Чтобы соединить их в одну строку, можно использовать функцию implode(): <?php $data = array("Иванов","Иван","Иванович"); $str = implode($data," "); echo $str; ?>
В результате работы этого скрипта получим строку: Иванов Иван Иванович У функции implode() существует псевдоним – функция join(), т.е. эти две функции отличаются лишь именами. Строки, содержащие html-код Достаточно часто мы работаем со строками, содержащими html-теги. Если отобразить такую строку в браузер с помощью обычных функций отображения данных echo() или print(), то мы не увидим самих html-тегов, а получим отформатированную в соответствии с этими тегами строку. Браузер обрабатывает все html-теги в соответствии со стандартом языка HTML. Иногда нам нужно видеть непосредственно строку, без обработки ее браузером. Чтобы этого добиться, нужно перед тем, как выводить, применить к ней функцию htmlspecialchars(). Функция htmlspecialchars (строка [, стиль кавычек [, кодировка]]) переводит специальные символы, такие как «<», «>», «&», «"» , «'» в такие сущности языка HTML, как «<», «>», «&», «"», «'» соответственно. Дополнительный аргумент стиль кавычек определяет, как должны интерпретироваться двойные и одинарные кавычки. Он может иметь одно из трех значений: ENT_COMPAT, ENT_QUOTES, ENT_NOQUOTES. Константа ENT_COMPAT означает, что двойные кавычки должны быть переведены в спецсимволы, а одинарные должны остаться без изменений. ENT_QUOTES говорит, что должны конвертироваться и двойные и одинарные кавычки, а ENT_NOQUOTES оставляет и те и другие кавычки без изменений. В параметре кодировка могут быть заданы такие кодировки, как UTF-8, ISO-8859-1 и другие, но ни одна русская кодировка здесь не поддерживается. <?php $new = htmlspecialchars("<a href='mailto:au@mail.ru'> Написать письмо</a>", ENT_QUOTES); echo $new; / * наша строка перекодируется в такую: <a href='mailto:au@mail.ru'> Написать письмо</a> */
В браузере мы увидим: <a href='mailto:au@mail.ru'> Написать письмо</a> Функция htmlspecialchars() перекодирует только наиболее часто используемые спецсимволы. Если необходимо конвертировать все символы в сущности HTML, следует задействовать функцию htmlentities(). Русские буквы при использовании этой функции тоже кодируются специальными последовательностями. Например, буква «А» заменяется комбинацией «À». Ее синтаксис и принцип действия аналогичен синтаксису и принципу действия htmlspecialchars(). Заключение Итак, мы завершили знакомство с функциями работы со строками языка PHP. Конечно же, мы затронули далеко не все существующие функции, а лишь малую часть. Мы изучили функции, позволяющие найти набор символов в строке, функции, заменяющие все вхождения одной строки на другую, функции разделения строки на части и соединения нескольких строк в одну, а также рассмотрели функции, позволяющие выводить на экран строки, содержащие html–код без их форматирования браузером. Работа с файловой системойСоздание файла Функция fopen Вообще говоря, в PHP не существует функции, предназначенной именно для создания файлов . Большинство функций работают с уже существующими файлами в файловой системе сервера. Есть несколько функций, которые позволяют создавать временные файлы, или, что то же самое, файлы с уникальным для текущей директории именем. А вот для того, чтобы создать самый обычный файл, нужно воспользоваться функцией, которая открывает локальный или удаленный файл. Называется эта функция fopen(). Что значит «открывает файл»? Это значит, что fopen связывает данный файл с потоком управления программы. Причем связывание бывает различным в зависимости от того, что мы хотим делать с этим файлом: читать его, записывать в него данные или делать и то и другое. Синтаксис этой функции такой: resource fopen ( имя_файла, тип_доступа [, use_include_path]) В результате работы эта функция возвращает указатель (типа ресурс) на открытый ею файл. В качестве параметров этой функции передаются: имя файла, который нужно открыть, тип доступа к файлу (определяется тем, что мы собираемся делать с ним) и, возможно, параметр, определяющий, искать ли указанный файл в include_path. Есть еще один опциональный параметр, но о нем мы говорить не будем, дабы не усложнять изложение. Обсудим подробнее каждый из этих трех параметров. Параметр имя_файла должен быть строкой, содержащей правильное локальное имя файла или URL-адрес файла в сети. Если имя файла начинается с указания протокола доступа (например, http://... или ftp://...), то интерпретатор считает это имя адресом URL и ищет обработчик указанного в URL протокола. Если обработчик найден, то PHP проверяет, разрешено ли работать с объектами URL как с обычными файлами (директива allow_url_fopen ). Если allow_url_fopen=off, то функция fopen вызывает ошибку и генерируется предупреждение. Если имя файла не начинается с протокола, то считается, что указано имя локального файла. Чтобы открыть локальный файл, нужно, чтобы PHP имел соответствующие права доступа к этому файлу. Параметр use_include_path, установленный в значение 1 или TRUE, заставляет интерпретатор искать указанный в fopen() файл в include_path. Напомним, что include_path - это директива из файла настроек PHP, задающая список директорий, в которых могут находиться файлы для включения. Кроме функции fopen() она используется функциями include() и require(). Параметр тип_доступа может принимать одно из следующих значений (см. таб. 9.1). Итак, чтобы создать файл, нужно, как бы нелепо это ни звучало, открыть несуществующий файл на запись. <?php $h = fopen("my_file.html","w"); /* открывает на запись файл my_file.html, если он существует, или создает пустой файл с таким именем, если его еще нет */ $h = fopen("dir/another_file.txt","w+"); /* открывает на запись и чтение или создает файл another_file.txt в директории dir */ $h = fopen( "http://www.server.ru/dir/file.php","r"); /* открывает на чтение файл, находящийся по указанному адресу*/ ?> Создавая файл, нужно учитывать, под какой операционной системой вы работаете, и под какой ОС предположительно этот файл будет читаться. Дело в том, что разные операционные системы по-разному отмечают конец строки. В Unix-подобных ОС конец строки обозначается \n, в системах типа Windows - \r\n. Windows предлагает специальный флаг t для перевода символов конца строки систем типа Unix в свои символы конца строки. В противоположность этому существует флаг b, используемый чаще всего для бинарных файлов, благодаря которому такой трансляции не происходит. Использовать эти флаги можно, просто дописав их после последнего символа выбранного типа доступа к файлу . Например, открывая файл на чтение, вместо r следует использовать rt, чтобы перекодировать все символы конца строки в \r\n. Если не использовать флаг b при открытии бинарных файлов, то могут появляться ошибки, связанные с изменением содержимого файла. Из соображений переносимости программы на различные платформы рекомендуется всегда использовать флаг b при открытии файлов с помощью fopen(). r Открывает файл только для чтения; устанавливает указатель позиции в файле на начало файла. r+ Открывает файл для чтения и записи; устанавливает указатель файла на его начало. w Открывает файл только для записи; устанавливает указатель файла на его начало и усекает файл до нулевой длины. Если файл не существует, то пытается создать его. w+ Открывает файл для чтения и записи; устанавливает указатель файла на его начало и усекает файл до нулевой длины. Если файл не существует, то пытается создать его. a Открывает файл только для записи; устанавливает указатель файла в его конец. Если файл не существует, то пытается создать его. a+ Открывает файл для чтения и записи; устанавливает указатель файла в его конец. Если файл не существует, то пытается создать его. x Создает и открывает файл только для записи; помещает указатель файла на его начало. Если файл уже существует, то fopen() возвращает false и генерируется предупреждение. Если файл не существует, то делается попытка создать его. Этот тип доступа поддерживается начиная с версии PHP 4.3.2 и работает только с локальными файлами. x+ Создает и открывает файл для чтения и записи; помещает указатель файла на его начало. Если файл уже существует, то fopen() возвращает false и генерируется предупреждение. Если файл не существует, то делается попытка создать его. Этот тип доступа поддерживается, начиная с версии PHP 4.3.2, и работает только с локальными файлами. Что происходит, если открыть или создать файл с помощью fopen не удается? В этом случае PHP генерирует предупреждение, а функция fopen возвращает как результат своей работы значение false. Такого рода предупреждения можно «подавить» (запретить) с помощью символа @ . Например, такая команда не выведет предупреждения, даже если открыть файл не удалось: $h = @fopen("dir/another_file.txt","w+"); Таким образом, функция fopen() позволяет создать только лишь пустой файл и сделать его доступным для записи. Как же записать данные в этот файл? Как прочитать данные из уже существующего файла? Прежде чем ответить на эти вопросы, рассмотрим, как закрыть установленное с помощью fopen() соединение. Закрытие соединения с файлом После выполнения необходимых действий с файлом, будь то чтение или запись данных или что-либо другое, соединение, установленное с этим файлом функцией fopen(), нужно закрыть . Для этого используют функцию fclose(). Синтаксис у нее следующий: fclose (указатель на файл) Эта функция возвращает TRUE, если соединение успешно закрыто, и FALSE - в противном случае. Параметр этой функции должен указывать на файл, успешно открытый, например, с помощью функции fopen(). <?php $h = fopen("my_file.html","w"); fclose($h); ?> Конечно, если не закрывать соединение с файлом, никаких ошибок выполнения скрипта не произойдет. Но в целом для сервера это может иметь серьезные последствия. Например, хакер может воспользоваться открытым соединением и записать в файл вирус, не говоря уже о лишней трате ресурсов сервера. Так что советуем всегда закрывать соединение с файлом после выполнения необходимых действий. Запись данных в файл Функция fwrite Для того чтобы записать данные в файл, доступ к которому открыт функцией fopen(), можно использовать функцию fwrite(). Синтаксис у нее следующий: int fwrite ( указатель на файл, строка [, длина]) Эта функция записывает содержимое строки в файл, на который указывает указатель на файл. Если указан дополнительный аргумент длина, то запись заканчивается после того, как записано количество символов, равное значению этого аргумента, или когда будет достигнут конец строки. В результате своей работы функция fwrite() возвращает число записанных байтов или false, в случае ошибки. Пример 9.3. Пусть в нашей рабочей директории нет файла my_file.html. Создадим его и запишем в него строку текста: <?php $h = fopen("my_file.html","w"); $text = "Этот текст запишем в файл."; if (fwrite($h,$text)) echo "Запись прошла успешно"; else echo "Произошла ошибка при записи данных"; fclose($h); ?> В результате работы этого скрипта в браузере мы увидим сообщение о том, что запись прошла успешно, а в файле my_file.html появится строка "Этот текст запишем в файл.". Если бы этот файл существовал до того, как мы выполнили этот скрипт, все находящиеся в нем данные были бы удалены. Если же мы напишем такой скрипт: <?php $h = fopen("my_file.html","a"); $add_text = "Добавим текст в файл."; if(fwrite($h,$add_text,7)) echo "Добавление текста прошло успешно<br>"; else echo "Произошла ошибка при добавлении данных<br>"; fclose($h); ?> то к строке, уже существующей в файле my_file.html, добавится еще семь символов из строки, содержащейся в переменной $add_text, т.е. слово «Добавим» Функция fwrite() имеет псевдоним fputs(), используемый таким же образом, что и сама функция. Далее мы рассмотрим, какие методы чтения данных из файла предлагает язык PHP. Чтение данных из файла Если мы хотим прочитать данные из существующего файла, одной функции fopen(), как и в случае с записью данных, недостаточно. Она лишь возвращает указатель на открытый файл, но не считывает ни одной строки из этого файла. Поэтому для того, чтобы прочитать данные из файла, нужно воспользоваться одной из специальных функций: file, readfile, file_get_contents, fread, fgets и т.п. Функция fread Эта функция осуществляет чтение данных из файла. Ее можно использовать и для чтения данных из бинарных файлов, не опасаясь их повреждения. Синтаксис fread() такой: string fread (указатель на файл, длина) При вызове этой функции происходит чтение данных длины (в байтах), определенной параметром длина, из файла, на который указывает указатель на файл. Параметр указатель на файл должен быть реально существующей переменной типа ресурс, содержащей в себе связь с файлом, открытую, например, с помощью функции fopen(). Чтение данных происходит до тех пор, пока не встретится конец файла или пока не будет прочитано указанное параметром длина число байтов. В результате работы функция fread() возвращает строку со считанной из файла информацией. Как вы заметили, в этой функции параметр длина - обязательный. Следовательно, если мы хотим считать весь файл в строку, нужно знать его длину. PHP может самостоятельно вычислить длину указанного файла. Для этого нужно воспользоваться функцией filesize(имя файла). В случае ошибки эта функция вернет false. К сожалению, ее можно использовать только для получения размера локальных файлов. Пример 9.4. Прочитаем содержимое файла my_file.html <?php $h = fopen("my_file.html","r+"); // отрываем файл на запись и чтение $content = fread($h, filesize("my_file.html")); // считываем содержимое файла в строку fclose($h); // закрываем соединение с файлом echo $content; // выводим содержимое файла // на экран браузера ?> Для того чтобы считать содержимое бинарного файла, например изображения, в таких системах, как Windows, рекомендуется открывать файл с помощью флага rb или ему подобных, содержащих символ b в конце. Функция filesize() кэширует результаты своей работы. Если изменить содержимое файла my_file.html и снова запустить приведенный выше скрипт, то результат его работы не изменится. Более того, если запустить скрипт, считывающий данные из этого файла с помощью другой функции (например, fgetss), то результат может оказаться таким, как если бы файл не изменился. Чтобы этого избежать, нужно очистить статический кэш, добавив в код программы команду clearstatcache(); Функция fgets С помощью функции fgets() можно считать из файла строку текста. Синтаксис этой функции практически такой же, как и у fread(), за исключением того, что длину считываемой строки указывать необязательно: string fgets ( указатель на файл [, длина]) В результате работы функция fgets() возвращает строку длиной (длина-1) байт из файла, на который указывает указатель на файл. Чтение заканчивается, если прочитано (длина-1) символов и встретился символ перевода строки или конец файла. Напомним, что в PHP один символ - это один байт. Если длина считываемой строки не указана (данная возможность появилась начиная с PHP 4.2.0), то считывается 1 Кбайт (1024 байт) текста или, что то же самое, 1024 символа. Начиная с версии PHP 4.3, если параметр длина не задан, считывается строка целиком. В случае ошибки функция fgets() возвращает false. Для версий PHP начиная с 4.3 эта функция безопасна для двоичных файлов. <?php $h = fopen("my_file.html","r+"); $content = fgets($h,2); // считает первый символ из // первой строки файла my_file.html fclose($h); echo $content; ?> Обе функции, fread() и fgets(), прекращают считывание данных из файла, если встречают конец файла. В PHP есть специальная функция, проверяющая, смотрит ли указатель позиции файла на конец файла. Это булева функция feof(), в качестве параметра которой передается указатель на соединение с файлом. Например, вот так можно считать все строки файла my_file.html: <?php $h = fopen("my_file.html","r"); while (!feof ($h)) { $content = fgets($h); echo $content,"<br>"; } fclose($h); ?> Функция fgetss Существует разновидность функции fgets() - функция fgetss(). Она тоже позволяет считывать строку из указанного файла, но при этом удаляет из него все встретившиеся html-теги, за исключением, быть может, некоторых. Синтаксис fgetss() такой: string fgetss(указатель на файл, длина [, допустимые теги]) Обратите внимание, что здесь аргумент длина обязательный. Пример 9.6. Пусть у нас имеется файл my_file.html следующего содержания: <h1>Без труда не вынешь и рыбку из пруда.</h1> <b>Тише едешь - дальше будешь</b> У семи нянек<i> дитя без глазу</i>. Выведем на экран все строки файла my_file.html, удалив из них все теги, кроме <b> и <i>: <?php $h = fopen("my_file.html","r"); while (!feof ($h)) { $content = fgetss($h,1024,'<b><i>'); echo $content,"<br>"; } fclose($h); ?> В результате работы этого скрипта получим: Без труда не вынешь и рыбку из пруда. Тише едешь - дальше будешь У семи нянек дитя без глазу. Функция fgetc Естественно, если можно считывать информацию из файла построчно, то можно считывать ее и посимвольно. Для этого предназначена функция fgetc(). Легко догадаться, что синтаксис у нее следующий: string fgetc ( указатель на файл ) Эта функция возвращает символ из файла, на который ссылается указатель на файл, и значение, вычисляемое как FALSE, если встречен конец строки. Вот так, например, можно считать файл по одному символу: <?php $h = fopen("my_file.html","r"); while (!feof ($h)) { $content = fgetc($h); echo $content,"<br>"; } fclose($h); ?> На самом деле для того чтобы прочитать содержимое файла, открывать соединение с ним посредством функции fopen() совсем не обязательно. В PHP есть функции, которые позволяют делать это, используя лишь имя файла. Это функции readfile( ), file( ) и file_get_contents( ). Рассмотрим каждую их них подробнее. Функция readfile Синтаксис: int readfile ( имя_файла [, use_include_path]) Функция readfile() считывает файл, имя которого передано ей в качестве параметра имя_файла, и выводит его содержимое на экран. Если дополнительный аргумент use_include_path имеет значение TRUE, то поиск файла с заданным именем производится и по директориям, входящим в include_path. В программу эта функция возвращает число считанных байтов (символов) файла, а в случае ошибки - FALSE. Сообщения об ошибке в этой функции можно подавить оператором @ . Пример 9.7. Следующий скрипт выведет на экран содержимое файла my_file1.html и размер этого файла, если он существует. В противном случае выведется наше сообщение об ошибке - строка "Error in readfile". <?php $n = @readfile ("my_file1.html"); /* выводит на экран содержимое файла и записывает его размер в переменную $n */ if (!$n) echo "Error in readfile"; /* если функция readfile() выполнилась с ошибкой, то $n=false и выводим сообщение об ошибке */ else echo $n; // если ошибки не было, то выводим число // считанных символов ?> С помощью функции readfile() можно читать содержимое удаленных файлов, указывая их URL-адрес в качестве имени файла, если эта опция не отключена в настройках сервера. Сразу же выводить содержимое файла на экран не всегда удобно. Порой нужно записать информацию из файла в переменную, чтобы в дальнейшем произвести с ней какие-либо действия. Для этого можно использовать функцию file() или file_get_contents(). Функция file Функция file() предназначена для считывания информации из файла в переменную типа массив. Синтаксис у нее такой же, как и у функции readfile(), за исключением того, что в результате работы она возвращает массив: array file ( имя_файла [, use_include_path]) Что за массив возвращает эта функция? Каждый элемент данного массива является строкой в файле, информацию из которого мы считываем (его имя задано аргументом имя_файла). Символ новой строки тоже включается в каждый из элементов массива. В случае ошибки функция file(), как и все уже рассмотренные, возвращает false. Дополнительный аргумент use_include_path опять же определяет, искать или нет данный файл в директориях include_path. Открывать удаленные файлы с помощью этой функции тоже можно, если не запрещено сервером. Начиная с PHP 4.3 работа с бинарными файлами посредством этой функции стала безопасной. Например, у нас имеется файл my_file.html следующего содержания: <h1>Без труда не вынешь и рыбку из пруда.</h1> <b>Тише едешь - дальше будешь</b> Прочитаем его содержимое с помощью функции file(): <?php $arr = file ("my_file.html"); foreach($arr as $i => $a) echo $i,": ", htmlspecialchars($a), "<br>"; ?> В результате на экран будет выведено следующее сообщение: 0: <h1>Без труда не вынешь и рыбку из пруда.</h1> 1: <b>Тише едешь - дальше будешь</b> Функция file_get_contents В версиях PHP начиная с 4.3 появилась возможность считывать содержимое файла в строку. Делается это с помощью функции file_get_contents(). Как и две предыдущие функции, в качестве параметров она принимает значение имени файла и, возможно, указание искать его в директориях include_path. Для порядка все равно приведем ее синтаксис: string file_get_contents ( имя_файла [, use_include_path]) Эта функция абсолютно идентична функции file(), только возвращает она содержимое файла в виде строки. Кроме того, она безопасна для обработки бинарных данных и может считывать информацию из удаленных файлов, если это не запрещено настройками сервера. Проверка существования файла Итак, создавать файл мы научились, записывать данные в него - научились, считывать данные из файла - тоже научились. Но вот вопрос: а что если файла, с которым мы пытаемся проделать все эти операции, не существует? Или он недоступен для чтения или записи? Очевидно, что в таком случае ни одна из изученных нами функций работать не будет и PHP выдаст сообщение об ошибке. Чтобы отслеживать такого рода ошибки, можно использовать функции file_exists(), is_writable(), is_readable(). Функция file_exists Синтаксис: bool file_exists (имя файла или директории) Функция file_exists() проверяет, существует ли файл или директория, имя которой передано ей в качестве аргумента. Если директория или файл в файловой системе сервера существует, то функция возвращает TRUE, в противном случае - FALSE. Результат работы этой функции кэшируется. Соответственно очистить кэш можно, как уже отмечалось, с помощью функции clearstatcache(). Для нелокальных файлов использовать функцию file_exists() нельзя. <?php $filename = 'c:/users/files/my_file.html'; if (file_exists($filename)) { print "Файл <b>$filename</b> существует"; } else { print "Файл <b>$filename</b> НЕ существует"; } ?> Функция is_writable Если кроме проверки существования файла нужно узнать еще, разрешено ли записывать информацию в этот файл, следует использовать функцию is_writable() или ее псевдоним - функцию is_writeable(). Синтаксис: bool is_writable (имя файла или директории) Эта функция возвращает TRUE, если файл (или директория) существует и доступен для записи. Доступ к файлу осуществляется под той учетной записью пользователя, под которой работает сервер (чаще всего это пользователь nobody или www). Результаты работы функции is_writable кэшируются. Функция is_readable Если кроме проверки существования файла нужно узнать еще, разрешено ли читать информацию из него, нужно использовать функцию is_readable(). Синтаксис: bool is_readable (имя файла) Эта функция работает подобно функции is_writable(). <?php $filename = 'c:/users/files/my_file.html'; if (is_readable($filename)) { print "Файл <b>$filename</b> существует и доступен для чтения"; } else { print "Файл <b>$filename</b> НЕ существует или НЕ доступен для чтения"; } ?> Удаление файла Последнее, что мы хотим изучить из действий над файлами, - это удаление файлов. Для того чтобы удалить файл с помощью языка PHP, нужно воспользоваться функцией unlink(). Синтаксис этой функции можно описать следующим образом: bool unlink ( имя_файла) Данная функция удаляет файл, имеющий имя имя_файла, возвращает TRUE в случае успеха этой операции и FALSE - в случае ошибки. Чтобы удалить файл, нужно тоже иметь соответствующие права доступа к нему (например, доступа только на чтение для удаления файла недостаточно). <?php $filename = 'c:/users/files/my_file.html'; unlink($filename); // удаляем файл с именем // c:/users/files/my_file.html ?> Загрузка файла на сервер Теперь решим более сложную и часто возникающую на практике задачу загрузки файла на сервер. Первое, что нужно сделать, чтобы загрузить файл на сервер, это создать html-форму. Для того чтобы с помощью этой формы можно было загружать файлы, она должна содержать атрибут enctype в теге form со значением multipart/form-data, а также элемент input типа file. Пример 9.11. <form enctype="multipart/form-data" action="parse.php" method="post"> <input type="hidden" name="MAX_FILE_SIZE" value="30000" /> Загрузить файл: <input type="file" name="myfile" /><br> <input type="submit" value="Отправить файл" /> </form> Заметим, что мы добавили в форме скрытое поле, которое содержит в себе максимальный допустимый размер загружаемого файла в байтах. При попытке загрузить файл, размер которого больше указанного в этом поле значения, будет зафиксирована ошибка. В браузере созданная нами форма будет выглядеть как строка для ввода текста с дополнительной кнопкой для выбора файла с локального диска (рис 9.1). Рис. 9.1. Пример формы для загрузки файла на сервер Теперь нужно написать скрипт, который будет обрабатывать полученный файл. Вся информация о загруженном на сервер файле содержится в глобальном массиве $_FILES. Этот массив появился начиная с PHP 4.1.0. Если включена директива register_globals, то значения переданных переменных доступны просто по их именам. Если мы загрузили с компьютера-клиента файл с именем critics.htm размером 15136 байт, то скрипт с единственной командой print_r($_FILES); выведет на экран следующее: Array ( [myfile] => Array ( [name] => critics.htm [type] => text/html [tmp_name] => C:\WINDOWS\TEMP\php49F.tmp [error] => 0 [size] => 15136 ) ) Вообще говоря, массив $_FILES всегда имеет следующие элементы: - $_FILES['myfile']['name'] - имя, которое имел файл на машине клиента. - $_FILES['myfile']['type'] - mime-тип отправленного файла, если браузер предоставил эту информацию. В нашем примере это text/html. - $_FILES['myfile']['size'] - размер загруженного файла в байтах. - $_FILES['myfile']['tmp_name'] - временное имя файла, под которым он был сохранен на сервере. - $_FILES['myfile']['error'] - код ошибки, появившейся при загрузке. Здесь 'myfile' - это имя элемента формы, с помощью которого была произведена загрузка файла на сервер. То есть оно может быть другим, если элемент формы назвать иначе. Но вот другие ключи (name, type и т. д.) остаются неизменными для любой формы. Если register_globals=On, то доступны также дополнительные переменные, такие как $myfile_name, которая эквивалентна $_FILES['myfile']['name'], и т.п. Ошибок при загрузке в PHP выделяют пять типов и соответственно $_FILES['myfile']['error'] может иметь пять значений: 0 - ошибки не произошло, файл загружен успешно 1 - загружаемый файл превышает размер, установленный директивой upload_max_filesize в файле настроек php.ini 2 - загружаемый файл превышает размер, установленный элементом MAX_FILE_SIZE формы html 3 - файл был загружен частично 4 - файл загружен не был По умолчанию загруженные файлы сохраняются во временной директории сервера, если другая директория не указана с помощью опции upload_tmp_dir в файле настроек php.ini. Переместить загруженный файл в нужную директорию можно с помощью функции move_uploaded_file(). Функция move_uploaded_file() имеет следующий синтаксис: bool move_uploaded_file (временное_имя_файла, место_назначения ) Эта функция проверяет, действительно ли файл, обозначенный строкой временное_имя_файла, был загружен через механизм загрузки HTTP методом POST. Если это так, то файл перемещается в файл, заданный параметром место_назначения (этот параметр содержит как путь к новой директории для хранения, так и новое имя файла). Если временное_имя_файла задает неправильный загруженный файл, то никаких действий произведено не будет, и move_uploaded_file() вернет FALSE. То же самое произойдет, если файл по каким-то причинам не может быть перемещен. В этом случае интерпретатор выведет соответствующее предупреждение. Если файл, заданный параметром место_назначения, существует, то функция move_uploaded_file() перезапишет его. <? /* В версиях PHP, более ранних, чем 4.1.0, вместо массива $_FILES нужно использовать массив $HTTP_POST_FILES */ $uploaddir = 'c:/uploads/'; // будем сохранять загружаемые // файлы в эту директорию $destination = $uploaddir . $_FILES['myfile']['name']; // имя файла оставим неизменным print "<pre>"; if (move_uploaded_file( $_FILES['myfile']['tmp_name'], $destination)) { /* перемещаем файл из временной папки в выбранную директорию для хранения */ print "Файл успешно загружен <br>"; } else { echo "Произошла ошибка при загрузке файла. Некоторая отладочная информация:<br>"; print_r($_FILES); } print "</pre>"; ?> Заключение Подведем итоги. В этой лекции мы изучили, как создавать файлы с помощью языка PHP, как записывать данные в файлы посредством PHP, как считывать из них информацию различными способами, как проверять существование и доступность файла для записи и чтения. Кроме того, мы рассмотрели задачу загрузки файла на сервер и обсудили основные связанные с ней переменные и функции языка PHP. Базы данных и СУБД. Введение в SQLВ данной лекции мы рассмотрим основные понятия теории баз данных и познакомим читателей с системой управления базами данных MySql, способами работы с ней, ее особенностями и реализацией языка запросов SQL в этой СУБД. В основе приводимых в лекции примеров лежит информационная модель виртуального музея истории информатики. Эта модель есть набор коллекций описания исторических личностей, экспонатов музея (артефактов), статей и изображений. Базы данных: основные понятия В жизни мы часто сталкиваемся с необходимостью хранить какую-либо информацию, а потому часто имеем дело и с базами данных. Например, мы используем записную книжку для хранения номеров телефонов своих друзей и планирования своего времени. Телефонная книга содержит информацию о людях, живущих в одном городе. Все это своего рода базы данных. Ну а раз это базы данных, то посмотрим, как в них хранятся данные. Например, телефонная книга представляет собой таблицу (табл. 10.1). В этой таблице данные – это собственно номера телефонов, адреса и ФИО., т.е. строки «Иванов Иван Иванович», «32-43-12» и т.п., а названия столбцов этой таблицы, т.е. строки «ФИО», «Номер телефона» и «Адрес» задают смысл этих данных, их семантику. Иванов Иван Иванович 32-43-12 ул. Ленина, 12, 43 Ильин Федор Иванович 32-32-34 пр. Маркса, 32, 45 Теперь представьте, что записей в этой таблице не две, а две тысячи, вы занимаетесь созданием этого справочника и где-то произошла ошибка (например, опечатка в адресе). Видимо, тяжеловато будет найти и исправить эту ошибку вручную. Нужно воспользоваться какими-то средствами автоматизации. Для управления большим количеством данных программисты (не без помощи математиков) придумали системы управления базами данных (СУБД). По сравнению с текстовыми базами данных электронные СУБД имеют огромное число преимуществ, от возможности быстрого поиска информации, взаимосвязи данных между собой до использования этих данных в различных прикладных программах и одновременного доступа к данным нескольких пользователей. Для точности дадим определение базы данных, предлагаемое Глоссарий.ру База данных – это совокупность связанных данных, организованных по определенным правилам, предусматривающим общие принципы описания, хранения и манипулирования, независимая от прикладных программ. База данных является информационной моделью предметной области. Обращение к базам данных осуществляется с помощью системы управления базами данных (СУБД). СУБД обеспечивает поддержку создания баз данных, централизованного управления и организации доступа к ним различных пользователей. Итак, мы пришли к выводу, что хранить данные независимо от программ, так, что они связаны между собой и организованы по определенным правилам, целесообразно. Но вопрос, как хранить данные, по каким правилам они должны быть организованы, остался открытым. Способов существует множество (кстати, называются они моделями представления или хранения данных). Наиболее популярные – объектная и реляционная модели данных. Автором реляционной модели считается Э. Кодд, который первым предложил использовать для обработки данных аппарат теории множеств (объединение, пересечение, разность, декартово произведение) и показал, что любое представление данных сводится к совокупности двумерных таблиц особого вида, известного в математике как отношение. Таким образом, реляционная база данных представляет собой набор таблиц (точно таких же, как приведенная выше), связанных между собой. Строка в таблице соответствует сущности реального мира (в приведенном выше примере это информация о человеке). Примеры реляционных СУБД: MySql, PostgreSql. В основу объектной модели положена концепция объектно-ориентированного программирования, в которой данные представляются в виде набора объектов и классов, связанных между собой родственными отношениями, а работа с объектами осуществляется с помощью скрытых (инкапсулированных) в них методов. Примеры объектных СУБД: Cache, GemStone (от Servio Corporation), ONTOS (ONTOS). В последнее время производители СУБД стремятся соединить два этих подхода и проповедуют объектно-реляционную модель представления данных. Примеры таких СУБД – IBM DB2 for Common Servers, Oracle8. Поскольку мы собираемся работать с Mysql, то будем обсуждать аспекты работы только с реляционными базами данных. Нам осталось рассмотреть еще два важных понятия из этой области: ключи и индексирование, после чего мы сможем приступить к изучению языка запросов SQL. Ключи Для начала давайте подумаем над таким вопросом: какую информацию нужно дать о человеке, чтобы собеседник точно сказал, что это именно тот человек, сомнений быть не может, второго такого нет? Сообщить фамилию, очевидно, недостаточно, поскольку существуют однофамильцы. Если собеседник человек, то мы можем приблизительно объяснить, о ком речь, например вспомнить поступок, который совершил тот человек, или еще как-то. Компьютер же такого объяснения не поймет, ему нужны четкие правила, как определить, о ком идет речь. В системах управления базами данных для решения такой задачи ввели понятие первичного ключа. Первичный ключ (primary key, PK) – минимальный набор полей, уникально идентифицирующий запись в таблице. Значит, первичный ключ – это в первую очередь набор полей таблицы, во-вторых, каждый набор значений этих полей должен определять единственную запись (строку) в таблице и, в-третьих, этот набор полей должен быть минимальным из всех обладающих таким же свойством. Поскольку первичный ключ определяет только одну уникальную запись, то никакие две записи таблицы не могут иметь одинаковых значений первичного ключа. Например, в нашей таблице (см. выше) ФИО и адрес позволяют однозначно выделить запись о человеке. Если же говорить в общем, без связи с решаемой задачей, то такие знания не позволяют точно указать на единственного человека, поскольку существуют однофамильцы, живущие в разных городах по одному адресу. Все дело в границах, которые мы сами себе задаем. Если считаем, что знания ФИО, телефона и адреса без указания города для наших целей достаточно, то все замечательно, тогда поля ФИО и адрес могут образовывать первичный ключ. В любом случае проблема создания первичного ключа ложится на плечи того, кто проектирует базу данных (разрабатывает структуру хранения данных). Решением этой проблемы может стать либо выделение характеристик, которые естественным образом определяют запись в таблице (задание так называемого логического, или естественного, PK), либо создание дополнительного поля, предназначенного именно для однозначной идентификации записей в таблице (задание так называемого суррогатного, или искусственного, PK). Примером логического первичного ключа является номер паспорта в базе данных о паспортных данных жителей или ФИО и адрес в телефонной книге (таблица выше). Для задания суррогатного первичного ключа в нашу таблицу можно добавить поле id (идентификатор), значением которого будет целое число, уникальное для каждой строки таблицы. Использование таких суррогатных ключей имеет смысл, если естественный первичный ключ представляет собой большой набор полей или его выделение нетривиально. Кроме однозначной идентификации записи, первичные ключи используются для организации связей с другими таблицами. Например, у нас есть три таблицы: содержащая информацию об исторических личностях (Persons), содержащая информацию об их изобретениях (Artifacts) и содержащая изображения как личностей, так и артефактов (Images) (рис 10.1). Первичным ключом во всех этих таблицах является поле id (идентификатор). В таблице Artifacts есть поле author, в котором записан идентификатор, присвоенный автору изобретения в таблице Persons. Каждое значение этого поля является внешним ключом для первичного ключа таблицы Persons. Кроме того, в таблицах Persons и Artifacts есть поле photo, которое ссылается на изображение в таблице Images. Эти поля также являются внешними ключами для первичного ключа таблицы Images и устанавливают однозначную логическую связь Persons-Images и Artifacts-Images. То есть если значение внешнего ключа photo в таблице личности равно 10, то это значит, что фотография этой личности имеет id=10 в таблице изображений. Таким образом, внешние ключи используются для организации связей между таблицами базы данных (родительскими и дочерними) и для поддержания ограничений ссылочной целостности данных. Рис. 10.1. Пример использования первичных ключей для организации связей с другими таблицами Индексирование Одна из основных задач, возникающих при работе с базами данных, – это задача поиска. При этом, поскольку информации в базе данных, как правило, содержится много, перед программистами встает задача не просто поиска, а эффективного поиска, т.е. поиска за сравнительно небольшое время и с достаточной точностью. Для этого (для оптимизации производительности запросов) производят индексирование некоторых полей таблицы. Использовать индексы полезно для быстрого поиска строк с указанным значением одного столбца. Без индекса чтение таблицы осуществляется по всей таблице, начиная с первой записи, пока не будут найдены соответствующие строки. Чем больше таблица, тем больше накладные расходы. Если же таблица содержит индекс по рассматриваемым столбцам, то база данных может быстро определить позицию для поиска в середине файла данных без просмотра всех данных. Это происходит потому, что база данных помещает проиндексированные поля поближе в памяти, так, чтобы можно было побыстрее найти их значения. Для таблицы, содержащей 1000 строк, это будет как минимум в 100 раз быстрее по сравнению с последовательным перебором всех записей. Однако в случае, когда необходим доступ почти ко всем 1000 строкам, быстрее будет последовательное чтение, так как при этом не требуется операций поиска по диску. Так что иногда индексы бывают только помехой. Например, если копируется большой объем данных в таблицу, то лучше не иметь никаких индексов. Однако в некоторых случаях требуется задействовать сразу несколько индексов (например, для обработки запросов к часто используемым таблицам). Если говорить о MySQL, то там существует три вида индексов: PRIMARY, UNIQUE, и INDEX, а слово ключ (KEY) используется как синоним слова индекс (INDEX). Все индексы хранятся в памяти в виде B-деревьев. PRIMARY – уникальный индекс (ключ) с ограничением, что все индексированные им поля не могут иметь пустого значения (т.е. они NOT NULL). Таблица может иметь только один первичный индекс, но он может состоять из нескольких полей. UNIQUE – ключ (индекс), задающий поля, которые могут иметь только уникальные значения. INDEX – обычный индекс (как мы описали выше). В MySqL, кроме того, можно индексировать строковые поля по заданному числу символов от начала строки. СУБД MySQL Продолжим разговор о СУБД MySQL. MySQL – это реляционная система управления базами данных. То есть данные в ее базах хранятся в виде логически связанных между собой таблиц, доступ к которым осуществляется с помощью языка запросов SQL. MySQL – свободно распространяемая система, т.е. платить за ее применение не нужно. Кроме того, это достаточно быстрая, надежная и, главное, простая в использовании СУБД, вполне подходящая для не слишком глобальных проектов. Работать с MySQL можно не только в текстовом режиме, но и в графическом. Существует очень популярный визуальный интерфейс (кстати, написанный на PHP) для работы с этой СУБД. Называется он PhpMyAdmin. Этот интерфейс позволяет значительно упростить работу с базами данных в MySQL. В текстовом режиме работа с базой данных выглядит просто как ввод команд в командную строку (рис 10.2), а результаты выборок возвращаются в виде своеобразных таблиц, поля в которых налезают друг на друга, если данные не помещаются на экран (рис 10.3). Рис. 10.2. Работа с MySQL в коммандной строке. Команда show databases — вывести все имеющиеся базы данных PhpMyAdmin позволяет пользоваться всеми достоинствами браузера, включая прокрутку изображения, если оно не умещается на экран. Многие из базовых SQL-функций работы с данными в PhpMyAdmin сведены к интуитивно понятным интерфейсам и действиям, напоминающим переход по ссылкам в Internet. Но тем не менее стоит все же поработать и в текстовом режиме. Рис. 10.3. Работа с MySQL в коммандной строке. Результат обработки команды show databases Перед тем как переходить к детальному изучению языка SQL, несколько слов об установке MySQL и подготовке к работе. Если вы не собираетесь заниматься администрированием сервера, то информация, приведенная ниже, пригодится вам только для общего развития. Итак, устанавливается MySQL очень просто – автоматически, пару раз нажмите OK, и все. После этого вы можете зайти в директорию, где лежат файлы типа mysql.exe, mysqld.exe и т.п. (у нас под Windows XP это c:\mysql\bin) Последний файл запускает Mysql-сервер. В некоторых системах сервер запускается в виде сервиса. После запуска сервера следует запустить mysql-клиент, запустив программу mysql.exe. Здесь даже пароля не спросят. Более того, если вы наберете shell> mysql.exe -u root или shell>mysql -u root mysql то получите все права администратора mysql сервера. Кстати, выполнять эти команды надо, находясь в той директории, где лежат файлы mysql.exe. Для начала, не вдаваясь в подробности команд, исправим эти два недочета (отсутствие пароля у администратора и возможность входа анонимным пользователям): shell> mysql -u root mysql mysql> UPDATE user SET Password=PASSWORD('new_password') WHERE user='root'; mysql> DELETE FROM user WHERE user=''; mysql> FLUSH PRIVILEGES; Все данные о пользователях MySQL хранит в таблице user в специальной базе данных mysql, доступ к которой имеет только администратор сервера. Поэтому, чтобы изменить какой-либо пароль, нужно изменить эту таблицу. Пароль задается с помощью функции PASSWORD, которая кодирует введенные данные. Кроме изменения пароля администратора, нужно еще удалить всех пользователей, не имеющих логина (команда DELETE). Команда Flush Privileges заставляет вступить в действие изменения, произошедшие в системной базе данных (mysql). Теперь создадим базу данных, с которой будем работать (мы все еще работаем как администратор сервера): mysql>create database book; Как можно заметить, все команды в MySQL заканчиваются точкой с запятой. Если вы забыли поставить этот знак, то выдается приглашение его поставить до тех пор, пока это не будет сделано: mysql> show tables -> -> Теперь последнее действие – создадим простого пользователя, предоставим ему доступ к созданной базе данных, и начнем работать. mysql> GRANT ALL PRIVILEGES ON book.* TO nina@localhost IDENTIFIED BY '123'; Команда GRANT наделяет пользователя nina, зашедшего на сервер с этой же машины (c localhost) и идентифицируемого паролем «123», определенными правами (в данном случае всеми) на все таблицы базы данных book. Теперь мы можем выйти и зайти как пользователь nina с соответствующим паролем: shell>mysql -u nina -p Enter password: *** Welcome to the MySQL monitor!... mysql> Если вы собираетесь пользоваться базой данных на чужом сервере, то его администратор проделает все описанные выше действия за вас, т.е. все настроит и создаст пользователя и базу данных. В следующей главе описаны команды языка SQL, которые пригодятся для работы с данными, хранящимися в СУБД MySQL. Язык SQL Итак, мы в общих чертах познакомились с основными понятиями теории баз данных, установили и настроили для работы MySQL. Теперь самое время научиться манипулировать данными, хранящимися в базах данных. Для этого нам понадобится SQL – структурированный язык запросов. Этот язык дает возможность создавать, редактировать и удалять информацию, хранящуюся в базах данных, создавать новые базы данных и многое другое. SQL является стандартом ANSI (Американский национальный институт стандартов) и ISO (Международная организация по стандартизации). Немного истории Первый международный стандарт языка SQL был принят в 1989 г., его часто называют SQL/89. Среди недостатков этого стандарта выделяют в первую очередь то, что многие важные свойства он устанавливал как определяемые в реализации. Отсюда произошло множество расхождений в реализациях языка разными производителями. Кроме того, высказывались претензии по поводу отсутствия в этом стандарте упоминаний о практических аспектах языка, таких как его встраивание в язык программирования Си. Следующий международный стандарт языка SQL был принят в конце 1992 г. И стал называться SQL/92. Он получился гораздо более точным и полным, чем SQL/89, хотя и не был лишен недостатков. В настоящее время большинство систем почти полностью реализуют этот стандарт. Однако, как известно, прогресс не остановишь, и в 1999 году появился новый стандарт SQL:1999, также известный как SQL3. SQL3 характеризуется как «объектно-ориентированный SQL» и является основой нескольких объектно-реляционных систем управления базами данных (например, ORACLE8 компании Oracle, Universal Server компании Informix и DB2 Universal Database компании IBM). Этот стандарт является не просто слиянием SQL-92 и объектной технологии. Он содержит ряд расширений традиционного SQL, а сам документ составлен таким образом, чтобы добиться более эффективной работы в области стандартизации в будущем. Если говорить о MySQL, то она соответствует начальному уровню SQL92, содержит несколько расширений этого стандарта и стремится к полной поддержке стандарта ANSI SQL99, но без ущерба для скорости и качества кода. Далее, говоря об основах языка SQL, будем придерживаться его реализации в СУБД MySQL. Основные операторы языка SQL Функции любой СУБД включают: - создание, удаление, изменение базы данных (БД); - добавление, изменение, удаление, назначение прав пользователя; - внесение, удаление и изменение данных в БД (таблиц и записей); - выборку данных из БД. К первым двум функциям имеют доступ только администраторы СУБД или привилегированные пользователи. Рассмотрим, как решаются последние две задачи (на самом деле это семь задач). Прежде чем что-либо делать с данными, нужно создать таблицы, в которых эти данные будут храниться, научиться изменять структуру этих таблиц и удалять их, если потребуется. Для этого в языке SQL существуют операторы CREATE TABLE, ALTER TABLE и DROP TABLE. Оператор CREATE TABLE Оператор CREATE TABLE создает таблицу с заданным именем в текущей базе данных. Правила для допустимых имен таблицы приведены в документации. Если нет активной текущей базы данных или указанная таблица уже существует, то возникает ошибка выполнения команды. В версии MySQL 3.22 и более поздних имя таблицы может быть указано как имя_базы_данных.имя_таблицы. Эта форма записи работает независимо от того, является ли указанная база данных текущей. В версии MySQL 3.23 при создании таблицы можно использовать ключевое слово TEMPORARY. Временная таблица автоматически удаляется по завершении соединения, а ее имя действительно только в течение данного соединения. Это означает, что в двух разных соединениях могут использоваться временные таблицы с одинаковыми именами без конфликта друг с другом или с существующей таблицей с тем же именем (существующая таблица скрыта, пока не удалена временная таблица). В версии MySQL 4.0.2 для создания временных таблиц необходимо иметь привилегии CREATE TEMPORARY TABLES. В версии MySQL 3.23 и более поздних можно использовать ключевые слова IF NOT EXISTS для того, чтобы не возникала ошибка, если указанная таблица уже существует. Следует учитывать, что при этом идентичность структур этих таблиц не проверяется. Каждая таблица представлена набором определенных файлов в директории базы данных. Синтаксис CREATE [TEMPORARY] TABLE [IF NOT EXISTS] имя_таблицы [(определение_столбца,...)] [опции_таблицы] [select_выражение] В выражении определение_столбца перечисляют, какие столбцы должны быть созданы в таблице. Каждый столбец таблицы может быть пустым (NULL), иметь значение по умолчанию, являться ключом или автоинкрементом. Кроме того, для каждого столбца обязательно указывается тип данных, которые будут в нем храниться. Если не указывается ни NULL, ни NOT NULL, то столбец интерпретируется так, как будто указано NULL. Если поле помечают как автоинкремент (AUTO_INCREMENT), то его значение автоматически увеличивается на единицу каждый раз, когда происходит добавление данных в таблицу и в это поле записывается пустое значение (NULL, т.е. ничего не записывается) или 0. Автоинкремент в таблице может быть только один, и при этом он обязательно должен быть проиндексирован. Последовательность AUTO_INCREMENT начинается с 1. Наличие автоинкремента является одной из особенностей MySQL. Формально описание столбца (определение_столбца) выглядит так: имя_столбца тип [NOT NULL | NULL] [DEFAULT значение_по_умолчанию] [AUTO_INCREMENT][PRIMARY KEY] [reference_definition] Тип столбца (тип в выражении определение_столбца) может быть одним из следующих: - целый: INT[(length)] [UNSIGNED] [ZEROFILL] - действительный: REAL[(length,decimals)] [UNSIGNED] [ZEROFILL] - символьный: CHAR(length) [BINARY] и VARCHAR(length) [BINARY] - дата и время: DATE и TIME - для работы с большими объектами: BLOB - текстовый: TEXT - перечислимое множество: ENUM(value1,value2,value3,...) и SET(value1,value2,value3,...) Полный список типов смотрите в документации MySQL. Вместо перечисления столбцов и их свойств в определении_столбца можно задавать списки ключевых и индексных полей, ограничения и проверки: PRIMARY KEY (имя_индексируемого_столбца, ...) или KEY [имя_индекса] (имя_индексируемого_столбца,...) или INDEX [имя_индекса] (имя_индексируемого_столбца,...) или UNIQUE [INDEX] [имя_индекса] (имя_индексируемого_столбца,...) или FULLTEXT [INDEX] [имя_индекса] (имя_индексируемого_столбца,...) или [CONSTRAINT symbol] FOREIGN KEY [имя_индекса] (имя_индексируемого_столбца,...) [reference_definition] или CHECK (expr) При задании всех этих элементов указывается список полей (столбцов), которые будут входить в индекс, ключ или ограничение, имя_индексируемого_столбца записывается следующим образом: имя_столбца [(длина_индекса)] FOREIGN KEY, CHECK и REFERENCES на самом деле ничего не делают в MySQL. Они добавлены только для совместимости с другими SQL-серверами. Поэтому на них мы останавливаться не будем. Кроме всего перечисленного, при создании таблицы можно указать некоторые ее свойства (опции_таблицы), например такие: - тип таблицы: TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM } - начальное значение счетчика автоинкремента: AUTO_INCREMENT = число - средняя длина строк в таблице: AVG_ROW_LENGTH = число - комментарии к таблице (строка из 60 символов): COMMENT = "строка" - максимальное и минимальное предполагаемое число строк: MAX_ROWS = число и MIN_ROWS = число И последний (опять же опциональный) элемент команды CREATE – это выражение SELECT (select_выражение). Синтаксис такой: [IGNORE | REPLACE] SELECT ... (любое корректное выражение SELECT) Если при создании таблицы в команде CREATE указывается выражение SELECT, то все поля, полученные выборкой, добавляются в создаваемую таблицу. Пример 10.1. Создадим таблицу Persons, структура которой была приведена на рисунке 10.1. mysql>CREATE TABLE Persons (id INT PRIMARY KEY AUTO_INCREMENT, first_name VARCHAR(50), last_name VARCHAR(100), death_date INT, description TEXT, photo INT, citienship CHAR(50) DEFAULT 'Russia'); С помощью специфичной для MySql команды SHOW можно просмотреть существующие базы данных, таблицы в базе данных и поля в таблице. Показать все базы данных: mysql>SHOW databases; Сделать текущей базу данных book и показать все таблицы в ней: mysql>use book; mysql>show tables; Показать все столбцы в таблице Persons: mysql> show columns from Persons; Оператор DROP TABLE Оператор DROP TABLE удаляет одну или несколько таблиц. Все табличные данные и определения удаляются, так что при работе с этой командой следует соблюдать осторожность. Синтаксис: DROP TABLE [IF EXISTS] имя_таблицы [, имя_таблицы,...] [RESTRICT | CASCADE] В версии MySQL 3.22 и более поздних можно использовать ключевые слова IF EXISTS, чтобы предупредить ошибку, если указанные таблицы не существуют. Опции RESTRICT и CASCADE позволяют упростить перенос программы с других СУБД. В данный момент они не задействованы. mysql> DROP TABLE IF EXISTS Persons, Artifacts, test; Оператор ALTER TABLE Оператор ALTER TABLE обеспечивает возможность изменять структуру существующей таблицы. Например, можно добавлять или удалять столбцы, создавать или уничтожать индексы или переименовывать столбцы либо саму таблицу. Можно также изменять комментарий для таблицы и ее тип. Синтаксис: ALTER [IGNORE] TABLE имя_таблицы alter_specification [, alter_specification ...] Можно производить следующие изменения в таблице (все они записываются в alter_specification): ADD [COLUMN] определение_столбца [FIRST | AFTER имя_столбца ] или ADD [COLUMN] (определение_столбца, определение_столбца,...) Здесь, как и далее, определение_столбца записывается так же, как при создании таблицы. ADD INDEX [имя_индекса] (имя_индексируемого_столбца,...) или ADD PRIMARY KEY (имя_индексируемого_столбца,...) или ADD UNIQUE [имя_индекса] (имя_индексируемого_столбца,...) или ADD FULLTEXT [имя_индекса] (имя_индексируемого_столбца,...) ALTER [COLUMN] имя_столбца {SET DEFAULT literal | DROP DEFAULT} или CHANGE [COLUMN] старое_имя_столбца определение_столбца или MODIFY [COLUMN] определение_столбца DROP [COLUMN] имя_столбца DROP PRIMARY KEY DROP INDEX имя_индекса RENAME [TO] новое_имя_таблицы ORDER BY поле или опции_таблицы Если оператор ALTER TABLE используется для изменения определения типа столбца, но DESCRIBE имя_таблицы показывает, что столбец не изменился, то, возможно, MySQL игнорирует данную модификацию по одной из причин, описанных в специальном разделе документации. Например, при попытке изменить столбец VARCHAR на CHAR MySQL будет продолжать использовать VARCHAR, если данная таблица содержит другие столбцы с переменной длиной. Оператор ALTER TABLE во время работы создает временную копию исходной таблицы. Требуемое изменение выполняется на копии, затем исходная таблица удаляется, а новая переименовывается. Это делается для того, чтобы в новую таблицу автоматически попадали все обновления, кроме неудавшихся. Во время выполнения ALTER TABLE исходная таблица доступна для чтения другими клиентами. Операции обновления и записи в этой таблице приостанавливаются, пока не будет готова новая таблица. Следует отметить, что при использовании любой другой опции для ALTER TABLE, кроме RENAME, MySQL всегда будет создавать временную таблицу, даже если данные, строго говоря, и не нуждаются в копировании (например, при изменении имени столбца). Пример10.3. Добавим в созданную таблицу Persons поле для записи года рождения человека: mysql> ALTER TABLE Persons ADD bday INTEGER AFTER last_name; Итак, мы научились работать с таблицами: создавать, удалять и изменять их. Теперь разберемся, как делать то же самое с данными, которые в этих таблицах хранятся. Оператор SELECT Оператор SELECT применяется для извлечения строк, выбранных из одной или нескольких таблиц. То есть с его помощью мы задаем столбцы или выражения, которые надо извлечь (select_выражения), таблицы (table_references), из которых должна производиться выборка, и, возможно, условие (where_definition), которому должны соответствовать данные в этих столбцах, и порядок, в котором эти данные нужно выдать. Кроме того, оператор SELECT можно использовать для извлечения строк, вычисленных без ссылки на какую-либо таблицу. Например, чтобы вычислить, чему равно 2*2, нужно просто написать mysql> SELECT 2*2; Упрощенно структуру оператора SELECT можно представить следующим образом: SELECT select_выражение1, select_выражение2, ... [FROM table_references [WHERE where_definition] [ORDER BY {число | имя_столбца | формула} [ASC | DESC], ...]] Квадратные скобки [ ] означают, что использование находящегося в них оператора необязательно, вертикальная черта | означает перечисление возможных вариантов. После ключевого слова ORDER BY указывают имя столбца, число (целое беззнаковое) или формулу и способ упорядочения (по возрастанию – ASC, или по убыванию – DESC). По умолчанию используется упорядочение по возрастанию. Когда в select_выражении мы пишем «*», это значит выбрать все столбцы. Кроме «*» в select_выражения могут использоваться функции типа max, min и avg. Пример 10.4. Выбрать из таблицы Persons все данные, для которых поле first_name имеет значение 'Александр': mysql> SELECT * FROM Persons WHERE first_name='Александр'; Выбрать название и описание (title, description) артефакта под номером 10: mysql> SELECT title,description FROM Artifacts WHERE id=10; Оператор INSERT Оператор INSERT вставляет новые строки в существующую таблицу. Оператор имеет несколько форм. Параметр имя_таблицы во всех этих формах задает таблицу, в которую должны быть внесены строки. Столбцы, для которых задаются значения, указываются в списке имен столбцов (имя_столбца) или в части SET. Синтаксис: INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] имя_таблицы [(имя_столбца,...)] VALUES (выражение,...),(...),... Эта форма команды INSERT вставляет строки в соответствии с точно указанными в команде значениями. В скобках после имени таблицы перечисляются столбцы, а после ключевого слова VALUES – их значения. Например: mysql> INSERT INTO Persons (last_name, bday) VALUES ('Иванов', '1934'); вставит в таблицу Persons строку, в которой значения фамилии (last_name) и даты рождения (bday) будут заданы соответственно как «Иванов» и «1934». INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] имя_таблицы [(имя_столбца,...)] SELECT ... Эта форма команды INSERT вставляет строки, выбранные из другой таблицы или таблиц. Например: mysql> INSERT INTO Artifacts (author) SELECT id FROM Persons WHERE last_name='Иванов' AND bday='1934'; вставит в таблицу Artifacts в поле «автор» (author) значение идентификатора, выбранного из таблицы Persons по условию, что фамилия человека Иванов. INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] имя_таблицы SET имя_столбца=выражение, имя_столбца=выражение, ... Например: mysql> INSERT INTO Persons SET last_name='Петров', first_name='Иван'; Эта команда вставит в таблицу Persons в поле last_name значение «Петров», а в поле first_name – строку «Иван». - Форма INSERT ... VALUES со списком из нескольких значений поддерживается в версии MySQL 3.22.5 и более поздних. Синтаксис выражения имя_столбца=выражение поддерживается в версии MySQL 3.22.10 и более поздних. Действуют следующие соглашения. - Если не указан список столбцов для INSERT ... VALUES или INSERT ... SELECT, то величины для всех столбцов должны быть определены в списке VALUES() или в результате работы SELECT. Если порядок столбцов в таблице неизвестен, для его получения можно использовать DESCRIBE имя_таблицы. - Любой столбец, для которого явно не указано значение, будет установлен в свое значение по умолчанию. Например, если в заданном списке столбцов не указаны все столбцы в данной таблице, то не упомянутые столбцы устанавливаются в свои значения по умолчанию. mysql> INSERT INTO имя_таблицы (col1,col2) VALUES(15,col1*2); Но нельзя указать: mysql> INSERT INTO имя_таблицы (col1,col2) VALUES(col2*2,15); Мы еще не обсудили три необязательных параметра, присутствующих во всех трех формах команды: LOW_PRIORITY, DELAYED и IGNORE. Параметры LOW_PRIORITY и DELAYED используются, когда с таблицей работает большое число пользователей. Они предписывают устанавливать приоритет данной операции перед операциями других пользователей. Если указывается ключевое слово LOW_PRIORITY, то выполнение данной команды INSERT будет задержано до тех пор, пока другие клиенты не завершат чтение этой таблицы. В этом случае клиент должен ожидать, пока данная команда вставки не будет завершена, что в случае интенсивного использования таблицы может потребовать значительного времени. В противоположность этому команда INSERT DELAYED позволяет данному клиенту продолжать операцию сразу же, независимо от других пользователей. Если в команде INSERT указывается ключевое слово IGNORE, то все строки, имеющие дублирующиеся ключи PRIMARY или UNIQUE в этой таблице, будут проигнорированы и не внесены в таблицу. Если не указывать IGNORE, то данная операция вставки прекращается при обнаружении строки, имеющей дублирующееся значение существующего ключа. Оператор UPDATE Синтаксис: UPDATE [LOW_PRIORITY] [IGNORE] имя_таблицы SET имя_столбца1=выражение1 [, имя_столбца2=выражение2, ...] [WHERE where_definition] [LIMIT число] Оператор UPDATE обновляет значения существующих столбцов таблицы в соответствии с введенными значениями. В выражении SET указывается, какие именно столбцы следует модифицировать и какие величины должны быть в них установлены. В выражении WHERE, если оно присутствует, задается, какие строки подлежат обновлению. В остальных случаях обновляются все строки. Если задано выражение ORDER BY, то строки будут обновляться в указанном в нем порядке. Если указывается ключевое слово LOW_PRIORITY, то выполнение данной команды UPDATE задерживается до тех пор, пока другие клиенты не завершат чтение этой таблицы. Если указывается ключевое слово IGNORE, то команда обновления не будет прервана, даже если возникнет ошибка дублирования ключей. Строки, из-за которых возникают конфликтные ситуации, обновлены не будут. Если в выражении, которое задает новое значение столбца, используется имя этого поля, то команда UPDATE использует для этого столбца его текущее значение. Например, следующая команда устанавливает столбец death_date в значение, на единицу большее его текущей величины: mysql> UPDATE Persons SET death_date=death_date+1; В версии MySQL 3.23 можно использовать параметр LIMIT #, чтобы убедиться, что было изменено только заданное количество строк. Например, такая операция заменит в первой строке нашей таблицы экспонатов название title на строку «Ламповая ЭВМ»: mysql> UPDATE Artifacts SET title='Ламповая ЭВМ' Limit 1; Оператор DELETE Оператор DELETE удаляет из таблицы имя_таблицы строки, удовлетворяющие заданным в where_definition условиям, и возвращает число удаленных записей. Если оператор DELETE запускается без определения WHERE, то удаляются все строки. Синтаксис: DELETE [LOW_PRIORITY] FROM имя_таблицы [WHERE where_definition] [LIMIT rows] Например, следующая команда удалит из таблицы Persons все записи, у которых поле «год рождения» (bday) больше 2003: mysql> DELETE FROM Persons WHERE bday>2003; Удалить все записи в таблице можно еще и с помощью такой команды: mysql> DELETE FROM Persons WHERE 1>0; Но этот метод работает гораздо медленнее, чем использование той же команды без условия: mysql> DELETE FROM Persons; Специфическая для MySQL опция LIMIT для команды DELETE указывает серверу максимальное количество строк, которые следует удалить до возврата управления клиенту. Эта опция может использоваться для гарантии того, что данная команда DELETE не потребует слишком много времени для выполнения. Заключение Итак, мы разобрались с основами реляционных баз данных, научились создавать простые и не очень SQL-запросы. Надеюсь, что большое количество технических деталей не помешало читателям получить представление о базовых элементах языка, поскольку все это наверняка пригодится для решения практических задач. Взаимодействие PHP и MySQLВ дистрибутив PHP входит расширение, содержащее встроенные функции для работы с базой данных MySQL. В этой лекции мы познакомимся с некоторыми основными функциями для работы с MySQL, которые потребуются для решения задач построения web-интерфейсов с целью отображения и наполнения базы данных. Возникает вопрос, зачем строить такие интерфейсы? Для того чтобы вносить информацию в базу данных и просматривать ее содержимое могли люди, не знакомые с языком запросов SQL. При работе с web-интерфейсом для добавления информации в базу данных человеку нужно просто ввести эти данные в html-форму и отправить их на сервер, а наш скрипт сделает все остальное. А для просмотра содержимого таблиц достаточно просто щелкнуть по ссылке и зайти на нужную страницу. Для наглядности будем строить эти интерфейсы для таблицы Artifacts, в которой содержится информация об экспонатах виртуального музея информатики. В предыдущей лекции мы уже приводили структуру этой коллекции, а также ее связи с коллекциями описания персон (Persons) и изображений (Images). Напомним, что каждый экспонат в коллекции Artifacts описывается с помощью следующих характеристик: - название (title); - автор (author); - описание (description); - альтернативное название (alternative); - изображение (photo). Название и альтернативное название являются строками менее чем 255 символов длиной (т.е. имеют тип VARCHAR(255)), описание - текстовое поле (имеет тип TEXT), а в полях "автор" и "изображение" содержатся идентификаторы автора из коллекции Persons и изображения экспоната из коллекции Images соответственно. Построение интерфейса для добавления информации Итак, у нас есть какая-то таблица в базе данных. Чтобы построить интерфейс для добавления информации в эту таблицу, нужно ее структуру (т.е. набор ее полей) отобразить в html-форму. Разобьем эту задачу на следующие подзадачи: - установка соединения с БД; - выбор рабочей БД; - получение списка полей таблицы; - отображение полей в html-форму. После этого данные, введенные в форму, нужно записать в базу данных. Рассмотрим все эти задачи по порядку. Установка соединения Итак, первое, что нужно сделать, - это установить соединение с базой данных. Воспользуемся функцией mysql_connect. Синтаксис mysql_connect ресурс mysql_connect ( [строка server [, строка username [, строка password [, логическое new_link [, целое client_flags]]]]]) Данная функция устанавливает соединение с сервером MySQL и возвращает указатель на это соединение или FALSE в случае неудачи. Для отсутствующих параметров устанавливаются следующие значения по умолчанию: server = 'localhost:3306' username = имя пользователя владельца процесса сервера password = пустой пароль Если функция вызывается дважды с одними и теми же параметрами, то новое соединение не устанавливается, а возвращается ссылка на старое соединение. Чтобы этого избежать, используют параметр new_link, который заставляет в любом случае открыть еще одно соединение. Параметр client_flags - это комбинация следующих констант: MYSQL_CLIENT_COMPRESS (использовать протокол сжатия), MYSQL_CLIENT_IGNORE_SPACE (позволяет вставлять пробелы после имен функций), MYSQL_CLIENT_INTERACTIVE (ждать interactive_timeout секунд - вместо wait_timeout - до закрытия соединения). Параметр new_link появился в PHP 4.2.0, а параметр client_flags - в PHP 4.3.0. Соединение с сервером закрывается при завершении исполнения скрипта, если оно до этого не было закрыто с помощью функции mysql_close(). Итак, устанавливаем соединение с базой данных на локальном сервере для пользователя nina с паролем "123": <? $conn = mysql_connect( "localhost", "nina","123") or die("Невозможно установить соединение: ". mysql_error()); echo "Соединение установлено"; mysql_close($conn); ?> Действие mysql_connect равносильно команде shell>mysql -u nina -p123 Выбор базы данных После установки соединения нужно выбрать базу данных, с которой будем работать. Наши данные хранятся в базе данных book. В MySQL выбор базы данных осуществляется с помощью команды use: mysql>use book; В PHP для этого существует функция mysql_select_db. Синтаксис mysql_select_db: логическое mysql_select_db ( строка database_name [, ресурс link_identifier]) Эта функция возвращает TRUE в случае успешного выбора базы данных и FALSE - в противном случае. Сделаем базу данных book рабочей: <? $conn = mysql_connect( "localhost","nina","123") or die("Невозможно установить соединение: ". mysql_error()); echo "Соединение установлено"; mysql_select_db("book"); ?> Получение списка полей таблицы Теперь можно заняться собственно решением задачи. Как получить список полей таблицы? Очень просто. В PHP и на этот случай есть своя команда - mysql_list_fields. Синтаксис mysql_list_fields ресурс mysql_list_fields ( строка database_name, строка table_name [, ресурс link_identifier]) Эта функция возвращает список полей в таблице table_name в базе данных database_name. Получается, что выбирать базу данных нам было необязательно, но это пригодится позже. Как можно заметить, результат работы этой функции - переменная типа ресурс. То есть это не совсем то, что мы хотели получить. Это ссылка, которую можно использовать для получения информации о полях таблицы, включая их названия, типы и флаги. Функция mysql_field_name возвращает имя поля, полученного в результате выполнения запроса. Функция mysql_field_len возвращает длину поля. Функция mysql_field_type возвращает тип поля, а функция mysql_field_flags возвращает список флагов поля, записанных через пробел. Типы поля могут быть int, real, string, blob и т.д. Флаги могут быть not_null, primary_key, unique_key, blob, auto_increment и т.д. Синтаксис у всех этих команд одинаков: строка mysql_field_name ( ресурс result, целое field_offset) строка mysql_field_type ( ресурс result, целое field_offset) строка mysql_field_flags ( ресурс result, целое field_offset) строка mysql_field_len ( ресурс result, целое field_offset) Здесь result - это идентификатор результата запроса (например, запроса, отправленного функциями mysql_list_fields или mysql_query (о ней будет рассказано позднее)), а field_offset - порядковый номер поля в результате. Вообще говоря, то, что возвращают функции типа mysql_list_fields или mysql_query, представляет собой таблицу, а точнее, указатель на нее. Чтобы получить из этой таблицы конкретные значения, нужно задействовать специальные функции, которые построчно читают эту таблицу. К таким функциям и относятся mysql_field_name и т.п. Чтобы перебрать все строки в таблице результата выполнения запроса, нужно знать число строк в этой таблице. Команда mysql_num_rows(ресурс result) возвращает число строк во множестве результатов result. А теперь попробуем получить список полей таблицы Artifacts (коллекция экспонатов). <? $conn = mysql_connect( "localhost","nina","123") or die("Невозможно установить соединение: ". mysql_error()); echo "Соединение установлено"; mysql_select_db("book"); $list_f = mysql_list_fields ( "book","Artifacts",$conn); $n = mysql_num_fields($list_f); for($i=0;$i<$n; $i++){ $type = mysql_field_type($list_f, $i); $name_f = mysql_field_name($list_f,$i); $len = mysql_field_len($list_f, $i); $flags_str = mysql_field_flags ( $list_f, $i); echo "<br>Имя поля: ". $name_f; echo "<br>Тип поля: ". $type; echo "<br>Длина поля: ". $len; echo "<br>Строка флагов поля: ". $flags_str . "<hr>"; } ?> В результате должно получиться примерно вот что (если в таблице всего два поля, конечно): Имя поля: id Тип поля: int Длина поля: 11 Строка флагов поля: not_null primary_key auto_increment Имя поля: title Тип поля: string Длина поля: 255 Строка флагов поля: Отображение списка полей в html-форму Теперь немножко подкорректируем предыдущий пример. Будем не просто выводить информацию о поле, а отображать его в подходящий элемент html-формы. Так, элементы типа BLOB переведем в textarea (заметим, что поле description, которое мы создавали с типом TEXT, отображается как имеющее тип BLOB), числа и строки отобразим в текстовые строки ввода <input type=text>, а элемент, имеющий метку автоинкремента, вообще не будем отображать, поскольку его значение устанавливается автоматически. Все это решается довольно просто, за исключением выделения из списка флагов флага auto_increment. Для этого нужно воспользоваться функцией explode. Синтаксис explode: массив explode( строка separator, строка string [, int limit]) Эта функция разбивает строку string на части с помощью разделителя separator и возвращает массив полученных строк. В нашем случае в качестве разделителя нужно взять пробел " ", а в качестве исходной строки для разбиения - строку флагов поля. Итак, создадим форму для ввода данных в таблицу Artifacts: Запись данных в базу данных Итак, форма создана. Теперь нужно сделать самое главное - отправить данные из этой формы в нашу базу данных. Как вы уже знаете, для того чтобы записать данные в таблицу, используется команда INSERT языка SQL. Например: mysql> INSERT INTO Artifacts SET title='Петров'; Возникает вопрос, как можно воспользоваться такой командой (или любой другой командой SQL) в PHP скрипте. Для этого существует функция mysql_query(). Синтаксис mysql_query ресурс mysql_query ( строка query [, ресурс link_identifier]) mysql_query() посылает SQL-запрос активной базе данных MySQL сервера, который определяется с помощью указателя link_identifier (это ссылка на какое-то соединение с сервером MySQL). Если параметр link_identifier опущен, используется последнее открытое соединение. Если открытые соединения отсутствуют, функция пытается соединиться с СУБД, аналогично функции mysql_connect() без параметров. Результат запроса буферизируется. Замечание: строка запроса НЕ должна заканчиваться точкой с запятой. Только для запросов SELECT, SHOW, EXPLAIN, DESCRIBE, mysql_query() возвращает указатель на результат запроса, или FALSE, если запрос не был выполнен. В остальных случаях mysql_query() возвращает TRUE, если запрос выполнен успешно, и FALSE - в случае ошибки. Значение, не равное FALSE, говорит о том, что запрос был выполнен успешно. Оно не говорит о количестве затронутых или возвращенных рядов. Вполне возможна ситуация, когда успешный запрос не затронет ни одного ряда. mysql_query() также считается ошибочным и вернет FALSE, если у пользователя недостаточно прав для работы с указанной в запросе таблицей. Итак, теперь мы знаем, как отправить запрос на вставку строк в базу данных. Заметим, что в предыдущем примере элементы формы мы назвали именами полей таблицы. Поэтому они будут доступны в скрипте insert.php, обрабатывающем данные формы, как переменные вида $_POST['имя_поля']. Итак, задачу добавления данных с помощью web-интерфейса мы решили. Однако тут есть одна тонкость. При решении мы не учитывали тот факт, что значения некоторых полей (author, photo) должны браться из других таблиц (Persons, Images). Поскольку MySQL с внешними ключами не работает, этот момент остается на совести разработчиков системы, т.е. на нашей совести. Нужно дописать программу таким образом, чтобы была возможность вводить в такие поля правильные значения. Но мы делать этого не будем, поскольку задача лекции состоит в том, чтобы познакомить читателя с элементами технологии, а не в том, чтобы создать работающую систему. Кроме того, имеющихся у читателя знаний вполне достаточно, чтобы решить эту проблему самостоятельно. Мы же обратимся к другой задаче - отображение данных, хранящихся в базе данных СУБД MySQL. Отображение данных, хранящихся в MySQL Чтобы отобразить какие-то данные в браузер с помощью PHP, нужно сначала получить эти данные в виде переменных PHP. При работе с MySQL без посредника (такого, как PHP) выборка данных производится с помощью команды SELECT языка SQL: mysql> SELECT * FROM Artifacts; В предыдущей главе мы говорили, что любой запрос, в том числе и на выборку, можно отправить на сервер с помощью функции mysql_query(); Там у нас стояла немного другая задача - получить данные из формы и отправить их с помощью запроса на вставку в базу данных. Результатом работы mysql_query() там могло быть только одно из выражений, TRUE или FALSE. Теперь же требуется отправить запрос на выбор всех полей, а результат отобразить в браузере. И здесь результат - это целая таблица значений, а точнее, указатель на эту таблицу. Так что нужны какие-то аналоги функции mysql_field_name(), только чтобы они извлекали из результата запроса не имя, а значение поля. Таких функций в PHP несколько. Наиболее популярные - mysql_result() и mysql_fetch_array(). Синтаксис mysql_result смешанное mysql_result (ресурс result, целое row [, смешанное field]) mysql_result() возвращает значение одной ячейки результата запроса. Аргумент field может быть порядковым номером поля в результате, именем поля или именем поля с именем таблицы через точку tablename.fieldname. Если для имени поля в запросе применялся алиас ('select foo as bar from...'), используйте его вместо реального имени поля. Работая с большими результатами запросов, следует задействовать одну из функций, обрабатывающих сразу целый ряд результата (например, mysql_fetch_row(), mysql_fetch_array() и т.д.). Так как эти функции возвращают значение нескольких ячеек сразу, они НАМНОГО быстрее mysql_result(). Кроме того, нужно учесть, что указание численного смещения (номера поля) работает намного быстрее, чем указание колонки или колонки и таблицы через точку. Вызовы функции mysql_result() не должны смешиваться с другими функциями, работающими с результатом запроса. Синтаксис mysql_fetch_array массив mysql_fetch_array ( ресурс result [, целое result_type]) Эта функция обрабатывает ряд результата запроса, возвращая массив (ассоциативный, численный или оба) с обработанным рядом результата запроса, или FALSE, если рядов больше нет. mysql_fetch_array() - это расширенная версия функции mysql_fetch_row(). Помимо хранения значений в массиве с численными индексами, функция возвращает значения в массиве с индексами по названию колонок. Если несколько колонок в результате будут иметь одинаковые названия, будет возвращена последняя колонка. Чтобы получить доступ к первым, следует использовать численные индексы массива или алиасы в запросе. В случае алиасов именно их вы не сможете использовать настоящие имена колонок, как, например, не сможете использовать "photo" в описанном ниже примере. select Artifacts.photo as art_image, Persons.photo as pers_image from Artifacts, Persons Важно заметить, что mysql_fetch_array() работает НЕ медленнее, чем mysql_fetch_row(), и предоставляет более удобный доступ к данным. Второй опциональный аргумент result_type в функции mysql_fetch_array() является константой и может принимать следующие значения: MYSQL_ASSOC, MYSQL_NUM и MYSQL_BOTH. Эта возможность добавлена в PHP 3.0.7. Значением по умолчанию является: MYSQL_BOTH. Используя MYSQL_BOTH, получим массив, состоящий как из ассоциативных индексов, так и из численных. MYSQL_ASSOC вернет только ассоциативные соответствия, а MYSQL_NUM - только численные. Замечание: имена полей, возвращаемые этой функцией, регистрозависимы. Теперь отобразим данные из Artifacts в виде таблицы в браузере: Сделаем то же самое с помощью mysql_fetch_array(): Заключение В этой лекции мы решили две задачи: добавление данных в базу данных и их отображение в браузере с помощью языка PHP. Для этого мы рассмотрели ряд функций, которые позволяют отправлять SQL-запросы к базе данных и обрабатывать полученные ответы. Используя приведенную здесь технологию, можно решить целый ряд похожих задач, таких как задачи изменения и удаления данных, задачи манипулирования таблицами базы данных (т.е. их создание, изменение и удаление) и т.п. Все это типовые задачи, возникающие при разработке систем управления данными, и умение их решать, как и умение работать с базами данных в целом, очень важно для web-программиста. Авторизация доступа с помощью сессийВ этой лекции мы разберем, что такое сессии и в чем их специфика в PHP, решим одну из основных задач, возникающих при построении более-менее сложных информационных систем (сайтов) - задачу авторизации доступа пользователей к ресурсам системы, а также обсудим безопасность построенного решения. Авторизация доступа Что такое авторизация доступа? Попробуем объяснить на примере из обычной жизни. Вы хотите взять в библиотеке книгу. Но эта услуга доступна только тем, у кого есть читательский билет. Можно сказать, что с помощью этого билета производится "авторизация доступа" к библиотечным ресурсам. Библиотекарь после предъявления ему читательского билета знает, кто берет книгу, и в случае необходимости (например, книгу долго не возвращают) может принять меры (позвонить должнику домой). Библиотекарь имеет гораздо больше прав, чем обычный посетитель: он может давать или не давать книги определенному посетителю, может выставлять напоказ новинки и убирать в архив редко читаемые книги и т.п. В информационных технологиях все примерно так же. В сети существует огромное количество ресурсов, т.е. множество "библиотек". У каждой из них свой "библиотекарь", т.е. человек или группа людей, отвечающих за содержание ресурса и предоставление пользователям информации. Их называют администраторами. Функции администратора, как правило, включают добавление новой информации, удаление и редактирование существующей, настройка способов отображения информации пользователю. А в функции пользователя (простого посетителя ресурса) входит только поиск и просмотр информации. Как же отличить пользователя от администратора? В реальной библиотеке это как-то очевидно, но если роли библиотекаря и посетителя библиотеки перенести в виртуальную реальность, то эта очевидность исчезает. Библиотекарь, как и посетитель, имеет доступ к библиотечным ресурсам через Internet. А согласно протоколу HTTP все клиенты абсолютно равноправны. Как же понять, кто зашел на сайт? Обычный пользователь (посетитель) или администратор (библиотекарь)? Если это простой пользователь, то как сохранить это знание, чтобы не допустить посетителя в закрытые архивы сайта? То есть возникает вопрос, как идентифицировать клиента, который послал запрос, и сохранять сведения о нем, пока он находится на сайте? Самый простой вариант, который приходит в голову, - это регистрация человека в системе и выдача ему аналога читательского билета, а именно логина и пароля для входа в административную часть системы. Эта информация хранится на компьютере-сервере, и при входе в систему проверяется соответствие введенных пользователем логина и пароля тем, что хранятся в системе. Правда, здесь по сравнению с реальной библиотекой ситуация изменяется: читательский билет требуется библиотекарю для входа в закрытую часть системы, а читатель может заходить на сайт свободно. В принципе можно регистрировать и простых посетителей. Тогда всех зарегистрированных пользователей нужно разделить на группы: библиотекари (администраторы) и читатели (простые пользователи), наделив их соответствующими правами. Мы не будем вдаваться в эти тонкости и воспользуемся самым простым вариантом, когда ввод логина и пароля требуется для доступа к некоторым страницам сайта. Пример 12.1. У нас имеется файл index.html - домашняя страничка Васи Петрова <html> <head><title>My home page</title></head> <body> Привет всем! Меня зовут Вася Петров и это моя домашняя страничка. <a href="secret_info.html">Для Пети</a> </body></html> и файл secret_info.html, который содержит секретную информацию, читать которую разрешено только Васиному другу Пете. <html> <head><title>Secret info</title></head> <body> Здесь я хочу делиться секретами с другом Петей.</p> </body></html> Если оставить оба эти файла как есть, то любой посетитель, кликнув на ссылку "Для Пети", попадет на секретную страничку. Чтобы этого избежать, нужно добавить промежуточный скрипт, который будет проверять, действительно ли Петя хочет попасть на секретную страничку. И сделать так, чтобы главный файл ссылался не сразу на secret_info.html, а сначала на этот скрипт. <html> <head><title>My home page</title></head> <body> <p>Привет всем! Меня зовут Вася Петров и это моя домашняя страничка. </p> <a href="authorize.php">Для Пети</a> </body> </html> Сам скрипт авторизации должен предоставлять форму для ввода логина и пароля, проверять их правильность и перенаправлять на секретную страничку, если проверка прошла успешно, и выдавать сообщение об ошибке в противном случае. <? if (!isset($_GET['go'])){ // проверяем, отправлены ли данные формой echo "<form> // форма для авторизации //(ввода логина и пароля) Login: <input type=text name=login> Password: <input type=password name=passwd> <input type=submit name=go value=Go> </form>"; }else { // если форма заполнена, то сравниваем логин // и пароль с правильными логином и паролем if ($_GET['login']=="pit" && $_GET['passwd']=="123") { Header("Location: secret_info.html"); //и перенаправляем на секретную страницу }else echo "Неверный ввод, попробуйте еще раз<br>"; } ?> Вроде бы все достаточно просто. Но допустим, у нас не одна секретная страничка, а несколько. Причем они связаны между собой перекрестными ссылками. Тогда возникает необходимость постоянно помнить пароль и логин посетителя сайта (если он таковой имеет). Чтобы решить эту проблему, можно в каждую страницу встроить скрипт, который будет передавать логин и пароль от страницы к странице в качестве скрытых параметров формы. Но такой способ не совсем безопасен: эти параметры можно перехватить и подделать. В PHP существует более удобный и безопасный метод решения проблемы хранения данных о посетителе в течение сеанса его работы с сайтом - это механизм сессий. Механизм сессий Cессии - это механизм, который позволяет создавать и использовать переменные, сохраняющие свое значение в течение всего времени работы пользователя с сайтом. Эти переменные для каждого пользователя имеют различные значения и могут использоваться на любой странице сайта до выхода пользователя из системы. При этом каждый раз, заходя на сайт, пользователь получает новые значения переменных, позволяющие идентифицировать его в течение этого сеанса или сессии работы с сайтом. Отсюда и название механизма - сессии. Задача идентификации пользователя решается путем присвоения каждому пользователю уникального номера, так называемого идентификатора сессии (SID, Session IDentifier). Он генерируется PHP в тот момент, когда пользователь заходит на сайт, и уничтожается, когда пользователь уходит с сайта, и представляет собой строку из 32 символов (например, ac4f4a45bdc893434c95dcaffb1c1811). Этот идентификатор передается на сервер вместе с каждым запросом клиента и возвращается обратно вместе с ответом сервера. Существует несколько способов передачи идентификатора сессии: С помощью cookies. Cookies были созданы специально как метод однозначной идентификации клиентов и представляют собой расширение протокола HTTP. В этом случае идентификатор сессии сохраняется во временном файле на компьютере клиента, пославшего запрос. Метод, несомненно, хорош, но многие пользователи отключают поддержку cookies на своем компьютере из-за проблем с безопасностью. С помощью параметров командной строки. В этом случае идентификатор сессии автоматически встраивается во все запросы (URL), передаваемые серверу, и хранится на стороне сервера. Например: адрес http://green.nsu.ru/test.php превращается в адрес http://green.nsu.ru/test.php?PHPSESSID=ac4f4a45bdc893434c95dcaffb1c1811 Этот способ передачи идентификатора используется автоматически, если у браузера, отправившего запрос, выключены cookies. Он достаточно надежный - передавать параметры в командной строке можно всегда. С другой стороны, идентификатор сессии можно подглядеть, воспользоваться сохраненным вариантом в строке браузера или подделать. Хотя, конечно, все эти проблемы либо надуманны либо их можно решить. Например, кто сможет запомнить строку из 32 различных символов? А если правильно организовать работу с сессиями (вовремя их уничтожать), то даже сохранившийся в браузере номер сессии ничего не даст. К вопросам безопасности мы еще вернемся в конце лекции. Кроме перечисленных вариантов передачи идентификатора сессии, известно еще несколько, но мы их рассматривать не будем ввиду их сложности. Настройка сессий Прежде чем начать работать с сессиями, следует разобраться в том, как корректно настраивать их обработку интерпретатором PHP. Сама работа с сессиями в PHP поддерживается по умолчанию. Это значит, что устанавливать никаких дополнительных элементов не нужно. А вот знать, что записано в настройках этого модуля, полезно, чтобы избежать ошибок при работе с ним. Настройки PHP, в том числе и для работы с сессиями, прописываются в файле php.ini. Обратимся к этому файлу. Как мы уже знаем, идентификатор сессии (число, по которому можно уникально идентифицировать клиента, пославшего запрос) сохраняется либо на компьютере-сервере, либо на компьютере-клиенте, либо и там, и там. Параметр session.save_path в php.ini, определяет, где на сервере будут храниться данные сессии. Из-за него чаще всего возникают проблемы для Windows-серверов, потому что по умолчанию значение session.save_path установлено в /tmp. И если в корневой директории сервера такой папки нет, то при запуске сессий будет выдаваться ошибка. Сервер может обрабатывать большое количество сессий одновременно, и все их временные файлы будут храниться в директории, заданной параметром session.save_path. Если система плохо работает с папками большого размера, то удобно использовать поддиректории. Для этого, кроме названия папки, в значение параметра добавляют еще и число, определяющее глубину вложенности поддиректорий в этой папке: N;/dir. Это значение нужно обязательно взять в кавычки, поскольку точка с запятой является одним из символов комментариев в файле настроек PHP. Все директории и поддиректории для хранения данных сессии нужно создать самостоятельно. Например: 2;/Temp определяет, что переменные сессий будут храниться в папках вида c:\Temp\0\a\, c:\Temp\0\b\ и т.п. Хранение данных на стороне клиента осуществляется с помощью cookies. Работу PHP с cookies можно настроить, в частности, с помощью параметров session.use_cookies, session.cookie_lifetime и т.п. Параметр session.use_cookies определяет, использовать ли cookies при работе с сессиями. По умолчанию эта опция включена (т.е. принимает значение "1"). Параметр session.cookie_lifetime задает длительность жизни cookies в секундах. По умолчанию это "0", т.е. данные в cookies считаются правильными до закрытия окна браузера. Кроме этих параметров, полезными могут оказаться session.name, определяющий имя сессии, session.auto_start, позволяющий автоматически запускать сессии, session.serialize_handler, задающий способ кодировки данных сессии, и параметр session.cache_expire, определяющий, через сколько минут устаревает документ в кэше. Имя сессии session.name по умолчанию устанавливается как PHPSESSID и используется в cookies как имя переменной, в которой хранится идентификатор сессии. Автоматический запуск сессий по умолчанию отключен, но его можно задать, сделав значение session.auto_start равным "1". Для кодирования данных сессии по умолчанию используется php. Устаревание данных, сохраненных в кэше, происходит через 180 минут. Существует еще множество настроек, с которыми можно познакомиться в документации или непосредственно в файле настроек php.ini. На наш взгляд, знакомства с перечисленными выше параметрами достаточно для работы с сессиями в PHP. Так что приступим. Работа с сессиями Создание сессии Первое, что нужно сделать для работы с сессиями (если они уже настроены администратором сервера), это запустить механизм сессий. Если в настройках сервера переменная session.auto_start установлена в значение "0" (если session.auto_start=1, то сессии запускаются автоматически), то любой скрипт, в котором нужно использовать данные сессии, должен начинаться с команды session_start(); Получив такую команду, сервер создает новую сессию или восстанавливает текущую, основываясь на идентификаторе сессии, переданном по запросу. Как это делается? Интерпретатор PHP ищет переменную, в которой хранится идентификатор сессии (по умолчанию это PHPSESSID) сначала в cookies, потом в переменных, переданных с помощью POST- и GET-запросов. Если идентификатор найден, то пользователь считается идентифицированным, производится замена всех URL и выставление cookies. В противном случае пользователь считается новым, для него генерируется новый уникальный идентификатор, затем производится замена URL и выставление cookies. Команду session_start() нужно вызывать во всех скриптах, в которых предстоит использовать переменные сессии, причем до вывода каких-либо данных в браузер. Это связано с тем, что cookies выставляются только до вывода информации на экран. Получить идентификатор текущей сессии можно с помощью функции session_id(). Для наглядности сессии можно задать имя с помощью функции session_name([имя_сессии]). Делать это нужно еще до инициализации сессии. Получить имя текущей сессии можно с помощью этой же функции, вызванной без параметров: session_name(); Пример 12.2. Создание сессии Переименуем наш файл index.html, чтобы обрабатывались php-скрипты, например в Index.php, создадим сессию и посмотрим, какой она получит идентификатор и имя. <? session_start(); // создаем новую сессию или // восстанавливаем текущую echo session_id(); // выводим идентификатор сессии ?> <html> <head><title>My home page</title></head> ... // домашняя страничка </html> <? echo session_name(); // выводим имя текущей сессии. // В данном случае это PHPSESSID ?> Если проделать то же самое с файлом authorize.php, то значения выводимых переменных (id сессии и ее имя) будут такими же, если перейти на него с index.php и не закрывать перед этим окно браузера (тогда идентификатор сессии изменится). Регистрация переменных сессии Однако от самих идентификатора и имени сессии нам пользы для решения наших задач немного. Мы же хотим передавать и сохранять в течение сессии наши собственные переменные (например, логин и пароль). Для того чтобы этого добиться, нужно просто зарегистрировать свои переменные: session_register(имя_переменной1, имя_переменной2, ...); Заметим, что регистрируются не значения, а имена переменных. Зарегистрировать переменную достаточно один раз на любой странице, где используются сессии. Имена переменных передаются функции session_register() без знака $. Все зарегистрированные таким образом переменные становятся глобальными (т.е. доступными с любой страницы) в течение данной сессии работы с сайтом. Зарегистрировать переменную также можно, просто записав ее значение в ассоциативный массив $_SESSION, т.е. написав $_SESSION['имя_переменной'] = 'значение_переменой'; В этом массиве хранятся все зарегистрированные (т.е. глобальные) переменные сессии. Доступ к таким переменным осуществляется с помощью массива $_SESSION['имя_переменной'] (или $HTTP_SESSION_VARS['имя_переменной'] для версии PHP 4.0.6 и более ранних). Если же в настройках php включена опция register_globals, то к сессионным переменным можно обращаться еще и как к обычным переменным, например так: $имя_переменной. Если register_globals=off (отключены), то пользоваться session_register() для регистрации переменных переданных методами POST или GET, нельзя, т.е. это просто не работает. И вообще, не рекомендуется одновременно использовать оба метода регистрации переменных, $_SESSION и session_register(). Пример 12.3. Регистрация переменных Зарегистрируем логин и пароль, вводимые пользователем на странице авторизации. <? session_start(); // создаем новую сессию или // восстанавливаем текущую if (!isset($_GET['go'])){ echo "<form> Login: <input type=text name=login> Password: <input type=password name=passwd> <input type=submit name=go value=Go> </form>"; }else { $_SESSION['login']=$_GET['login']; // регистрируем переменную login $_SESSION['passwd']=$_GET['passwd']; // регистрируем переменную passwd // теперь логин и пароль - глобальные // переменные для этой сессии if ($_GET['login']=="pit" && $_GET['passwd']=="123") { Header("Location: secret_info.php"); // перенаправляем на страницу // secret_info.php }else echo "Неверный ввод, попробуйте еще раз<br>"; } print_r($_SESSION); // выводим все переменные сессии ?> Теперь, попав на страничку secret_info.php, да и на любую другую страницу сайта, мы сможем работать с введенными пользователем логином и паролем, которые будут храниться в массиве $_SESSION. Таким образом, если изменить код секретной странички (заметьте, мы переименовали ее в secret_info.php) так: <?php session_start(); // создаем новую сессию или // восстанавливаем текущую print_r($_SESSION); // выводим все переменные сессии ?> <html> <head><title>Secret info</title></head> <body> <p>Здесь я хочу делиться секретами с другом Петей. </body> </html> То мы получим в браузере на секретной странице следующее: Array ( [login] => pit [passwd] => 123 ) Здесь я хочу делиться секретами с другом Петей. В итоге получим список переменных, зарегистрированных на authorize.php и, собственно, саму секретную страничку. Что это нам дает? Допустим, хакер хочет прочитать секреты Васи и Пети. И он как-то узнал, как называется секретная страничка (или странички). Тогда он может попытаться просто ввести ее адрес в строке браузера, минуя страницу авторизации (ввода пароля). Чтобы избежать такого проникновения в наши тайны, нужно дописать всего пару строк в код секретных страничек: <?php session_start(); // создаем новую сессию или // восстанавливаем текущую print_r($_SESSION); // выводим все переменные сессии if (!($_SESSION['login']=="pit" && $_SESSION['passwd']==123)) // проверяем правильность // пароля-логина Header("Location: authorize.php"); // если ошибка, то перенаправляем на // страницу авторизации ?> <html> <head><title>Secret info</title></head> ... // здесь располагается //секретная информация :) </html> Удаление переменных сессии Кроме умения регистрировать переменные сессии (т.е. делать их глобальными на протяжении всего сеанса работы), полезно также уметь удалять такие переменные и сессию в целом. Функция session_unregister(имя_переменной) удаляет глобальную переменную из текущей сессии (т.е. удаляет ее из списка зарегистрированных переменных). Если регистрация производилась с помощью $_SESSION ($HTTP_SESSION_VARS для версии PHP 4.0.6 и более ранних), то используют языковую конструкцию unset(). Она не возвращает никакого значения, а просто уничтожает указанные переменные. Где это может пригодиться? Например, для уничтожения данных о посетителе (в частности, логина и пароля) после его ухода с секретной странички. Если правильные логин и пароль сохранятся и окно браузера после посещения сайта не закрыли, то любой другой пользователь этого компьютера сможет прочитать закрытую информацию. Пример 12.4. Уничтожение переменных сессии В файл secret_info.php добавим строчку для выхода на главную страницу: <?php // ... php код ?> <html> <head><title>Secret info</title></head> ... // здесь располагается // секретная информация :) <a href="index.php">На главную</a> </html> В Index.php уничтожим логин и пароль, введенные ранее: <? session_start(); session_unregister('passwd'); // уничтожаем пароль unset($_SESSION['login']); // уничтожаем логин print_r($_SESSION); // выводим глобальные переменные сессии ?> <html> <head><title>My home page</title></head> ... // домашняя страничка </html> Теперь, чтобы попасть на секретную страницу, нужно будет опять вводить логин и пароль. Для того чтобы сбросить значения всех переменных сессии, можно использовать функцию session_unset(); Уничтожить текущую сессию целиком можно командой session_destroy(); Она не сбрасывает значения глобальных переменных сессии и не удаляет cookies, а уничтожает все данные, ассоциируемые с текущей сессией. <? session_start(); // инициализируем сессию $test = "Переменная сессии"; $_SESSION['test']= $test; // регистрируем переменную $test. // если register_globals=on, // то можно использовать // session_register('test'); print_r($_SESSION); // выводим все глобальные переменные echo session_id(); // выводим идентификатор сессии echo "<hr>"; session_unset(); // уничтожаем все глобальные // переменные сессии print_r($_SESSION); echo session_id(); echo "<hr>"; session_destroy(); // уничтожаем сессию print_r($_SESSION); echo session_id(); ?> В результате работы этого скрипта будут выведены три строки: в первой - массив с элементом test и его значением, а также идентификатор сессии, во второй - пустой массив и идентификатор сессии, в третьей - пустой массив. Таким образом, видно, что после уничтожения сессии уничтожается и ее идентификатор, и мы больше не можем ни регистрировать переменные, ни вообще производить какие-либо действия с сессией. Безопасность Вообще говоря, cледует понимать, что использование механизма сессий не гарантирует полной безопасности системы. Для этого нужно принимать дополнительные меры. Обратим внимание на проблемы с безопасностью, которые могут возникнуть при работе с сессиями и, в частности, с теми программами, что мы написали. Во-первых, опасно передавать туда-сюда пароль, его могут перехватить. Кроме того, мы зарегистрировали его как глобальную переменную сессии, значит, он сохранился в cookies на компьютере-клиенте. Это тоже плохо. И вообще, пароли и логины по-хорошему должны храниться в базе данных. Пусть информация о пользователях хранится в базе данных "test" (в таблице "users"), а мы имеем к ней доступ под логином my_user и паролем my_passwd. Во-вторых, что делать, если кто-то написал скрипт подбора пароля для секретной страницы? В этом случае на страницу авторизации много раз должен стучаться какой-то посторонний скрипт. Поэтому нужно просто проверять, с нашего ли сайта пришел запрос на авторизацию, и если нет, то не пускать его дальше. Адрес страницы, с которой поступил запрос, можно получить с помощью глобальной переменной $_SERVER['HTTP_REFERER']). Хотя, конечно, если за взлом сайта взялись всерьез, то значение этой переменной тоже подменят (например, с помощью того же PHP). Тем не менее проверку ее значения можно считать одним из важнейших шагов на пути к обеспечению безопасности своего сайта. Вроде бы первые две проблемы решены. Но есть еще одна. Что делать, если хакер просто допишет в строку запроса значение какой-нибудь глобальной переменной (например, логина)? Вообще это возможно, только если register_globals=On. Просто иначе мы используем для работы с глобальными переменными массив $_SESSION и с ним такие фокусы не проходят. Все же попробуем решить и эту проблему. Для этого нужно очистить строку запроса перед тем, как сравнивать значения параметров. То есть сначала сбросим значение $user_login. Потом данную переменную нужно опять зарегистрировать, но не как новую, а как уже существующую. Для этого знак доллара при регистрации НЕ опускается. Вот что получилось: <?php unset($user_login); // уничтожаем переменную session_start(); // создаем новую сессию или // восстанавливаем текущую session_register($user_login); // регистрируем переменную // как уже существующую if (!($user_login=="pit")) // проверяем логин Header("Location: authorize.php"); // если ошибка, то перенаправляем // на страницу авторизации ?> <html> <head><title>Secret info</title></head> ... // здесь располагается // секретная информация :) </html> Заключение Итак, мы познакомились с сессиями и основными способами работы с ними, проблемами, возникающими при их использовании, и возможными решениями этих проблем. Надеюсь, что после прочтения лекции читателям стало ясно, насколько удобны и просты в использовании сессии, а приведенные примеры пригодятся на практике. Регулярные выраженияПонятие регулярного выражения Регулярное выражение (regular expression, сокращенно РВ) – это технология, которая позволяет задать шаблон и осуществить поиск данных, соответствующих этому шаблону, в заданном тексте, представленном в виде строки. Кроме того, с помощью регулярных выражений можно изменить и удалить данные, разбить строку по шаблону на подстроки и многое другое. Одно из распространенных применений РВ – это проверка строки на соответствие каким-либо правилам. Например, следующее РВ предназначено для проверки того, что строка содержит корректный e-mail–адрес: /^\w+([\.\w]+)*\w@\w((\.\w)*\w+)*\.\w{2,3}$/ Выглядит, конечно, жутко, но зато работает. И если уметь пользоваться этим механизмом виртуозно, то жить становится легче. Вернемся к нашему определению РВ. В нем несколько раз повторяется термин «шаблон». Что это такое? В принципе, интуитивно понятно, но попробуем все же пояснить. Давайте подумаем, что представляет собой корректный e-mail–адрес. Это набор букв, цифр и символов подчеркивания, после которых идет специальный символ «собака» @, затем еще один такой же набор, содержащий имя сервера, точку (.) и две или три буквы, указывающие на зону домена, к которой принадлежит почтовый ящик (ru, com, org и т.д.). Приведенное выше РВ формализует данное описание на языке, понятном компьютеру. И описывает не какой-то конкретный электронный адрес, а все возможные корректные электронные адреса. Таким образом, производится формальное задание множества правильных e-mail'ов с помощью шаблона регулярного выражения. Другие примеры шаблонов – это шаблоны MS Word и html-формы. Механизм регулярных выражений задает правила построения шаблонов и осуществляет поиск данных по этому шаблону в указанной строке. В дальнейшем изложении термины РВ и «шаблон» часто будут использоваться как синонимы, но важно понимать, что это не совсем одно и то же. Шаблон задает какой-то тип данных, а РВ – это механизм, который производит поиск и включает в себя шаблон и опции поиска, а также задает язык написания шаблонов. Регулярные выражения в PHP Регулярные выражения пришли из UNIX и Perl. Как упоминалось выше, с помощью регулярных выражений можно искать и изменять текст, разбивать строку на подстроки и т.д. В PHP существуют такие удобные и мощные средства работы со строками, как explode (разбиение строки на подстроки), strstr (нахождение подстроки), str_replace (замена всех вхождений подстроки). Возникает вопрос – зачем придумывать что-то еще? Основное преимущество РВ заключается в том, что они позволяют организовать более гибкий поиск, т.е. найти то, о чем нет точного знания, но есть примерное представление. Например, нужно найти все семизначные номера телефонов, встречающиеся в тексте. Мы не ищем какой-то заранее известный нам номер телефона, мы знаем только, что искомый номер состоит из семи цифр. Для этого можно воспользоваться следующим РВ: /\d{3}-\d{2}-\d{2}/m В PHP существует два различных механизма для обработки регулярных выражений: POSIX-совместимые и Perl-совместимые (сокращенно PCRE). Их синтаксис во многом похож, однако Perl-совместимые регулярные выражения более мощные и к тому же работают намного быстрее. Начиная с версии PHP 4.2.0, PCRE входят в набор базовых модулей и подключены по умолчанию. POSIX-совместимые РВ включены по умолчанию только в версию PHP для Windows. Основные функции для работы с Perl-совместимыми регулярными выражениями: preg_match(pattern, string, [result, flags]) и preg_match_all(pattern, string, result, [flags])>, где: pattern – шаблон регулярного выражения; string – строка, в которой производится поиск; result – содержит массив результатов (нулевой элемент массива содержит соответствие всему шаблону, первый – первому «захваченному» подшаблону и т.д.); flags – необязательный параметр, определяющий то, как упорядочены результаты поиска. Эти функции осуществляют поиск по шаблону и возвращают информацию о том, сколько раз произошло совпадение. Для preg_match() это 0 (нет совпадений) или 1, поскольку поиск прекращается, как только найдено первое совпадение. Функция preg_match_all() производит поиск до конца строки и поэтому находит все совпадения. Все точные совпадения содержатся в первом элементе массива result у каждой из этих функций (для preg_match_all() этот элемент – тоже массив). Про «захват» элементов будет рассказано в разделе, посвященном подвыражениям. Аналогом preg_match является булева функция POSIX-расширения ereg(string pattern, string string [, array regs]) Функция ereg() возвращает TRUE, если совпадение найдено, и FALSE – в противном случае. Приводимые далее примеры можно тестировать на перечисленных функциях. Например, так: <? //строка, в которой нужно что-то найти $str = "Мой телефонный номер: ". "33-22-44. Номер моего редактора: ". "222-44-55 и 323-22-33"; //шаблон, по которому искать. //Задает поиск семизначных номеров. $pattern = "/\d{3}-\d{2}-\d{2}/m"; //функция, осуществляющая поиск $num_match = preg_match_all ($pattern, $str, $result); //вывод результатов поиска for ($i=0;$i<$num_match;$i++) echo "Совпадение $i: ". $result[0][$i]."<br>"; ?> Синтаксис регулярных выражений Строгое определение регулярного выражения выглядит довольно громоздко. Начнем с неформального описания. Регулярное выражение представляет собой строку. Эта строка состоит из собственно регулярного выражения (шаблона), выделенного с помощью специального символа разделителя (это могут быть символы «/» , «|», «{«, «!» и т.п ) и модификатора, влияющего на способ обработки РВ. В дальнейшем это описание будет расширено. Например, в регулярном выражении /\d{3}-\d{2}-\d{2}/m символ «/»является разделителем, \d{3}-\d{2}-\d{2} – непосредственно регулярное выражение (шаблон), а m – модификатор. Мощь регулярных выражений порождена в основе своей их способностью включать в шаблон альтернативы и повторения. Они кодируются в шаблоне с помощью метасимволов. Метасимвол отличается от любого другого символа тем, что имеет специальное значение. Одним из основных метасимволов является обратный слэш «\». Он меняет тип символа, следующего за ним, на противоположный, т.е. если это был обычный символ, то он МОЖЕТ превратиться в метасимвол, если это был метасимвол, то он теряет свое специальное значение и становится обычным символом (это нужно для того, чтобы вставлять в текст специальные символы как обычные). Например, символ d в обычном режиме не имеет никаких специальных значений, но \d есть метасимвол, означающий «любая цифра». Символ «.» в обычном режиме означает «любой единичный символ», а «\.» означает просто точку. Другое назначение обратного слэша – кодирование непечатных символов, таких как : \n – cимвол перевода строки; \e – символ escape; \t – cимвол табуляции; \xhh – символ в шестнадцатеричном коде, например \x41 есть буква A и т.д. Еще одно назначение обратного слэша – обозначение генерируемых символьных типов, таких как: \d – любая десятичная цифра (0-9); \D – любой символ, не являющийся десятичной цифрой; \s – любой пустой символ (пробел или табуляция); \S – любой символ, не являющийся пустым; \w – символ, используемый для написания Perl-слов (это буквы, цифры и символ подчеркивания), так называемый «словарный символ»; \W – несловарный символ (все символы, кроме определяемых \w). Что имеется в виду под «символьным типом»? Просто каждый метасимвол принимает значение (одно) из класса возможных значений, заданных автоматически или вручную. Символьные типы, задаваемые пользователем, описываются с помощью квадратных скобок (подробнее об этом позже). Выше приведены символьные типы, диапазон значений которых заранее определен языком программирования. Пример использования приведенных выше метасимволов: /\d\d\d plus \d is \w\w\w/ Это РВ означает: трехзначное число, за которым следует подстрока plus, любая цифра, затем is и слово из трех словарных символов. В частности, данному РВ удовлетворяют строки: «123 plus 3 is sum», «213 plus 4 is 217». Вообще различают два множества метасимволов: те, что распознаются в любом месте шаблона, за исключением внутренности квадратных скобок, и те, что распознаются внутри квадратных скобок. Квадратные скобки [ ] применяются для описания подмножеств и внутри регулярного выражения рассматриваются как один символ, который может принимать значения, перечисленные внутри этих скобок. Однако если первым символом внутри скобок является ^, то значением символьного класса могут быть только символы, НЕ перечисленные внутри скобок. Примеры: - Символьный класс [абвгд] задает один из символов а, б, в, г, д, а класс [^абвгд] задает любой символ, кроме а, б, в, г, д. - Если написать [2бул]ки], то это выражение интерпретируется как один из символов 2, б, у, л, за которым следует строка ки], потому что первая встретившаяся закрывающая квадратная скобка (разбор происходит слева направо) заканчивает определение символьного класса. То есть это РВ совпадет с одной из строк 2ки], бки], уки] или лки]. - С помощью РВ [0-9А-Яа-я] можно задать любую букву или цифру. Метасимволы, распознаваемые вне квадратных скобок, можно разделить на группы следующим образом: определяющие положение искомого текста в строке, связанные с подвыражениями, ограничивающие символьный класс, квантификаторы и перечисление альтернатив. Примеры (^ и $) $str = "11 aaa bbb ". "ccc 22 ddd ". "eee ggg 33"; \ Переходный символ со множеством назначений ^ Отрицание класса, но только если это первый символ (например, «^\d» задает все, кроме цифр) - Задает диапазон символов (например, «0-9» задает все цифры, «A-Z» – все латинские буквы) ] Вычисляет символьный класс Регулярное выражение /\d\d/m может быть сопоставлено следующим подстрокам: 11, 22, 33. Если в начале РВ стоит ^, то совпадения ищутся в начале строки, поэтому выражение /^\d\d/m найдет только 11. Когда в конце РВ стоит знак доллара $, поиск производится в конце строки, поэтому выражение /\d\d$/m найдет только 33. Шаблону же /^\d\d\d$/ будет удовлетворять строка, целиком состоящая из трехзначного числа (т.е. она и начинается и заканчивается этим числом). - <? //считываем файл в строку $str = file_get_contents('1.htm'); $pattern = "!^<[^/]+>!mU"; // осуществляем поиск $n = preg_match_all ($pattern, $str, $res); // выводим результаты for ($i=0;$i<$n;$i++) echo htmlspecialchars($res[0][$i]). "<br>"; ?> \ Переходный символ со множеством назначений ^ Объявляет начало объекта (или строки в многострочном режиме). То есть этот символ определяет, что искомый текст должен находиться в начале строки. Альтернатива: «\A» $ Объявляет конец объекта (или строки в многострочном режиме). То есть этот символ определяет, что искомый текст должен находиться в конце строки. Альтернативы: «\Z», «\z» . Совпадает с любым символом, кроме символа перевода строки (по умолчанию) [ Начинает определение символьного класса ] Заканчивает определение символьного класса | Разделяет перечисление альтернативных вариантов ( Начинает подшаблон регулярное (подвыражение) ) Заканчивает подшаблон ? Расширяет значение «(», квантификаторов 0 или 1, и квантификатор минимизации * 0 или больше повторений (квантификатор) + 1 или больше повторений (квантификатор) { Начинает минимальный/максимальный квантификатор } Заканчивает минимальный/максимальный квантификатор Шаблон ограничен восклицательными знаками. Первая «^» значит, что мы ищем совпадения в начале строк, потом идет символ «<» – его и ищем в строке, после него должно идти все, что угодно, кроме обратного слэша (конструкция «[^\]» ), «+» говорит, что стоящий перед ним символ повторяется один и более раз и заканчивается все это символом «>». Таким образом, выделяются все теги в начале строк. - <? //считываем файл в строку $str = file_get_contents('1.htm'); $pattern = "!\s[А-Яа-я]+". "\s([А-Я]\.\s*)([А-Я]\.\s*)$!m"; // шаблон ограничен восклицатель- // ными знаками, m – модификатор, // включающий многострочный режим // первый \s означает, что перед // фамилией должен идти пустой // символ (например, пробел) // [А-Яа-я] задает одну из букв // алфавита в любом регистре,а в // комбинации со знаком плюс // определяет,что эта буква // повторяется один и более // раз следующий \s означает, что // между фамилией и инициалами // должен быть пробел // Далее идет подвыражение, // определяющее инициалы. // Это буква от А до Я, после // которой стоит точка ('\.') // Экранируем точку, чтобы // избавиться от ее специального // значения. После буквы с точкой // может идти или не идти пробел // или несколько. Вся конструкция // повторяется минимум два раза. // Последний символ $ означает, // что фамилия с инициалами // должны находиться в конце // строки. //осуществляем поиск $n = preg_match_all ($pattern, $str, $res); // выводим результаты for ($i=0;$i<$n;$i++) echo htmlspecialchars($res[0][$i]). "<br>"; ?> Примеры ( | и .) - Пусть имеется некий текст. Нам нужно найти всех упомянутых в нем людей со званиями. <? $str = "Доцент Смирнов совершил". "открытие. Его учителем была ". "профессор Иванова. ". "Этим открытием Смирнов ". "завоевал себе степень ". "доктора. Раньше он был ". "только кандидат."; $pattern = "/(профессор|доцент)". "\s[А-Яа-я]+(\s|\.)/i"; // осуществляем поиск $n = preg_match_all ($pattern, $str, $res); // выводим результаты for ($i=0;$i<$n;$i++) echo htmlspecialchars($res[0][$i]). "<br>"; ?> Метасимвол прямая черта « | » позволяет задавать альтернативные варианты. В примере мы хотели найти всех профессоров или доцентов. Для этого было создано подвыражение «(профессор|доцент)». После звания через пробел фамилия человека, которому оно принадлежит, – для этого существует комбинация «\s[А-Яа-я]+». После фамилии идет либо опять пробел, либо точка, если это конец предложения. Получаем опять два альтернативных варианта: «(\s|\.)» (здесь точка экранируется обратным слэшем, чтобы она понималась как обычная точка, без специального значения). Подвыражения (подшаблоны) В нескольких примерах мы уже использовали подвыражения. Настало время разобраться, что же это такое и какими свойствами они обладают В РВ подшаблоны выделяют, заключая в круглые скобки. Для их обозначения кроме термина «подшаблон» также используют термин «подвыражение». Подшаблоны могут быть вложенными. Выделение части регулярного выражения в виде регулярного подвыражения делает следующее. Например, шаблон жар(кое|птица|) - совпадает с одним из слов «жаркое», «жарптица» и «жар». Тогда как без скобок это было бы «жаркое», «птица» и пустая строка. Например, имеется такой шаблон: победитель получит ((золотую|позолоченный) (медаль|кубок)) - и строка, в которой ищутся совпадения с этим шаблоном: «победитель получит золотую медаль». Тогда кроме этой фразы будут еще захвачены и выданы как результаты поиска следующие совпадения в подвыражениях: «золотую медаль», «золотую», «медаль», пронумерованные 1, 2, 3 соответственно. Однако это не всегда удобно. Для того чтобы избавиться от «захватывающего» эффекта подвыражения, после открывающей скобки пишут «?:». Тогда это подвыражение в результат поиска не включается и при нумерации остальных подшаблонов с «захватывающим» эффектом не учитывается. победитель получит ((?:золотую|позолоченный) (медаль|кубок)) Тогда в условиях предыдущего примера получим искомую строку «победитель получит золотую медаль» и строки «золотую медаль», «медаль», пронумерованные 1 и 2 соответственно. Если в html-файле название находится после <body> и отделено от него только пробелами или переводами строк, заключено в тег <h1> и после него тоже может идти сколько-то пробелов и переводов строк, то его можно найти с помощью следующего скрипта: <? //считываем файл в строку $str = file_get_contents('1.htm'); $pattern = "/<body.*?>[\n\s]*<h1>". "(.*?)<\/h1>[\n\s]*/m"; // осуществляем поиск $n = preg_match_all ($pattern, $str, $res); echo $res[1][0]; // выводим заголовок ?> Заметим, что здесь выводится первое захваченное подвыражение, поскольку нам интересно только само название, а не все РВ. Так как в этом РВ есть только одно подвыражение, то его значение содержится в нулевом элементе первого массива результатов. Повторения (квантификаторы) В предыдущих примерах мы часто писали комбинации типа \d\d. Это значит, что цифра должна повторяться два раза. А что же делать, если повторений очень много или мы не знаем, сколько именно? Оказывается, нужно использовать специальные метасимволы. Повторения описываются с помощью так называемых квантификаторов (метасимволов, задающих количественные отношения). Существует два типа квантификаторов: общие (задаются с помощью фигурных скобок) и сокращенные (это исторически сложившиеся сокращения наиболее распространенных квантификаторов). Квантификаторы могут следовать за любым из перечисленных элементов: - одиночный символ (возможно, в комбинации с обратным слэшем); - метасимвол «точка»; - символьный класс; - обратная ссылка (о них расскажем позднее); - подшаблон. Общие квантификаторы задают минимальное и максимальное число дозволенных повторений элемента; эти два числа, разделенные запятой, заключаются в фигурные скобки. Числа не должны превышать 65 536 и первое число должно быть меньше или равно второму. Например, x{1,3} говорит о том, что символ «x» должен повторяться минимум один, а максимум три раза. Соответственно этому шаблону удовлетворяют строки: x, xx, xxx. Если второй параметр отсутствует, но запятая есть, то повторений может быть сколько угодно. Таким образом, [aeuoi]{2,} значит, что любой из символов «a», «e», «u», «o», «i» в строке может повторяться два и более раз, а регулярное выражение \d{3} задает ровно три цифры. Сокращенные квантификаторы задают наиболее используемые количественные отношения (повторения). Они придуманы для удобства, чтобы не перегружать и без того сложные выражения лишним синтаксисом. Исходя из исторических традиций три наиболее часто встречающихся квантификатора имеют следующие обозначения: * эквивалентно {0,} – то есть это ноль и более повторений; + эквивалентно {1,} – то есть это одно и более повторений; ? эквивалентно {0,1} – то есть это ноль или одно повторение. Есть еще один важный момент, на который стоит обратить внимание при изучении квантификаторов. По умолчанию все квантификаторы «жадные», они стараются захватить как можно больше повторений элемента. То есть если указать, что символ должен повторяться один и более раз (например, с помощью *), совпадение произойдет со строкой, содержащей наибольшее число повторений указанного символа. Это может создать проблемы, например, при попытке выделить комментарии в программе на языке Cи или PHP. Комментарии в Cи и PHP записываются между символами /* и */, внутри которых тоже могут встречаться символы * и /. И попытка выявить Си-комментарии с помощью шаблона /\* .* \*/ в строке /* первый комментарий */ не комментарий /* второй комментарий */ не увенчается успехом из-за «жадности» элемента «.*» (будет найдена также строка «не комментарий»). Для решения этой проблемы нужно написать знак вопроса после квантификатора. Тогда он перестанет быть «жадным» и попытается захватить как можно меньшее число повторений элемента, к которому он применен (квантификатор применяется к элементу, что стоит перед ним). Так что шаблон /\* .*? \*/ успешно выделяет Си-комментарии. В PHP существует опция PCRE_UNGREEDY, которая делает все квантификаторы «не жадными» по умолчанию и «жадными», если после них идет знак вопроса. <? //Рассмотрим html-файл, где имеется //следующая строка: $str = "<div id=1>Привет</div> ". "<p>Текст, не заключенный в тег ". "div</p><div id=2>Пока</div>"; // Если мы хотим найти текст, // содержащийся между тегами div, // естественно написать такой шаблон: $pattern = "!<div id=1>.*</div>!si"; // Но этот шаблон слишком "жадный" // и захватит также и текст, // заключенный в нашем примере между // тегами <p>. Чтобы этого избежать, // нужно написать следующий шаблон, // отличающийся только наличием знака // вопроса, который запрещает // квантификатору быть "жадным". $pattern1 = "!<div id=1>.*?</div>!si"; // Запускаем поиск в строке $str // совпадений с шаблонами // $pattern и $pattern1 $s = preg_match_all ($pattern, $str, $res); $js = preg_match_all ($pattern1, $str, $res1); //выводим результаты поиска // функция htmlspecialchars позволяет // выводить html без // его обработки браузером echo "Жадный шаблон:". htmlspecialchars($res[0][0]). "<br>"; echo "Нежадный шаблон:". htmlspecialchars($res1[0][0]); ?> Результаты работы скрипта: «Жадный» шаблон:<div id=1>Привет</div> <p>Текст,не заключенный в тег div</p> <div id=2>Пока</div> «Нежадный» шаблон:<div id=1>Привет</div> Теперь мы в принципе можем решить задачу выделения содержания из html-файла, если оно заключено в теге <div id=content>. Предлагаем читателям проделать это самостоятельно. Модификаторы PCRE Еще один немаловажный элемент регулярного выражения – это список применяемых к нему модификаторов. Модификаторы – это выдаваемая интерпретатору регулярных выражений инструкция по обработке данного выражения. Например, считать, что все символы регулярного выражения соответствуют как большим, так и маленьким буквам в строке, где производится поиск. Примеры модификаторов приведены в таблице 13.3. i (PCRE_CASELESS) Если указан этот модификатор, то буквы в шаблоне совпадают с буквами и верхнего, и нижнего регистра в строке m (PCRE_MULTILINE) По умолчанию строка, подающаяся на вход интерпретатору РВ, рассматривается как состоящая из одной линии. Этот модификатор включает поддержку многострокового режима s (PCRE_DOTALL) Если установлен этот модификатор, то метасимвол точка «.» совпадает с любым символом, ВКЛЮЧАЯ символ перевода строки x (PCRE_EXTENDED) Заставляет интерпретатор игнорировать пробелы между символами в шаблоне, за исключением пробелов, экранированных обратным слэшем или находящихся внутри символьного класса, а также между неэкранированным символом # вне символьного класса и символом новой строки U (PCRE_UNGREEDY) Этот модификатор инвертирует «жадность» квантификаторов, т.е. они становятся «нежадными» по умолчанию и «жадными» если предшествуют символу «?» Регулярные выражения для «продвинутых» В последующих разделах обсуждаются более сложные конструкции работы с регулярными выражениями, без которых в принципе можно обойтись. Поэтому мы не будем в них особо углубляться, а приведем лишь общие сведения. Обратная ссылка Вне определения символьного класса (это тот, что задается квадратными скобками) комбинация обратный слэш и цифра больше нуля (например, \1) называется обратной ссылкой и представляет собой ссылку на захваченное ранее регулярное подвыражение. Этих подвыражений ровно столько, сколько открывающихся круглых скобок (перед которыми нет знака вопроса) стоит левее данного элемента. Обратная ссылка совпадает с конкретным выбранным значением подвыражения, на которое она ссылается, а не с любым возможным значением этого подвыражения. Таким образом, шаблон (ответствен|надеж)ный проявляет \1ность совпадет со строками «ответственный проявляет ответственность», «надежный проявляет надежность» и не совпадет со строкой «ответственный проявляет надежность». Обратные ссылки могут использоваться внутри подвыражений. При первом использовании подвыражения ссылка внутри него не срабатывает, но при последующих повторениях подшаблона она работает, как описано выше. Утверждения Утверждение – это проверка символов, следующих до или после текущего символа. Простейшие утверждения закодированы последовательностями \A, \Z, ^, $ и т.д. Более сложные утверждения кодируются с помощью подшаблонов. Постараемся вкратце описать, как это делается. Существует два типа утверждений: те, что смотрят за текущую позицию в исходной строке («смотрящие вперед»), и те, что смотрят на символы перед текущей позицией («смотрящие назад»). Утверждения, закодированные подшаблонами, сравниваются как обычные подшаблоны, за исключением того, что при их обработке не происходит изменения текущей позиции. «Смотрящие вперед» утверждения ищут совпадения в строке за текущей позицией поиска и начинаются с (?= для позитивных утверждений и с (?! для негативных. Например, \w+(?=;) совпадает со словом, заканчивающимся точкой с запятой (не включая точку с запятой в результат поиска), и foo(?!bar) совпадает с любым появлением foo, после которого нет bar. Как все происходит? Берем строку и ищем в ней foo. Как только нашли, заглядываем вперед (текущая позиция при этом не меняется) и смотрим, идет ли далее bar. Если нет, то совпадение с шаблоном найдено, иначе продолжаем поиск. Регулярное выражение (?!foo)bar не найдет все вхождения bar, перед которыми нет foo, потому что оно «смотрит вперед», а перед ним никаких символов нет. Поэтому в данном шаблоне ?!foo всегда верно. «Смотрящие назад» утверждения ищут совпадения перед текущей позицией. Позитивные утверждения этого типа начинаются с (?<= , негативные – с (?<! . Смотрящим назад утверждениям позволено искать только строки фиксированной длины, т.е. в них нельзя использовать квантификаторы. Например, (?<!foo)bar находит все появления bar, перед которыми нет foo. В начале лекции мы хотели научиться находить в html-файле упоминание об авторе. Это можно сделать с помощью «смотрящих назад» утверждений в РВ (хотя можно и проще). <? //считываем файл в строку $str = file_get_contents('1.htm'); $pattern = "/(?<=Автор:)\s[А-Я]". "[а-я]*\s([А-Я]\.\s*){1,2}/m"; // осуществляем поиск $n = preg_match_all ($pattern, $str, $res); // выводим результаты for ($i=0;$i<$n;$i++) echo htmlspecialchars($res[0][$i]). "<br>"; ?> Часть РВ после утверждения определяет, что мы ищем строку (ФИО), которая начинается с пробела, большой буквы, затем идут маленькие буквы в произвольном количестве, пробел и инициалы через точку. Утверждение задает то, что перед данной строкой должно стоять «Автор:». Дату можно вычислить похожим образом. Оставляем это в качестве упражнения. Условные подвыражения Как в любом языке программирования, в РВ существуют условные конструкции. Применяются они к подвыражениям. То есть можно заставить процессор РВ выбирать подшаблон в зависимости от условия или выбирать между двумя альтернативными шаблонами в зависимости от результата утверждения или от того, совпал ли предыдущий захваченный подшаблон. Существуют две формы условных подвыражений: (?(условие)шаблон_выполняемый_если_ условие_верно) (?(условие)шаблон_если_условие_верно |шаблон_если_условие_неверно) Существует два типа условий. Если текст между круглыми скобками состоит из последовательности цифр, то условие удовлетворяется, если захваченное подвыражение с этим номером ранее совпало. ( \( )? [^()]+ (?(1) \) ) Первая часть этого РВ опционально совпадает с открывающейся круглой скобкой, и если этот символ присутствует, то устанавливает его как первое захваченное подвыражение. Вторая часть совпадает с одним или более символами, не заключенными в круглые скобки. Третья часть РВ – это условное подвыражение, которое проверяет, совпало ли первое множество скобок или нет (попалась ли нам в строке открывающая круглая скобка). Если попалась, то есть объект (строка) начинается с символа «(», то условие верно и вычисляется условный шаблон, а именно требуется наличие закрывающей круглой скобки. В противном случае подшаблон ни с чем не совпадает. Если условие – не последовательность цифр, то оно должно быть утверждением. Это может быть позитивное или негативное «смотрящее вперед» или «смотрящее назад» утверждение. (?(?=[^a-z]*[a-z])\d{2}-[a-z]{3}-\d{2} |\d{2}-\d{2}-\d{2}) Условие здесь – позитивное «смотрящее вперед» утверждение. Оно совпадает с любой последовательностью не букв, после которых идет буква. Другими словами, оно проверяет присутствие хотя бы одной буквы в строке для поиска. Если буква найдена, то производится сравнение по первому альтернативному варианту шаблона (\d{2}-[a-z]{3}-\d{2}), иначе – по второму (\d{2}-\d{2}-\d{2}). Этому шаблону удовлетворяют строки двух типов: dd-aaa-dd или dd-dd-dd, где d – любая цифра, a – любая буква. Заключение Итак, мы рассмотрели механизм регулярных выражений, их синтаксис и семантику, показали примеры их использования. Безусловно, эта лекция не охватывает все тонкости данного механизма. О регулярных выражениях пишут целые книги! Мы же постарались лишь в общих чертах познакомить читателей с их основами. Вообще механизм регулярных выражений присутствует почти во всех языках программирования с небольшими отличиями, но суть остается той же. Так что надеемся, что знания, полученные в процессе чтения этой лекции, помогут при изучении других языков и пригодятся на практике. Взаимодействие PHP и XMLВведение Прежде чем начать изучать способы работы PHP с XML, полезно хотя бы вкратце ознакомиться с самой технологией XML. В первой части лекции будут рассмотрены основные понятия этой технологии, базовый синтаксис XML–документов, области ее применения, а также понятие DOM XML. Вторая часть лекции посвящена непосредственно вопросам обработки XML при помощи PHP. Сюда входит установка расширения DOM XML, описание и примеры использования некоторых встроенных в PHP функций для обработки XML-документов. В качестве примера рассмотрим XML-файл, содержащий описания личностей, и попытаемся научиться добавлять, удалять и находить личность или отдельные элементы ее описания в этом файле с помощью PHP. XML Основные понятия XML (Extensible Markup Language) – это расширяемый язык разметки, являющийся подмножеством языка SGML и поэтому имеющий общие с ним цели – разметка любого типа документов. XML имеет много общего с языком разметки HTML, но они различны по своему назначению. HTML, как и XML, является подмножеством SGML, но предназначен только для отображения информации в браузере. XML же предназначен для структурированного хранения информации и не содержит никаких конструкций для отображения этих данных. Для отображения данных, хранящихся в XML-формате, используются различного рода преобразователи, например язык трансформаций XSLT. Комбинация XML + преобразователь позволяет достичь того же результата, что и использование HTML-форматированного отображения документа пользователю. Однако в случае использования XML и преобразователя данные хранятся отдельно от их представления, т. е. от инструкций о том, как они должны отображаться. XML очень похож на HTML по своему синтаксису, за исключением того, что теги XML можно придумывать собственные. По аналогии с HTML внутри XML-тегов можно использовать атрибуты. Каждый тег XML должен иметь соответствующий закрывающий тег (в HTML это не является обязательным). Каждый XML-документ начинается со строки декларации, например: <?xml version='1.0' encoding='UTF-16' standalone='yes' ?> которая указывает на версию языка XML (атрибут version), кодировку текста в этом документе (атрибут encoding) и показывает, существует ли документ сам по себе или зависит от других файлов (атрибут standalone). Приведем пример XML-документа. Пусть у нас имеется письмо. Содержащуюся в нем информацию можно хранить в следующем виде: <?xml version='1.0' encoding='KOI8-R' standalone='yes' ?> <note title="Письмо"> <to>Маша Петрова</to> <body>Привет, Маша! Как у тебя дела? У меня все хорошо. Собираюсь приехать к тебе в гости.</body> <from>Вася Иванов</from> </note> Сохранив этот файл, например, как note.xml, мы можем переслать его кому-нибудь, просмотреть с помощью браузера или использовать в программе. В частности, можно использовать этот XML-документ в своей PHP-программе. С помощью XML можно создать документ для описания любой информации. Для того чтобы другие разработчики могли создавать документы в этом же XML-языке (т.е. используя те же теги) и чтобы можно было проверить правильность созданного документа (его соответствие выбранному XML-языку), нужно формально описать используемый XML-язык. Это делается с помощью механизмов DTD (Document Type Definition) или Schemas, которые дают возможность создавать шаблоны для новых типов документов. Для экспериментов в домашних условиях создавать DTD-описания необязательно. Важно понимать, что XML на самом деле вовсе не язык, а стандарт для создания языков, отвечающих критериям XML. Между XML-документами могут быть установлены ссылки, один документ может включать в себя другой, т.е. структура документа может быть сколь угодно сложной. Элементы в XML-документах могут быть связаны между собой отношениями родитель/потомок или родственник/родственник. В нашем примере <note> является родителем <to> , который в свою очередь является потомком <note>, а <to>, <body> и <from> являются родственниками. Собственно текст тоже является потомком элемента, например, «Вася Иванов» – потомок элемента <from>. Такая структура называется деревом; любые части дерева, имеющие потомков, называются ветвями, а не имеющие – листьями. Объектная модель XML-документа Структура XML-документа очень напоминает объектную модель: она иерархична, одни элементы могут быть потомками других. Любой XML-документ можно представить в качестве объектной модели. При этом одни элементы (например, теги) становятся объектами, другие (атрибуты и текстовые элементы) – их свойствами. Например, для документа <parent> <child id="text">Добрый день!</child> </parent> объектная модель может выглядеть как показано на рис. 14.1 Прямоугольниками изображены объекты, овалом – свойства объектов. Рис. 14.1. Пример объектной модели Для каждого конкретного XML-документа можно создать свою объектную модель, но есть более общая объектная модель, применимая для любого XML-документа, вне зависимости от его структуры. Эта модель называется DOM (Document Object Model). Обычно DOM добавляется как слой между XML-парсером и приложением, которому требуется информация из документа. То есть парсер берет данные из документа и передает их в DOM. Затем DOM используется приложениями более высокого уровня. Хотя DOM расшифровывается как «объектная модель документа», работает DOM преимущественно с интерфейсами, а не с объектами. Интерфейс – это соглашение, по которому поддерживаются определенные свойства и методы, применяемые к некоему объекту. На рис. 14.2 показано, как приведенный выше XML-документ представляется в DOM: Рис. 14.2. Пример DOM (Document Object Model) Каждый прямоугольник представляет собой объект, имена в прямоугольниках соответствуют интерфейсам, которые будут реализованы каждым объектом. Каждый объект может реализовывать несколько подходящих интерфейсов. Например, объект представляющий символьные данные «Hello,World!», реализует интерфейсы Text, CharacterData, Node. Преимущество модели DOM состоит в том, что она универсальна, т.е. может применяться для документов любого типа. Ее использование может существенно упростить обработку XML-документов. Но вернемся к технологии XML. Для чего она используется? В основном для хранения и передачи данных. В последнее время хранение данных в виде набора XML-файлов рассматривается даже как альтернатива реляционным базам данных. Но наиболее прогрессивные разработчики стремятся задействовать совместно XML и базы данных, пользуясь достоинствами обеих технологий. В частности, XML удобнее использовать для передачи данных, а базу данных – для их хранения и обработки. Например, главная компания и ее филиал в другом городе имеют два разных сайта поддержки, написанных на PHP. На сайте филиала нужно сообщать обо всех основных событиях, происходящих в главной компании, и наоборот. Поэтому обе организации ежедневно обмениваются новостями в универсальном формате XML, а эти файлы создаются и обрабатываются PHP-скриптами и отображаются на обоих сайтах. Взаимодействие PHP и XML Расширения SAX и DOM XML Для работы с XML-документами можно использовать язык PHP. В PHP для этого существует два модуля, реализующие два разных стандарта обработки XML-данных: SAX (Simple API for XML) и DOM (Document Object Model). Стандарт SAX (http://www.saxproject.org) не является стандартом W3C и описывает метод обработки XML-документов для получения из них данных. То есть этот метод обработки XML-документов позволит только прочитать данные из XML-документа, и не более того. Создавать и изменять XML-документы с его помощью невозможно. SAX основан на так называемом событийном программировании. Его особенность заключается в том, что вы предоставляете парсеру XML набор собственных функций, которые будут заниматься обработкой различных типов XML-данных (элементов (тегов), текста и т.п.), а парсер затем будет сам вызывать ваши функции в процессе обработки XML-документа, передавая им найденные данные. Функции будут вызываться в той же последовательности, в которой соответствующие данные располагаются в XML-документе. Другим стандартом для обработки XML-данных является DOM – стандарт W3C, спецификацию которого можно найти на сайте консорциума (http://www.w3c.org/DOM). В отличие от SAX, этот метод позволяет производить любые операции с XML-данными в достаточно удобной форме – представляя XML-документ как дерево объектов. Модуль, реализующий этот стандарт, называется DOM XML. Он не входит в основной набор модулей PHP, но может быть установлен как расширение. API этого модуля старается как можно более точно следовать стандарту DOM level 2. Кроме того, существует множество дополнительных функций. Эти функции включены для совместимости с предыдущими версиями расширения, и использовать их в новых скриптах не рекомендуется. Кроме того, у расширения DOMXML есть проблемы с русской кодировкой. Парсер обрабатывает текст только в кодировке UTF-8, поэтому текст нужно каждый раз перекодировать с помощью функции iconv. Отсюда и необходимость установки расширения iconv вместе с DOM XML. Модуль DOM XML является мощным и удобным в использовании средством обработки XML-документов. В данной лекции мы будем рассматривать именно его. Установка расширения DOM XML Для того чтобы установить расширение DOM XML, нужно сделать следующее. - В файле настроек PHP (php.ini) раскомментировать строку, касающуюся этого расширения (extension=php_domxml.dll для Windows, либо extension=php_domxml.so для Linux-платформ). - Скопировать файл расширения (php_domxml.dll или php_domxml.so) в папку, где находятся расширения (extension_dir). - Подключить расширение iconv так же, как в пунктах выше (иногда это расширение устанавливается автоматически вместе с domxml). - Скопировать дополнительные библиотеки в системную папку system (Windows 98) или system32 (WindowsNT/2000/XP). В первую очередь это библиотеки libxml2 и iconv, затем libxslt, libexslt и zlib. - Перезапустить сервер. Следует проверить, правильно ли установлена переменная extension_dir в файле настройки php.ini. Если она не указывает на директорию, где находятся библиотеки расширений PHP, то ни одно из расширений подключить не удастся. Чтобы проверить, установилось ли расширение, можно создать простейший скрипт, который будет выводить все настройки PHP-интерпретатора (это делает функция phpinfo() ). Другой вариант – попробовать использовать какую-нибудь функцию из данного расширения. Например, можно попробовать получить версию используемой библиотеки libxml с помощью функции domxml_version(). Но этот способ не очень хорош, поскольку расширение экспериментальное (это значит, что некоторые функции в каких-то определенных условиях могут и не работать), да и функции еще надо изучить, прежде чем их использовать. <? // выводит информацию о настройках PHP phpinfo(); // отображает используемую версию // библиотеки libxml echo domxml_version(); ?> Взаимодействие PHP и XML посредством DOM XML Что происходит, если взаимодействие PHP и XML осуществляется с помощью объектной модели стандарта DOM? Модуль DOM XML определяет в PHP несколько классов, таких как DomNode, DomDocument, DomElement, DomText и DomAttribute, большинство из которых идут из ядра стандарта DOM. Почти для всех классов (в частности, для перечисленных выше) класс DomNode является родительским, поэтому его свойства и методы наследуются всеми остальными классами. Если рассмотреть произвольный XML-документ, то классу DomDocument будет соответствовать сам этот документ, классу DomElement – каждый XML-тег, классу DomAttribute – атрибуты тегов, а классу DomText – содержание XML-элементов. В то же время классу DomNode будет соответствовать каждый из перечисленных элементов XML-документа. Рассмотрим коллекцию, содержащую описания персон. Если каждую из них мы описываем с помощью таких характеристик, как фамилия, имя, дата рождения и электронный адрес, то структура коллекции «Личности», где хранится информация обо всех известных нам персонах, может быть представлена следующим образом. <?xml version="1.0"?> <collection> <person id="10"> <name> <first>Nick</first> <last>Petrov</last> </name> <birth> <day>23</day> <month>12</month> <year>89</year> </birth> <email> nick@ngs.ru </email> </person> <person id="20"> <name> <first>Bob</first> <last>Ivanov</last> </name> <birth> <day>03</day> <month>05</month> <year>90</year> </birth> <email> bob@ngs.ru </email> </person> </collection> В дальнейшем, приводя примеры, мы будем использовать этот файл. Нам необходимо научиться читать, добавлять, изменять и искать информацию, находящуюся в XML-файлах. Перевод данных XML-файла в объекты и классы PHP Первое, что нужно сделать, если мы хотим работать с XML-данными в PHP при помощи расширения DOM XML, это перевести имеющиеся данные в объекты и классы DOM. Это можно сделать несколькими способами. Синтаксис: object domxml_open_mem (string str) - В качестве параметра эта функция принимает строку str, содержащую XML-документ. Результатом ее работы является объект класса, называемого DOMDocument. Синтаксис: object domxml_open_file (string filename) - Эта функция обрабатывает XML-файл, имя которого задается параметром filename, и переводит его в объект класса DOMDocument. Доступ к файлу производится только на чтение. Такие функции, как domxml_open_mem() и domxml_open_file(), как правило, нужно вызывать перед вызовом любых других функций, связанных с расширением DOM. Эти функции преобразуют XML-файл в дерево объектов. К таким объектам можно обращаться с помощью различных методов. В частности, для выделения корневого элемента используется метод DomDocument->document_element(). Еще существует функция domxml_new_doc(string version), которая создает новый пустой XML-документ. Ее параметром является номер версии создаваемого документа. Но ее мы касаться не будем, а будем считать, что XML-файл уже создан. <? //считываем файл "persons.xml" в строку $xmlstr = join('',file('persons.xml')); // переводим строку с xml-файлом // в дерево объектов. Если операция // прошла неудачно, то выводим // ошибку и прекращаем работу. if(!$dom = domxml_open_mem($xmlstr)) { echo "Ошибка при разборе документа\n"; exit; } // можно посмотреть, как выглядит // этот объект print_r($dom); echo "<hr>"; // выделяем корневой элемент // дерева объектов. // В нашем случае это будет // элемент <collection> $root = $dom->document_element(); print_r($root); echo "<hr>"; ?> Итак, каждому элементу XML-файла мы поставили в соответствие какой-то объект. Теперь нужно научиться перемещаться по дереву объектов и обращаться с этими объектами: получать и изменять их значения, находить их потомков и предков, удалять объекты. Обход дерева объектов Для получения значения текущего узла (вне зависимости от его типа) используют метод DomNode->node_value() или DomNode->get_content() для получения содержимого узла. Для получения значения атрибута используется метод DomElement->get_attribute (attr_name). А метод DomNode->child_nodes() возвращает массив потомков данного узла. Для того чтобы сделать обход дерева объектов, полезно еще уметь различать объекты по типам, т.е. определять, является ли узел элементом (тегом), текстом, атрибутом и т.п. Для этого используются специальные константы. XML_ELEMENT_NODE определяет, является ли узел элементом, XML_ATTRIBUTE_NODE определяет, является ли узел атрибутом, и XML_TEXT_NODE определяет, является ли узел куском текста. Эти константы имеют целочисленные значения 1, 2 и 3 соответственно. Использование этих констант полезно, поскольку переводы строки, применяемые для удобочитаемости XML-файлов, тоже становятся узлами. <? // сначала делаем то же, // что и в предыдущем примере $xmlstr = join('',file('persons.xml')); if(!$dom = domxml_open_mem($xmlstr)) { echo "Ошибка при разборе документа\n"; exit; } $root = $dom->document_element(); // Получаем массив потомков // родительского узла // (в нашем случае это массив <person>) $nodes = $root->child_nodes(); print_r($nodes); echo "<hr>"; // Начинаем обработку каждого // узла в массиве foreach($nodes as $node){ // Если текущий узел – один // из узлов <person>, то // продолжаем ее обработку, // чтобы получить информацию // об этой личности if ($node->tagname=='person'){ // Создаем массив, куда // будем собирать информацию // о рассматриваемой личности $currentPers = array(); // Получаем id личности, // который хранится в атрибуте 'id' $currentPers['id'] = $node->get_attribute('id'); // Получаем массив потомков // <person>. Это вся // информация о личности // (<name>,<birth> и т.д.) $persons_info = $node->child_nodes(); // Перебираем все дочерние // узлы $node foreach ($persons_info as $info){ // проверяем, является ли узел // элементом (xml-тегом) if ($info->type== XML_ELEMENT_NODE) { // тогда метод tagname // возвратит имя этого // элемента (тега), а метод // get_content() – // его содержимое $currentPers[$info->tagname] = $info->get_content(); } } // выводим на экран полученные // массивы print_r ($currentPers); echo "<br>"; } } ?> Итак, мы научились обходить дерево XML. Теперь можно попытаться что-нибудь найти в XML-файле. Правда, делать это не совсем удобно опять же из-за переносов строк, которые мы использовали при написании XML-файла. Пусть наш XML-файл записан в строку, а точнее, в нем есть следующая строка: ... <person id="20"> <name> <first>Иван</first> <last>Иванов</last> </name> ... Тогда в наш предыдущий пример вставим (после вывода на экран полученных массивов) строчку для поиска электронного адреса Ивана Иванова. ... $str = $currentPers["email"]; if ($currentPers["name"] == "Иван Иванов" ) echo "Здравствуйте, Иван! " . "Ваш e-mail $str"; ... Добавление новых элементов в XML-документ Далее разберем задачу, как можно добавить в нашу базу данных новую личность средствами php. Сначала нужно скопировать описание личности (считаем, что все личности описываются с помощью стандартного набора характеристик, как в файле persons.xml). Это делается с помощью метода DomNode->clone_node(). Таким образом, мы клонируем элемент <person> и все его внутренние элементы (содержание тегов не копируется). Потом можно установить какие-нибудь значения для элементов описания личности. Например, задать имя человека, дату его рождения и т.п. В конце нужно записать полученное описание личности в качестве потомка корневого элемента в дерево DOM с помощью метода DomNode->append_child(new_node), где в качестве параметра передается созданный объект (новый узел). В PHP до версии 4.3 перед добавлением потомка к узлу с помощью данной функции этот потомок сначала копировался. Таким образом, новый узел являлся новой копией, которая могла изменяться без изменения узла, переданного как параметр в эту функцию. В более поздних версиях PHP новый узел удаляется из существующего контекста, если он уже есть в дереве. Такое поведение соответствует спецификациям W3C. Для удаления узла можно воспользоваться методом, применив его к узлу, который требуется удалить, т.е. DomNode->unlink_node(). Заключение Итак, мы изучили ряд функций, позволяющих манипулировать данными, хранящимися в XML-формате. Это, конечно же, далеко не полный перечень существующих функций. В версии PHP5 он значительно усовершенствован и в большей степени соответствует стандарту DOM. Тем не менее знание приведенных здесь основных функций может оказаться полезным при решении конкретных прикладных задач. Использование шаблонов в PHPЧто такое шаблоны и зачем они нужны Что такое шаблон в языке программирования? Можно сказать, что шаблон - это текст с переменными внутри него. При обработке шаблона происходит замена переменных на их значения. В одной из лекций мы уже рассматривали пример шаблона. Это был шаблон для отображения документов. Пользователь создавал строку текста, размеченного с помощью html-тегов, и вставлял в нее специальные метасимволы (вида <!имя элемента>), которые наша программа впоследствии заменяла на значения соответствующих элементов. Для чего нам был нужен такой шаблон? Чтобы, например, можно было изменить стиль отображения документа, не меняя кода программы. Наиболее распространенный ответ на вопрос, зачем нужны шаблоны, звучит примерно так: шаблоны нужны для того, чтобы отделить логику работы приложения от способа представления данных, т. е. от дизайна. Приведенный пример шаблона - один из самых простых. Для его обработки используется только функция подстановки str_replace(). Чаще всего для того, чтобы работать с шаблонами, создают библиотеки классов. В принципе создавать свою библиотеку не обязательно, поскольку существует множество свободно распространяемых библиотек шаблонов, над функциональностью которых трудятся большие коллективы разработчиков, стараясь сделать их универсальными, мощными и быстрыми. Некоторые из таких библиотек мы и рассмотрим. Но для начала сформулируем задачу, на примере решения которой будем демонстрировать использование различных шаблонов. Итак, задача: Требуется сгенерировать web-страницу со списком статей, имеющихся в базе данных. Для простоты считаем, что статья имеет название title, автора author, краткое содержание abstract и полное содержание fulltext, которое представлено либо в виде текста в базе данных, либо в виде ссылки на файл. Список должен быть организован так, чтобы при щелчке мышью на названии статьи ее полное содержание появлялось в новом окне. Шаблоны подстановки Как можно решить такую задачу способом простой подстановки, т.е. тем методом, которым мы решили задачу отображения документов? Нужно придумать шаблон для этой страницы и где-то его хранить (в файле или в базе данных). Очевидно, что мы не можем придумать шаблон для всей страницы, потому что не знаем, сколько статей в базе данных. В шаблоне же мы договорились использовать только html и метасимволы <!имя элемента>. Поэтому мы можем написать только шаблон для одной строки списка, который уже программно надо преобразовать в нужное количество строк. <li><a href="<!fulltext>" target=new><!title></a> (<!author>)<br><p><!abstract></p>
Кроме того, здесь есть еще одна загвоздка - с отображением ссылки на полный текст статьи. Если мы будем действовать по правилу подстановки (менять все метасимволы на их значения из базы данных), то может получиться, что вместо <!fulltext> вставим не ссылку на текст, а сам текст. То есть для этого элемента нужна дополнительная проверка перед заменой и какие-то дополнительные действия в случае, если в поле fulltext содержится текст статьи, а не ссылка на файл. Не будем усложнять себе жизнь и договоримся, что в поле fulltext всегда содержится только ссылка на файл. Тогда задачу можно решить следующим образом: <? $li_tmpl = file_get_contents("tmpl.html"); // считываем шаблон строки из файла // устанавливаем соединение и выбираем // базу данных $conn = mysql_connect("localhost", "nina","123") or die("Cant connect"); mysql_select_db("book"); $sql = "SELECT * FROM Articles"; $q = mysql_query($sql,$conn); // отправляем запрос $num = mysql_num_rows($q); for($i=0; $i<$num; $i++){ $tmpl .= $li_tmpl; $tmpl = str_replace("<!title>", mysql_result($q,$i,"title"),$tmpl); $tmpl = str_replace("<!author>", mysql_result($q,$i,"author"),$tmpl); $tmpl = str_replace("<!abstract>", mysql_result($q,$i,"abstract"),$tmpl); $tmpl = str_replace("<!fulltext>", mysql_result($q,$i,"fulltext"),$tmpl); } echo $tmpl; ?>
Если шаблон был такой, как приведен выше, то получим примерно следующее. Введение в PHP (Савельева Н.В.) Лекция дает представление о том, что такое язык PHP, для чего он создавался изначально и как используется теперь, какими возможностями обладает. Установка и настройка ПО (Иванов Иван) Рекомендации по установке и настройке web-сервера и интерпретатора PHP В принципе метод достаточно прост и удобен, но требует дополнительных усилий программиста при возникновении задач более сложных, чем простая подстановка значений. Для решения задач, где требуется делать подстановку целых блоков или даже проверять условия, создают классы шаблонов, такие как FastTemplate и Smarty. Обсудим их подробнее. Шаблоны FastTemplate FastTemplate - это набор классов, позволяющих реализовать работу с шаблонами. Логику добавить в шаблон FastTemplate нельзя, вся она должна находиться в коде программы. Идея работы шаблонов FastTemplate заключается в том, что любая большая страница состоит из множества кусочков, самые маленькие из которых - обычные строки текста, и они получают имя и значение. Что представляет собой файл шаблона FastTemplate? Это обычный html-файл, в котором могут встречаться переменные особого вида, впоследствии обрабатываемые методами класса FastTemplate. Синтаксис переменных в шаблонах FastTemplate описывается следующим выражением: {([A-Z0-9_]+)} Это значит, что переменная должна начинаться с фигурной скобки "{". Второй и последующие символы должны быть буквами верхнего регистра от A до Z, цифрами или символами подчеркивания. Переменная вычисляется с помощью закрывающей фигурной скобки "}". {TITLE} {AUTH20} {TOP_OF_PAGE}
Как уже было сказано, основная идея FastTemplate - создание страницы с помощью вложенных шаблонов. Например, для решения нашей задачи можно создать три файла шаблона: <html> <head><title>{TITLE_}</title> </head> <body> {MAIN} </body> </html>
<ul> {LIST_ELEMENT} </ul>
<li><a href="{FULLTEXT}">{TITLE}</a> ({AUTHOR}) <br> <p> {ABSTRACT}
Шаблоны мы создали - работу дизайнера выполнили. Теперь нужно научиться их обрабатывать, т.е. выполнить работу программиста. Сейчас создадим программу для обработки приведенных выше шаблонов. Перед началом работы с шаблонами FastTemplate нужно подключить этот набор классов к нашей программе. В реальной жизни набор классов FastTemplate записан в один файл, как правило, с названием class.FastTemplate.php3, поэтому подключить его можно, например, с помощью команды: include("class.FastTemplate.php3"); Следующий важный шаг - это создание объекта класса FastTemplate, с которым впоследствии мы будем работать: $tpl = new FastTemplate( "/path/to/templates"); В качестве параметра передается путь к месту, где находятся наши шаблоны. Методы FastTemplate Далее необходимо изучить методы, которые можно применять к созданному объекту класса FastTemplate. Параллельно обратим внимание, как их можно использовать для решения нашей задачи. Для работы с FastTemplate нужно знать четыре основных метода: define, assign, parse и FastPrint. Метод define Синтаксис: define( array ( ключ => значение, ключ1 => значение1, ... ))
Метод define() связывает имя файла шаблона с более коротким именем, которое можно будет использовать в программе. То есть "ключ" - это имя, которое мы будем использовать в программе для ссылки на файл шаблона, имя которого записано в строке "значение". Реальные имена файлов шаблонов не рекомендуется использовать нигде, кроме метода define. При вызове метода define() происходит загрузка всех определенных в нем шаблонов. $tpl->define( array (main => "main.tpl", list_f => "list.tpl", list_el=> "list_element.tpl" ));
Здесь мы задаем псевдонимы именам файлов шаблонов. Эти псевдонимы, т.е. переменные main, list_f и list_el, будут использоваться в программе вместо соответствующих имен файлов main.tpl, list.tpl и list_element.tpl. Метод assign Синтаксис: assign( (пары ключ/значение) или ( array(пары ключ/значение) ) Метод assign() присваивает переменным значения, "ключ" - это имя переменной, а "значение" - значение, которое ей нужно присвоить. Чтобы переменная в шаблоне была заменена значением, это значение нужно задать ей с помощью метода assign(). Согласно синтаксису этот метод можно использовать в двух различных формах. В FastTemplate есть только один массив, поэтому, если вы повторно задаете значение одному и тому же ключу, оно будет перезаписано. $tpl->assign(array( TITLE => "Установка и настройка ПО", TITLE => "Введение в PHP" ));
Здесь мы дважды устанавливаем значение переменной, доступной в файлах шаблона по имени TITLE. Эта переменная будет иметь последнее присвоенное ей значение, т.е. она равна строке "Введение в PHP". Метод parse Синтаксис: parse (возвращаемая переменная, источники шаблонов) Метод parse() - самый основной в FastTemplate. Он устанавливает значение возвращаемой переменной равным обработанным шаблонам из указанных источников. Метод может использоваться в трех формах: простой, составной и присоединяющей. $tpl->parse(MAIN, "main"); // простая форма $tpl->parse(MAIN, array ("list_f", "main")); // составная форма $tpl->parse(MAIN, ".list_el"); // присоединяющая форма
В простой форме шаблон с псевдонимом "main" загружается (если еще не был загружен), все его переменные подставляются, и результат сохраняется как значение переменной MAIN. Если переменная {MAIN} появится в более поздних шаблонах, то вместо нее будет подставлено значение, полученное в результате обработки шаблона "main". Это позволяет создавать вложенные шаблоны. Составная форма метода parse() создана для того, чтобы упростить вложение шаблонов друг в друга. Следующие записи эквивалентны: $tpl->parse(MAIN, "list_f"); $tpl->parse(MAIN, ".main");
это то же самое что и $tpl->parse(MAIN, array("list_f", "main")); Когда используется составная форма, важно, чтобы каждый шаблон, идущий в списке после первого, содержал ту переменную, в которую передаются результаты обработки шаблона. В примере выше main должен содержать переменную {MAIN}, поскольку именно в нее передаются результаты обработки шаблона list_f. Если main не содержит переменной {MAIN}, то результаты обработки шаблона list_f будут потеряны. Присоединяющий стиль позволяет добавлять результаты обработки шаблона к переменной результата. Точка перед псевдонимом файла шаблона говорит FastTemplate о том, что нужно присоединить результат обработки этого шаблона к возвращенным результатам, а не перезаписывать его. Такой стиль наиболее часто используется при построении таблиц с переменным числом рядов, получаемых, например, в результате запроса к базе данных. Метод FastPrint Синтаксис: FastPrint(обработанная переменная) Метод FastPrint() печатает содержимое переданной в него обработанной переменной. Если он вызван без параметров, то печатается последняя использованная методом parse() переменная. $tpl->FastPrint(); /* если продолжать предыдущий пример, то эта функция напечатает значение переменной MAIN */ $tpl->FastPrint("MAIN"); // эта функция сделает тоже самое
Если нужно печатать не на экран, а, например, в файл, то получить ссылку на данные можно с помощью метода fetch(). $data = $tpl->fetch("MAIN"); fwrite($fd, $data); // запись данных в файл
Решение задачи с помощью шаблонов FastTemplate Теперь попробуем собрать воедино все изученные методы, чтобы решить нашу задачу. Заметим, что решение задачи получилось несколько более сложным, чем в первом случае, когда использовалась только функция регулярной замены. Зато здесь мы можем изменять три различных шаблона (документа в целом, списка и элемента списка). Этот класс шаблонов появился еще до выхода PHP4 для работы с PHP3. Чтобы протестировать приведенные примеры, нужно скачать библиотеку классов FastTemplate и скопировать этот файл в свою рабочую директорию. Если вы работаете с PHP4, то в файл class.FastTemplate.php3 нужно внести пару изменений, о которых написано в документации, поставляющейся вместе с этой библиотекой. Шаблоны Smarty Smarty - один из действующих проектов PHP, его официальный сайт - http://smarty.php.net. Там можно скачать как сам набор классов Smarty, так и документацию к нему. Этот набор классов для обработки шаблонов - гораздо более мощный и функциональный, чем FastTemplate.Чем отличается Smarty от классов шаблонов типа FastTemplate? Прежде всего, он не отделяет полностью логику от содержания. Логика, касающаяся отображения данных, может присутствовать в шаблоне, считают разработчики Smarty. Поэтому в шаблоне Smarty могут быть условные операторы, операторы вставки файлов, операторы изменения переменных, циклы и т.п. Другая особенность Smarty - это компиляция шаблонов. Шаблоны переводятся в php-код, и интерпретатор PHP производит все необходимые действия по подстановке значений. Для ускорения работы скомпилированные шаблоны кэшируются. Рассмотрим некоторые основные конструкции механизма шаблонов Smarty. Установка Первое, с чего мы начнем, - это установка Smarty. Здесь все не так просто, как с FastTemplate. Smarty состоит не из одного php-файла с описанием класса, а из целого набора различных файлов-библиотек. Для того чтобы работать с шаблонами Smarty, нужно сделать эти библиотеки доступными для всех ваших программ. Находятся они в каталоге /libs/ дистрибутива Smarty. Файл, в котором содержится определение самого класса Smarty, называется Smarty.class.php. Чтобы проверить, доступны ли библиотеки класса Smarty, нужно написать такой скрипт: <? require('Smarty.class.php'); // подключаем файл с // описанием класса Smarty $smarty = new Smarty; // создаем экземпляр класса Smarty ?>
Если при его исполнении появилась ошибка, то нужно попробовать один из перечисленных ниже вариантов. <? // подключаем файл с описанием класса require('c:/users/my/Smarty/libs/ Smarty.class.php'); $smarty = new Smarty; // создаем экземпляр класса Smarty ?>
- Добавить директорию, где содержатся библиотеки, в include_path (в этом случае код менять не нужно). <? define("SMARTY_DIR", "c:/users/my/Smarty/libs/index.html"); require(SMARTY_DIR."Smarty.class.php"); $smarty = new Smarty; ?>
Теперь, после того как мы убедились, что библиотеки будут найдены, нужно создать директории, необходимые для работы Smarty, по умолчанию имеющие такие имена: - templates - директория, где мы будем хранить созданные шаблоны; - templates_c - директория, где Smarty будет хранить скомпилированные шаблоны; - configs - директория для хранения конфигурационных файлов; - cache - директория для кэша. Эти имена задаются свойствами $template_dir, $compile_dir, $config_dir, $cache_dir класса Smarty, поэтому их можно переопределить. Рекомендуется использовать различные наборы директорий для каждого приложения, работающего с шаблонами Smarty. Доступ к перечисленным директориям осуществляется библиотекой Smarty и никогда не выполняется напрямую через web-браузер. Поэтому, чтобы избежать проблем с безопасностью, лучше всего разместить эти директории там, куда нет доступа через www. Создадим перечисленные Smarty директории по адресу c:/smarty_dirs/book/. Заметим, что прочитать отсюда данные через браузер нельзя. Пусть наша программа (или приложение) находится по адресу /~my/tasks/book/. Для директорий $compile_dir и $cache_dir Smarty потребуется доступ на запись, так что нужно установить соответствующие им права для сетевого пользователя, с которым ваш сервер работает по умолчанию (обычно это www или nobody). Чтобы протестировать сделанные настройки, создадим простой шаблон и программу, обрабатывающую его с использованием механизма Smarty. index.tpl (является Smarty шаблоном и находится в директории шаблонов c:/smarty_dirs/book/templates/) {* Шаблон Smarty *} Привет, {$name}!
index.php (является кодом нашей программы и находится в директории /~my/tasks/book/ или, что то же самое, в директории c:/users/my/tasks/book/) В результате должны получить: Привет, Вася! Все настройки, необходимые для работы нашего приложения, можно вынести в отдельный файл и организовать их в качестве расширения класса Smarty. Далее более подробно рассмотрим, из каких элементов могут состоять шаблоны Smarty и как их обрабатывать внутри php-скрипта. Начнем с синтаксиса шаблонов. Основной синтаксис Smarty - не просто класс для обработки шаблонов, он определяет целый язык построения шаблонов. Мы коснемся только основных его элементов. Итак, что представляет собой шаблон Smarty? Это набор специальных конструкций (переменных, вызовов функций и методов и т.п) и html-тегов. Все элементы (теги) языка шаблонов Smarty заключаются между символами-ограничителями. По умолчанию это символы фигурных скобок "{" и "}", но их можно изменить. Все, что не заключено в такие ограничители, Smarty рассматривает как константы, не требующие обработки. В шаблоне index.tpl, приведенном выше, {$name} - это переменная, а строки "Привет," и "!" - не изменяющиеся в процессе обработки шаблона константы. Комментарии в Smarty записываются между двумя звездочками: {* Это комментарий. После обработки шаблона он на экране не отображается *}
Каждый Smarty тег либо выводит значение переменной, либо вызывает какую-либо функцию. Функция записывается следующим образом: {имя_функции атрибут1="значение1" атрибут2="значение2"} Переменные в шаблоне могут быть нескольких типов: Переменные, значение которым присваивается в php-скрипте пользователя, должны иметь перед именем знак доллара. Например: {$first_name} Элементы массива, значения которых были присвоены в php-скрипте пользователя, доступны в шаблоне с помощью синтаксиса {$имя_массива.ассоциативный_ключ}. Например: {$person.last_name} Элементы не ассоциативного массива доступны с помощью синтаксиса квадратных скобок: {имя_массива[числовой_индекс]} Например: {$person[2]} Свойства объектов, заданные в php-скрипте, доступны в шаблоне с помощью такого синтаксиса: {имя_объекта->имя_свойства} Например: {$person->email} Переменные, загруженные из конфигурационных файлов (что это такое, мы расскажем чуть позже), заключаются между символами #. Также они доступны как элементы ассоциативного массива $smarty.config. Например: {#bodyBgColor#} или {$smarty.config.bodyBgColor} Кроме того, существует переменная {$smarty}, зарезервированная для некоторых специальных переменных шаблона, таких как переменные HTTP запроса, даты и времени, и т.п. В шаблонах Smarty определен ряд модификаторов, которые можно применять к переменным, пользовательским функциям или строкам с тем, чтобы модифицировать их значения. Чтобы применить модификатор, нужно указать его название после вертикальной черты, следующей за именем переменной, функции или строкой, к которой он применяется. Например, чтобы перевести значение переменной {$title} в верхний регистр, нужно применить к ней модификатор upper, т.е. написать следующее: {$title|upper} Можно использовать сразу несколько модификаторов, отделяя их друг от друга прямой вертикальной чертой. Например, {$title|upper|truncate} переведет значение переменной в верхний регистр и урежет до 80 символов. Перечислять все имеющиеся модификаторы мы не будем. Их список можно найти в документации Smarty. Скажем только, что с их помощью можно посчитать число символов, слов и параграфов, дописать строку, задать формат вывода даты и времени, сделать регулярную замену и многое другое. Конфигурационные файлы Конфигурационные файлы используются для того, чтобы управлять глобальными переменными, используемыми в шаблоне, с помощью одного файла. Их идея очень похожа на таблицы стилей css. Конфигурационный файл содержит набор переменных и их значения. Перед именем переменной не ставится никаких дополнительных символов типа знака доллара. Значение переменной по желанию заключают в кавычки (двойные или одинарные), если оно состоит из нескольких строк, то его заключают в тройные кавычки. # глобальные переменные pageTitle = "List of documents" bodyBgColor = #000000 tableBgColor = #000000 rowBgColor = #00ff00 [Customer] pageTitle = "Список статей" Intro = """Это значение состоит из нескольких строк. Поэтому его нужно заключить в тройные кавычки.""" # скрытая секция [.Database] host=localhost db=book user=nina pass=123
Конфигурационный файл может состоять из нескольких разделов (секций), каждая из которых имеет имя и может загружаться отдельно от остальных секций. Имя секции заключается в квадратные скобки. Кроме секций в конфигурационном файле могут содержаться глобальные переменные - они не входят ни в одну секцию и всегда загружаются при загрузке конфигурационного файла. Если загружается какая-то одна секция, то загружаются ее переменные и глобальные переменные. Если переменная существует и как глобальная переменная, и как переменная секции, то используется переменная секции. Если вы одинаково назовете две переменные внутри одной секции, то будет использоваться последняя из них. В приведенном выше примере две секции - Customer и Database, а кроме них заданы глобальные переменные pageTitle, bodyBgColor, tableBgColor и rowBgColor. Чтобы спрятать значение переменной или секцию целиком, нужно перед ее именем поставить точку. В таком случае при загрузке конфигурационного файла эти данные нельзя будет прочесть. В примере мы сделали скрытой секцию Database, чтобы нельзя было узнать пароль и имя пользователя, применяемые для установки соединения. Комментарии в конфигурационном файле можно обозначать символом #. Загрузка конфигурационных файлов производится с помощью встроенной функции или метода config_load, подробнее об этом мы расскажем в следующей главе. Методы Для работы с шаблонами класс Smarty определяет набор методов. Рассмотрим несколько основных методов. Метод assign Синтаксис: void assign (смешанное значение); void assign (имя переменной, смешанное значение);
Метод используется для того, чтобы присвоить значения переменным шаблона. Можно передавать ассоциативные массивы, содержащие пары имя/значение переменных, или передавать пары имя/значение для каждой переменной в отдельности. <?php // передаем пары имя/значение для // переменной Name и // Address в отдельности $smarty->assign("Name","Вася"); $smarty->assign("Address",$addr); // здесь $addr может быть и массивом // передаем ассоциативный массив $smarty->assign(array( "city" => "Новосибирск", "street" => "Пирогова")); // таким образом, переменные city и street // получат соответствующие значения ?>
Метод append Синтаксис: void append (смешанное значение); void append (имя переменной, смешанное значение); void append (имя переменной, смешанное значение, слияние);
Принцип действия этого примерно такой же, как и у assign. Метод append позволяет присоединить элемент к массиву. Если вы присоединяете значение к строковой переменной, то она преобразуется в массив, и значение добавляется уже в него. Так же, как и в assign, здесь можно передавать пары ключ/значение или ассоциативные массивы, содержащие эти пары. Если указать третий аргумент слияние равным TRUE, то значение будет не присоединено в качестве еще одного элемента, а слито воедино с текущим массивом. $smarty->append(array( title => $title, author => $author))
Здесь если title была строкой, то она становится массивом и к нему добавляется еще один элемент со значением $title. То же самое происходит с переменной author. Метод config_load void config_load(имя файла, [имя секции]); Метод загружает конфигурационный файл и встраивает его в шаблон. Аналогично этому методу действует функция config_load. Пример: $smarty->config_load("task.conf","Database"); Метод display void display(шаблон); Метод отображает шаблон. У этого метода есть еще два опциональных параметра, о которых можно прочитать в документации. Метод fetch string fetch(шаблон); Этот метод возвращает обработанный шаблон в строковую переменную, вместо того чтобы выводить его на экран. У этого метода есть еще два опциональных параметра, о которых можно прочитать в документации. Встроенные функции Smarty поставляется с набором встроенных функций, интегрированных в язык шаблонов. Нельзя создавать свои функции с такими же именами или модифицировать встроенные функции. Опишем некоторые из таких функций. Функция config_load Синтаксис: {config_load file="имя_файла" } Эта функция используется для загрузки в шаблон переменных из конфигурационных файлов. Кроме имени загружаемого файла, у этой функции может быть еще несколько дополнительных параметров. Например, параметр section, в котором указывают имя секции для загрузки. Более подробную информацию об этих и других параметрах можно получить из документации Smarty. Пример: {config_load file="task.conf"} Функция capture Синтаксис: {capture name="имя_блока" assign="имя_переменной"} ... {/capture} Эта функция предназначена для того, чтобы собирать в переменную выходные данные шаблона вместо того, чтобы выводить их на экран. Все, что находится между {capture name="varname"} и {/capture}, будет записано в переменную с именем varname. Захваченный таким образом контент может быть использован в шаблоне посредством специальной переменной $smarty.capture.varname, где varname - значение, переданное в атрибут name функции capture. Если имя переменной не задано, будет использовано имя default. Второй параметр assign задает имя переменной, которой будет присвоено захваченное выходное значение. Этот параметр, как и name, не обязательный. Функция section Синтаксис: {section name="имя_секции" loop="переменная_для_выч-ния_числа_итераций" [,start="индекс_начальной_позиции"] [, step="шаг"] [,max="максимум_итераций"] [,show="показывать_ли_секцию"] }... {/section}
Секция Section - это цикл для обхода элементов массива. Обязательными являются параметры name, с помощью которого задается имя секции, и loop, который представляет собой переменную, определяющую число итераций цикла. Как правило, loop - это переменная типа массив, и число итераций секции равно числу элементов этого массива. Чтобы вывести переменную внутри цикла, нужно после имени переменной указать в квадратных скобках имя секции. {section name=art loop=$title} Название: {$title[art]}<br> {/section}
Функция foreach Синтаксис: {foreach from="имя_массива" item="имя_текущего_элемента"} ... {/foreach}
Кроме того, можно использовать дополнительные атрибуты key - имя ключа для текущего элемента массива и name - имя цикла, с помощью которого можно будет получать доступ к его свойствам. Атрибуты from и item - обязательные. Циклы foreach являются альтернативой циклам section. Действие функции foreach очень похоже на работу цикла foreach в языке PHP. {foreach from=$articles item=art} Title: {$art}<br> {/foreach}
Циклы foreach имеют свои собственные свойства. Получить доступ к ним можно таким образом: {$smarty.foreach.foreachname.varname}, где foreachname - это имя цикла, заданное его параметром name, а varname - имя свойства. Оператор if, elseif, else Синтаксис: {if выражение} блок_действий {elseif выражение1} блок_действий1 {else} блок_действий2 {/if}
Действие оператора практически аналогично оператору if...elseif...else языка PHP. В выражениях могут использоваться следующие операторы сравнения: eq, ne, neq, gt, lt, lte, le, gte, ge, is even, is odd, is not even, is not odd, not, mod, div by, even by, odd by, ==, !=, >, <, <=, >=. Каждый из них обязательно должен быть отделен от окружающих его значений пробелами. В выражениях можно использовать круглые скобки и вызывать php-функции. {if $name eq "Вася"} Добро пожаловать, Вася. {elseif $name eq "Петя"} Добро пожаловать, Петя. {else} Добро пожаловать. А вы кто? {/if}
{* этот пример не будет работать, поскольку не поставлены пробелы вокруг операторов сравнения *} {if $name=="Вася" || $name=="Петя"} ... {/if}
Решение задачи с помощью шаблонов Smarty Теперь, после знакомства с основными конструкциями Smarty, мы можем попытаться решить задачу отображения списка документов. Шаблон списка будет выглядеть следующим образом: {* Smarty template index.tpl *} {config_load file="task.conf" } <html> <head><title>{#pageTitle#}</title> </head> <body> <ol> {section name=art loop=$title} <li><a href="{$fulltext[art]}"> {$title[art]}</a> ({$author[art]}) <br> <p> {$abstract[art]} {/section} </ol> </body> </html>
В файле конфигурации task.conf будем хранить название страницы и параметры для доступа к базе данных: # глобальные переменные pageTitle = "List of documents" [Customer] pageTitle = "Список статей" [Database] host=localhost db=book user=nina pass=123
Скрипт (index.php), обрабатывающий написанный нами шаблон, может выглядеть таким образом: Как вы, скорее всего, заметили, программа получилась еще более громоздкой, чем в первых двух случаях, когда использовалась простая замена значений и шаблоны FastTemplate. Действительно, механизм Smarty гораздо более сложен, чем тот же FastTemplate, но зато и более функционален. Заключение Итак, в этой лекции было рассказано о том, что такое шаблоны и как их можно использовать при программировании web-приложений на языке PHP. Мы рассмотрели три способа решения задачи отображения сложного списка документов: с помощью функции регулярной замены, с помощью класса шаблонов FastTemplate и с помощью языка шаблонов Smarty. При этом мы познакомились с основными свойствами, методами и функциями классов FastTemplate и Smarty. ЛитератураУчебники к курсу1. Савельева Н.В. Интернет-университет информационных технологий - ИНТУИТ.ру, 2005 2. Анисимов А.Е., Пупышев В.В. БИНОМ. Лаборатория знаний, Интернет-университет информационных технологий - ИНТУИТ.ру, 2006 3. Непейвода Н.Н. Интернет-университет информационных технологий - ИНТУИТ.ру, 2005 4. Сузи Р.А. БИНОМ. Лаборатория знаний, Интернет-университет информационных технологий - ИНТУИТ.ру, 2006 5. Терехов А.Н. БИНОМ. Лаборатория знаний, Интернет-университет информационных технологий - ИНТУИТ.ру, 2006 Список литературы 1. http://www.php.net/ 2. http://chukotken.boom.ru/php/2.html 3. http://www.ict.nsc.ru/win/docs/html-gd/html-gd.dhtml 4. Федотов А. М. http://www.ict.nsc.ru/win/fedotov/inter/index.html 5. http://netadmin.ws/art/43.html 6. Храмцов П. Б. 7. Эйзенберг Э., Мелтон Д. 8. http://www.mysql.ru/docs/man/index.html 9. C. Shifflett Перевод С. Бресь 10. Грималовский А. 11. Куликов А. http://www.koulikov.cc Программное обеспечение1. PHP || The PHP Group http://www.php.net/index.html Скрипт-язык для web-программирования. Наиболее гибкий, удобный, простой и достаточно мощный язык, созданный специально для web-программистов, который интерпретируется и выполняется на сервере. Значительно облегчает написание динамических web-страниц. 2. Apache || Apache Software Foundation http://www.apache.org/index.html HTTP (WEB) сервер Самый популярный web-сервер, именно на этом сервере работают большинство сайтов Интернета. Информация об установке: http://httpd.apache.org/docs/2.2/install.html Лицензия: Apache License, Version 2.0, January 2004, http://www.apache.org/licenses/ Требования к системе (Windows): До 50Мб дискового пространства для установки, после установки примерно 10Мб. Требования к системе (Linux): До 50Мб дискового пространства для установки, после установки примерно 10Мб. 3. Apache Ant || Apache Software Foundation http://www.apache.org/index.html Пакет Apache Ant это Java-основанное средство сборки. Пакет Apache Ant это Java-основанное средство сборки. Теоретически, это разновидность make, но без складок make. Ant другой. Вместо модели, где он расширялся бы с помощью shell-основанных команд, Ant расширяется Java классами. Вместо написания команд оболочки, файлы конфигурации основаны на XML и вызываются из дерева целей, в котором различные задачи были запущены. Каждая задача запускается объектом, который включает особый интерфейс задачи. Лицензия: Apache License, Version 2.0, January 2004, http://www.apache.org/licenses/ |
|
||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх |
||||
|