Справочное руководство по библиотеке PHP libxml
Руководство по расширению PHP libxml: перехват ошибок разбора, обработка XML через SimpleXML или DOM, защита от XXE.
Это руководство охватывает расширение PHP libxml: что это такое, как PHP-парсеры XML сообщают об ошибках через него и как безопасно разбирать, валидировать и изменять XML. Главная идея состоит в том, что libxml — это общий слой обработки ошибок и конфигурации, лежащий в основе SimpleXML, DOM и XMLReader. Понимая его, вы сможете легче отлаживать любое XML-расширение в PHP.
Что такое PHP libxml?
libxml — это C-библиотека, которую PHP использует практически для всей обработки XML. Расширение PHP libxml открывает доступ к обработке ошибок и параметрам парсера этой библиотеки из вашего кода. Как правило, вы не вызываете её напрямую — вместо этого вы используете расширение более высокого уровня, построенное поверх неё:
- SimpleXML — быстрый, похожий на работу с массивом способ чтения XML.
- DOM (
DOMDocument) — полный доступ для чтения и записи дерева документа. XMLReader/XMLWriter— потоковый, экономичный по памяти разбор больших файлов.
Когда любое из этих расширений сталкивается с некорректным XML, оно сообщает об ошибке через libxml. Функции libxml_* позволяют перехватывать, изучать и очищать эти ошибки.
Установка PHP libxml
Расширение libxml поставляется в комплекте и включено по умолчанию — устанавливать ничего не нужно. Убедиться в его активности можно во время выполнения:
<?php
var_dump(extension_loaded('libxml')); // bool(true)
echo LIBXML_DOTTED_VERSION; // e.g. "2.9.14" — the linked libxml2 version
?>Если пользовательская сборка возвращает false, перекомпилируйте PHP с флагом --with-libxml (в старых версиях PHP использовался --enable-libxml).
Обработка ошибок libxml
Это наиболее важная часть расширения. По умолчанию некорректный документ вызывает предупреждения PHP, что неудобно обрабатывать в продакшне. Вместо этого переключитесь в режим внутренней обработки ошибок: libxml будет накапливать ошибки в буфере, который вы читаете самостоятельно.
<?php
// Stop warnings; buffer errors instead.
libxml_use_internal_errors(true);
$broken = '<root><item>unclosed</root>';
$xml = simplexml_load_string($broken);
if ($xml === false) {
foreach (libxml_get_errors() as $error) {
// Each $error is a LibXMLError object.
printf(
"[%s] line %d: %s",
$error->level === LIBXML_ERR_FATAL ? 'fatal' : 'warning',
$error->line,
trim($error->message)
);
echo PHP_EOL;
}
libxml_clear_errors(); // Empty the buffer so it doesn't leak into later parses.
}
?>Объект LibXMLError содержит поля: level (LIBXML_ERR_WARNING, LIBXML_ERR_ERROR, LIBXML_ERR_FATAL), code, message, line, column и file. Связанные функции:
libxml_use_internal_errors()— включить/выключить буферизацию.libxml_get_errors()— вернуть все буферизованные ошибки в виде массива.libxml_get_last_error()— вернуть только последнюю ошибку.libxml_clear_errors()— очистить буфер.
Разбор XML-документов
Наиболее распространённое использование XML в PHP — чтение документа. simplexml_load_string() (и её файловый аналог simplexml_load_file()) возвращают false при ошибке, поэтому всегда используйте их в режиме внутренней обработки ошибок:
<?php
libxml_use_internal_errors(true);
$source = '<catalog><book id="1">PHP Basics</book></catalog>';
$xml = simplexml_load_string($source);
if ($xml === false) {
echo "Failed to parse XML." . PHP_EOL;
foreach (libxml_get_errors() as $error) {
echo trim($error->message) . PHP_EOL;
}
libxml_clear_errors();
} else {
echo "Loaded: " . $xml->book . PHP_EOL; // Loaded: PHP Basics
echo "id = " . $xml->book['id'] . PHP_EOL; // id = 1
}
?>Параметры парсера (константы libxml)
Большинство XML-функций принимают битовую маску $options из констант LIBXML_*. Объединяйте их с помощью оператора побитового ИЛИ (|):
<?php
$xml = simplexml_load_string(
'<a> <b>text</b> </a>',
'SimpleXMLElement',
LIBXML_NOCDATA | LIBXML_NOBLANKS // drop CDATA wrappers + ignore whitespace-only nodes
);
echo $xml->b; // text
?>Наиболее часто используемые параметры:
| Константа | Эффект |
|---|---|
LIBXML_NOBLANKS | Удалять пустые (содержащие только пробелы) узлы. |
LIBXML_NOCDATA | Объединять секции CDATA как обычный текст. |
LIBXML_NOERROR / LIBXML_NOWARNING | Подавлять ошибки / предупреждения. |
LIBXML_COMPACT | Оптимизация для документов с большим числом узлов. |
LIBXML_NOENT | Подставлять сущности — опасно для непроверенных данных (см. ниже). |
Безопасность: непроверенный XML и XXE
Атаки типа XML eXternal Entity (XXE) позволяют вредоносному документу читать локальные файлы или инициировать сетевые запросы. Никогда не разрешайте загрузку сущностей для данных, которым вы не доверяете. Безопасные настройки по умолчанию в современном PHP (7.0+) уже отключают загрузку внешних сущностей, поэтому правило простое:
- Не передавайте
LIBXML_NOENTилиLIBXML_DTDLOADпри разборе непроверенного XML. - На PHP < 8.0 можно дополнительно вызвать
libxml_disable_entity_loader(true)в качестве жёсткой защиты. Подробности смотрите вlibxml_disable_entity_loader()(функция устарела в 8.0+, так как загрузка по умолчанию отключена).
Валидация XML-документов
libxml может проверять документ по DTD или XSD-схеме. DOMDocument::schemaValidate() — наиболее прямой способ, и ошибки валидации поступают через тот же буфер:
<?php
libxml_use_internal_errors(true);
$doc = new DOMDocument();
if (!$doc->load('example.xml')) {
echo "Could not load document." . PHP_EOL;
exit;
}
if ($doc->schemaValidate('example.xsd')) {
echo "The XML document is valid." . PHP_EOL;
} else {
echo "Validation failed:" . PHP_EOL;
foreach (libxml_get_errors() as $error) {
echo " line {$error->line}: " . trim($error->message) . PHP_EOL;
}
libxml_clear_errors();
}
?>Для очень больших файлов предпочтительнее использовать потоковый XMLReader, который валидирует по мере чтения без загрузки всего документа в память:
<?php
$reader = new XMLReader();
$reader->open('example.xml');
$reader->setSchema('example.xsd'); // attach the XSD before reading
$valid = true;
while ($reader->read()) {
if (!$reader->isValid()) {
$valid = false;
break;
}
}
$reader->close();
echo $valid ? "Document is valid." : "Document is not valid.";
?>Изменение XML-документов
Для изменения документа обычно используется DOM. Пример ниже создаёт документ в памяти (без внешних файлов), добавляет узел и выводит результат:
<?php
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->formatOutput = true; // pretty-print the output
// Build a root, then add a child element with text content.
$root = $doc->createElement('catalog');
$doc->appendChild($root);
$book = $doc->createElement('book', 'Learning PHP');
$book->setAttribute('id', '42');
$root->appendChild($book);
echo $doc->saveXML();
// <?xml version="1.0" encoding="UTF-8"?>
// <catalog>
// <book id="42">Learning PHP</book>
// </catalog>
?>При редактировании файла с диска: загрузите его, найдите нужный узел с помощью getElementsByTagName(), добавьте дочерний элемент к этому узлу (а не к документу, который может содержать только один корневой элемент), затем сохраните его с помощью save().
Когда это использовать?
- Чтение конфигурационных данных или лент (RSS/Atom, SOAP-ответы, карты сайта) — разбирайте с помощью SimpleXML, защищайтесь с помощью режима внутренних ошибок.
- Валидация загружаемых файлов — отклоняйте документы, не прошедшие
schemaValidate(), прежде чем доверять им. - Генерация XML для API или экспорта — создавайте с помощью DOM, чтобы атрибуты и кодировка обрабатывались корректно.
- Отладка ошибок "некорректный XML" — читайте
libxml_get_errors(), чтобы увидеть точную строку и столбец.
Заключение
Расширение libxml — это фундамент XML-стека PHP. Паттерн, который работает везде: вызовите libxml_use_internal_errors(true), выполните разбор или валидацию, затем проверьте libxml_get_errors() и libxml_clear_errors(). Далее выбирайте подходящий инструмент — SimpleXML для быстрого чтения, DOM для редактирования, XMLReader для больших файлов — и передавайте параметры LIBXML_* для управления поведением парсера. Отключайте загрузку сущностей для непроверенных данных, и ваша обработка XML будет одновременно надёжной и безопасной.