Перетворення SQL в XML за допомогою PHP (вихідні коди)

Введення


Можливо, ви чули про PEAR – архіві додатків та розширень PHP (PHP Extension and Application Repository). Цей проект, підтримуваний співтовариством користувачів, націлений на створення великої бібліотеки високоякісного відкритого коду, який допоможе програмістам на PHP прискорити розробку програм. Вже давно PEAR, схожий по ідеї з архівом CPAN для Perl, є першим місцем, де я шукаю цікаві і корисні віджети PHP + XML. Деякі з них використовують клас XML_Serializer, дуже зручний для перетворення структур даних PHP в послідовну форму об'єктів XML; клас XML_XUL, що надає інтерфейс API для розробки додатків Mozilla XUL, і клас XML_SVG, який реалізує методи програмного побудови векторної графіки у форматі SVG.


У цій статті я розповім вам ще про один елемент розділу XML архіву PEAR – класі XML_Query2XML. Цей клас реалізує програмний інтерфейс для швидкого та ефективного перетворення результуючого безлічі даних SQL в правильно оформлений XML-код. Використовуючи трохи фантазії, можна перетворити ці дані в інші формати з допомогою XSLT або інтегрувати їх з іншими додатками, що використовують XML.


Встановлення необхідного програмного забезпечення


Пакет XML_Query2XML активно розробляється і підтримується Лукасом Фейлером (Lukas Feiler) і випускається для спільноти PHP під ліцензією LGPL. Для нього вимагається PHP 5.0 (або пізнішої версії). Простіше всього встановити цей пакет за допомогою автоматичної настановної програми PEAR, яка повинна за замовчуванням присутнім у вашій збірці PHP. Щоб встановити цей пакет, просто виконайте наступну команду в командному рядку shell:


shell>pear install XML_Query2XML


Програма установки PEAR з'єднується з сервером пакетів PEAR, завантажує пакет і встановлює його у відповідну папку у вашій системі.


Щоб встановити пакет вручну, зайдіть на Web-сайт PEAR, завантажте архів з пакетом і розархівуйте файли в потрібну директорію. Врахуйте, що для процесу ручної установки необхідно знання організаційної структури пакета PEAR.


На цьому етапі треба також пам'ятати про деякі залежності пакунка:



  1. Для зв'язку з необхідною СУБД пакет XML_Query2XML використовує один з рівнів абстракції бази даних DB, MDB2 або ADOdb, тому в системі повинен бути встановлений один з цих рівнів абстракції і драйвер відповідної бази даних. У прикладах, наведених у цій статті, використовується рівень абстракції MDB2, який разом з драйвером MySQL MDB2_Driver_mysql також можна знайти в архіві пакетів PEAR. Як було описано раніше, для встановлення обох пакетів ви можете скористатися програмою автоматичної установки PEAR або завантажити з с Web-сайту PEAR.
  2. У прикладах, наведених у цій статті, використовується демонстраційна база даних MySQL world, Вже заповнена і містить зв'язані таблиці даних про міста і країни. Посилання на завантаження бази даних MySQL worldнаведена внизу сторінки.
  3. Для прикладів, наведених у цій статті, потрібно, щоб збірка PHP підтримувала функції PHP DOM, XSL і SimpleXML. Ці функції включені за замовчуванням в PHP 5.x.
  4. Передбачається, що ви знаєте функції PHP DOM і SimpleXML, а також технології XML, XPath і XSL.

Всі приклади, наведені в цій статті, були перевірені на XML_Query2XML версії 1.2.1.


Перетворення SQL в XML


Якщо встановлені всі необхідні компоненти, ви можете почати вивчення XML_Query2XML з наступного простого сценарію PHP:


Лістинг 1. Просте перетворення SQL в XML





<?php
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$sql = “SELECT * FROM Country”;
$xml = $q2x->getFlatXML($sql);

/ / Відправляємо результат в браузер
header(“Content-Type: text/xml”);
$xml->formatOutput = true;
echo $xml->saveXML();
} catch (Exception $e) {
echo $e->getMessage();
}
?>


Цей сценарій демонструє приклад використання класу XML_Query2XML. Спочатку сценарій включає файли класів XML_Query2XML і MDB2, а потім ініціалізує примірник рівня абстракції MDB2 за допомогою методу factory(). На вхід цього методу передається рядок DSN, що містить інформацію про тип СУБД, ім'я користувача та пароль, а також назві бази даних. Одержаний примірник MDB2 використовується для ініціалізації примірника XML_Query2XML, представленого об'єктом $q2x.


Коли ви сформували рядок DSN і створили екземпляр об'єкта XML_Query2XML, приходить час виконати запит SQL до СУБД і перетворити результат в XML. Ця дія реалізується в методі getFlatXML() класу XML_Query2XML, який використовується в основному для простих запитів типу SELECT. На виході цей метод повертає правильно оформлений документ XML, що містить результуюче безліч SQL. Він буде виглядати приблизно так:


Лістинг 2. Документ XML, сформований в результаті роботи лістингу 1 (скорочений)





<?xml version=”1.0″ encoding=”UTF-8″?>
<root>
<row>
<code>AFG</code>
<name>Afghanistan</name>
<continent>Asia</continent>
<region>Southern and Central Asia</region>
<surfacearea>652090.00</surfacearea>
<indepyear>1919</indepyear>
<population>22720000</population>
<lifeexpectancy>45.9</lifeexpectancy>
<gnp>5976.00</gnp>
<gnpold></gnpold>
<localname>Afganistan/Afqanestan</localname>
<governmentform> Islamic Emirate </ governmentform>
<headofstate>Mohammad Omar</headofstate>
<capital>1</capital>
<code2>AF</code2>
</row>
<row>
<code>NLD</code>
<name>Netherlands</name>
<continent>Europe</continent>
<region>Western Europe</region>
<surfacearea>41526.00</surfacearea>
<indepyear>1581</indepyear>
<population>15864000</population>
<lifeexpectancy>78.3</lifeexpectancy>
<gnp>371362.00</gnp>
<gnpold>360478.00</gnpold>
<localname>Nederland</localname>
<governmentform> Constitutional Monarchy </ governmentform>
<headofstate>Beatrix</headofstate>
<capital>5</capital>
<code2>NL</code2>
</row>
<row>
<code>ANT</code>
<name>Netherlands Antilles</name>
<continent>North America</continent>
<region>Caribbean</region>
<surfacearea>800.00</surfacearea>
<indepyear></indepyear>
<population>217000</population>
<lifeexpectancy>74.7</lifeexpectancy>
<gnp>1941.00</gnp>
<gnpold></gnpold>
<localname>Nederlandse Antillen</localname>
<governmentform>Nonmetropolitan Territory of
The Netherlands</governmentform>
<headofstate>Beatrix</headofstate>
<capital>33</capital>
<code2>AN</code2>
</row>

</root>

Якщо уважно вивчити наведений вище документ XML, стає видно чітка структура. Кожна запис з результуючого безлічі SQL представлена елементом <row>, А окремі поля кожного запису розташовані всередині відповідного <row>. Назви вкладених елементів відповідають іменам полів таблиці, до якої виконується запит, а елемент документа – корінь дерева XML – називається, відповідно, <root>.


Перетворення вихідного XML за допомогою XSL


Звичайно ж, створення XML із запиту SQL – це, як правило, тільки половина роботи; після цього з ним треба щось зробити. З документом XML можна зробити безліч речей, однак найчастіше його перетворять за допомогою XSLT в будь-який інший формат, наприклад, HTML або RSS. Беручи це до уваги, складемо невелику таблицю стилів XSL для перетворення виводу XML з лістингу 2 в просту сторінку HTML.


Лістинг 3. Таблиця стилів XSL





<?xml version=”1.0″ encoding=”UTF-8″?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match=”/root”>
<html>
<head>
<style type=”text/css”>
td { text-align: center; padding: 3px; }
.head { font-style: italic; }
</style>
</head>
<body>
<table class=borderall>
<thead>
<tr>
<xsl:for-each select="row[1]/*">
<td class=”head”>
<xsl:value-of select="local-name(.)"/>
</td>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:apply-templates/>
</tbody>
</table>
</body>
</html>
</xsl:template>

<xsl:template match=”row”>
<tr>
<xsl:apply-templates/>
</tr>
</xsl:template>

<xsl:template match=”row/*”>
<td>
<xsl:value-of select=”.”/>
</td>
</xsl:template>
</xsl:stylesheet>


У Лістингу 4 наведено виправлений сценарій PHP, в якому тепер використовуються функції XSL PHP для перетворення виведення XML_Query2XML:


Лістинг 4. Перетворення виводу SQL в XML за допомогою XSL





<?php
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$sql = “SELECT * FROM Country”;
$xml = $q2x->getFlatXML($sql);

/ / Зчитуємо дані таблиці стилів XSL
$xsl = new DOMDocument;
$xsl->load(“country.xsl”);

/ / Инициализируем механізм XSLT
$xslp = new XSLTProcessor;

/ / Підключаємо об'єкт таблиці стилів XSL
$xslp->importStyleSheet($xsl);

/ / Забезпечуємо перетворення
header(“Content-Type: text/html”);
echo $xslp->transformToXML($xml);
} catch (Exception $e) {
echo $e->getMessage();
}
?>


Перша частина цього сценарію така ж, як і в Лістингу 1; вона формує документ XML, що містить результати виконання запиту SQL і записує його в змінну $xml як екземпляр DOMDocument. Потім ініціалізується екземпляр класу XSLTProcessor і з допомогою методу importStyleSheet() імпортується таблиця стилів XSL. Після цього викликається метод transformToXML(), Який отримує в якості аргументу вихідні дані XML, який перетворює документ XML в сторінку HTML за правилами, описаним в таблиці стилів XSL.


На малюнку 1 показано, як буде виглядати результат:


Малюнок 1. Документ HTML, сформований програмою з лістингу 4

Налаштування виводу XML


Показаний в попередніх прикладах метод getFlatXML() гарний тільки в тих випадках, коли вам необхідно швидко виконати перетворення SQL в XML. Якщо ж вам потрібно виконати більш складне завдання – наприклад, вивести певні поля результуючого безлічі в якості атрибутів, а не елементів, або визначити власні назви елементів, – вам слід скористатися методом getXML() класу XML_Query2XML. Цей метод дозволяє дуже точно налаштувати вихідний документ XML, включаючи його структуру і стиль.


Розглянемо приклад, наведений у Лістингу 5:


Лістинг 5. Налаштування виведення SQL в XML





<?php
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$sql = “SELECT * FROM Country”;
$xml = $q2x->getXML($sql, array(
“idColumn” => “code”,
“rootTag” => “countries”,
“rowTag” => “country”,
“attributes” => array(“code”),
"Elements" => array ("name", "continent", "area" => "surfacearea")
)
);

/ / Відправляємо результат в браузер
header(“Content-Type: text/xml”);
$xml->formatOutput = true;
echo $xml->saveXML();
} catch (Exception $e) {
echo $e->getMessage();
}
?>


Метод getXML() приймає два аргументи: запит SQL, який необхідно виконати, і масив параметрів, що визначають формат створюваного документа XML. У Таблиці 1 наводиться опис кожного з параметрів, згаданих в наведеному вище лістингу:


Таблиця 1. Параметри методу getXML ()





















Параметр


Чим управляє


rootTag


Назва елемента документа (за замовчуванням: root)


rowTag


Назва елемента, що представляє кожен рядок (за умовчанням: row)


idColumn


Первинний ключ результуючого масиву даних


attributes


Перелік полів, які повинні виводитися як атрибути XML


elements


Перелік полів, які повинні виводитися як елементи XML


У Лістингу 6 показано, на що буде схожий висновок цього сценарію:


Лістинг 6. Документ XML, сформований програмою з лістингу 5 (скорочений)





<?xml version=”1.0″ encoding=”UTF-8″?>
<countries>
<country code=”AFG”>
<name>Afghanistan</name>
<continent>Asia</continent>
<area>652090.00</area>
</country>
<country code=”NLD”>
<name>Netherlands</name>
<continent>Europe</continent>
<area>41526.00</area>
</country>
<country code=”ANT”>
<name>Netherlands Antilles</name>
<continent>North America</continent>
<area>800.00</area>
</country>

</countries>

Зверніть увагу, що в цьому документі XML містяться не всі поля безлічі даних, а лише вказані в масивах elements і attributes, А поля, зазначені в масиві attributes, З'являються в якості атрибутів елемента <country>, А не дочірніх вузлів.


Ви також пам'ятаєте, що за замовчуванням назви атрибутів і елементів в документі XML співпадають з назвами полів запиту. Однак при використанні методу getXML(), Ви можете змінити ці назви, вказавши інші значення в масивах attributes і elements у вигляді пар ключ-значення. Приклад: поле, зване surfacearea в результатах SQL, в документ XML виводиться просто як елемент <area>.


Робота з сполуками SQL


Пакет XML_Query2XML також надає засоби додавання вмісту результату одного запиту в інший за допомогою XML. Ця функція найчастіше використовується при роботі із з'єднаннями або запитами, пов'язаними яким-небудь іншим чином, крім того, вона корисна у випадках, коли ви бажаєте розбити один великий запит на кілька менших з метою підвищення продуктивності.


Щоб розібратися в цьому, повернемось до бази даних world і розглянемо дві її таблиці, Country і City, Які пов'язані один з одним зовнішнім ключем code.


Тепер давайте припустимо, що ви бажаєте створити дерево документа XML, у якому елементи <city> будуть вкладені в елементи <country>. Припустимо також, що ви бажаєте обмежити висновок п'ятьма містами кожної країни, що мають найбільшу кількість населення, а також хочете, щоб значення полів відображаються не елементами, а атрибутами. Коротше, нам потрібен документ XML, схожий на наступний приклад:


Лістинг 7. Очікувана структура XML після SQL ¬-з'єднання (скорочена)





<?xml version=”1.0″ encoding=”UTF-8″?>
<countries>
<country code="IND" name="India" region="Southern and Central Asia">
<cities>
<city name="Mumbai (Bombay)" district="Maharashtra" population="10500000"/>
<city …/>
<city …/>
<city …/>
<city …/>
</cities>
</country>
<country …>

</country>

</countries>

У Лістингу 8 наведено код, необхідний для формування такого документа XML:


Лістинг 8. Створення документа XML, відповідного вимогам, за даними з'єднання SQL





<?php
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$sql_1 = “SELECT * FROM Country”;
$ Sql_2 = "SELECT * FROM City WHERE CountryCode =? ORDER BY Population DESC LIMIT 5";
$xml = $q2x->getXML($sql_1, array(
“idColumn” => “code”,
“rootTag” => “countries”,
“rowTag” => “country”,
"Attributes" => array ("code", "name", "continent"),
“elements” => array(“cities” => array(
"Sql" => array ("data" => array ("code"), "query" => $ sql_2),
“idColumn” => “id”,
“rootTag” => “cities”,
“rowTag” => “city”,
"Attributes" => array ("name", "district", "population"))
)
)
);

/ / Відправляємо результат в браузер
header(“Content-Type: text/xml”);
$xml->formatOutput = true;
echo $xml->saveXML();
} catch (Exception $e) {
echo $e->getMessage();
}
?>


Основний момент, на який варто звернути увагу в цьому прикладі – це масив elements. На відміну від коду в Лістингу 5, де в цьому масиві фактично був лише перелік полів результату, що відображаються у вигляді елементів, тут виконується значно більш складна функція. Спочатку в ньому визначається новий елемент, <cities>, Що пов'язується з масивом параметрів, що містить пари ключ-значення. Єдиним новим ключем в цьому масиві параметрів є ключ sql, Що визначає запит SQL, який буде використаний для наповнення елемента <cities>.


Варто витратити кілька хвилин на те, щоб вивчити ключ sql. Цей ключ пов'язаний з асоціативним масивом, який, у свою чергу, містить два ключі:



Зверніть увагу, що в другому запиті міститься знак питання (?), Що визначає поле для підстановки – під час виконання запиту він замінюється поточним значенням полів, зазначених у масиві data. Або, приводячи конкретний приклад, якщо запис, який повернув зовнішній запит для поля code, має значення “IND”, То це значення “IND” буде підставлено у внутрішній питання замість знака ?.


Тепер вам повинні бути зрозумілі здібності XML_Query2XML. Ви можете заповнити кожен масив elements окремим запитом SQL, що дозволить створювати необмежено вкладені результати запитів. Крім того, оскільки кожен масив elements може посилатися на поля з батьківських вимог, можна створювати послідовності вкладених запитів (аналог сполук SQL), які будуть пов'язані між собою за певними полях.


Результат роботи сценарію буде виглядати наступним чином:


Лістинг 9. Документ XML, створений в результаті роботи сценарію з лістингу 8 (скорочений)





<?xml version=”1.0″ encoding=”UTF-8″?>
<countries>
<country code="AFG" name="Afghanistan" continent="Asia">
<cities>
<city name="Kabul" district="Kabol" population="1780000"/>
<city name="Qandahar" district="Qandahar" population="237500"/>
<city name="Herat" district="Herat" population="186800"/>
<city name="Mazar-e-Sharif" district="Balkh" population="127800"/>
</cities>
</country>
<country code="NLD" name="Netherlands" continent="Europe">
<cities>
<city name="Amsterdam" district="Noord-Holland" population="731200"/>
<city name="Rotterdam" district="Zuid-Holland" population="593321"/>
<city name="Haag" district="Zuid-Holland" population="440900"/>
<city name="Utrecht" district="Utrecht" population="234323"/>
<city name="Eindhoven" district="Noord-Brabant" population="201843"/>
</cities>
</country>

</countries>

Настав момент створити нову таблицю стилів XSL, яка буде враховувати нову структуру XML:


Лістинг 10. Таблиця стилів XSL для перетворення лістингу 9





<?xml version=”1.0″ encoding=”UTF-8″?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match=”/countries”>
<html>
<head>
<style type=”text/css”>
td { text-align: center; padding: 3px; }
.head { font-style: italic; }
</style>
</head>
<body>
<xsl:for-each select=”country”>
<h2> <xsl:value-of select="@name"/> – <xsl: value-of
select=”@continent”/></h2>
<table class=borderall>
<thead>
<tr>
<xsl:for-each select="cities/city[1]/@*">
<td class=”head”>
<xsl:value-of select="name(.)"/>
</td>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:apply-templates/>
</tbody>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>

<xsl:template match=”cities/city”>
<tr>
<xsl:for-each select=”@*”>
<td>
<xsl:value-of select=”.”/>
</td>
</xsl:for-each>
</tr>
</xsl:template>
</xsl:stylesheet>


Звичайно ж, ви також повинні змінити вихідний сценарій PHP для перетворення сформованого XML за допомогою цієї таблиці стилів. Зміна, втім, будуть невеликими:


Лістинг 11. Перетворення документа XML, сформованого сценарієм з лістингу 8





<?php
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$sql_1 = “SELECT * FROM Country”;
$ Sql_2 = "SELECT * FROM City WHERE CountryCode =? ORDER BY Population DESC LIMIT 5";
$xml = $q2x->getXML($sql_1, array(
“idColumn” => “code”,
“rootTag” => “countries”,
“rowTag” => “country”,
"Attributes" => array ("code", "name", "continent"),
“elements” => array(“cities” => array(
"Sql" => array ("data" => array ("code"), "query" => $ sql_2),
“idColumn” => “id”,
“rootTag” => “cities”,
“rowTag” => “city”,
"Attributes" => array ("name", "district", "population"))
)
)
);

/ / Зчитуємо дані таблиці стилів XSL
$xsl = new DOMDocument;
$xsl->load(“countries.xsl”);

/ / Инициализируем механізм XSLT
$xslp = new XSLTProcessor;

/ / Підключаємо об'єкт таблиці стилів XSL
$xslp->importStyleSheet($xsl);

/ / Забезпечуємо перетворення
header(“Content-Type: text/html”);
echo $xslp->transformToXML($xml);
} catch (Exception $e) {
echo $e->getMessage();
}
?>


На Малюнку 2 показано, як виглядає перетворений XML:


Малюнок 2. Документ HTML, створений у Лістингу 11

Існує безліч варіантів використання можливостей організації вкладеності, крім того, XML_Query2XML пропонує безліч варіантів додаткового налаштування виводу документів XML.


Фільтрація записів SQL за допомогою XPath


Як можна припустити, обмежити кількість записів, що виводяться методом getXML(), Досить легко. Досить просто додати відповідне умова WHERE до запиту SQL. Іншим способом є використання конструкцій XPath для створення обмежених підмножин дерева XML і його повернення зухвалому клієнту.


У Лістингу 12 наведено простий приклад реалізації цього способу шляхом модифікації лістингу 11 і обмеження виводяться в XML даних тільки країнами і містами, розташованими в Європі, за допомогою умов XPath:


Лістинг 12. Обмеження даних, що виводяться з SQL в XML за допомогою XPath





<?php
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$sql_1 = “SELECT * FROM Country”;
$ Sql_2 = "SELECT * FROM City WHERE CountryCode =? ORDER BY Population DESC LIMIT 5";
$xml = $q2x->getXML($sql_1, array(
“idColumn” => “code”,
“rootTag” => “countries”,
“rowTag” => “country”,
"Attributes" => array ("code", "name", "continent"),
“elements” => array(“cities” => array(
"Sql" => array ("data" => array ("code"), "query" => $ sql_2),
“idColumn” => “id”,
“rootTag” => “cities”,
“rowTag” => “city”,
"Attributes" => array ("name", "district", "population"))
)
)
);

/ / Тепер фільтруємо XML ще раз за допомогою XPath
/ / Повертаємо в DOMNodeList тільки ті вузли <country>,
/ / У яких є атрибут "continent = Europe"
$xpath = new DOMXPath($xml);
$ Nodelist = $ xpath-> query ("/ countries / country [@ continent =" Europe "]");

/ / Створюємо нове дерево DOM з допомогою безлічі XPath
/ / Створюємо кореневий елемент
/ / Імпортуємо всі вузли зі списку і вставляємо в нове дерево DOM
$dom = new DOMDocument;
$root = $dom->createElement(“countries”);
$dom->appendChild($root);
$x = 0;
while ($node = $nodelist->item($x)) {
$node = $dom->importNode($node, true);
$root->appendChild($node);
$x++;
}

/ / Виконуємо друк XML
header(“Content-Type: text/xml”);
$dom->formatOutput = true;
echo $dom->saveXML();
} catch (Exception $e) {
echo $e->getMessage();
}
?>


Перша частина сценарію залишилася незмінною – два вкладених запиту SQL, для формування списку країн і міст внутрішній запит використовує дані з зовнішнього. Проте цього разу замість того, щоб виводити XML безпосередньо на друк або передавати його оброблювачу XSLT, ініціалізується об'єкт DOMXPath і з вихідного дерева XML створюється новий список DOMNodeList. Щоб вивести в цей список тільки елементи <country>, містять атрибут continent зі значенням Europe, Використовується запит XPath. Після створення списку DOMNodeList ініціалізується новий об'єкт DOMDocument і в нього імпортується цей DOMNodeList, формуючи новий документ XML.


У Лістингу 13 показаний фрагмент результату:


Лістинг 13. Результат XML, сформований програмою в лістингу 12 (скорочений)





<?xml version=”1.0″?>
<countries>
<country code="NLD" name="Netherlands" continent="Europe">
<cities>
<city name="Amsterdam" district="Noord-Holland" population="731200"/>
<city name="Rotterdam" district="Zuid-Holland" population="593321"/>
<city name="Haag" district="Zuid-Holland" population="440900"/>
<city name="Utrecht" district="Utrecht" population="234323"/>
<city name="Eindhoven" district="Noord-Brabant" population="201843"/>
</cities>
</country>
<country code=”ALB” name=”Albania” continent=”Europe”>
<cities>
<city name="Tirana" district="Tirana" population="270000"/>
</cities>
</country>
<country code=”AND” name=”Andorra” continent=”Europe”>
<cities>
<city name="Andorra la Vella" district="Andorra la Vella" population="21189"/>
</cities>
</country>
<country code=”BEL” name=”Belgium” continent=”Europe”>
<cities>
<city name="Antwerpen" district="Antwerpen" population="446525"/>
<city name="Gent" district="East Flanderi" population="224180"/>
<city name="Charleroi" district="Hainaut" population="200827"/>
<city name="Liège" district="Liège" population="185639"/>
<city name="Bruxelles [Brussel]" district="Bryssel" population="133859"/>
</cities>
</country>

<countries>

Злиття даних з декількох джерел


При розробці реальних додатків на базі XML малоймовірно, що документ XML буде містити інформацію тільки з одного джерела. На додаток до одного або кількох запитам SQL він може також включати у собі дані з дискових файлів, від зовнішніх Web-сервісів або з системної таблиці процесів. Для обліку цієї ситуації XML_Query2XML надає спосіб інтеграції в документ XML, що повертається методом getXML(), Даних із джерел, відмінних від SQL.


Пакет XML_Query2XML дозволяє розроблювачам визначати власні функції зовнішнього виклику, викликані певними елементами в сформованому документі XML. Ці функції необхідні для отримання потрібних даних, перетворення їх у формат XML і повернення цього XML (у вигляді примірника DOMNode) викликає функції у форматі, відповідному для вставки у відповідну позицію в дереві документа XML. У виклику getXML() перед назвою функції виклику вказується символ гратки (#), На вхід буде автоматично подаватися поточна запис SQL.


У вас може виникнути питання, наскільки дійсно корисна ця функція. Найкраще продемонструвати це на прикладі. По-перше, припустимо, що ви бажаєте сформувати документ XML, у якому будуть перераховані країни і найбільш населені міста. Ви вже бачили багато прикладів, які роблять точно те ж саме. Щоб зробити приклад більш цікавим, давайте доповнимо XML координатами широти та довготи кожного міста за даними Web-сервісу GeoNames.


Код представлений в Лістингу 14:


Лістинг 14. Інтеграція даних Web-сервісів з результатами перетворення SQL в XML





<?php
ini_set(“max_execution_time”, 120);
/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost / world"));

/ / Формуємо запит SQL
/ / Отримуємо результат в XML
$ Sql = "SELECT Country.Code2 AS code, Country.Name AS country, City.Name AS city,
City.Population AS population FROM Country, City
WHERE Country.Code = City.CountryCode GROUP BY City.CountryCode
HAVING City.Population = MAX (City.Population) ORDER BY City.Population
DESC LIMIT 15″;
$xml = $q2x->getXML($sql, array(
“idColumn” => “code”,
“rootTag” => “countries”,
“rowTag” => “country”,
"Attributes" => array ("code", "name" => "country"),
“elements” => array(“city” => array (
“elements” => array(
“name” => “city”,
“population”,
“location” => “#getLocation”),
)
),
)
);

/ / Виводимо XML
header(“Content-Type: text/html”);
$xml->formatOutput = true;
print $xml->saveXML();
} catch (Exception $e) {
echo $e->getMessage();
}

/ / Функція отримання даних Web-сервісу GeoNames
/ / Викликаємо GeoNames і передаємо назва країни та міста
/ / Створюємо фрагмент документа XML з повернутими значеннями
function getLocation($record) {
/ / Отримуємо дані і формат в об'єкт SimpleXML
$sxml = simplexml_load_string(file_get_contents(
“http://ws.geonames.org/search?maxRows=1&name=” .
urlencode (utf8_encode ($ record ["city"])). "& Country =".
urlencode(utf8_encode($record[“code”]))));

/ / Витягаємо дані з об'єкта SimpleXML
/ / Перетворимо їх у фрагменти DOMNode
$dom = new DOMDocument();
/ / Формуємо вузол <lat>
$lat = $dom->createElement(“lat”);
$ Lat-> appendChild ($ dom-> createTextNode ($ sxml-> geoname {0} -> lat));
/ / Формуємо вузол <long>
$long = $dom->createElement(“long”);
$ Long-> appendChild ($ dom-> createTextNode ($ sxml-> geoname {0} -> lng));
return array($lat, $long);
}
?>


Виклик getXML() в Лістингу 14 виконує запит SELECT, який групує міста по країнах і вибирає міста з найбільшим населенням. Після цього дані перетворюються на документ XML наступного виду (Лістинг 15):


Лістинг 15. Документ XML, сформований кодом з лістингу 14 на першому етапі (скорочений)





<?xml version=”1.0″ encoding=”UTF-8″?>
<countries>
<country code=”AW” name=”Aruba”>
<city>
<name>Oranjestad</name>
<population>29034</population>
</city>
</country>

</countries>

Тепер потрібно отримати координати широти і довготи кожного з міст і додати їх у сформований раніше дерево документа (див. Лістинг 14). Ця інформація запрошується на Web-сервісі GeoNames, доступному в стилі REST, у якого відкритий метод search(), Який повертає географічну інформацію зазначеного місця.


У Лістингу 16 показаний приклад пакета, який висилає GeoNames у відповідь на запит “Berlin, Germany”:


Лістинг 16. Приклад відповідного пакету GeoNames





<?xml version=”1.0″ encoding=”UTF-8″ standalone=”no”?>
<geonames>
<totalResultsCount>807</totalResultsCount>
<geoname>
<name>Berlin</name>
<lat>52.5166667</lat>
<lng>13.4</lng>
<geonameId>2950159</geonameId>
<countryCode>DE</countryCode>
<countryName>Germany</countryName>
<fcl>P</fcl>
<fcode>PPLC</fcode>
</geoname>
</geonames>

Як бачите, у відповідному пакеті міститься різна інформація про вказаному місці, в тому числі й дані, які нас цікавлять – координати широти і довготи.


Давайте тепер уважно подивимося на виклик getXML() в Лістингу 13. Зверніть увагу, що ключ location масиву параметрів є пов'язаною, але не з полем з результату запиту, а з функцією зовнішнього виклику getLocation(). Це означає, що кожного разу, коли getXML() обробляє запис з результуючого безлічі SQL, він викликає getLocation() для цього запису як асоціативний масив пар полі-значення. Метод getLocation(), У свою чергу, за допомогою REST викликає метод search() Web-сервісу GeoNames, передає йому як параметр назва міста і країни із запису SQL і отримує відповідь як об'єкт SimpleXML. Після цього для отримання елементів <lat> і <lng> з відповідного пакету, їх перетворення в два окремих примірника DOMNode та їх передачі getXML() у вигляді масиву для вставки в дерево можна використовувати нотацію SimpleXML.


Наприкінці обробки вихідний документ буде виглядати так, як показано в Лістингу 17:


Лістинг 17. Остаточний вихідний документ XML, що формується в Лістингу 14 (скорочений)





<?xml version=”1.0″ encoding=”UTF-8″?>
<countries>
<country code=”IN” name=”India”>
<city>
<name>Mumbai (Bombay)</name>
<population>10500000</population>
<location>
<lat>18.975</lat>
<long>72.8258333</long>
</location>
</city>
</country>
<country code=”KR” name=”South Korea”>
<city>
<name>Seoul</name>
<population>9981619</population>
<location>
<lat>37.5663889</lat>
<long>126.9997222</long>
</location>
</city>
</country>

</countries>

Як видно з цього прикладу, використання власних функцій зовнішнього виклику – це найпростіший шлях перетворення даних з різних джерел в документ XML, що формується методом getXML(). Сценарій з лістингу 14 підключається до зовнішнього Web-сервісу; так само просто можна імпортувати в кінцеве дерево документа XML зовнішній файл або результат виконання виклику XML-RPC.


Створення резервної копії бази даних


Ще одним корисним додатком пакету XML_Query2XML є копіювання вмісту таблиць бази даних у формат XML для подальшого зберігання і резервування. Логіка сценарію резервного копіювання дуже проста: отримати перелік таблиць бази даних, послідовно пройти за цим списком і виконати запити DESC ? і SELECT * FROM ? SQL для вилучення відповідно схеми і записів кожної з таблиць. Якщо ви уважно вивчали статтю, ви, напевно, вже уявляєте собі, як повинен виглядати виконує цю функцію метод getXML().


Вирішити це завдання здається на перший погляд не так просто, головним чином з-за обмежень рівня абстракції MDB2, а саме, через те, що він не може обробляти символи підстановки для назв таблиць і колонок у підготовлених запитах. Це ускладнює використання згаданих раніше запитів DESC ? і SELECT * FROM ?, Так як рівень MDB2 буде просто видавати помилку, якщо зустріне такі запити.


Що робити в цій ситуації? Проявити трохи фантазії, як в Лістингу 18:


Лістинг 18. Формування дампа структури та вмісту бази даних у форматі XML





<?php
ini_set(“max_execution_time”, 120);

/ / Включаємо необхідні файли
include “XML/Query2XML.php”;
include “MDB2.php”;

/ / Встановлюємо назва бази даних
$db = “world”;

try {
/ / Инициализируем об'єкт Query2XML
$ Q2x = XML_Query2XML:: factory (MDB2:: factory ("mysql: / / root: pass @ localhost /". $ Db));

/ / Виконуємо запит SQL для отримання списку таблиць
/ / Примітки: цей запит буде різним для різних баз даних
$sql = “SHOW TABLES”;
$xml = $q2x->getXML($sql, array(
“idColumn” => false,
“rootTag” => “database”,
“rowTag” => “table”,
"Attributes" => array ("name" => "tables_in_". $ Db))
);

/ / Отримуємо список всіх вузлів <table>
$nodelist = $xml->getElementsByTagName(“table”);

/ / Проходимо по кожному з вузлів
$x = 0;
while ($node = $nodelist->item($x)) {
/ / Витягаємо назва таблиці
$ Table = $ node-> attributes-> getNamedItem ("name") -> nodeValue;

/ / Отримуємо опис таблиці
/ / У вигляді документа DOM
/ / Примітка: цей запит буде різним для різних баз даних
$sql_1 = “DESC ” . $table;
$schema = $q2x->getXML($sql_1, array (
“idColumn” => “field”,
“rowTag” => “define”,
“rootTag” => “schema”,
“elements” => array(“*”))
);

/ / Отримуємо вміст таблиці
/ / У вигляді іншого документа DOM
$sql_2 = “SELECT * FROM ” . $table;
$data = $q2x->getXML($sql_2, array (
“idColumn” => false,
“rowTag” => “record”,
“rootTag” => “data”,
“elements” => array(“*”))
);

/ / Проходимо по документу DOM $ schema
/ / Використовуємо XPath для отримання вузла <schema> і всіх дочірніх вузлів
/ / Імпортуємо його в кореневу XML, у відповідний елемент <table>
/ / Автор ідеї: Ігор Краус (Igor Kraus), http://www.php.net/simplexml
$xpath = new DOMXPath($schema);
$query = $xpath->query(“//schema”);
for ($i = 0; $i < $query->length; $i++) {
$ Xml-> documentElement-> childNodes-> item ($ x) -> appendChild (
$ Xml-> importNode ($ query-> item ($ i), true));
}

/ / Виконуємо ту ж операцію для документа DOM $ data
$xpath = new DOMXPath($data);
$query = $xpath->query(“//data”);
for ($i = 0; $i < $query->length; $i++) {
$ Xml-> documentElement-> childNodes-> item ($ x) -> appendChild (
$ Xml-> importNode ($ query-> item ($ i), true));
}

/ / Збільшуємо лічильник для наступного проходу
$x++;
}

/ / Записуємо результат на диск
/ / Виводимо повідомлення про успішне завершення або помилку
$xml->formatOutput = true;
if ($xml->save(“/tmp/dump.xml”)) {
echo “Data successfully saved!”;
} else {
echo “Data could not be saved!”;
}
} catch (Exception $e) {
echo $e->getMessage();
}
?>


Виглядає досить складно, але насправді все просто:



  1. По-перше, отримуємо назва всіх таблиць поточної бази даних. Вид запиту SQL для отримання цього списку буде різним для різних баз даних. У сценарії, наведеному в Лістингу 18, використовується запит для MySQL SHOW TABLES, Але він не буде працювати в інших СУБД. Якщо ви використовуєте іншу систему баз даних, цей запит потрібно змінити. Результат виконання цієї команди у вигляді документа XML зберігається у змінній $xml і має вигляд, представлений в Лістингу 19:

    Лістинг 19. Результат XML, що формується програмної в лістингу 18 на першому етапі





    <?xml version=”1.0″ encoding=”UTF-8″?>
    <database>
    <table name=”City”/>
    <table name=”Country”/>
    <table name=”CountryLanguage”/>
    </database>


  2. Після цього отримуємо набір всіх елементів <table>, Створених на попередньому кроці, викликаючи в циклі метод getElementsByTagName(). На кожному кроці циклу створюються два нових документа XML: $schema, В якому міститься інформація про структуру полів таблиці (дивися Лістинг 20), і $data, В якому зберігаються всі записи таблиці (дивися Лістинг 21):

    Лістинг 20. Документ XML, що містить схему таблиці





    <?xml version=”1.0″ encoding=”UTF-8″?>
    <schema>
    <define>
    <field>ID</field>
    <type>int(11)</type>
    <null>NO</null>
    <key>PRI</key>
    <default/>
    <extra>auto_increment</extra>
    </define>
    <define>
    <field>Name</field>
    <type>char(35)</type>
    <null>NO</null>
    <key/>
    <default/>
    <extra/>
    </define>
    <define>

    </define>
    </schema>

    Лістинг 21. Документ XML, який містить записи таблиці





    <?xml version=”1.0″ encoding=”UTF-8″?>
    <data>
    <record>
    <id>1</id>
    <name>Kabul</name>
    <countrycode>AFG</countrycode>
    <district>Kabol</district>
    <population>1780000</population>
    </record>
    <record>
    <id>2</id>
    <name>Qandahar</name>
    <countrycode>AFG</countrycode>
    <district>Qandahar</district>
    <population>237500</population>
    </record>
    <record>

    </record>
    </data>


  3. Продовжуючи ту ж ітерацію циклу, імпортуємо два незалежних документа XML, $schema і $data, У батьківський документ $xml. Як ви вже бачили з попередніх прикладів, XPath надає простий спосіб вилучення фрагментів вузла XML з $schema і $xml; Інші обробляє метод importNode() розширення DOM, вставляючи ці фрагменти у відповідну точку основного дерева XML.

    У Лістингу 22 наведено фрагмент кінцевого документа:


    Лістинг 22. Кінцевий документ XML, що формується програмою в лістингу 18





    <?xml version=”1.0″ encoding=”UTF-8″?>
    <database>
    <table name=”City”>
    <schema>
    <define>
    <field>ID</field>
    <type>int(11)</type>
    <null>NO</null>
    <key>PRI</key>
    <default/>
    <extra>auto_increment</extra>
    </define>
    <define>
    <field>Name</field>
    <type>char(35)</type>
    <null>NO</null>
    <key/>
    <default/>
    <extra/>
    </define>

    </schema>
    <data>
    <record>
    <id>1</id>
    <name>Kabul</name>
    <countrycode>AFG</countrycode>
    <district>Kabol</district>
    <population>1780000</population>
    </record>
    <record>
    <id>2</id>
    <name>Qandahar</name>
    <countrycode>AFG</countrycode>
    <district>Qandahar</district>
    <population>237500</population>
    </record>

    </data>
    </table>
    <table>

    </table>
    </database>


У Лістингу 23 представлено інше, більш елегантне рішення, запропоноване Лукасом Фейлером (Lukas Feiler), розробником класу XML_Query2XML:


Лістинг 23. Альтернативний спосіб формування дампа структури та вмісту бази даних в XML





<?php
ini_set(“max_execution_time”, 120);

/ / Автор: Lukas Feiler, http://www.lukasfeiler.com
/ / Включаємо файли
require_once “XML/Query2XML.php”;
require_once “MDB2.php”;

/ / Инициализируем рівень абстракції MDB
/ / Завантажуємо диспетчер MDB
$mdb2 = MDB2::factory(“mysql://root:pass@localhost/world”);
$mdb2->loadModule(“Manager”);

/ / Инициализируем об'єкт Query2XML
$q2x = XML_Query2XML::factory($mdb2);

/ / Отримуємо перелік таблиць
$tables = $mdb2->listTables();

/ / Динамічно створюємо масив $ options
/ / Для кожної таблиці
$elements = array();
for ($i=0; $i<count($tables); $i++) {
$elements[“table” . $i] = array(
“rowTag” => “table”,
“attributes” => array(
“name” => “:” . $tables[$i]
),
“elements” => array(
“record” => array(
“idColumn” => false,
“sql” => “SELECT * FROM ” . $tables[$i],
“elements” => array(
“*”
)
)
)
);
}

/ / Отримуємо дані з таблиць в XML
$xml = $q2x->getXML(
false,
array(
“idColumn” => false,
“rowTag” => “__tables”,
“rootTag” => “database”,
“elements” => $elements
)
);

/ / Записуємо результат на диск
/ / Виводимо повідомлення про успішне завершення або помилку
$xml->formatOutput = true;
if ($xml->save(“/tmp/dump.xml”)) {
echo “Data successfully saved!”;
} else {
echo “Data could not be saved!”;
}
?>


Це рішення спочатку завантажує модуль MDB2 Manager і за допомогою його методу listTables() отримує список всіх таблиць бази даних, незалежно від типу бази. Після цього воно проходить за списком таблиць і на кожній ітерації динамічно формує новий масив elements. Після обробки всіх таблиць виклик методу getXML() з динамічно створеним масивом elements, створює дамп всієї бази даних в форматі XML, який записується на диск. У Лістингу 24 показаний фрагмент вихідного файлу:


Лістинг 24. Документ XML, отриманий в результаті роботи програми з лістингу 23





<?xml version=”1.0″ encoding=”UTF-8″?>
<database>
<table name=”city”>
<record>
<id>1</id>
<name>Kabul</name>
<countrycode>AFG</countrycode>
<district>Kabol</district>
<population>1780000</population>
</record>
<record>
<id>2</id>
<name>Qandahar</name>
<countrycode>AFG</countrycode>
<district>Qandahar</district>
<population>237500</population>
</record>

</table>
<table name=”country”>
<record>
<code>AFG</code>
<name>Afghanistan</name>
<continent>Asia</continent>
<region>Southern and Central Asia</region>
<surfacearea>652090.00</surfacearea>
<indepyear>1919</indepyear>
<population>22720000</population>
<lifeexpectancy>45.9</lifeexpectancy>
<gnp>5976.00</gnp>
<gnpold/>
<localname>Afganistan/Afqanestan</localname>
<governmentform> Islamic Emirate </ governmentform>
<headofstate>Mohammad Omar</headofstate>
<capital>1</capital>
<code2>AF</code2>
</record>

</table>
<table>

</table>
</database>

Висновок


Як видно з наведених раніше лістингів, можливості пакета XML_Query2XML значно ширше простого форматування безлічі даних SQL в XML. Він може служити основою для широкого спектру додатків, від простих конвертерів SQL в HTML до інструментів, які створюють складні документи XML з безлічі джерел даних, включаючи Web-сервіси, дискові файли та різноманітні бази даних. Тому цей пакет буде цінним доповненням до інструментарію будь-якого розробника PHP. Спробуйте використовувати його наступного разу, коли вам знадобиться інтерфейс між додатком PHP / XML і базою даних SQL, і переконайтеся в цьому самі!

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*