В наши дни XML стал частью ландшафта практически во всех областях разработки программного обеспечения — не более, чем в Интернете. Те, кто использует обычные XML- приложения, такие как RSS и XML-RPC, вероятно, найдут общедоступные библиотеки, специально предназначенные для того, чтобы помочь им работать с форматами, устраняя необходимость повторного изобретения колесика.
Но для «специальных» XML-документов вы можете быть сами по себе, и вы вполне можете потратить драгоценное время на сбор кода для его анализа. Вы также можете столкнуться с необходимостью предоставления данных в виде XML, чтобы сделать их доступными для какой-либо другой системы или приложения, и хотя XML, в конце концов, представляет собой просто текст, генерирование документа, который подчиняется правилам XML для правильной формы, может быть хитрее, чем кажется. Введите: PEAR :: XML_Serializer , «Швейцарский армейский нож» для XML.
Если вы поддерживаете связь с SitePoint, вы уже пробовали PEAR :: XML_Serializer, читая « Начало работы с PEAR» . В этой статье я подробно расскажу о XML_Serializer и покажу, как можно сделать работу с XML проще простого. Если у вас есть какие-либо сомнения относительно XML в целом, попробуйте Введение в XML .
Сегодняшняя иерархия тегов:
- Введение: что делает PEAR :: XML_Serializer и как его установить
- API XML_Serializer: обзор класса сериализации с простыми примерами
- API XML_Unserializer: обзор класса десериализации с большим количеством примеров
- Управление информацией о конфигурации: PEAR :: XML_Serializer применяется для управления файлом конфигурации XML
- Веб-сервисы с PEAR :: XML_Serializer: обмен данными между системами
Обратите внимание, что версия PEAR :: XML_Serializer, используемая для этой статьи, была 0,91. Чтобы упростить жизнь, я сохранил весь код из этих примеров в архив, который вы можете скачать здесь .
Вступление
PEAR :: XML_Serializer — результат тяжелой работы Стефана Шмидта, одного из самых плодовитых разработчиков PHP в Германии. Есть реальный шанс, что вы уже столкнулись с PHP-проектом, над которым работал Стефан, если вы когда-либо рассматривали PHP Application Tools (PAT) (как в patTemplate, patUser и многих других). На самом деле, если вы посмотрите на публикации и презентации , вы можете подумать, удалось ли Стефану как-то клонировать себя.
PEAR :: XML_Serializer работает по принципу, что XML может быть представлен как нативные типы PHP (переменные). Другими словами, вы можете создать некоторый массив в PHP, передать его в XML_Serializer, и он вернет вам XML-документ, представляющий массив. Он также способен к обратному преобразованию — предоставьте ему документ XML, и он может десериализовать его для вас, возвращая структуру данных PHP, представляющую документ.
Волшебство за кулисами — это функции отражения PHP, такие как is_array () , get_class () и get_object_vars () . Согласно Википедии , «отражение — это способность программы исследовать и, возможно, изменять свою структуру высокого уровня во время выполнения».
Это означает, что PEAR :: XML_Serializer, учитывая некоторую произвольную структуру данных PHP, довольно неплохо работает, превращая ее в полезное представление XML и наоборот. Конечно, это основано на «догадках», и вы можете обнаружить, что получающиеся преобразования не всегда соответствуют ожиданиям. Чтобы дать вам больше контроля, PEAR :: XML_Serializer имеет ряд опций времени выполнения, которые влияют на то, как он выполняет преобразования. Я посмотрю на API классов и кратко изложу доступные опции.
Некоторые вероятные проблемы, к которым вы можете применить PEAR :: XML_Serializer, включают управление конфигурацией приложения с помощью XML-документа (config.xml), построение веб-сервисов на основе REST, хранение данных в XML для последующего вызова вашими приложениями, общая система в систему обмен данными и практически любой «быстрый и грязный» анализ, который необходимо выполнить в кратчайшие сроки.
Вы можете избежать использования PEAR :: XML_Serializer при анализе больших XML-документов (порядка мегабайт) или при работе со сложными произвольными XML-документами (такими как XHTML). Как и DOM API, XML_Serializer анализирует весь XML-документ и строит из него структуру данных PHP в памяти. Большие документы могут привести к попаданию PHP в memory_limit (см. Php.ini) и таким операциям, как зацикливание, хотя структура данных будет дорогой. Собственный синтаксический анализатор PHP, как правило, в таких случаях является лучшим выбором, позволяя вам работать с небольшими «кусками» и контролировать использование памяти.
Между тем, для таких документов, как XHTML, API PEAR :: XML_Serializer слишком упрощен, чтобы дать вам степень точного контроля, которая вам потребуется. Когда вы ознакомитесь с тем, как его использовать, попробуйте десериализовать домашнюю страницу SitePoint или сгенерировать XHTML путем сериализации структуры данных PHP, и вы быстро поймете, что я имею в виду. DOM, как правило, лучший выбор для манипулирования XML.
Чтобы использовать PEAR :: XML_Serializer, вам также необходимо установить PEAR :: XML_Parser (оболочка для PHP-расширения SAX) и PEAR :: XML_Util (предоставляет ряд удобных методов для работы с XML). XML_Parser часто устанавливается вместе с самим PEAR, но, если вы не используете ни того, ни другого, введите в командной строке следующую команду, чтобы установить все:
$ pear install XML_Parser $ pear install XML_Util $ pear install XML_Serializer
Конечно, это предполагает, что у вас установлен PEAR — см. Начало работы с PEAR для получения инструкций по установке PEAR.
PEAR :: XML_Serializer предоставляет два API с классами XML_Serializer
и XML_Unserializer
. Первый, XML_Serializer, используется для преобразования структур данных PHP в XML, а XML_Unserializer выполняет обратную операцию, превращая XML в структуру данных PHP. В обоих случаях доступно только несколько методов открытого класса, что позволяет быстро преобразовать простые преобразования. Дальнейшее управление поведением классов требует установки «опций», обычно путем передачи ассоциативного массива PHP в конструктор класса, с которым вы работаете. Я должен признаться, что я менее чем очарован обработкой конфигурации таким образом, как я уже писал ранее, и PEAR :: XML_Serializer, возможно, доказывает это; поиск поддерживаемых опций требует обхода исходного кода (чтобы упростить вашу жизнь, полный список уже готов). В любом случае, PEAR :: XML_Serializer остается отличным инструментом для работы с XML.
API XML_Serializer
Я начну с XML_Serializer class
, который используется для преобразования структур данных PHP в XML, сначала суммирую API, а затем проиллюстрирую его несколькими примерами. Чтобы описать API, я буду использовать понятие сигнатуры функции, общее для руководства по PHP:
return_type function_name(type param_name, [type optional_param_name])
Основные открытые методы, доступные в классе XML_Serializer
:
-
object XML_Serializer
([параметры массива])
Конструктор принимает необязательный массив опций (см. Ниже). -
mixed serialize
(смешанные данные, [параметры массива])
Передайте этот метод структуре данных PHP, и он выполняет сериализацию в XML. Возвращаемое значение либо TRUE в случае успеха, либо объект PEAR Error, если возникли проблемы. Другие параметры также могут быть переданы в качестве второго аргумента (см. Ниже). -
mixed getSerializedData()
Этот метод возвращает сериализованный документ XML в виде строки или объекта ошибки PEAR, если сериализованный XML недоступен. -
void setOption
(имя строки, смешанное значение)
Этот метод устанавливает индивидуальную опцию. -
void resetOptions()
Используйте этот метод для сброса всех параметров в их стандартные состояния.
Доступные опции для XML_Serializer:
-
addDecl
(по умолчанию = FALSE):
добавлять ли открывающую инструкцию обработки XML,<?xml version="1.0"?>
-
encoding
(по умолчанию = «»):
кодировка символов XML, которая будет добавлена к открывающему объявлению XML, например<?xml version="1.0" encoding="ISO-8859-1"?>
-
addDoctype
(по умолчанию = FALSE):
добавлять ли декларацию DOCTYPE к документу -
doctype
(default = null):
укажите URI, которые будут использоваться в объявлении DOCTYPE (см. примеры ниже) -
indent
(по умолчанию = «»):
строка, используемая для отступа тегов XML, чтобы сделать его более удобным для человеческого глаза -
linebreak
(по умолчанию = «n»):
также используется для форматирования, этот символ вставляется после каждого открывающего и закрывающего тега -
indentAttributes
(по умолчанию = FALSE):
строка, используемая для отступа атрибутов сгенерированных тегов XML. Если установлено специальное значение «_auto», он выстроит все атрибуты под одним и тем же столбцом, вставляя символ перевода строки между каждым атрибутом. -
defaultTagName
(default = «XML_Serializer_Tag»):
имя тега, используемое для сериализации значений в индексированном массиве -
mode
(default = «default»):
если установлено значение ‘simplexml’, элементы индексированных массивов будут помещены в теги с тем же именем, что и их родительские элементы. Подробнее об этом ниже. -
rootName
(default = «»):
Тег для назначения корневому тегу документа XML. Если не указано, для корневого имени будет использоваться тип корневого элемента в структуре данных PHP (например, «массив»). -
rootAttributes
(default = array ()):
ассоциативный массив значений для преобразования в атрибуты корневого тега, ключи становятся именами атрибутов. Будьте осторожны при использовании этого, так как вы должны убедиться, что ключи и значения будут иметь допустимые атрибуты XML. -
scalarAsAttributes
(по умолчанию = FALSE):
для ассоциативных массивов, если значения являются скалярными типами (например, строки, целые числа), они будут назначены их родительскому узлу в качестве атрибутов, используя ключ массива в качестве имени атрибута. -
prependAttributes
(default = «»):
строка, которая будет добавлена к именам любых сгенерированных атрибутов тега. -
typeHints
(по умолчанию = FALSE):
определяет, должен ли исходный тип переменной значения PHP, представленного тегом, сохраняться как атрибут в сериализованном документе XML. Смотрите ниже пример. -
typeAttribute
(default = «_type»):
если используются typeHints, типы будут храниться в теге XML с использованием атрибута с именем этой опции. Если у вас есть переменная PHP, такая как$myVariable = 'Hello World!'
сериализованное XML-представление по умолчанию будет<myVariable _type="string">Hello World!</myVariable>
если используются typeHints. -
keyAttribute
(default = «_originalKey»):
атрибут, используемый для хранения исходного ключа элементов индексированного массива. Используется только когда typeHints включены. -
classAttribute
(default = «_class»):
при сериализации объектов (с включенными typeHints) этот атрибут будет использоваться для хранения имени класса, из которого был создан объект.
Существует еще одна специальная опция. ‘overrideOptions’ используется при передаче параметров в метод serialize()
. Если присвоено значение «ИСТИНА», параметры, передаваемые конструктору, будут игнорироваться в пользу значений параметров по умолчанию и любых других параметров, передаваемых методу serialize()
.
Простой пример сериализации структуры данных PHP с XML_Serializer выглядит следующим образом:
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Serializer require_once 'XML/Serializer.php'; // Some data to transform $palette = array('red', 'green', 'blue'); // An array of serializer options $serializer_options = array ( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'rootName' => 'palette', 'defaultTagName' => 'color', ); // Instantiate the serializer with the options $Serializer = &new XML_Serializer($serializer_options); // Serialize the data structure $status = $Serializer->serialize($palette); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the XML document header('Content-type: text/xml'); echo $Serializer->getSerializedData(); ?>
Имя файла: palette1.php
Здесь вы можете увидеть, как обычно используются параметры. Мне нужно построить массив $serializer_options
и передать его конструктору XML_Serializer.
Обратите внимание, что изменение отчетов об ошибках является обязательным условием, если вы обычно работаете с включенным полным отчетом об ошибках. Текущая версия PEAR :: XML_Serializer генерирует сообщения об ошибках PHP, такие как «преобразование массива в строку», ни одно из которых не является серьезным, но приведет к сообщениям об ошибках.
Результирующий XML выглядит так:
<?xml version="1.0" encoding="ISO-8859-1"?> <palette> <color>red</color> <color>green</color> <color>blue</color> </palette>
Поскольку структура данных является индексированным массивом, я использовал опцию 'defaultTagName'
чтобы дать имя тегам, представляющим элементы массива.
Теперь давайте использовать вместо этого ассоциативный массив:
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Serializer require_once 'XML/Serializer.php'; // Some data to transform $palette = array( 'red' => 45, 'green' => 240, 'blue' => 120 ); // An array of serializer options $serializer_options = array ( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'rootName' => 'palette', ); // Instantiate the serializer with the options $Serializer = &new XML_Serializer($serializer_options); // Serialize the data structure $status = $Serializer->serialize($palette); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the XML document header('Content-type: text/xml'); echo $Serializer->getSerializedData(); ?>
Имя файла: palette2.php
И получившийся XML выглядит следующим образом:
<?xml version="1.0" encoding="ISO-8859-1"?> <palette> <red>45</red> <green>240</green> <blue>120</blue> </palette>
Обратите внимание, что имена тегов теперь соответствуют ключам переменной $palette
. Если я снова воспользуюсь этим примером, добавив 'scalarAsAttributes'
(см. Пример файла palette3.php), вот XML, который я получаю:
<?xml version="1.0" encoding="ISO-8859-1"?> <palette blue="120" green="240" red="45" />
Скалярные целочисленные значения теперь становятся атрибутами корневого тега, а не представляются в виде отдельных тегов.
Другой пример показывает, что происходит, когда вы сериализуете объекты и используете опцию typeHints:
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Serializer require_once 'XML/Serializer.php'; // A class to store color information class ColorInformation { var $hue; var $value; function ColorInformation($hue = NULL, $value = NULL) { $this->hue = $hue; $this->value = $value; } } // Some data to transform $palette = array(); $palette[] = &new ColorInformation('red', 45); $palette[] = &new ColorInformation('green', 240); $palette[] = &new ColorInformation('blue', 120); // An array of serializer options $serializer_options = array ( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'indentAttributes' => '_auto', 'rootName' => 'palette', 'defaultTagName' => 'color', 'typeHints' => TRUE, ); // Instantiate the serializer with the options $Serializer = &new XML_Serializer($serializer_options); // Serialize the data structure $status = $Serializer->serialize($palette); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the XML document header('Content-type: text/xml'); echo $Serializer->getSerializedData(); ?>
Имя файла: palette4.php
Соответствующий XML выглядит так:
<?xml version="1.0" encoding="ISO-8859-1"?> <palette _type="array"> <color _class="colorinformation" _originalKey="0" _type="object"> <hue _type="string">red</hue> <value _type="integer">45</value> </color> <color _class="colorinformation" _originalKey="1" _type="object"> <hue _type="string">green</hue> <value _type="integer">240</value> </color> <color _class="colorinformation" _originalKey="2" _type="object"> <hue _type="string">blue</hue> <value _type="integer">120</value> </color> </palette>
При 'typeHints'
добавляются атрибуты, которые описывают исходную структуру данных, с некоторыми подробностями, '_type'
относится к исходному типу переменной PHP, '_class'
хранит имя класса любых сериализованных объектов, а '_originalKey'
является ключом индексированного массива, в котором был найден этот элемент, если применимо.
Функциональность 'typeHints'
полезна, когда вам нужно убедиться, что при десериализации документа вы вернетесь именно с того, с чего начали. Возможно, вы захотите использовать 'typeHints'
если вы используете PEAR :: XML_Serializer для хранения постоянных данных, которые ваш код получит позже. Обратите внимание, что когда вам необходимо точное представление структуры данных PHP с использованием typeHints, рекомендуется избегать использования опции 'scalarAsAttributes'
, которая теряет информацию о скалярных типах.
Наконец, в следующем примере показано, как можно добавить объявления DOCTYPE, в этом примере, для визуализации XHTML:
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Serializer require_once 'XML/Serializer.php'; // PHP data structure representing an XHTML document $xhtml = array ( 'head' => array ( 'title' => 'XHTML with XML_Serializer', ), 'body' => array ( 'h1' => 'XHTML with XML_Serializer', 'p' => 'It's possible but not recommended', ), ); // XML_Serializer options $serializer_options = array ( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'rootName' => 'html', 'rootAttributes' => array ( 'xmlns' => 'http://www.w3.org/1999/xhtml', 'lang' => 'en', 'xml:lang' => 'en' ), 'addDoctype' => TRUE, 'doctype' => array ( 'id' => '-//W3C//DTD XHTML 1.0 Strict//EN', 'uri' => 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd' ), ); // Create the serializer $Serializer = &new XML_Serializer($serializer_options); // Serialize the XHTML $status = $Serializer->serialize($xhtml); // Check for errors if (PEAR::isError($status)) { die($status->getMessage()); } // Send the right HTTP header // See http://www.juicystudio.com/tutorial/xhtml/mime.asp for more info if (stristr($_SERVER[HTTP_ACCEPT], 'application/xhtml+xml')) { header('Content-Type: application/xhtml+xml; charset=ISO-8859-1'); } else { header('Content-Type: text/html; charset=ISO-8859-1'); } // Display the XML document echo $Serializer->getSerializedData(); ?>
Имя файла: xhtml.php
Обратите внимание на вариант 'doctype'
. Определенный здесь массив фактически определяется PEAR :: XML_Util в его getDocTypeDeclaration()
. Также обратите внимание, что я использовал 'rootAttributes'
для добавления атрибутов к корневому HTML-тегу. Вот итоговый XHTML:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>XHTML with XML_Serializer</title> </head> <body> <h1>XHTML with XML_Serializer</h1> <p>It's possible but not recommended</p> </body> </html>
Этот пример предназначен исключительно для того, чтобы показать, как можно использовать объявления DOCTYPE. На практике я не рекомендую вам генерировать XHTML с XML_Serializer, поскольку API не предоставляет вам точного контроля над преобразованием, и вы, скорее всего, потратите много времени на работу с гигантскими массивами PHP и, как правило, потерю волос.
API XML_Unserializer
Вы видели основы API XML_Serializer; XML_Unserializer, который используется для преобразования XML в структуры данных PHP, по сути является его «зеркальным отображением».
Основными публичными методами являются:
-
object XML_Unserializer
([параметры массива])
Этот конструктор принимает необязательный массив опций (описан ниже). -
mixed unserialize
(смешанные данные, [логический isFile], [параметры массива])
Первый аргумент, data, может быть строкой, содержащей документ XML, путь к файлу, содержащему XML (в этом случае второй аргумент isFile должен быть установлен в значение true), или ресурс PHP, реализующий API Steams , например файл Вы уже открыли или экземпляр одного из потоковых упаковщиков PEAR . Третий необязательный аргумент, options, представляет собой массив опций, как описано ниже. Значение, возвращаемое изunserialize()
будет либо TRUE в случае успеха, либо объектом PEAR Error. -
mixed getUnserializedData()
Это возвращает либо структуру данных PHP, представленную документом XML, либо объект ошибки PEAR. -
mixed getRootName()
Этот метод возвращает имя корневого элемента (обычно невидимого в структуре данных, возвращаемойfrom getUnserializedData()
) или объекта ошибки PEAR. -
void setOption
(имя строки, смешанное значение)
Используйте это, чтобы установить индивидуальную опцию. -
void resetOptions()
Этот метод сбрасывает все параметры в их состояние по умолчанию.
Опции, доступные для использования с XML_Unserializer:
-
parseAttributes
(по умолчанию = FALSE)
следует ли превращать атрибуты тега в массивы; если установлено значение false, атрибуты игнорируются -
attributesArray
(по умолчанию = FALSE)
имя, используемое для идентификации массива в атрибутах переключателя, будет помещено -
prependAttributes
(default = «»)
использовать для добавления ключей массива, в которые будут помещены атрибуты -
complexType
(default = «array»)
если typeHint не найден, сложные типы (теги, содержащие сочетание CDATA и других дочерних тегов) будут преобразованы в массивы -
contentName
(default = «_content»)
для сложных типов CDATA внутри тега будет помещен в результирующий массив -
tagMap
(по умолчанию = array ())
позволяет сопоставить имя тега XML с именем класса PHP (см., например, ниже); переопределяет значение classAttribute, если в документе XML есть несериализованные typeHints -
keyAttribute
(default = «_originalKey»)
определяет атрибут, используемый в качестве typeHint для ключа индексированного массива (см. параметр XML_Serializer с тем же именем) -
typeAttribute
(default = «_type»)
определяет тип данных PHP, используемый как typeHint (см. параметр XML_Serializer с тем же именем) -
classAttribute
(default = «_class»)
определяет имя PHP класса PHP, для которого этот тег представляет объект (см. параметр XML_Serializer с тем же именем)
Как и в случае с классом XML_Serializer, опция ‘overrideOptions’ может быть передана методу unserialize()
чтобы игнорировать значения, уже переданные конструктору.
Чтобы обеспечить базовую иллюстрацию XML_Unserializer, я возьму выходные данные из примеров XML_Serializer, которые вы видели выше, и посмотрим, что мы получим, когда мы их не сериализуем.
Вот обратный пример первого примера:
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Unserializer require_once 'XML/Unserializer.php'; // The XML document $doc = <<<EOD <?xml version="1.0" encoding="ISO-8859-1"?> <palette> <color>red</color> <color>green</color> <color>blue</color> </palette> EOD; // Instantiate the serializer $Unserializer = &new XML_Unserializer(); // Serialize the data structure $status = $Unserializer->unserialize($doc); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the PHP data structure echo '<pre>'; print_r($Unserializer->getUnserializedData()); echo '</pre>'; ?>
Имя файла: upalette1.php
Результирующая структура данных выглядит следующим образом:
Array ( [color] => Array ( [0] => red [1] => green [2] => blue ) )
Здесь вы видите необходимость соблюдать осторожность при работе с XML_Serializer — исходная структура данных PHP была следующей:
$palette = array('red', 'green', 'blue');
Если вы хотите убедиться, что вернулись именно к той структуре данных, с которой вы начали при работе с индексированными массивами, вам нужно включить ‘typeHints’ при сериализации их в XML.
Переходя ко второму примеру, код по сути такой же:
// Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Unserializer require_once 'XML/Unserializer.php'; // The XML document $doc = <<<EOD <?xml version="1.0" encoding="ISO-8859-1"?> <palette> <red>45</red> <green>240</green> <blue>120</blue> </palette> EOD; // Instantiate the serializer $Unserializer = &new XML_Unserializer(); // Serialize the data structure $status = $Unserializer->unserialize($doc); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the PHP data structure echo '<pre>'; print_r($Unserializer->getUnserializedData()); echo '</pre>'; ?>
Имя файла: upalette2.phpВ результате структура данных PHP теперь:
Array ( [red] => 45 [green] => 240 [blue] => 120 )
На этот раз он соответствует исходной структуре данных, в которой также используется ассоциативный массив (но обратите внимание, что значения массива будут иметь тип 'string', а не тип 'integer'):
$palette = array( 'red' => 45, 'green' => 240, 'blue' => 120 );
Третий пример требует, чтобы я сказал unserializer для анализа атрибутов:
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Unserializer require_once 'XML/Unserializer.php'; // The XML document $doc = <<<EOD <?xml version="1.0" encoding="ISO-8859-1"?> <palette blue="120" green="240" red="45" /> EOD; // Array of options $unserializer_options = array ( 'parseAttributes' => TRUE ); // Instantiate the serializer $Unserializer = &new XML_Unserializer($unserializer_options); // Serialize the data structure $status = $Unserializer->unserialize($doc); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the PHP data structure echo '<pre>'; print_r($Unserializer->getUnserializedData()); echo '</pre>'; ?>
Получившаяся структура данных PHP снова более или менее соответствует тому, с чего я начал, хотя целые числа теперь являются строками:
Array ( [blue] => 120 [green] => 240 [red] => 45 )
Наконец, когда я отменяю сериализацию документа, содержащего подсказки типов, мне нужно убедиться, что класс ColorInformation определен. В исходном примере класс ColorInformation использовал конструктор для передачи внешних переменных в свои поля. Как и PHP-функция unserialize () , XML_Unserializer не предоставляет механизм для вызова методов класса при десериализации объектов; скорее, он устанавливает свойства объекта напрямую (поэтому будьте осторожны):
<?php // Set error reporting to ignore notices error_reporting(E_ALL ^ E_NOTICE); // Include XML_Unserializer require_once 'XML/Unserializer.php'; // A class to store color information class ColorInformation { var $hue; var $value; function ColorInformation($hue = NULL, $value = NULL) { $this->hue = $hue; $this->value = $value; } } // The XML document $doc = <<<EOD <?xml version="1.0" encoding="ISO-8859-1"?> <palette _type="array"> <color _class="colorinformation" _originalKey="0" _type="object"> <hue _type="string">red</hue> <value _type="integer">45</value> </color> <color _class="colorinformation" _originalKey="1" _type="object"> <hue _type="string">green</hue> <value _type="integer">240</value> </color> <color _class="colorinformation" _originalKey="2" _type="object"> <hue _type="string">blue</hue> <value _type="integer">120</value> </color> </palette> EOD; // Instantiate the serializer $Unserializer = &new XML_Unserializer(); // Serialize the data structure $status = $Unserializer->unserialize($doc); // Check whether serialization worked if (PEAR::isError($status)) { die($status->getMessage()); } // Display the PHP data structure echo '<pre>'; print_r($Unserializer->getUnserializedData()); echo '</pre>'; ?>
Результирующая структура данных:
Array ( [0] => colorinformation Object ( [hue] => red [value] => 45 ) [1] => colorinformation Object ( [hue] => green [value] => 240 ) [2] => colorinformation Object ( [hue] => blue [value] => 120 ) )
Это соответствует исходной структуре данных, игнорируя проблему с конструктором:
$palette = array(); $palette[] = &new ColorInformation('red', 45); $palette[] = &new ColorInformation('green', 240); $palette[] = &new ColorInformation('blue', 120);
Примечания по сериализации объектов:
Как и в случае встроенных в PHP функций serialize () и unserialize () , при преобразовании PEAR :: XML_Serializer в XML и обратно он будет пытаться вызывать функции
__sleep()
и__wakeup()
для этих объектов, если они были определены. Это дает вам возможность выполнять операции, такие как подключение или отключение от базы данных, определяя их в этих методах. См. Руководство по PHP для __sleep () и __wakeup () для более подробной информации.Когда объекты не сериализуются XML_Unserializer, он сначала пытается пересобрать их, используя оригинальный класс, но для этого ваш код должен убедиться, что класс доступен. Если он не может найти определение класса, предполагая, что вы используете PHP4, он вместо этого будет использовать встроенное в PHP определение stdClass. В прошлый раз, когда я смотрел, PHP5 отказался от поддержки stdClass, поэтому еще неизвестно, что произойдет в таких случаях.
В настоящее время XML_Serializer не имеет возможности представлять объекты, которые содержат ссылки друг на друга. Список TODO, поставляемый с пакетом, указывает на то, что поддержка планируется в ближайшем будущем.
Управление информацией о конфигурации
Теперь, когда вы получили полное представление о том, что предлагает PEAR :: XML_Serializer, и увидели несколько базовых примеров, пришло время сделать что-то полезное с этим.
Большинству приложений PHP требуется некоторая форма конфигурации, чтобы они могли «понимать» среду, в которой они используются, например, имя домена веб-сервера, адрес электронной почты администратора, параметры подключения к базе данных и так далее. Существует несколько общих подходов к обработке этого в PHP: от простого сценария PHP со списком переменных, требующих редактирования, до использования функции parse_ini_file () (обратите внимание, что PHP может анализировать INI-файл быстрее, чем он может включать, и разобрать эквивалентный скрипт PHP).
XML делает другой выбор: он относительно удобен для редактирования вручную, сравнительно легко анализировать и генерировать и позволяет создавать более сложные структуры данных, чем ini-файл. С другой стороны, получение данных конфигурации из файла XML может быть медленным по сравнению с альтернативами (хотя некоторые приемы с генерацией кода PHP могут помочь вам обойти это, но это другая история).
Помимо проблем с производительностью, вот один подход, использующий PEAR :: XML_Serializer и класс, который позволяет вам получать и изменять параметры конфигурации.
Во-первых, я определяю два класса: один для хранения данных конфигурации, а второй для управления доступом к ним:
<?php /** * The name of file used to store config data */ define ('CONFIG_FILE', 'config.xml'); /** * Stores configuration data */ class Config { /** * Array of configuration options * @var array * @access private */ var $options = array(); /** * Returns the value of a configuration option, if found * @param string name of option * @return mixed value if found or void if not * @access public */ function get($name) { if (isset($this->options[$name])) { return $this->options[$name]; } } /** * Sets a configuration option * @param string name of option * @param mixed value of option * @return void * @access public */ function set($name, $value) { $this->options[$name] = $value; } }
Класс
Config
действует как простое хранилище значений, предоставляя доступ через методыget()
иset()
./** * Provides a gateway to the Config class, managing its serialization */ class ConfigManager { /** * Returns a singleton instance of Config * @return Config * @access public * @static */ function &instance() { static $Config = NULL; if (!$Config) { $Config = ConfigManager::load(); } return $Config; } /** * Loads the Config instance from it's XML representation * @return Config * @access private * @static */ function load() { error_reporting(E_ALL ^ E_NOTICE); require_once 'XML/Unserializer.php'; $Unserializer = &new XML_Unserializer(); if (file_exists(CONFIG_FILE)) { $status = $Unserializer->unserialize(CONFIG_FILE, TRUE); if (PEAR::isError($status)) { trigger_error ($status->getMessage(), E_USER_WARNING); } $Config = $Unserializer->getUnserializedData(); } else { $Config = new Config(); } return $Config; } /** * Stores the Config instance, serializing it to an XML file * @return boolean TRUE on succes * @access public * @static */ function store() { error_reporting(E_ALL ^ E_NOTICE); require_once 'XML/Serializer.php'; $Config = &ConfigManager::instance(); $serializer_options = array ( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'typeHints' => TRUE, ); $Serializer = &new XML_Serializer($serializer_options); $status = $Serializer->serialize($Config); $success = FALSE; if (PEAR::isError($status)) { trigger_error($status->getMessage(), E_USER_WARNING); } $data = $Serializer->getSerializedData(); if (!$fp = fopen(CONFIG_FILE, 'wb')) { trigger_error('Cannot open ' . CONFIG_FILE); } else { if (!fwrite($fp, $data, strlen($data))){ trigger_error( 'Cannot write to ' . CONFIG_FILE, E_USER_WARNING ); } else { $success = TRUE; } fclose($fp); } return $success; } } ?>
Имя файла: configmanager.php
Класс
ConfigManager
немного сложнее. Я объясню ключевые моменты здесь, но если у вас есть какие-то конкретные вопросы, не стесняйтесь оставлять их в обсуждении в конце этой статьи.Статический метод
instance()
использует трюк PHP4 для создания экземпляров Singleton на объекте. Знаете ли вы о шаблоне разработки Singleton или нет, методinstance()
позволяет мне получать один и тот же экземпляр Config из любого места в моем коде, просто вызываяConfigManager::instance()
, чтобы убедиться, что любой изменения, которые происходят с объектом Config, доступны везде, где он используется.Методы
load()
иstore()
обрабатывают сериализацию и десериализацию объекта Config в XML. Методload()
предназначен для вызова только методомinstance()
то время как методstore()
должен вызываться в конце выполнения моего приложения, если были внесены какие-либо внешние изменения в объект Config. Обратите внимание, что в этих методах я использую Ленивые Включения, чтобы свести к минимуму количество разборов PHP-движка. Могут быть случаи, когдаload()
вызывается, но неstore()
, когда код, использующий его, должен только получить значения конфигурации, а не изменить их. В этих случаях включение классаXML_Serializer
для каждого запросаXML_Serializer
к бесполезным расходам.Теперь, используя PEAR :: HTML_QuickForm , я могу создать форму для редактирования файла конфигурации. На этот раз я собираюсь пропустить объяснение HTML_QuickForm (дополнительные примеры можно найти в пакете и руководствах в Антологии PHP ). Убедитесь, что он установлен, набрав:
$ pear install HTML_Quickform
Версия HTML_Quickform, используемая здесь, была 3.2.2.
Код формы:
<?php require_once 'configmanager.php'; require_once 'HTML/QuickForm.php'; // Fetch the singleton instance of Config $Config = &ConfigManager::instance(); // Build a form with PEAR::HTML_QuickForm $Form = new HTML_QuickForm('labels_example', 'post'); $Form->addElement('text', 'domain', 'Domain'); $Form->addRule('domain', 'Please enter a domain name', 'required', NULL, 'client'); $Form->addRule('domain', 'Please enter a valid domain name', 'regex', '/^(www.)?.+.(com|net|org)$/', 'client'); $Form->addElement('text', 'email', 'Email'); $Form->addRule('email', 'Please enter an email address', 'required', NULL, 'client'); $Form->addRule('email', 'Please enter a valid email address', 'email', NULL, 'client'); $Form->addElement('text', 'docroot', 'Document Root'); $Form->addRule('docroot', 'Please enter the document root', 'required', NULL, 'client'); $Form->addRule('docroot', 'Please enter a valid document root', 'callback', 'is_dir'); $Form->addElement('text', 'tmp', 'Tmp Dir'); $Form->addRule('tmp', 'Please enter the tmp dir', 'required', NULL, 'client'); $Form->addRule('tmp', 'Please enter a valid tmp dir', 'callback', 'is_dir'); $Form->addElement('text', 'db_host', 'DB Host'); $Form->addRule('db_host', 'Please enter a value for DB Host', 'required', NULL, 'client'); $Form->addRule('db_host', 'Please enter a valid value for DB Host', 'regex', '/^[a-zA-Z0-9.]+$/', 'client'); $Form->addElement('text', 'db_user', 'DB User'); $Form->addRule('db_user', 'Please enter a value for DB User', 'required', NULL, 'client'); $Form->addRule('db_user', 'Please enter a valid value for DB User', 'regex', '/^[a-zA-Z0-9]+$/', 'client'); $Form->addElement('text', 'db_pass', 'DB Password'); $Form->addRule('db_pass', 'Please enter a value for DB Password', 'required', NULL, 'client'); $Form->addRule('db_pass', 'Please enter a valid value for DB Password', 'regex', '/^[a-zA-Z0-9]+$/', 'client'); $Form->addElement('text', 'db_name', 'DB Name'); $Form->addRule('db_name', 'Please enter a value for DB Name', 'required', NULL, 'client'); $Form->addRule('db_name', 'Please enter a valid value for DB Name', 'regex', '/^[a-zA-Z0-9]+$/', 'client'); $Form->addElement('submit', null, 'Update'); // Initialize $db array as needed $db = $Config->get('db'); if (!is_array($db)) $db = array(); if (!isset($db['db_host'])) $db['db_host'] = ''; if (!isset($db['db_user'])) $db['db_user'] = ''; if (!isset($db['db_pass'])) $db['db_pass'] = ''; if (!isset($db['db_name'])) $db['db_name'] = ''; // Set initial form values from Config $Form->setDefaults(array( 'domain' => $Config->get('domain'), 'email' => $Config->get('email'), 'docroot' => $Config->get('docroot'), 'tmp' => $Config->get('tmp'), 'db_host' => $db['db_host'], 'db_user' => $db['db_user'], 'db_pass' => $db['db_pass'], 'db_name' => $db['db_name'], )); // If the form is valid update the configuration file if ($Form->validate()) { $result = $Form->getSubmitValues(); $Config->set('domain',$result['domain']); $Config->set('email',$result['email']); $Config->set('docroot',$result['docroot']); $Config->set('tmp',$result['tmp']); $db['db_host'] = $result['db_host']; $db['db_user'] = $result['db_user']; $db['db_pass'] = $result['db_pass']; $db['db_name'] = $result['db_name']; $Config->set('db', $db); if (ConfigManager::store()) { echo "Config updated successfully"; } else { echo "Error updating configuration"; } } else { echo '<h1>Edit ' . CONFIG_FILE . '</h1>'; $Form->display(); } ?>
Имя файла: configedit.php
Сначала я
ConfigManager
экземплярConfig
изConfigManager
и использую его для заполнения значений формы по умолчанию. Мне нужно инициализировать массив$db
на тот случай, если файл config.xml редактируется впервые (т.е. он еще не существует), аConfig
содержит пустые значения.Как только форма отправлена как успешно проверенная, я
ConfigManager::store()
значения обратно в экземплярConfig
, а затем вызываюConfigManager::store()
чтобы обновить документ config.xml последними значениями.Вот как выглядит форма в браузере:
Сохраненный файл config.xml выглядит следующим образом:
<?xml version="1.0" encoding="ISO-8859-1"?> <config _class="config" _type="object"> <options _type="array"> <domain _type="string">www.sitepoint.com</domain> <email _type="string">[email protected]</email> <docroot _type="string">/www</docroot> <tmp _type="string">/tmp</tmp> <db _type="array"> <db_host _type="string">db.sitepoint.com</db_host> <db_user _type="string">phpclient</db_user> <db_pass _type="string">secret</db_pass> <db_name _type="string">sitepointdb</db_name> </db> </options> </config>
Имя файла: config.xml
Использование шрифтов делает его немного недружелюбным для человеческого глаза, но все же возможно редактировать этот файл вручную, если это будет необходимо.
Теперь, используя другой скрипт, я могу получить доступ к значениям в config.xml:
<?php require_once 'configmanager.php'; // Fetch the singleton instance of Config $Config = &ConfigManager::instance(); ?> <h1><?php echo CONFIG_FILE; ?></h1> <table> <tr> <td>Domain:</td><td><?php echo $Config->get('domain'); ?></td> </tr> <tr> <td>Email:</td><td><?php echo $Config->get('email'); ?></td> </tr> <tr> <td>Docroot:</td><td><?php echo $Config->get('docroot'); ?></td> </tr> <tr> <td>Tmp Dir:</td><td><?php echo $Config->get('tmp'); ?></td> </tr> <?php $db = $Config->get('db'); ?> <tr> <td>DB Host:</td><td><?php echo $db['db_host']; ?></td> </tr> <tr> <td>DB User:</td><td><?php echo $db['db_user']; ?></td> </tr> <tr> <td>DB Pass:</td><td><?php echo $db['db_pass']; ?></td> </tr> <tr> <td>DB Name:</td><td><?php echo $db['db_name']; ?></td> </tr> </table>
Имя файла: configview.php
Здесь я просто отображаю их в таблице, чтобы вы могли видеть, как это работает. Поскольку я могу вызывать
ConfigManager::instance()
из любого места в моем коде и получать актуальную ссылку на объектConfig
, легко получить значения, хранящиеся в ico
это когда мне нужно настроить поведение моего приложения. Кроме того, поскольку я работаю с экземпляромConfig
Singleton, накладные расходы на несериализацию базового XML-документа должны быть понесены только один раз, при первом получении экземпляраConfig
.Использование PEAR :: XML_Serializer в этом примере помогает мне избежать увязки с мелочью XML, что позволяет мне сосредоточить свои усилия на коде, который основан на нем и имеет прямое значение для моего приложения.
Веб-сервисы с PEAR :: XML_Serializer
Другая область, в которой PEAR :: XML_Serializer может оказаться полезной, - это обмен данными между системой или между приложениями. Такие пакеты, как PEAR :: SOAP и PEAR :: XML_RPC, предоставляют реализации соответствующих протоколов Web-сервисов, но SOAP и XML-RPC - не единственные способы перемещения данных из A в B.
Amazon, например, предоставляет то, что обычно называют интерфейсом REST для своего веб-сайта (краткий обзор веб-служб REST см. В разделе « Создание веб-служб способом REST» ). Это означает, что вы можете получить доступ к данным о продуктах, которые Amazon продает, не используя ничего, кроме URL. В результате вы получаете ответ с такого URL-адреса, как XML-документ, содержащий данные, которые вы обычно находите в HTML на такой странице. Предоставляя данные в виде XML, Amazon упрощает анализ с удаленного веб-сайта и отображение с использованием собственного HTML. Полную информацию можно найти на amazon.com/webservices (вам необходимо зарегистрироваться в качестве партнера).
Что касается PEAR :: XML_Serializer, то очень просто проанализировать XML, предоставляемый Amazon, и превратить его в веб-страницу:
<?php // Include PEAR::HTTP_Request require_once 'HTTP/Request.php'; // Include PEAR::XML_Unserializer require_once 'XML/Unserializer.php'; // Your Amazon associate ID $assoc_id = 'sitepoint'; // Allow the Amazon book search keyword to be entered via the URL if (!isset($_GET['keyword']) || !preg_match('/^[a-zA-Z]+$/', $_GET['keyword'])) { $_GET['keyword'] = 'php'; } // Build the URL to access the Amazon XML $amazon_url = 'http://rcm.amazon.com/e/cm?t=' . $assoc_id . '&l=st1&search=' . $_GET['keyword'] . '&mode=books&p=102&o=1&f=xml'; // Create the HTTP_Request object, specifying the URL $Request = &new HTTP_Request($amazon_url); // Set proxy server as necessary // $Request->setProxy('proxy.myisp.com', '8080', 'harryf', 'secret'); // Send the request for the feed to the remote server $status = $Request->sendRequest(); // Check for errors if (PEAR::isError($status)) { die("Connection problem: " . $status->toString()); } // Check we got an HTTP 200 status code (if not there's a problem) if ($Request->getResponseCode() != '200') { die("Request failed: " . $Request->getResponseCode()); } // Get the XML from Amazon $amazon_xml = $Request->getResponseBody(); // Create an instance of XML_Unserializer $Unserializer = new XML_Unserializer(); // Unserialize the XML $status = $Unserializer->unserialize($amazon_xml); // Check for errors if (PEAR::isError($status)) { die($status->getMessage()); } // Get the PHP data structure from the XML $amazon_data = $Unserializer->getUnserializedData(); ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>Amazon Search for <?php echo $_GET['keyword']; ?></title> </head> <body> <h1>Amazon Search for <?php echo $_GET['keyword']; ?></h1> <p> <a href="?keyword=Linux">Search for Linux</a> | <a href="?keyword=Apache">Search for Apache</a> | <a href="?keyword=MySQL">Search for MySQL</a> | <a href="?keyword=PHP">Search for PHP</a> </p> <table> <tr> <td> <?php foreach ($amazon_data['product'] as $product) { ?> <table width="600"> <tr> <th><?php echo nl2br(wordwrap($product['title'], 40)); ?></th> <td rowspan="2" align="right"> <a href="<?php echo $product['tagged_url']; ?>"> <img src="<?php echo $product['small_image']; ?>" border="0" /> </a> </td> </tr> <tr> <td> Author: <?php echo $product['author']; ?><br /> ISBN: <?php echo $product['asin']; ?><br /> Price: <?php echo $product['our_price']; ?><br /> </td> </tr> </table> <?php } ?> </td> </tr> </table> </body> </html>
Имя файла: amazon.php
Код здесь, по сути, такой же, как вы видели ранее, в конце Приступая к работе с PEAR , для анализа RSS-канала. Я использовал PEAR :: HTTP_Request (версия 1.2) в качестве HTTP-клиента, чтобы предоставить мне более подробные отчеты об ошибках. Остальное - просто несериализация данных Amazon.
Вот как выглядит (несколько грубый) HTML в браузере:
Конечно, это не останавливается на разборе чужого XML. Как насчет того же на вашем сайте? Вот простой пример:
<?php // An array simulating a database result set $products = array( array('code' => '000325', 'item' => 'Hamster', 'price' => 13.99), array('code' => '005523', 'item' => 'Parrot', 'price' => 76.99), array('code' => '000153', 'item' => 'Snake', 'price' => 49.99), ); // If ?mime=xml is in the URL, display XML if (isset($_GET['mime']) && $_GET['mime'] == 'xml') { error_reporting(E_ALL ^ E_NOTICE); require_once 'XML/Serializer.php'; // Lazy include $serializer_options = array ( 'addDecl' => TRUE, 'encoding' => 'ISO-8859-1', 'indent' => ' ', 'rootName' => 'products', 'defaultTagName' => 'product', ); $Serializer = &new XML_Serializer($serializer_options); $status = $Serializer->serialize($products); if (PEAR::isError($status)) { die($status->getMessage()); } // Display the XML header('Content-type: text/xml'); echo $Serializer->getSerializedData(); } else { // Otherwise the HTML equivalent ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>Product Catalog</title> </head> <body> <h1>Product Catalog</h1> <table> <tr> <th>Product Code</th> <th>Item</th> <th>Price</th> </tr> <?php foreach ($products as $product) { ?> <tr> <td><?php echo $product['code']; ?></td> <td><?php echo $product['item']; ?></td> <td><?php echo $product['price']; ?></td> </tr> <?php } ?> </table> </body> </html> <?php } ?>
Имя файла: products.php
Если кто-то добавляет
?mime=xml
к URL-адресу, используемому для просмотра этого скрипта, вместо получения страницы в виде HTML, он получает следующий XML:<?xml version="1.0" encoding="ISO-8859-1"?> <products> <product> <code>000325</code> <item>Hamster</item> <price>13.99</price> </product> <product> <code>005523</code> <item>Parrot</item> <price>76.99</price> </product> <product> <code>000153</code> <item>Snake</item> <price>49.99</price> </product> </products>
Это позволяет очень легко отображать ваши данные на удаленном партнерском веб-сайте, если вы того пожелаете. Пока вы храните код, который имеет дело с доступом к данным и манипулирует ими, отдельно от кода, который имеет дело с представлением его конечному пользователю, не должно быть проблем с предоставлением «альтернативного представления XML» с использованием XML_Serializer.
Добавьте к этому библиотеку, такую как JOX , которая предоставляет аналогичный XML-сериализатор для Java Beans, и вы получите удобный механизм для общения PHP и Java.
Заворачивать
Как вы видели в этой статье, PEAR :: XML_Serializer предоставляет очень удобный инструмент для работы с XML. Вы видели, как использовать PEAR :: XML_Serializer, и теперь у вас есть представление о типах проблем, для которых он подходит. Есть еще несколько незначительных глюков, которые нужно устранить (используемая здесь версия 0.9.1 - бета-статус), но в целом PEAR :: XML_Serializer работает надежно, и я пока не нашел каких-либо ошибок, препятствующих показу.
Что наиболее важно, PEAR :: XML_Serializer предоставляет подход к анализу XML, который избавляет вас от лишней работы с деталями XML. Как описано в Обзоре API и методов для обработки XML , PEAR :: XML_Serializer предоставляет « API для преобразования объектов в XML». Хотя этот подход имеет ограничения, для решения типов проблем, которые вы видели в этой статье, API преобразования объектов в XML значительно упрощает жизнь.
Благодаря PHP5-упаковке значительно улучшена поддержка XML, с поддержкой XML Schema и Relax NG, новые возможности могут открыться для PEAR :: XML_Serializer для обработки того, что в настоящее время достигается с помощью «typeHints». И с этим приходят дальнейшие возможности взаимодействия с Java (через JAXB ) и .NET (через его XmlSerializer ).