Проект, работающий на технологиях
XML, требует иного подхода к формированию документов.
Инструменты, сделанные для формирования HTML-документов, часто
оказываются непригодными, и требуются новые, имеющие несколько
иную концепцию.
Проект, работающий на технологиях XML, требует иного
подхода к формированию документов. Инструменты, сделанные для
формирования HTML-документов, часто оказываются непригодными,
и требуются новые, имеющие несколько иную концепцию.
Скажем, как после добавления в проект поддержки XSLT
формировать в XML данные, получаемые из базы? Оказывается, что
делать это при помощи класса шаблона ничуть не проще, чем
составлять документ прямо в скрипте:
<?php
$result = mysql_query("SELECT DATE_FORMAT(news_date, '%e.%c.%Y') AS date_typed,
DATE_FORMAT(news_date, '%d.%m.%Y') AS date_url, title, announce FROM news
ORDER BY news_date DESC LIMIT 10");
if(!mysql_error()) {
$xml .= "\r\n";
while($row = mysql_fetch_assoc($result))
$xml .= "
{$row[date_url]}{$row[date_typed]}{$row[announce]}\r\n";
$xml .= "\r\n";
}
?>
Согласитесь, с классом шаблона мы получили бы примерно тот
же объём кода (а ещё файлы .tpl на диске).
Очевидно, что всё, что можно было вывести из скриптов php,
уже выведено. Дата в приведённом примере форматируется базой
данных и возвращается сразу в нужном нам формате,
форматирование с подсветкой четных строк, нумерацией и ещё
чем-нибудь делаются в XSLT. Код, которым мы выбираем данные из
базы, максимально упрощён и повторяется из раза в раз.
Возникает естественное желание сжать его в минимальную
конструкцию.
Размышление: Если вы хотите решить какую-то
проблему на php, поищите в архивах скриптов — скорее
всего для вашей задачи найдётся уже готовое решение. Чужой
скрипт, конечно, не будет идеальным решением (потому что
наилучшим приближением к идеалу будет решение, написанное
специально для задачи), но, скорее всего, будет пригодно к
использованию. Вы можете быть противником применения чужого
кода в своих проектах, либо не быть уверены в том, что
найденный код будет работать как надо (разработчики PEAR, как известно,
предупреждают, что весь их проект — вечная бета). Однако
посмотреть, как чужой скрипт работает, будет полезно, чтобы
написать свой собственный. Заглянув в чужой код, и
профессиональный разработчик, и разработчик среднего уровня
смогут лучше понять подходы к решению задачи и написать своё
собственное, если это понадобится.
В конце сентября, прочитав присланный мне материал про
PEAR::DB, я решил поискать на PEAR и класс для
автоматического преобразования результатов запроса к базе
данных в XML. В разделе XML я нашёл класс, созданный как раз для моей проблемы — XML_sql2xml.
Класс использует для доступа к базам данных другой класс
библиотеки PEAR — PEAR::DB. Для операций с XML-документом
использует функции DOM XML (это требует установки соответствующего модуля php).
Преобразует результат запроса в XML-дерево и возвращает его
либо как текст XML-документа, либо как DOM-объект.
Установка PEAR
Тема не была серьёзно описана в материале о
PEAR::DB, поэтому будем разбираться.
Библиотеки PEAR раньше распространялись с дистрибутивом
php, но больше этого не будет — видимо, чтобы устаревшие
версии классов не распространялись с дистрибутивами php, их
оттуда убрали. Теперь архивы с классами нужно брать с сервера pear.php.net.
Проблемы с установкой PEAR вызваны тем, что во всех его
файлах указываются пути относительно корня директории pear, то
есть для вызова PEAR::DB делается команда
include_once("DB/${type}.php");
PHP, если не найден подключаемый файл, пытается искать его
в своей директории или в директории, куда установлен PEAR.
Однако не все его устанавливают на тестовой машине, а чтобы
исключить проблемы несовместимости и отсутствия PEAR на
хостинге, многие предпочли бы положить необходимые файлы
библиотеки в поддиректорию разрабатываемого проекта.
Как вы могли увидеть из строки кода выше, файлы
подключаются относительно include_path (который по умолчанию
является директорией, из которой работает скрипт, либо
директорией, в которую установлен PEAR). Если в проекте
include_path устанавливается, вам повезло. Можно положить
вызываемые скрипты в эту директорию и наслаждаться жизнью.
Если include_path не устанавливается, его можно установить
в .htaccess такой строкой:
php_value include_path my_dir/pear
Вместо my_dir поставьте адрес нужной директории. Можно
поменять include_path "на лету" в скрипте:
<?php
ini_set("include_path", "my_dir/pear");
include("DB.php");
$dsn = "mysql://user:pass@host/db_name";
$db = DB::connect($dsn, true);
if (DB::isError($db)) {
die ($db->getMessage());
}
$db->setFetchMode(DB_FETCHMODE_ASSOC);
ini_restore("include_path");
?>
После подключения файлов PEAR лучше вернуть include_path на
место командой ini_restore. В комментариях к предыдущей статье меня спросили, зачем нужен ini_restore.
Во-первых, у меня все скрипты работают из корня сайта, а
подключаемые файлы находятся в разных директориях (например,
содержимое страницы сайта может быть статичным XML-файлом, а
может быть скриптом, который будет вызываться основными
скриптами). Пути к подключаемым файлам я указываю тоже от
корня. PEAR на сайте появилась не сразу, поэтому ini_restore
нужен, чтобы подключить библиотеку, и это не мешало остальным
скриптам работать по-старому. Во-вторых, в php.ini или в
.htaccess может указываться путь к директории с подключаемыми
файлами, а PEAR, по-моему, лучше положить в отдельную
директорию, чтобы библиотека не захламляла рабочую. Впрочем,
каждый делает как ему удобнее.
Однако хостинг-провайдер может держать php в безопасном
режиме, который запрещает менять include_path. В таком случае,
конечно, можно попробовать положить файлы PEAR в корень сайта
или вручную править все include в файлах — больше ничем
разработчики помочь не могут. В новостях PEAR пишут, что Стиг Баккен сообщил о плане добавить новую
директиву '{get,set,restore}_include_path()', чтобы можно было
менять iclude_path даже в безопасном режиме.
DOM XML в PHP
DOM (Document Object Model) — модель работы с
документом, в которой документ содержит объекты, которыми
можно манипулировать. Модель DOM является стандартом W3C. Функции DOM
XML в php — это одна из реализаций данной модели.
При работе с DOM вы оперируете с переменными, являющимися
ссылками на объекты нескольких классов. Список классов и их
функций можно узнать в соответствующем
разделе руководства по php.
Стандартный модуль php_domxml не поддерживает кириллицу.
При работе с ним вам придётся конвертировать данные на входе в
UTF-8, а на выходе — обрабатывать сущности вроде
&x440;. Чтобы установить DOM XML с поддержкой кириллицы на
рабочей машине под Win32, рекомендую скачать мой
архив, в котором находится модуль php_domxml
скомпилированный с поддержкой кириллицы и необходимые для его
работы библиотеки iconv, libxml, libxslt и libexslt. Положите
файл php_domxml.dll в extenstion_dir, а остальные
библиотеки — в директорию c:\windows\system.
Несколько примеров по работе с DOM XML:
<?php
// Создание XML-документа
$xmldoc = domxml_new_doc("1.0");
// В условиях документа создаётся элемент под названием my_root.
$my_element = $xmldoc->create_element("my_root");
// Затем этот элемент присоединяется к документу как узел-потомок. До этой операции в
//документе нет корневого узла!
$my_root = $xmldoc->append_child($my_element);
// Создаётся ещё один элемент — текст и добавляется как потомок к корневому узлу.
$my_element = $xmldoc->create_text_node(iconv("windows-1251", "UTF-8", "Это содержимое
//корневого узла XML-документа."));
$my_root->append_child($my_element);
// XML-документ преобразуется в текстовый вид и выводится
print($xmldoc->dump_mem());
?>
К сожалению, в приведённом примере
возникнут проблемы с русскими символами — на выходе они
опять превращаются в &xXXX;. Чтобы модуль domxml понял,
что идёт работа с русской кодировкой, нужно на входе дать
XML-документ с параметром encoding="windows-1251" вот так:
<?php
// Создание объекта документа из текстовой строки
$xmldoc = domxml_open_mem('<?xml version="1.0" encoding="windows-1251"?>');
// Cсылку на корневой узел документа записываем в переменную $my_root (название
//переменной значения не имеет).
$my_root = $xmldoc->document_element();
// Создаём текстовый узел.
$my_element = $xmldoc->create_text_node(iconv("windows-1251", "UTF-8",
"Это содержимое корневого узла XML-документа."));
// Присоединяем текстовый узел к корневому.
$my_root->append_child($my_element);
print($xmldoc->dump_mem());
?>
Следующий пример показывает, как можно удалять
элементы:
<?php
$xmldoc = domxml_open_mem('<?xml version="1.0" encoding="windows-1251"?>
Нечто
');
$my_root = $xmldoc->document_element();
// В массив $children записываются все потомки узла my_root.
$children = $my_root->child_nodes();
// Уничтожается первый потомок (узел something)
$children[0]->unlink_node();
// Создаётся новый узел под названием new и записывается в переменную $new
$new = $my_root->append_child($xmldoc->create_element("new"));
// К этому узлу добавляется потомок — текстовый узел
$new->append_child($xmldoc->create_text_node(iconv("windows-1251", "UTF-8",
"Это содержимое нового узла XML-документа.")));
print($xmldoc->dump_mem());
?>
в результате получится такой XML-документ:
<?xml version="1.0" encoding="windows-1251"?>
<my_root><new>Это содержимое нового узла XML-документа.</new></my_root>
Кстати, если изменить исходный XML-документ на
такой:
<?xml version="1.0" encoding="windows-1251"?>
<my_root><something>Нечто</something>
<new>Это содержимое нового узла XML-документа.</new></my_root>
Предоставляю вам возможность догадаться, почему это
произошло.
Конечно же, приведённые примеры — самое простое из
того, что можно делать в DOM XML. Кроме построения нового это
и самые хитрые преобразования документа, и XSL-трансформация
при помощи библиотеки libxslt, не уступающей в
функциональности Sablotron, а в скорости превосходящей его в
два раза. Перед нами открываются огромные возможности по
работе с документом, проблема — как организовать и
систематизировать эту работу.
На ум приходит следующая схема преемника классов шаблонов:
вызывается скрипт, который открывает стандартный XML-файл и
включает буферизацию данных. Все скрипты тупо выдают
XML-данные в print. Вызывается второй скрипт, который
останавливает буферизацию, берёт данные из буфера, дописывает
к ним в начале "<?xml version="1.0"
encoding="windows-1251"?><root>" и "</root>" в
конце, затем преобразует в объект DOM, открывает корневой
элемент и берёт всех массив потомков. Полученные узлы
вставляет в основной XML-документ (который тоже открыт как
объект), результат преобразует через XSLT и выдаёт
пользователю.
На этом заканчиваем краткое описание DOM XML и переходим к
классу sql2xml.
Класс SQL2XML
Вся функциональность, которая нужна для преобразования
результатов SQL-запросов в XML, есть в этом классе. Для
соединения с базой данных класс использует либо существующее
соединение класса PEAR::DB, либо своё собственное (точнее, он
создаёт в себе объект класса DB). Из результата запроса
строится XML-дерево. Пример из руководства по классу:
mysql> select * from bands;
+----+--------------+------------+-------------+-------------+
| id | name | birth_year | birth_place | genre |
+----+--------------+------------+-------------+-------------+
| 1 | The Blabbers | 1998 | London | Rock'n'Roll |
| 2 | Only Stupids | 1997 | New York | Hip Hop |
+----+--------------+------------+-------------+-------------+
mysql> select * from albums;
+----+---------+------------------+------+-----------------+
| id | bandsID | title | year | comment |
+----+---------+------------------+------+-----------------+
| 1 | 1 | BlaBla | 1998 | Their first one |
| 2 | 1 | More Talks | 2000 | The second one |
| 3 | 2 | All your base... | 1999 | The Classic |
+----+---------+------------------+------+-----------------+
Это набор данных. А теперь вызов класса и результаты
работы. php-код:
<?php
include_once("XML/sql2xml.php");
$sql2xmlclass = new xml_sql2xml("mysql://username:password@localhost/xmltest");
$xmlstring = $sql2xmlclass->getxml("select * from bands");
?>
Результат выводится и в текстовом виде, и как DOM-объект
(что весьма удобно при генерации документов через DOMXML). Так
же можно из всего XML-дерева выдернуть одно значение при
помощи выражения XPath. Ещё очень хорошая особенность: раз уж
строятся деревья, и всё оперируется в XML, почему бы запросы с
объединением "один-ко-многим" не делать в виде вложенных друг
в друга узлов <row>. php-код:
<?php
include_once("XML/sql2xml.php");
$sql2xml = new xml_sql2xml("mysql://username:password@localhost/xmltest");
$xmlstring = $sql2xml->getxml("select * from bands left join albums on bands.id = bandsID");
?>
Впрочем, если вы хотите получить обычный результат запроса,
это свойство можно отключить. Если имена узлов для результата
и для ряда вас не устраивают, можно их поменять. Если вас не
устраивает формат (всё в текстовых узлах, а не, например, в
атрибутах), можно преобразовать полученный DOM-объект в нужный
вам. На мой взгляд, этого не понадобится, поскольку если на
сайте до этого уже использовался XSLT, исправить XSL-файл не
представляет особой сложности.
Итак, класс вполне пригоден к использованию. Если он вас
чем-то не устраивает, можно, глядя на существующий, написать
свой собственный. Исправлять данный класс вполне можно,
поскольку манипуляции с DOM-объектами не намного сложнее
внутреннего устройства классов шаблонов. Я для себя исправил
ошибки call-time pass-by-reference в классе версии 0.3 (версия
0.3.1 — это как раз мой багфикс) а так же заменил старые
не поддерживаемые функции и конструкции DOM XML на новые.
Сейчас работаю над тем, как справиться с проблемой кодировки документа (объект документа там создаётся функцией
domxml_new_doc, а для создания из текстовой строки требуется
основательно переделать существующую в классе sql2xml
функцию).