Отображение данных, хранящихся в 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
Пример 11.1. Запрос с дублирующимися именами колонок (html, txt)
Важно заметить, что 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 в виде таблицы в браузере:
Листинг 11.1.1. Отображение данных из Artifacts в виде таблицы в браузере (html, txt)
Сделаем то же самое с помощью mysql_fetch_array():
Листинг 11.1.2. Отображение данных из Artifacts в виде таблицы в браузере. Вариант 2 (html, txt)
Отображение списка полей в html-форму
Теперь немножко подкорректируем предыдущий пример. Будем не просто выводить информацию о поле, а отображать его в подходящий элемент html-формы. Так, элементы типа BLOB переведем в textarea (заметим, что поле description, которое мы создавали с типом TEXT, отображается как имеющее тип BLOB), числа и строки отобразим в текстовые строки ввода <input type=text>, а элемент, имеющий метку автоинкремента, вообще не будем отображать, поскольку его значение устанавливается автоматически.
Все это решается довольно просто, за исключением выделения из списка флагов флага auto_increment. Для этого нужно воспользоваться функцией explode.
Синтаксис explode:
массив explode( строка separator, строка string [, int limit])
Эта функция разбивает строку string на части с помощью разделителя separator и возвращает массив полученных строк.
В нашем случае в качестве разделителя нужно взять пробел " ", а в качестве исходной строки для разбиения - строку флагов поля.
Итак, создадим форму для ввода данных в таблицу Artifacts:
Листинг 11.0.1. Форма для ввода данных в таблицу Artifacts (html, txt)
Получение списка полей таблицы
Теперь можно заняться собственно решением задачи. Как получить список полей таблицы? Очень просто. В 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-форму.
После этого данные, введенные в форму, нужно записать в базу данных. Рассмотрим все эти задачи по порядку.
создаем форму для ввода данных
<? $conn=mysql_connect("localhost","nina","123"); // устанавливаем соединение $database = "book"; $table_name = "Artifacts"; mysql_select_db($database); // выбираем базу данных для // работы $list_f = mysql_list_fields($database,$table_name); // получаем список полей в базе $n = mysql_num_fields($list_f); // число строк в результате // предыдущего запроса (т.е. сколько всего // полей в таблице Artifacts) echo "<form method=post action=insert.php>"; // создаем форму для ввода данных echo " <table CELLSPACING=0 width=50% ><tr> <TD BGCOLOR='#005533' align=center><font color='#FFFFFF'> <b> Add new row in $table_name</b></font></td></tr><tr><td></td></tr></TABLE>"; echo "<table CELLSPACING=1 cellpadding=0 width=50% >"; // для каждого поля получаем его имя, тип, длину и флаги 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); // из строки флагов делаем массив, // где каждый элемент массива - флаг поля $flags = explode(" ", $flags_str); foreach ($flags as $f){ if ($f == 'auto_increment') $key = $name_f; // запоминаем имя автоинкремента } /* для каждого поля, не являющегося автоинкрементом, в зависимости от его типа выводим подходящий элемент формы */ if ($key <> $name_f){ echo "<tr><td bgcolor='#C2E3B6'><font size=2> <b> ". $name_f ."</b></font></td>"; switch ($type){ case "string": $w = $len/5; echo "<td><input type=text name=\"$name_f\" size = $w ></td>"; break; case "int": $w = $len/4; echo "<td><input type=text name=\"$name_f\" size = $w ></td>"; break; case "blob": echo "<td><textarea rows=6 cols=60 name=\"$name_f\"></textarea></td>"; break; } } echo "</tr>"; } echo "</table>"; echo "<input type=submit name='add' value='Add'>"; echo "</form>"; ?> |
Листинг 11.0.1. Форма для ввода данных в таблицу Artifacts |
Закрыть окно |
Установка соединения
Итак, первое, что нужно сделать, - это установить соединение с базой данных. Воспользуемся функцией 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. Для этого мы рассмотрели ряд функций, которые позволяют отправлять SQL-запросы к базе данных и обрабатывать полученные ответы. Используя приведенную здесь технологию, можно решить целый ряд похожих задач, таких как задачи изменения и удаления данных, задачи манипулирования таблицами базы данных (т.е. их создание, изменение и удаление) и т.п. Все это типовые задачи, возникающие при разработке систем управления данными, и умение их решать, как и умение работать с базами данных в целом, очень важно для web-программиста.
Запись данных в базу данных
Итак, форма создана. Теперь нужно сделать самое главное - отправить данные из этой формы в нашу базу данных. Как вы уже знаете, для того чтобы записать данные в таблицу, используется команда 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['имя_поля'].
Листинг 11.0.2. insert.php (html, txt)
Итак, задачу добавления данных с помощью web-интерфейса мы решили. Однако тут есть одна тонкость. При решении мы не учитывали тот факт, что значения некоторых полей (author, photo) должны браться из других таблиц (Persons, Images). Поскольку MySQL с внешними ключами не работает, этот момент остается на совести разработчиков системы, т.е. на нашей совести. Нужно дописать программу таким образом, чтобы была возможность вводить в такие поля правильные значения. Но мы делать этого не будем, поскольку задача лекции состоит в том, чтобы познакомить читателя с элементами технологии, а не в том, чтобы создать работающую систему. Кроме того, имеющихся у читателя знаний вполне достаточно, чтобы решить эту проблему самостоятельно. Мы же обратимся к другой задаче - отображение данных, хранящихся в базе данных СУБД MySQL.
Авторизация доступа
Что такое авторизация доступа? Попробуем объяснить на примере из обычной жизни. Вы хотите взять в библиотеке книгу. Но эта услуга доступна только тем, у кого есть читательский билет. Можно сказать, что с помощью этого билета производится "авторизация доступа" к библиотечным ресурсам. Библиотекарь после предъявления ему читательского билета знает, кто берет книгу, и в случае необходимости (например, книгу долго не возвращают) может принять меры (позвонить должнику домой). Библиотекарь имеет гораздо больше прав, чем обычный посетитель: он может давать или не давать книги определенному посетителю, может выставлять напоказ новинки и убирать в архив редко читаемые книги и т.п.
В информационных технологиях все примерно так же. В сети существует огромное количество ресурсов, т.е. множество "библиотек". У каждой из них свой "библиотекарь", т.е. человек или группа людей, отвечающих за содержание ресурса и предоставление пользователям информации. Их называют администраторами. Функции администратора, как правило, включают добавление новой информации, удаление и редактирование существующей, настройка способов отображения информации пользователю. А в функции пользователя (простого посетителя ресурса) входит только поиск и просмотр информации.
Как же отличить пользователя от администратора? В реальной библиотеке это как-то очевидно, но если роли библиотекаря и посетителя библиотеки перенести в виртуальную реальность, то эта очевидность исчезает. Библиотекарь, как и посетитель, имеет доступ к библиотечным ресурсам через Internet. А согласно протоколу HTTP все клиенты абсолютно равноправны. Как же понять, кто зашел на сайт? Обычный пользователь (посетитель) или администратор (библиотекарь)? Если это простой пользователь, то как сохранить это знание, чтобы не допустить посетителя в закрытые архивы сайта? То есть возникает вопрос, как идентифицировать клиента, который послал запрос, и сохранять сведения о нем, пока он находится на сайте?
Самый простой вариант, который приходит в голову, - это регистрация человека в системе и выдача ему аналога читательского билета, а именно логина и пароля для входа в административную часть системы. Эта информация хранится на компьютере-сервере, и при входе в систему проверяется соответствие введенных пользователем логина и пароля тем, что хранятся в системе. Правда, здесь по сравнению с реальной библиотекой ситуация изменяется: читательский билет требуется библиотекарю для входа в закрытую часть системы, а читатель может заходить на сайт свободно. В принципе можно регистрировать и простых посетителей. Тогда всех зарегистрированных пользователей нужно разделить на группы: библиотекари (администраторы) и читатели (простые пользователи), наделив их соответствующими правами. Мы не будем вдаваться в эти тонкости и воспользуемся самым простым вариантом, когда ввод логина и пароля требуется для доступа к некоторым страницам сайта.
Пример 12.1.
У нас имеется файл index.html - домашняя страничка Васи Петрова
<html> <head><title>My home page</title></head> <body> Привет всем! Меня зовут Вася Петров и это моя домашняя страничка. <a href="secret_info.html">Для Пети</a> </body></html>
Листинг 12.1. Домашняя страничка Васи Петрова (html, txt)
и файл secret_info.html, который содержит секретную информацию, читать которую разрешено только Васиному другу Пете.
<html> <head><title>Secret info</title></head> <body> Здесь я хочу делиться секретами с другом Петей.</p> </body></html>
Листинг 12.1a. secret_info.html (html, txt)
Если оставить оба эти файла как есть, то любой посетитель, кликнув на ссылку "Для Пети", попадет на секретную страничку. Чтобы этого избежать, нужно добавить промежуточный скрипт, который будет проверять, действительно ли Петя хочет попасть на секретную страничку. И сделать так, чтобы главный файл ссылался не сразу на secret_info.html, а сначала на этот скрипт.
<html> <head><title>My home page</title></head> <body> <p>Привет всем! Меня зовут Вася Петров и это моя домашняя страничка. </p> <a href="authorize.php">Для Пети</a> </body> </html>
Листинг 12.1b. Index.html (html, txt)
Сам скрипт авторизации должен предоставлять форму для ввода логина и пароля, проверять их правильность и перенаправлять на секретную страничку, если проверка прошла успешно, и выдавать сообщение об ошибке в противном случае.
<? 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>"; } ?>
Листинг 12.1c. authorize.php (html, txt)
Вроде бы все достаточно просто. Но допустим, у нас не одна секретная страничка, а несколько. Причем они связаны между собой перекрестными ссылками. Тогда возникает необходимость постоянно помнить пароль и логин посетителя сайта (если он таковой имеет). Чтобы решить эту проблему, можно в каждую страницу встроить скрипт, который будет передавать логин и пароль от страницы к странице в качестве скрытых параметров формы. Но такой способ не совсем безопасен: эти параметры можно перехватить и подделать. В PHP существует более удобный и безопасный метод решения проблемы хранения данных о посетителе в течение сеанса его работы с сайтом - это механизм сессий.
Безопасность
Вообще говоря, cледует понимать, что использование механизма сессий не гарантирует полной безопасности системы. Для этого нужно принимать дополнительные меры. Обратим внимание на проблемы с безопасностью, которые могут возникнуть при работе с сессиями и, в частности, с теми программами, что мы написали.
Во-первых, опасно передавать туда-сюда пароль, его могут перехватить. Кроме того, мы зарегистрировали его как глобальную переменную сессии, значит, он сохранился в cookies на компьютере-клиенте. Это тоже плохо. И вообще, пароли и логины по-хорошему должны храниться в базе данных. Пусть информация о пользователях хранится в базе данных "test" (в таблице "users"), а мы имеем к ней доступ под логином my_user и паролем my_passwd.
Во-вторых, что делать, если кто-то написал скрипт подбора пароля для секретной страницы? В этом случае на страницу авторизации много раз должен стучаться какой-то посторонний скрипт. Поэтому нужно просто проверять, с нашего ли сайта пришел запрос на авторизацию, и если нет, то не пускать его дальше. Адрес страницы, с которой поступил запрос, можно получить с помощью глобальной переменной $_SERVER['HTTP_REFERER']). Хотя, конечно, если за взлом сайта взялись всерьез, то значение этой переменной тоже подменят (например, с помощью того же PHP). Тем не менее проверку ее значения можно считать одним из важнейших шагов на пути к обеспечению безопасности своего сайта.
Листинг 12.6. authorize.php (html, txt)
Вроде бы первые две проблемы решены. Но есть еще одна. Что делать, если хакер просто допишет в строку запроса значение какой-нибудь глобальной переменной (например, логина)? Вообще это возможно, только если 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>
Листинг 12.7. secret_info.php (html, txt)
Листинг 12.6. authorize.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>
Листинг 12.7. secret_info.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. Так что приступим.
Привет всем! Меня зовут Вася
<html> <head><title>My home page</title></head> <body> Привет всем! Меня зовут Вася Петров и это моя домашняя страничка. <a href="secret_info.html">Для Пети</a> </body></html> |
Листинг 12.1. Домашняя страничка Васи Петрова |
Закрыть окно |
<? 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(); ?> |
Пример 12.5. Уничтожение сессии и глобальных переменных |
Закрыть окно |
<? session_start(); // создаем новую сессию или // восстанавливаем текущую $conn = mysql_connect("localhost", "my_user","my_passwd"); // устанавливаем соединение с БД mysql_select_db("test"); // выбираем рабочую базу данных $SERVER_ROOT = "http://localhost/~nina/tasks/sessions/"; // где находятся наши скрипты /* с помощью регулярного выражения ^$SERVER_ROOT и функции eregi проверяем, начинается ли адрес ссылающегося скрипта, т.е. строка $_SERVER['HTTP_REFERER']) со строки $SERVER_ROOT (как у нас) */ if(eregi("^$SERVER_ROOT", $_SERVER['HTTP_REFERER'])){ // если да, то делаем почти то же, что и // раньше, пароль регистрировать не будем if (!isset($_POST['go'])){ echo "<form method=POST > Login: <input type=text name=login> Password: <input type=password name=passwd> <input type=submit name=go value=Go> </form>"; }else { /* запрос к базе данных: выбираем из таблицы users login, который совпадает с переданным по запросу, причем пароль у него тоже должен совпасть с введенным пользователем. Если этого нет, то считаем, что логин и пароль введены неверно */ $sql = "SELECT login FROM users WHERE login='" . $_POST['login'] . "' AND passwd='" . $_POST['passwd'] . "';"; $q = mysql_query($sql,$conn); // отправляем запрос к БД $n = mysql_num_rows($q); // число строк в ответе на запрос if (!$n==0){ $_SESSION['user_login']=$_POST['login']; // регистрируем переменную login Header("Location: secret_info.php"); // перенаправляем на страницу secret_info.php }else echo "Неверный ввод, попробуйте еще раз<br>"; } print_r($_SESSION); // выводим все переменные сессии } ?> |
Листинг 12.6. authorize.php |
Закрыть окно |
<?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> |
Листинг 12.7. secret_info.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); // выводим все переменные сессии ?>
Листинг 12.3a. authorize.php (html, txt)
Теперь, попав на страничку secret_info.php, да и на любую другую страницу сайта, мы сможем работать с введенными пользователем логином и паролем, которые будут храниться в массиве $_SESSION. Таким образом, если изменить код секретной странички (заметьте, мы переименовали ее в secret_info.php) так:
<?php session_start(); // создаем новую сессию или // восстанавливаем текущую print_r($_SESSION); // выводим все переменные сессии ?> <html> <head><title>Secret info</title></head> <body> <p>Здесь я хочу делиться секретами с другом Петей. </body> </html>
Листинг 12.3b. secret_info.php (html, txt)
То мы получим в браузере на секретной странице следующее:
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>
Листинг 12.3c. 2-я версия secret_info.php (html, txt)
Создание сессии
Первое, что нужно сделать для работы с сессиями (если они уже настроены администратором сервера), это запустить механизм сессий. Если в настройках сервера переменная 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 ?>
Пример 12.2. Создание сессии (html, txt)
Если проделать то же самое с файлом authorize.php, то значения выводимых переменных (id сессии и ее имя) будут такими же, если перейти на него с index.php и не закрывать перед этим окно браузера (тогда идентификатор сессии изменится).
Удаление переменных сессии
Кроме умения регистрировать переменные сессии (т.е. делать их глобальными на протяжении всего сеанса работы), полезно также уметь удалять такие переменные и сессию в целом.
Функция 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>
Пример 12.4a. secret_info.php (html, txt)
В Index.php уничтожим логин и пароль, введенные ранее:
<? session_start(); session_unregister('passwd'); // уничтожаем пароль unset($_SESSION['login']); // уничтожаем логин print_r($_SESSION); // выводим глобальные переменные сессии ?> <html> <head><title>My home page</title></head> ... // домашняя страничка </html>
Пример 12.4b. Index.php (html, txt)
Теперь, чтобы попасть на секретную страницу, нужно будет опять вводить логин и пароль.
Для того чтобы сбросить значения всех переменных сессии, можно использовать функцию 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(); ?>
Пример 12.5. Уничтожение сессии и глобальных переменных (html, txt)
В результате работы этого скрипта будут выведены три строки: в первой - массив с элементом test и его значением, а также идентификатор сессии, во второй - пустой массив и идентификатор сессии, в третьей - пустой массив. Таким образом, видно, что после уничтожения сессии уничтожается и ее идентификатор, и мы больше не можем ни регистрировать переменные, ни вообще производить какие-либо действия с сессией.
с ними, проблемами, возникающими при
Итак, мы познакомились с сессиями и основными способами работы с ними, проблемами, возникающими при их использовании, и возможными решениями этих проблем. Надеюсь, что после прочтения лекции читателям стало ясно, насколько удобны и просты в использовании сессии, а приведенные примеры пригодятся на практике.
Модификаторы PCRE
Еще один немаловажный элемент регулярного выражения – это список применяемых к нему модификаторов. Модификаторы – это выдаваемая интерпретатору регулярных выражений инструкция по обработке данного выражения. Например, считать, что все символы регулярного выражения соответствуют как большим, так и маленьким буквам в строке, где производится поиск. Примеры модификаторов приведены в таблице 13.3.
i (PCRE_CASELESS) | Если указан этот модификатор, то буквы в шаблоне совпадают с буквами и верхнего, и нижнего регистра в строке |
m (PCRE_MULTILINE) | По умолчанию строка, подающаяся на вход интерпретатору РВ, рассматривается как состоящая из одной линии. Этот модификатор включает поддержку многострокового режима |
s (PCRE_DOTALL) | Если установлен этот модификатор, то метасимвол точка «.» совпадает с любым символом, ВКЛЮЧАЯ символ перевода строки |
x (PCRE_EXTENDED) | Заставляет интерпретатор игнорировать пробелы между символами в шаблоне, за исключением пробелов, экранированных обратным слэшем или находящихся внутри символьного класса, а также между неэкранированным символом # вне символьного класса и символом новой строки |
U (PCRE_UNGREEDY) | Этот модификатор инвертирует «жадность» квантификаторов, т.е. они становятся «нежадными» по умолчанию и «жадными» если предшествуют символу «?» |
Обратная ссылка
Вне определения символьного класса (это тот, что задается квадратными скобками) комбинация обратный слэш и цифра больше нуля (например, \1) называется обратной ссылкой и представляет собой ссылку на захваченное ранее регулярное подвыражение. Этих подвыражений ровно столько, сколько открывающихся круглых скобок (перед которыми нет знака вопроса) стоит левее данного элемента.
Обратная ссылка совпадает с конкретным выбранным значением подвыражения, на которое она ссылается, а не с любым возможным значением этого подвыражения. Таким образом, шаблон
(ответствен|надеж)ный проявляет \1ность
совпадет со строками «ответственный проявляет ответственность», «надежный проявляет надежность» и не совпадет со строкой «ответственный проявляет надежность».
Обратные ссылки могут использоваться внутри подвыражений. При первом использовании подвыражения ссылка внутри него не срабатывает, но при последующих повторениях подшаблона она работает, как описано выше.
Подвыражения (подшаблоны)
В нескольких примерах мы уже использовали подвыражения. Настало время разобраться, что же это такое и какими свойствами они обладают
В РВ подшаблоны выделяют, заключая в круглые скобки. Для их обозначения кроме термина «подшаблон» также используют термин «подвыражение». Подшаблоны могут быть вложенными. Выделение части регулярного выражения в виде регулярного подвыражения делает следующее.
Локализует множество альтернатив.
Например, шаблон
жар(кое|птица|)
совпадает с одним из слов «жаркое», «жарптица» и «жар». Тогда как без скобок это было бы «жаркое», «птица» и пустая строка. Устанавливает подшаблон как «захватывающий» подшаблон. Это значит, что, когда какая-то подстрока в тексте совпала с шаблоном, все подстроки, которые совпали с подшаблонами этого РВ, тоже возвращаются в качестве результата. Скобки, обозначающие начало подшаблона, пересчитываются слева направо (начиная с 1) для того, чтобы узнать, сколько подшаблонов нужно захватить.
Например, имеется такой шаблон:
победитель получит ((золотую|позолоченный) (медаль|кубок))
и строка, в которой ищутся совпадения с этим шаблоном: «победитель получит золотую медаль». Тогда кроме этой фразы будут еще захвачены и выданы как результаты поиска следующие совпадения в подвыражениях: «золотую медаль», «золотую», «медаль», пронумерованные 1, 2, 3 соответственно.
Однако это не всегда удобно. Для того чтобы избавиться от «захватывающего» эффекта подвыражения, после открывающей скобки пишут «?:». Тогда это подвыражение в результат поиска не включается и при нумерации остальных подшаблонов с «захватывающим» эффектом не учитывается.
победитель получит ((?:золотую|позолоченный) (медаль|кубок))
Пример 13.1. Применение ?: (html, txt)
Тогда в условиях предыдущего примера получим искомую строку «победитель получит золотую медаль» и строки «золотую медаль», «медаль», пронумерованные 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]; // выводим заголовок ?>
Пример 13.2. Поиск названия в html-файле (html, txt)
Заметим, что здесь выводится первое захваченное подвыражение, поскольку нам интересно только само название, а не все РВ. Так как в этом РВ есть только одно подвыражение, то его значение содержится в нулевом элементе первого массива результатов.
Понятие регулярного выражения
Регулярное выражение (regular expression, сокращенно РВ) – это технология, которая позволяет задать шаблон и осуществить поиск данных, соответствующих этому шаблону, в заданном тексте, представленном в виде строки.
Кроме того, с помощью регулярных выражений можно изменить и удалить данные, разбить строку по шаблону на подстроки и многое другое.
Одно из распространенных применений РВ – это проверка строки на соответствие каким-либо правилам. Например, следующее РВ предназначено для проверки того, что строка содержит корректный e-mail–адрес:
/^\w+([\.\w]+)*\w@\w((\.\w)*\w+)*\.\w{2,3}$/
Выглядит, конечно, жутко, но зато работает. И если уметь пользоваться этим механизмом виртуозно, то жить становится легче.
Вернемся к нашему определению РВ. В нем несколько раз повторяется термин «шаблон». Что это такое? В принципе, интуитивно понятно, но попробуем все же пояснить.
Давайте подумаем, что представляет собой корректный e-mail–адрес. Это набор букв, цифр и символов подчеркивания, после которых идет специальный символ «собака» @, затем еще один такой же набор, содержащий имя сервера, точку (.) и две или три буквы, указывающие на зону домена, к которой принадлежит почтовый ящик (ru, com, org и т.д.). Приведенное выше РВ формализует данное описание на языке, понятном компьютеру. И описывает не какой-то конкретный электронный адрес, а все возможные корректные электронные адреса. Таким образом, производится формальное задание множества правильных e-mail'ов с помощью шаблона регулярного выражения. Другие примеры шаблонов – это шаблоны MS Word и html-формы.
Механизм регулярных выражений задает правила построения шаблонов и осуществляет поиск данных по этому шаблону в указанной строке.
В дальнейшем изложении термины РВ и «шаблон» часто будут использоваться как синонимы, но важно понимать, что это не совсем одно и то же. Шаблон задает какой-то тип данных, а РВ – это механизм, который производит поиск и включает в себя шаблон и опции поиска, а также задает язык написания шаблонов.
Повторения (квантификаторы)
В предыдущих примерах мы часто писали комбинации типа \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]); ?>
Пример 13.3. Использование "жадных" квантификаторов (html, txt)
Результаты работы скрипта:
«Жадный» шаблон:<div id=1>Привет</div> <p>Текст,не заключенный в тег div</p> <div id=2>Пока</div> «Нежадный» шаблон:<div id=1>Привет</div>
Теперь мы в принципе можем решить задачу выделения содержания из html-файла, если оно заключено в теге <div id=content>. Предлагаем читателям проделать это самостоятельно.
победитель получит
победитель получит ((?:золотую|позолоченный) (медаль|кубок)) |
Пример 13.1. Применение ?: |
Закрыть окно |
<? // считываем файл в строку $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]; // выводим заголовок ?> |
Пример 13.2. Поиск названия в html-файле |
Закрыть окно |
<? //Рассмотрим 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]); ?> |
Пример 13.3. Использование "жадных" квантификаторов |
Закрыть окно |
<? //считываем файл в строку $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>"; ?> |
Пример 13.4. «Смотрящие назад» утверждения |
Закрыть окно |
( \( )? [^()]+ (?(1) \) ) |
Пример 13. 5. Условные подвыражения |
Закрыть окно |
(?(?=[^a-z]*[a-z])\d{2}-[a-z]{3}-\d{2} |\d{2}-\d{2}-\d{2}) |
Пример 13. 6. Условные подвыражения |
Закрыть окно |
Регулярные выражения для «продвинутых»
В последующих разделах обсуждаются более сложные конструкции работы с регулярными выражениями, без которых в принципе можно обойтись. Поэтому мы не будем в них особо углубляться, а приведем лишь общие сведения.
Регулярные выражения в 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А-Яа-я] можно задать любую букву или цифру.
Метасимволы, распознаваемые вне квадратных скобок, можно разделить на группы следующим образом: определяющие положение искомого текста в строке, связанные с подвыражениями, ограничивающие символьный класс, квантификаторы и перечисление альтернатив.
Регулярное выражение /\d\d/ m может быть сопоставлено следующим подстрокам: 11, 22, 33. Если в начале РВ стоит ^, то совпадения ищутся в начале строки, поэтому выражение /^\d\d/m найдет только 11.
Когда в конце РВ стоит знак доллара $, поиск производится в конце строки, поэтому выражение /\d\d$/m найдет только 33.
Шаблону же /^\d\d\d$/ будет удовлетворять строка, целиком состоящая из трехзначного числа (т.е. она и начинается и заканчивается этим числом).
Найдем все html-теги, расположенные в начале каждой строки файла 1.htm.
<? //считываем файл в строку $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) \) )
Пример 13.5. Условные подвыражения (html, txt)
Первая часть этого РВ опционально совпадает с открывающейся круглой скобкой, и если этот символ присутствует, то устанавливает его как первое захваченное подвыражение.
Вторая часть совпадает с одним или более символами, не заключенными в круглые скобки.
Третья часть РВ – это условное подвыражение, которое проверяет, совпало ли первое множество скобок или нет (попалась ли нам в строке открывающая круглая скобка). Если попалась, то есть объект (строка) начинается с символа «(», то условие верно и вычисляется условный шаблон, а именно требуется наличие закрывающей круглой скобки. В противном случае подшаблон ни с чем не совпадает.
Если условие – не последовательность цифр, то оно должно быть утверждением. Это может быть позитивное или негативное «смотрящее вперед» или «смотрящее назад» утверждение.
(?(?=[^a-z]*[a-z])\d{2}-[a-z]{3}-\d{2} |\d{2}-\d{2}-\d{2})
Пример 13.6. Условные подвыражения (html, txt)
Условие здесь – позитивное «смотрящее вперед» утверждение. Оно совпадает с любой последовательностью не букв, после которых идет буква. Другими словами, оно проверяет присутствие хотя бы одной буквы в строке для поиска. Если буква найдена, то производится сравнение по первому альтернативному варианту шаблона (\d{2}-[a-z]{3}-\d{2}), иначе – по второму (\d{2}-\d{2}-\d{2}). Этому шаблону удовлетворяют строки двух типов: dd-aaa-dd или dd-dd-dd, где d – любая цифра, a – любая буква.
Утверждения
Утверждение – это проверка символов, следующих до или после текущего символа. Простейшие утверждения закодированы последовательностями \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>"; ?>
Пример 13.4. «Смотрящие назад» утверждения (html, txt)
Часть РВ после утверждения определяет, что мы ищем строку (ФИО), которая начинается с пробела, большой буквы, затем идут маленькие буквы в произвольном количестве, пробел и инициалы через точку. Утверждение задает то, что перед данной строкой должно стоять «Автор:».
Дату можно вычислить похожим образом. Оставляем это в качестве упражнения.
мы рассмотрели механизм регулярных выражений,
Итак, мы рассмотрели механизм регулярных выражений, их синтаксис и семантику, показали примеры их использования. Безусловно, эта лекция не охватывает все тонкости данного механизма. О регулярных выражениях пишут целые книги! Мы же постарались лишь в общих чертах познакомить читателей с их основами. Вообще механизм регулярных выражений присутствует почти во всех языках программирования с небольшими отличиями, но суть остается той же. Так что надеемся, что знания, полученные в процессе чтения этой лекции, помогут при изучении других языков и пригодятся на практике.
Добавление новых элементов в XML-документ
Далее разберем задачу, как можно добавить в нашу базу данных новую личность средствами php.
Сначала нужно скопировать описание личности (считаем, что все личности описываются с помощью стандартного набора характеристик, как в файле persons.xml). Это делается с помощью метода DomNode->clone_node(). Таким образом, мы клонируем элемент <person> и все его внутренние элементы (содержание тегов не копируется).
Потом можно установить какие-нибудь значения для элементов описания личности. Например, задать имя человека, дату его рождения и т.п. В конце нужно записать полученное описание личности в качестве потомка корневого элемента в дерево DOM с помощью метода DomNode->append_child(new_node), где в качестве параметра передается созданный объект (новый узел).
В PHP до версии 4.3 перед добавлением потомка к узлу с помощью данной функции этот потомок сначала копировался. Таким образом, новый узел являлся новой копией, которая могла изменяться без изменения узла, переданного как параметр в эту функцию. В более поздних версиях PHP новый узел удаляется из существующего контекста, если он уже есть в дереве. Такое поведение соответствует спецификациям W3C.
Для удаления узла можно воспользоваться методом, применив его к узлу, который требуется удалить, т.е. DomNode->unlink_node().
Пример 14.5. Добавление описания новой личности в каталог (html, txt)
Объектная модель 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-скриптами и отображаются на обоих сайтах.
Обход дерева объектов
Для получения значения текущего узла (вне зависимости от его типа) используют метод 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>"; } } ?>
Пример 14.4. Обход дерева XML (html, txt)
Итак, мы научились обходить дерево XML. Теперь можно попытаться что-нибудь найти в XML-файле. Правда, делать это не совсем удобно опять же из-за переносов строк, которые мы использовали при написании XML-файла. Пусть наш XML-файл записан в строку, а точнее, в нем есть следующая строка:
... <person id="20"> <name> <first>Иван</first> <last>Иванов</last> </name> ...
Тогда в наш предыдущий пример вставим (после вывода на экран полученных массивов) строчку для поиска электронного адреса Ивана Иванова.
... $str = $currentPers["email"]; if ($currentPers["name"] == "Иван Иванов" ) echo "Здравствуйте, Иван! " . "Ваш e-mail $str"; ...
Основные понятия
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-файла в объекты и классы PHP
Первое, что нужно сделать, если мы хотим работать с XML-данными в PHP при помощи расширения DOM XML, это перевести имеющиеся данные в объекты и классы DOM. Это можно сделать несколькими способами.
С помощью функции domxml_open_mem.
Синтаксис:
object domxml_open_mem (string str)
В качестве параметра эта функция принимает строку str, содержащую XML-документ. Результатом ее работы является объект класса, называемого DOMDocument. С помощью функции domxml_open_file.
Синтаксис:
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>"; ?>
Пример 14.3. Перевод XML-файла в дерево объектов PHP и выделение корневого элемента (html, txt)
Итак, каждому элементу XML-файла мы поставили в соответствие какой-то объект. Теперь нужно научиться перемещаться по дереву объектов и обращаться с этими объектами: получать и изменять их значения, находить их потомков и предков, удалять объекты.
установлено ли расширение DOM
<? // выводит информацию о настройках PHP phpinfo(); // отображает используемую версию // библиотеки libxml echo domxml_version(); ?> |
Пример 14.1. Проверка, установлено ли расширение DOM 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> |
Пример 14.2. Коллекция «Личности» в виде XML-файла (persons.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>"; ?> |
Пример 14.3. Перевод XML-файла в дерево объектов PHP и выделение корневого элемента |
Закрыть окно |
<? // сначала делаем то же, // что и в предыдущем примере $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>"; } } ?> |
Пример 14.4. Обход дерева XML |
Закрыть окно |
// Для того чтобы добавить описание // новой личности, нужно знать, // как описывается каждая личность. // Выбираем элемент person, // который содержит описание личности $elements = $dom->get_elements_by_tagname("person"); $element = $elements[0]; //вычисляем родителя и потомков $parent = $element->parent_node(); $children = $element->child_nodes(); // клонируем элемент person $person = $element->clone_node(); // устанавливаем новой // личности идентификатор $attr = $person->set_attribute("id", "30"); // если у личности были потомки, // то их тоже надо клонировать foreach ($children as $child){ //клонируем ребенка $node = $child->clone_node(); //получаем массив внуков $grand_children = $child->child_nodes(); // если ребенок имеет потомков, //т.е. массив внуков не пуст, то if (count($grand_children)<>1){ //клонируем каждого внука //и присоединяем к уже //клонированному ребенку foreach($grand_children as $grand_child){ $lastnode = $grand_child->clone_node(); //записываем в нужные теги //подходящие значения if ($grand_child->tagname=="first") $cont = $lastnode->set_content("Nina"); if ($grand_child->tagname=="last") $cont = $lastnode->set_content("Saveljeva"); if ($grand_child->tagname=="day") $cont = $lastnode->set_content("7"); if ($grand_child->tagname=="month") $cont = $lastnode->set_content("06"); if ($grand_child->tagname=="year") $cont = $lastnode->set_content("1981"); $newlastnode = $node->append_child($lastnode); } } if ($child->tagname=="email") { $cont = $node->set_content("help@intuit.ru"); } $newnode2 = $person->append_child($node); } $newnode = $parent->append_child($person); //dump_mem создает XML-документ из dom //представления echo "<PRE>"; $xmlfile = $dom->dump_mem(true); // посмотрим в браузере, // что получилось echo htmlentities($xmlfile); echo "</PRE>"; // запишем полученный XML-файл // в файл "test.xml" $h = fopen("test.xml","a"); if (!fwrite($h, $xmlfile)) { print "Cannot write " . "to file ($filename)"; exit; } } |
Пример 14.5. Добавление описания новой личности в каталог |
Закрыть окно |
Расширения 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(); ?>
Пример 14.1. Проверка, установлено ли расширение DOM XML (html, txt)
Прежде чем начать изучать способы
Прежде чем начать изучать способы работы PHP с XML, полезно хотя бы вкратце ознакомиться с самой технологией XML. В первой части лекции будут рассмотрены основные понятия этой технологии, базовый синтаксис XML–документов, области ее применения, а также понятие DOM XML. Вторая часть лекции посвящена непосредственно вопросам обработки XML при помощи PHP. Сюда входит установка расширения DOM XML, описание и примеры использования некоторых встроенных в PHP функций для обработки XML-документов.
В качестве примера рассмотрим XML-файл, содержащий описания личностей, и попытаемся научиться добавлять, удалять и находить личность или отдельные элементы ее описания в этом файле с помощью PHP.
Взаимодействие 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>
Пример 14.2. Коллекция «Личности» в виде XML-файла (persons.xml) (html, txt)
В дальнейшем, приводя примеры, мы будем использовать этот файл.
Нам необходимо научиться читать, добавлять, изменять и искать информацию, находящуюся в XML-файлах.
мы изучили ряд функций, позволяющих
Итак, мы изучили ряд функций, позволяющих манипулировать данными, хранящимися в XML-формате. Это, конечно же, далеко не полный перечень существующих функций. В версии PHP5 он значительно усовершенствован и в большей степени соответствует стандарту DOM. Тем не менее знание приведенных здесь основных функций может оказаться полезным при решении конкретных прикладных задач.
Что такое шаблоны и зачем они нужны
Что такое шаблон в языке программирования? Можно сказать, что шаблон - это текст с переменными внутри него. При обработке шаблона происходит замена переменных на их значения.
В одной из лекций мы уже рассматривали пример шаблона. Это был шаблон для отображения документов. Пользователь создавал строку текста, размеченного с помощью html-тегов, и вставлял в нее специальные метасимволы (вида <!имя элемента>), которые наша программа впоследствии заменяла на значения соответствующих элементов. Для чего нам был нужен такой шаблон? Чтобы, например, можно было изменить стиль отображения документа, не меняя кода программы.
Наиболее распространенный ответ на вопрос, зачем нужны шаблоны, звучит примерно так: шаблоны нужны для того, чтобы отделить логику работы приложения от способа представления данных, т. е. от дизайна.
Приведенный пример шаблона - один из самых простых. Для его обработки используется только функция подстановки str_replace(). Чаще всего для того, чтобы работать с шаблонами, создают библиотеки классов. В принципе создавать свою библиотеку не обязательно, поскольку существует множество свободно распространяемых библиотек шаблонов, над функциональностью которых трудятся большие коллективы разработчиков, стараясь сделать их универсальными, мощными и быстрыми. Некоторые из таких библиотек мы и рассмотрим. Но для начала сформулируем задачу, на примере решения которой будем демонстрировать использование различных шаблонов.
Итак, задача:
Требуется сгенерировать web-страницу со списком статей, имеющихся в базе данных. Для простоты считаем, что статья имеет название title, автора author, краткое содержание abstract и полное содержание fulltext, которое представлено либо в виде текста в базе данных, либо в виде ссылки на файл. Список должен быть организован так, чтобы при щелчке мышью на названии статьи ее полное содержание появлялось в новом окне.
Функция capture
Синтаксис:
{capture name="имя_блока" assign="имя_переменной"} ... {/capture}
Эта функция предназначена для того, чтобы собирать в переменную выходные данные шаблона вместо того, чтобы выводить их на экран. Все, что находится между {capture name="varname"} и {/capture}, будет записано в переменную с именем varname. Захваченный таким образом контент может быть использован в шаблоне посредством специальной переменной $smarty.capture.varname, где varname - значение, переданное в атрибут name функции capture. Если имя переменной не задано, будет использовано имя default.
Второй параметр assign задает имя переменной, которой будет присвоено захваченное выходное значение. Этот параметр, как и name, не обязательный.
Функция config_load
Синтаксис:
{config_load file="имя_файла" }
Эта функция используется для загрузки в шаблон переменных из конфигурационных файлов. Кроме имени загружаемого файла, у этой функции может быть еще несколько дополнительных параметров. Например, параметр section, в котором указывают имя секции для загрузки. Более подробную информацию об этих и других параметрах можно получить из документации Smarty.
Пример:
{config_load file="task.conf"}
Функция foreach
Синтаксис:
{foreach from="имя_массива" item="имя_текущего_элемента"} ... {/foreach}
Кроме того, можно использовать дополнительные атрибуты key - имя ключа для текущего элемента массива и name - имя цикла, с помощью которого можно будет получать доступ к его свойствам. Атрибуты from и item - обязательные.
Циклы foreach являются альтернативой циклам section. Действие функции foreach очень похоже на работу цикла foreach в языке PHP.
{foreach from=$articles item=art} Title: {$art}<br>
{/foreach}
Пример 15.9. Цикл foreach (html, txt)
Циклы foreach имеют свои собственные свойства. Получить доступ к ним можно таким образом: {$smarty.foreach.foreachname.varname}, где foreachname - это имя цикла, заданное его параметром name, а varname - имя свойства.
Функция section
Синтаксис:
{section name="имя_секции" loop="переменная_для_выч-ния_числа_итераций" [,start="индекс_начальной_позиции"] [, step="шаг"] [,max="максимум_итераций"] [,show="показывать_ли_секцию"] }... {/section}
Секция Section - это цикл для обхода элементов массива. Обязательными являются параметры name, с помощью которого задается имя секции, и loop, который представляет собой переменную, определяющую число итераций цикла. Как правило, loop - это переменная типа массив, и число итераций секции равно числу элементов этого массива. Чтобы вывести переменную внутри цикла, нужно после имени переменной указать в квадратных скобках имя секции.
{section name=art loop=$title} Название: {$title[art]}<br> {/section}
Пример 15.8. Цикл для обхода элементов массива (html, txt)
Конфигурационные файлы
Конфигурационные файлы используются для того, чтобы управлять глобальными переменными, используемыми в шаблоне, с помощью одного файла. Их идея очень похожа на таблицы стилей css. Конфигурационный файл содержит набор переменных и их значения. Перед именем переменной не ставится никаких дополнительных символов типа знака доллара. Значение переменной по желанию заключают в кавычки (двойные или одинарные), если оно состоит из нескольких строк, то его заключают в тройные кавычки.
# глобальные переменные pageTitle = "List of documents" bodyBgColor = #000000 tableBgColor = #000000 rowBgColor = #00ff00 [Customer] pageTitle = "Список статей" Intro = """Это значение состоит из нескольких строк. Поэтому его нужно заключить в тройные кавычки.""" # скрытая секция [.Database] host=localhost db=book user=nina pass=123
Пример 15.5. Пример конфигурационного файла (html, txt)
Конфигурационный файл может состоять из нескольких разделов (секций), каждая из которых имеет имя и может загружаться отдельно от остальных секций. Имя секции заключается в квадратные скобки. Кроме секций в конфигурационном файле могут содержаться глобальные переменные - они не входят ни в одну секцию и всегда загружаются при загрузке конфигурационного файла. Если загружается какая-то одна секция, то загружаются ее переменные и глобальные переменные. Если переменная существует и как глобальная переменная, и как переменная секции, то используется переменная секции. Если вы одинаково назовете две переменные внутри одной секции, то будет использоваться последняя из них. В приведенном выше примере две секции - Customer и Database, а кроме них заданы глобальные переменные pageTitle, bodyBgColor, tableBgColor и rowBgColor.
Чтобы спрятать значение переменной или секцию целиком, нужно перед ее именем поставить точку. В таком случае при загрузке конфигурационного файла эти данные нельзя будет прочесть. В примере мы сделали скрытой секцию Database, чтобы нельзя было узнать пароль и имя пользователя, применяемые для установки соединения.
Комментарии в конфигурационном файле можно обозначать символом #.
Загрузка конфигурационных файлов производится с помощью встроенной функции или метода config_load, подробнее об этом мы расскажем в следующей главе.
Метод append
Синтаксис:
void append (смешанное значение); void append (имя переменной, смешанное значение); void append (имя переменной, смешанное значение, слияние);
Принцип действия этого примерно такой же, как и у assign. Метод append позволяет присоединить элемент к массиву. Если вы присоединяете значение к строковой переменной, то она преобразуется в массив, и значение добавляется уже в него. Так же, как и в assign, здесь можно передавать пары ключ/значение или ассоциативные массивы, содержащие эти пары. Если указать третий аргумент слияние равным TRUE, то значение будет не присоединено в качестве еще одного элемента, а слито воедино с текущим массивом.
$smarty->append(array( title => $title, author => $author))
Пример 15.7. Использование метода append() (html, txt)
Здесь если title была строкой, то она становится массивом и к нему добавляется еще один элемент со значением $title. То же самое происходит с переменной author.
Метод assign
Синтаксис:
assign( (пары ключ/значение) или ( array(пары ключ/значение) )
Метод assign() присваивает переменным значения, "ключ" - это имя переменной, а "значение" - значение, которое ей нужно присвоить. Чтобы переменная в шаблоне была заменена значением, это значение нужно задать ей с помощью метода assign(). Согласно синтаксису этот метод можно использовать в двух различных формах. В FastTemplate есть только один массив, поэтому, если вы повторно задаете значение одному и тому же ключу, оно будет перезаписано.
$tpl->assign(array( TITLE => "Установка и настройка ПО", TITLE => "Введение в PHP" ));
Пример 15.3. Использование метода assign() (html, txt)
Здесь мы дважды устанавливаем значение переменной, доступной в файлах шаблона по имени TITLE. Эта переменная будет иметь последнее присвоенное ей значение, т.е. она равна строке "Введение в PHP".
Синтаксис:
assign( (пары ключ/значение) или ( array(пары ключ/значение) )
Метод assign() присваивает переменным значения, "ключ" - это имя переменной, а "значение" - значение, которое ей нужно присвоить. Чтобы переменная в шаблоне была заменена значением, это значение нужно задать ей с помощью метода assign(). Согласно синтаксису этот метод можно использовать в двух различных формах. В FastTemplate есть только один массив, поэтому, если вы повторно задаете значение одному и тому же ключу, оно будет перезаписано.
$tpl->assign(array( TITLE => "Установка и настройка ПО", TITLE => "Введение в PHP" ));
Пример 15.3. Использование метода assign()
Здесь мы дважды устанавливаем значение переменной, доступной в файлах шаблона по имени TITLE. Эта переменная будет иметь последнее присвоенное ей значение, т.е. она равна строке "Введение в PHP".
Метод config_load
void config_load(имя файла, [имя секции]);
Метод загружает конфигурационный файл и встраивает его в шаблон. Аналогично этому методу действует функция config_load.
Пример: $smarty->config_load("task.conf","Database");
Метод define
Синтаксис:
define( array ( ключ => значение, ключ1 => значение1, ... ))
Метод define() связывает имя файла шаблона с более коротким именем, которое можно будет использовать в программе. То есть "ключ" - это имя, которое мы будем использовать в программе для ссылки на файл шаблона, имя которого записано в строке "значение". Реальные имена файлов шаблонов не рекомендуется использовать нигде, кроме метода define. При вызове метода define() происходит загрузка всех определенных в нем шаблонов.
$tpl->define( array (main => "main.tpl", list_f => "list.tpl", list_el=> "list_element.tpl" ));
Пример 15.2. Использование метода define() (html, txt)
Здесь мы задаем псевдонимы именам файлов шаблонов. Эти псевдонимы, т.е. переменные main, list_f и list_el, будут использоваться в программе вместо соответствующих имен файлов main.tpl, list.tpl и list_element.tpl.
Метод display
void display(шаблон);
Метод отображает шаблон. У этого метода есть еще два опциональных параметра, о которых можно прочитать в документации.
Метод FastPrint
Синтаксис:
FastPrint(обработанная переменная)
Метод FastPrint() печатает содержимое переданной в него обработанной переменной. Если он вызван без параметров, то печатается последняя использованная методом parse() переменная.
$tpl->FastPrint(); /* если продолжать предыдущий пример, то эта функция напечатает значение переменной MAIN */ $tpl->FastPrint("MAIN"); // эта функция сделает тоже самое
Пример 15.4. Использование метода FastPrint() (html, txt)
Если нужно печатать не на экран, а, например, в файл, то получить ссылку на данные можно с помощью метода fetch().
$data = $tpl->fetch("MAIN"); fwrite($fd, $data); // запись данных в файл
Метод fetch
string fetch(шаблон);
Этот метод возвращает обработанный шаблон в строковую переменную, вместо того чтобы выводить его на экран. У этого метода есть еще два опциональных параметра, о которых можно прочитать в документации.
Метод 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 о том, что нужно присоединить результат обработки этого шаблона к возвращенным результатам, а не перезаписывать его. Такой стиль наиболее часто используется при построении таблиц с переменным числом рядов, получаемых, например, в результате запроса к базе данных.
Методы FastTemplate
Далее необходимо изучить методы, которые можно применять к созданному объекту класса FastTemplate. Параллельно обратим внимание, как их можно использовать для решения нашей задачи.
Для работы с FastTemplate нужно знать четыре основных метода: define, assign, parse и FastPrint.
Оператор 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}
Пример 15.10. Операторы if, elseif, else (html, txt)
{* этот пример не будет работать, поскольку не поставлены пробелы вокруг операторов сравнения *} {if $name=="Вася" || $name=="Петя"} ... {/if}
Пример 15.11. Неработающий пример (html, txt)
Основной синтаксис
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. Скажем только, что с их помощью можно посчитать число символов, слов и параграфов, дописать строку, задать формат вывода даты и времени, сделать регулярную замену и многое другое.
с помощью закрывающей фигурной скобки
{TITLE} {AUTH20} {TOP_OF_PAGE} |
Пример 15.1. Вычисление переменной с помощью закрывающей фигурной скобки "}" |
Закрыть окно |
$tpl->define( array (main => "main.tpl", list_f => "list.tpl", list_el=> "list_element.tpl" )); |
Пример 15.2. Использование метода define() |
Закрыть окно |
$tpl->assign(array( TITLE => "Установка и настройка ПО", TITLE => "Введение в PHP" )); |
Пример 15.3. Использование метода assign() |
Закрыть окно |
$tpl->FastPrint(); /* если продолжать предыдущий пример, то эта функция напечатает значение переменной MAIN */ $tpl->FastPrint("MAIN"); // эта функция сделает тоже самое |
Пример 15.4. Использование метода FastPrint() |
Закрыть окно |
# глобальные переменные pageTitle = "List of documents" bodyBgColor = #000000 tableBgColor = #000000 rowBgColor = #00ff00 [Customer] pageTitle = "Список статей" Intro = """ Это значение состоит из нескольких строк. Поэтому его нужно заключить в тройные кавычки.""" # скрытая секция [.Database] host=localhost db=book user=nina pass=123 |
Пример 15.5. Пример конфигурационного файла |
Закрыть окно |
<?php // передаем пары имя/значение для // переменной Name и // Address в отдельности $smarty->assign("Name","Вася"); $smarty->assign("Address",$addr); // здесь $addr может быть и массивом // передаем ассоциативный массив $smarty->assign(array( "city" => "Новосибирск", "street" => "Пирогова")); // таким образом, переменные city и street // получат соответствующие значения ?> |
Пример 15.6. Использование метода assign() |
Закрыть окно |
$smarty->append(array( title => $title, author => $author)) |
Пример 15.7. Использование метода append() |
Закрыть окно |
{section name=art loop=$title} Название: {$title[art]}<br> {/section} |
Пример 15.8. Цикл для обхода элементов массива |
Закрыть окно |
{foreach from=$articles item=art} Title: {$art}<br> {/foreach} |
Пример 15.9. Цикл foreach |
Закрыть окно |
{if $name eq "Вася"} Добро пожаловать, Вася. {elseif $name eq "Петя"} Добро пожаловать, Петя. {else} Добро пожаловать. А вы кто? {/if} |
Пример 15.10. Операторы if, elseif, else |
Закрыть окно |
{* этот пример не будет работать, поскольку не поставлены пробелы вокруг операторов сравнения *} {if $name=="Вася" || $name=="Петя"} ... {/if} |
Пример 15.11. Неработающий пример |
Закрыть окно |
<? define("SMARTY_DIR","c:/users/nina/Smarty/libs/"); require(SMARTY_DIR."Smarty.class.php"); $smarty = new Smarty; $smarty->template_dir = "c:/smarty_dirs/book/templates/"; $smarty->compile_dir = "c:/smarty_dirs/book/templates_c/"; $smarty->config_dir = "c:/smarty_dirs/book/configs/"; $smarty->cache_dir = "c:/smarty_dirs/book/cache/"; // вышеприведенный блок лучше вынести в отдельный файл $smarty->config_load("task.conf","Database"); $host = $smarty->get_config_vars("host"); $user = $smarty->get_config_vars("user"); $pass = $smarty->get_config_vars("pass"); $db = $smarty->get_config_vars("db"); $conn = mysql_connect($host, $user, $pass) or die("Cant connect"); mysql_select_db($db); $sql = "SELECT * FROM Articles"; $q = mysql_query($sql,$conn); $num = mysql_num_rows($q); for($i=0; $i<$num; $i++){ $title = mysql_result($q,$i,"title"); $author = mysql_result($q,$i,"author"); $abs = mysql_result($q,$i,"abstract"); $full = mysql_result($q,$i,"fulltext"); $smarty->append(array( title => $title, author => $author, abstract => $abs, fulltext => $full )); } $smarty->display("index.tpl"); ?> |
Листинг 15.12. index.php |
Закрыть окно |
Решение задачи с помощью шаблонов FastTemplate
Теперь попробуем собрать воедино все изученные методы, чтобы решить нашу задачу.
Листинг 15.4.1. Решение задачи с помощью шаблонов FastTemplate (html, txt)
Заметим, что решение задачи получилось несколько более сложным, чем в первом случае, когда использовалась только функция регулярной замены. Зато здесь мы можем изменять три различных шаблона (документа в целом, списка и элемента списка).
Этот класс шаблонов появился еще до выхода PHP4 для работы с PHP3. Чтобы протестировать приведенные примеры, нужно скачать библиотеку классов FastTemplate и скопировать этот файл в свою рабочую директорию. Если вы работаете с PHP4, то в файл class.FastTemplate.php3 нужно внести пару изменений, о которых написано в документации, поставляющейся вместе с этой библиотекой.
Решение задачи с помощью шаблонов 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), обрабатывающий написанный нами шаблон, может выглядеть таким образом:
Листинг 15.12. index.php (html, txt)
Как вы, скорее всего, заметили, программа получилась еще более громоздкой, чем в первых двух случаях, когда использовалась простая замена значений и шаблоны FastTemplate. Действительно, механизм Smarty гораздо более сложен, чем тот же FastTemplate, но зато и более функционален.
Шаблоны FastTemplate
FastTemplate - это набор классов, позволяющих реализовать работу с шаблонами. Логику добавить в шаблон FastTemplate нельзя, вся она должна находиться в коде программы. Идея работы шаблонов FastTemplate заключается в том, что любая большая страница состоит из множества кусочков, самые маленькие из которых - обычные строки текста, и они получают имя и значение.
Что представляет собой файл шаблона FastTemplate? Это обычный html-файл, в котором могут встречаться переменные особого вида, впоследствии обрабатываемые методами класса FastTemplate.
Синтаксис переменных в шаблонах FastTemplate описывается следующим выражением: {([A-Z0-9_]+)}
Это значит, что переменная должна начинаться с фигурной скобки "{". Второй и последующие символы должны быть буквами верхнего регистра от A до Z, цифрами или символами подчеркивания. Переменная вычисляется с помощью закрывающей фигурной скобки "}".
{TITLE} {AUTH20} {TOP_OF_PAGE}
Пример 15.1. Вычисление переменной с помощью закрывающей фигурной скобки "}" (html, txt)
Как уже было сказано, основная идея FastTemplate - создание страницы с помощью вложенных шаблонов. Например, для решения нашей задачи можно создать три файла шаблона:
main.tpl (Этот шаблон будет выводить страницу в целом)
<html> <head><title>{TITLE_}</title> </head> <body> {MAIN} </body> </html> list.tpl (будет описывать, как выводить список в целом)
<ul> {LIST_ELEMENT} </ul> list_element.tpl (описывает непосредственно элемент списка)
<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");
В качестве параметра передается путь к месту, где находятся наши шаблоны.
Шаблоны подстановки
Как можно решить такую задачу способом простой подстановки, т.е. тем методом, которым мы решили задачу отображения документов?
Нужно придумать шаблон для этой страницы и где-то его хранить (в файле или в базе данных). Очевидно, что мы не можем придумать шаблон для всей страницы, потому что не знаем, сколько статей в базе данных. В шаблоне же мы договорились использовать только 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 |
Шаблоны 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 (в этом случае код менять не нужно).Установить константу SMARTY_DIR.
<? define("SMARTY_DIR", "c:/users/my/Smarty/libs/"); 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/)
Листинг 15.4.2. index.php (html, txt)
В результате должны получить:
Привет, Вася!
Все настройки, необходимые для работы нашего приложения, можно вынести в отдельный файл и организовать их в качестве расширения класса Smarty.
Далее более подробно рассмотрим, из каких элементов могут состоять шаблоны Smarty и как их обрабатывать внутри php-скрипта. Начнем с синтаксиса шаблонов.
{* Шаблон Smarty *} Привет, {$name}!
index.php (является кодом нашей программы и находится в директории /~my/tasks/book/ или, что то же самое, в директории c:/users/my/tasks/book/)
<? // загружаем Smarty-библиотеку и создаем экземпляр класса define("SMARTY_DIR","c:/users/my/Smarty/libs/"); require(SMARTY_DIR."Smarty.class.php"); $smarty = new Smarty; // указываем, где находятся Smarty-директории $smarty->template_dir = "c:/smarty_dirs/book/templates/"; $smarty->compile_dir = "c:/smarty_dirs/book/templates_c/"; $smarty->config_dir = "c:/smarty_dirs/book/configs/"; $smarty->cache_dir = "c:/smarty_dirs/book/cache/"; $smarty->assign("name","Вася"); // присваиваем переменной // name значение Вася $smarty->display("index.tpl"); // выводим обработанный // шаблон ?>
Листинг 15.4.2. index.php
В результате должны получить:
Привет, Вася!
Все настройки, необходимые для работы нашего приложения, можно вынести в отдельный файл и организовать их в качестве расширения класса Smarty.
Далее более подробно рассмотрим, из каких элементов могут состоять шаблоны Smarty и как их обрабатывать внутри php-скрипта. Начнем с синтаксиса шаблонов.
Встроенные функции
Smarty поставляется с набором встроенных функций, интегрированных в язык шаблонов. Нельзя создавать свои функции с такими же именами или модифицировать встроенные функции. Опишем некоторые из таких функций.
в этой лекции было рассказано
Итак, в этой лекции было рассказано о том, что такое шаблоны и как их можно использовать при программировании web-приложений на языке PHP. Мы рассмотрели три способа решения задачи отображения сложного списка документов: с помощью функции регулярной замены, с помощью класса шаблонов FastTemplate и с помощью языка шаблонов Smarty. При этом мы познакомились с основными свойствами, методами и функциями классов FastTemplate и Smarty.