Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 110 · От основ к практике

libxml: ошибки XML, настройки парсера и внешние зависимости

От первого XML-элемента до понятной диагностики импорта: все функции libxml, LibXMLError, флаги, загрузчики и границы внешних чтений.

XML от поставщика: понять структуру, затем принять решение

Наглядная модель

От XML поставщика до решения приложения

Формат, внешние зависимости и предметные данные проверяются на разных этапах.

  1. <order>42</order>

    Текст документа от внешнего источника.

  2. Парсер и его политика

    Проверяет запись, ограничивает доступ к зависимостям.

  3. Документ или диагностика

    DOM хранит структуру; LibXMLError объясняет отказ.

  4. Проверка заказа

    Приложение проверяет номер, полномочия и бизнес-правила.

Успешный XML-разбор ещё не означает, что заказ допустим. Очистка сообщений не исправляет исходный документ.

Нужен понятный отказ импорта: «invoice.xml, строка 2 — файл не принят», а техническое предупреждение не должно попасть в страницу покупателя. Для чтения полей служат SimpleXML и DOM. libxml объединяет их диагностику и правила загрузки XML-зависимостей. Начнём с исправного заказа, затем намеренно повредим его закрывающий тег.

Поставщик прислал <order>42</order>. Приложению нужно получить номер заказа, но сначала убедиться, что документ вообще разбирается. libxml — общий слой XML-библиотек PHP: он предоставляет сообщения, опции парсера и управление внешними зависимостями. DOM и SimpleXML дают удобный доступ к самому содержимому.

От текста к решению импорта
  1. Вход: <order>42</order>
  2. Парсер проверяет открывающий и закрывающий теги
  3. DOM: элемент order содержит текст42
  4. Приложение проверяет номер и сохраняет заказ

XML — текст с явно вложенными элементами: <order>42</order> содержит элемент order и текст42. Парсер проверяет эту запись и строит доступную программе структуру. new DOMDocument() создаёт документ, ->loadXML($text) пытается разобрать строку; === false означает неудачу. Функции libxml_* здесь управляют сообщениями и загрузкой зависимостей парсера, а не заменяют DOM/SimpleXML.

Скобки передают аргументы функции; результат сохраняется через =. try/finally возвращает изменённый режим после операции. Вызов и возврат функции, Ошибки и исключения объясняют эти конструкции отдельно.

Исправный документ даёт номер заказаСамостоятельный пример
<?php
declare(strict_types=1);

$document = new DOMDocument();
if ($document->loadXML('<order>42</order>') === false) {
    throw new RuntimeException('XML заказа не разобран');
}
echo 'Получен заказ № ', $document->documentElement->textContent, "\n";
Ожидаемый результат
Получен заказ № 42

new DOMDocument создаёт пустой документ, loadXML разбирает строку. documentElement выбирает корень order, textContent читает его текст. Это ещё не проверка существования заказа или прав отправителя; ниже добавляем обработку отказа парсера.

Нельзя заменить XML-парсер regex для произвольного документа: вложенность, namespace, escaping, CDATA и entities имеют свои правила. JSON и XML — разные форматы интеграций. Выбирает контракт сервиса, а PHP после разбора работает с объектами/значениями, понятными приложению.

Какие инструменты используют libxml

Общий движок — разные интерфейсы
  1. DOM: дерево с редактированием
  2. SimpleXML: удобное чтение XML
  3. XMLReader/Writer: последовательное чтение/запись
  4. libxml: общие опции, диагностика и загрузчик

Поддержка функций libxml появилась с PHP 5.1. Руководство перечисляет связанные расширенияDOM, SimpleXML, SOAP, XSL, XML, XMLReader и XMLWriter; в историческом списке также WDDX и XMLRPC. Последние не означают наличие этих модулей в современной стандартной сборке. У каждого расширения собственная доступность и отдельная справочная категория.

Различить общий движок и интерфейс DOMСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(extension_loaded('libxml'), class_exists(DOMDocument::class));
echo LIBXML_DOTTED_VERSION, "\n";
Ожидаемый результат
bool(true)
bool(true)
2.11.9

Это вывод проверенного стенда. Версия PHP 8.5.11 не означает, что вместе с ней загружена libxml 2.13: здесь 2.11.9. Нельзя использовать новую константу без проверки зависимости.

ВеткаPHPМинимальнаяlibxml
До8.02.6.0
8.0–8.32.9.0
8.4+2.9.4

Для современной сборкиPHP 7.4+ libxml включена по умолчанию; отключение при сборке — --without-libxml. Поиск библиотеки/заголовков/флагов выполняет pkg-config. PKG_CONFIG_PATH задаёт каталоги .pc до configure, например /path/to/libxml2/prefix/lib/pkgconfig:/lib/pkgconfig. Это настройка сборки, не параметр внутриPHP-скрипта.

Историческая установка до PHP 7.4

Старый configure ищет инструмент
  1. --with-libxml-dir указываетDIR
  2. ПроверяетсяDIR/bin/xml2-config
  3. Затем/usr/local/bin/xml2-config
  4. Затем/usr/bin/xml2-config

В старых ветках libxml включалась по умолчанию, отключалась --disable-libxml. Необязательный --with-libxml-dir задавал место установки; если xml2-config не находился там, configure продолжал стандартный поиск. Эти команды нужны для понимания старых сборочных файлов, а не для развёртывания нового сервиса на неподдерживаемойPHP.

LibXMLError: понятная карточка проблемы вместо загадочного предупреждения

LibXMLError содержит сведения о сообщении парсера. Это объект данных, а не Throwable: catch(LibXMLError) не является способом перехвата XML-ошибки. Объект получают через libxml_get_errors или libxml_get_last_error после операции.

Привязать сообщение к исходной строке
  1. loadXML не разобрал вход
  2. LibXMLError хранит level/code/line/column
  3. message объясняет причину, file показывает источник
  4. Показываем безопасную краткую диагностику
Публичное свойствоКак читать
int levelLIBXML_ERR_WARNING, ERROR или FATAL. Уровни libxml не совпадают с PHP E_WARNING/E_ERROR.
int codeКод причины из xmlError API libxml; версию и описание сверяют с движком.
int columnКолонка; реализована не везде, часто0. Не рисуйте точный указатель в начало строки на основании 0.
string messageТекст сообщения, может заканчиваться переводом строки и содержать входные данные.
string fileИмя источника; для XML из строки обычно пусто. Не раскрывайте серверный путь посетителю.
int lineСтрока ошибки; входные строки обычно нумеруются с 1, аPHP-массивстрок — с 0.
Связать LibXMLError с конкретной строкой файлаСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $document = new DOMDocument();
    $document->load(__DIR__ . '/invoice.xml');
    $errors = libxml_get_errors();
    $error = $errors[0];
    echo basename($error->file), ':', $error->line, ": документ не разобран\n";
    $lines = file(__DIR__ . '/invoice.xml', FILE_IGNORE_NEW_LINES);
    echo 'Строка исходника: ', $lines[$error->line - 1], "\n";
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
invoice.xml:2: документ не разобран
Строка исходника: <order>42</invoice>

В закрытой локальной лаборатории можно показать известную строку: /invoice закрыл незакрытый order. line начинается с 1, индекс PHP-массива — с 0, поэтому вычитаем 1. Для чужого документа публично не печатайте его содержимое; column=0 не означает надёжно известную позицию символа.

Подготовить краткую строку для собственного журналаСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $document = new DOMDocument(); $document->loadXML('<order>');
    $error = libxml_get_last_error();
    if ($error !== false) {
        $level = match ($error->level) {
            LIBXML_ERR_WARNING => 'warning', LIBXML_ERR_ERROR => 'error',
            LIBXML_ERR_FATAL => 'fatal', default => 'unknown',
        };
        echo 'XML ', $level, ' at line ', $error->line, "\n";
    }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
XML fatal at line 1

Сначала было отдельное чтение объекта, теперь небольшое форматирование через match. Не включаем полный XML и секреты в журнал. При выводе в HTML требуется отдельное экранирование.

Все константы: выбираем поведение осознанно

Флаги — целые именованные переключатели. Оператор | объединяет битовые флаги, но только для операции, которая их поддерживает. Числа уровней ошибок и версий к ним прибавлять не нужно. Один и тотже числовой бит может иметь разный смысл при разборе и сохранении.

Опции связаны с этапом
  1. Разбор: NOCDATA превращает CDATA в текстовый узел
  2. Проверка схемы: SCHEMA_CREATE может добавить значения
  3. Сохранение: NOEMPTYTAG меняет пустой тег
  4. Задаём опции для конкретного метода
КонстантаНазначение и ограничения
LIBXML_VERSIONint: версия заголовков сборки в числовой форме, например21109. Не складывайте/не комбинируйте её как флаг парсинга.
LIBXML_DOTTED_VERSIONstring: та же версия сборки с точками, например2.11.9.
LIBXML_LOADED_VERSIONstring: версия реально загруженного ядра libxml; может быть числовой строкой21109. Сравнивайте сборку и runtime отдельно.
LIBXML_BIGLINESПравильные номера строк больше65535; PHP7.0+/libxml 2.9+. Не увеличивает допустимый размер документа сам по себе.
LIBXML_COMPACTОптимизация размещения небольших узлов, libxml 2.6.21+. Возможный выигрыш измеряют на собственном документе, не обещают ускорение любого XML.
LIBXML_DTDATTRДобавлять атрибуты по умолчанию изDTD. Может потребовать внешние сущности; нужна политика доступа.
LIBXML_DTDLOADЗагружать внешний subsetDTD. Подключает зависимости документа; не включайте автоматически для произвольного XML.
LIBXML_DTDVALIDПроверять документ поDTD. Может активировать внешние чтения; синтаксический разбор и валидация разные задачи.
LIBXML_HTML_NOIMPLIEDНе добавлять неявные html/body и другие элементы старого HTML-парсера; PHP5.4+/libxml 2.7.7+. Не является режимом XML и не делает парсерHTML5.
LIBXML_HTML_NODEFDTDНе добавлять defaultDOCTYPE старым HTML-парсером; PHP5.4+/libxml 2.7.8+.
LIBXML_NOBLANKSУдалять blank-узлы по правилам парсера. Не считать универсальным trim: в смешанном содержимом пробелы могут быть значимыми.
LIBXML_NOCDATAПредставлять CDATA как обычные текстовые узлы. Не декодирует произвольный вложенный XML и не выполняет содержащийся вCDATA PHP.
LIBXML_NOEMPTYTAGПри сохранении DOMDocument::save/saveXML разворачивать пустые элементы: <item/> → <item></item>.
LIBXML_NOENTПодставлять entities. Название обманчиво: это НЕ запрет сущностей. Без отдельной политики возможныXXE/чтения внешних данных.
LIBXML_NOERRORПодавлять сообщения об ошибках в поддерживающей опцию операции; это не делает документ правильным.
LIBXML_NONETОграничить сетевые обращения парсера. Не запрещает чтение локальных файлов и не заменяет контроль пользовательского resolver/всей загрузки.
LIBXML_NOWARNINGПодавлять warning-сообщения поддерживающего парсера. Не отключает все предупрежденияPHP и не означает успешную валидацию.
LIBXML_NOXMLDECLОпция сериализации для пропуска XML-декларации; libxml 2.6.21+. Применимость проверяйте у конкретного метода: не каждый PHP wrapper передаёт все save-флаги.
LIBXML_NO_XXEЗапрет внешних сущностей при подстановке: PHP 8.4+/libxml 2.13+. На старойlibxml константы может не быть даже вPHP8.5; проверяют defined.
LIBXML_NSCLEANУдалять избыточные объявления namespace при разборе. Это не удаление пространств имён и не способ игнорировать их при поиске.
LIBXML_PARSEHUGEПоднимать некоторые встроенные пределы глубины/размера/рекурсииentities. Только для доверенного ограниченного источника; большой документ может исчерпать память или стек.
LIBXML_PEDANTICБолее строгая диагностика, PHP5.4+. Дополнительные сообщения не заменяют прикладную схему.
LIBXML_RECOVERРазрешить recovery при разборе, PHP 8.4+. Восстановленная структура может отличаться от намерения отправителя; для заказов нельзя молча принять потерянные поля.
LIBXML_XINCLUDEПодстановкаXInclude в поддерживающих pull-парсерах, прежде всегоXMLReader. DOM использует собственный методXInclude; это отдельные внешние зависимости.
LIBXML_SCHEMA_CREATEСоздавать default/fixed-узлы приXSD-валидации; PHP5.5.2+/libxml 2.6.14+. Проверка схемы может дополнить дерево.
LIBXML_ERR_NONEУровень0: нет ошибки.
LIBXML_ERR_WARNINGПредупреждение: необходимо оценить его смысл для операции.
LIBXML_ERR_ERRORВосстановимая ошибка на уровне libxml; это не разрешение приложению принять неверный документ.
LIBXML_ERR_FATALФатальная ошибка парсера; не путать с PHP E_ERROR.
Увидеть изменение типа узла, а не его смыслаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = '<order><![CDATA[A&B]]></order>';
$withCdata = new DOMDocument(); $withCdata->loadXML($xml);
$asText = new DOMDocument(); $asText->loadXML($xml, LIBXML_NOCDATA);
echo $withCdata->documentElement->firstChild->nodeType, "\n";
echo $asText->documentElement->firstChild->nodeType, "\n";
echo $asText->documentElement->textContent, "\n";
Ожидаемый результат
4
3
A&B

В DOM 4 — CDATA,3 — текст. Содержимое A&B сохранилось. Если внутри CDATA лежит строка с XML, это ещё не узлы вложенного документа; вторичное парсирование должно быть явным условием договора.

Выбрать форму пустого элемента при сохраненииСамостоятельный пример
<?php
declare(strict_types=1);

$document = new DOMDocument();
$document->loadXML('<order/>');
echo $document->saveXML($document->documentElement), "\n";
echo $document->saveXML($document->documentElement, LIBXML_NOEMPTYTAG), "\n";
Ожидаемый результат
<order/>
<order></order>

XML-смысл двух записей одинаков. Такая опция нужна, например, для совместимости с особенным получателем, а не для изменения данных заказа.

Внешние сущности: документ может запросить ещё один источник

DTD может объявить сущность, которая подставляет текст, а также описать допустимую структуру. Внешний идентификатор может указывать на путь или адрес. При неправильной настройке входной XML способен инициировать чтения от имени процесса PHP — это основа риска XXE.

Граница доверия проходит до внешнего чтения
  1. Поставщик прислал XML
  2. В нём есть ссылка на зависимость
  3. Политика разрешает только известную схему
  4. Незнакомый ID отклоняется, клиент не выбирает произвольный файл или URL

Для обычного импорта не включайте NOENT/DTDLOAD/DTDATTR/DTDVALID без потребности. NO_XXE работает только при PHP 8.4+/libxml 2.13+; NONET отдельно незащищает от локальных чтений. Для требуемой валидации используйте известные локальные схемы и libxml_set_external_entity_loader. Запрет внешних чтений не отменяет лимиты размера, глубины, времени и числа обрабатываемых записей.

Если приходит <name>A&B</name> с литералом & в XML-исходнике, этот XML повреждён: амперсанд там записывается как &amp; либо корректная секция CDATA. Слепая замена каждого & испортит уже экранированные последовательности. Парсер не обязан угадывать намерение поставщика; сохраняйте ограниченный диагностический образец и проясняйте контракт.

PARSEHUGE и RECOVER не являются универсальными исправлениями. Первый ослабляет лимиты, второй может потерять часть структуры. Для платежа или заказа нельзя молча принимать такой результат. Большой XML лучше обрабатывать поточно через XMLReader, когда задача это позволяет.

Практика: один неверный документ не портит следующий импорт

Перед импортом каталога определите допустимый размер, нужный корневой элемент, обязательные поля, доверенные схемы и поведение при ошибке. Проверьте некорректный XML, затем корректный. У второй попытки не должно оказаться старых сообщений.

Проверяем два уровня
  1. loadXML: можно ли построить структуру?
  2. root/order: тот ли формат прислал сервис?
  3. amount/id: допустимы ли значения?
  4. Сохраняем только после проверок, очищаем диагностику
Отличить правильный XML от правильного договораСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
try {
    foreach (['<order>', '<invoice>42</invoice>', '<order>42</order>'] as $xml) {
        libxml_clear_errors();
        $document = new DOMDocument();
        if (!$document->loadXML($xml)) { echo "invalid syntax\n"; continue; }
        if ($document->documentElement->tagName !== 'order') { echo "wrong document\n"; continue; }
        echo 'order text: ', $document->documentElement->textContent, "\n";
    }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
invalid syntax
wrong document
order text: 42

Следующий шаг — проверить, что 42 является допустимым идентификатором, найти заказ и проверить полномочия. «XML разобрался» — только первая ступень. Проверка внешних значений.

Полный указатель функций libxml

ФункцияДля чего нужна
libxml_use_internal_errorsПринять повреждённый XML от поставщика без вывода технических предупреждений посетителю и разобрать причины программно.
libxml_get_errorsПолучить все диагностические сообщения текущей XML-операции, чтобы объяснить поставщику, что именно не разобралось.
libxml_get_last_errorУзнать последнее XML-сообщение для краткой диагностики, не считая его полной историей ошибки.
libxml_clear_errorsЗакончить обработку XML-диагностики, чтобы следующая попытка не унаследовала прежние сообщения.
libxml_get_external_entity_loaderСохранить чужой XML-загрузчик перед временной настройкой, чтобы после импорта вернуть прежнее поведение приложения.
libxml_set_external_entity_loaderРазрешить XML использовать только заранее известную схему или запретить внешние зависимости, вместо произвольного чтения адресов из документа.
libxml_disable_entity_loaderПонять старую защитную настройку XML и заменить её осознанной политикой загрузки в современном PHP.
libxml_set_streams_contextЗадать тайм-аут и заголовки PHP-потока, когда XML-библиотека сама открывает документ по HTTP.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

libxml_use_internal_errorsПринять повреждённый XML от поставщика без вывода технических предупреждений посетителю и разобрать причины программно.

PHP5.1+ · libxml

libxml_use_internal_errors(?bool $use_errors = null): bool

Принять повреждённый XML от поставщика без вывода технических предупреждений посетителю и разобрать причины программно.

Часть вызоваСмысл и границы
use_errorstrue включает накопление libxml-ошибок для вашего обработчика, false возвращает стандартный вывод и очищает накопленный список. null/вызов без аргумента только читает текущий режим.
ВозвратПредыдущее состояние bool; это не признак успешного разбора документа. Сохраните его и восстановите в finally.
Версии / границыСPHP8 аргумент nullable и default null; ранее default false. Это не общий обработчик PHP Warning, TypeError или исключений других расширений.
Повреждённый заказ не становится успешным
  1. Включили накопление
  2. loadXML видит незакрытый order → false
  3. Читаем список ошибок
  4. Очищаем список и восстанавливаем режим
Управлять тем, куда попадут XML-ошибкиСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
try {
    $document = new DOMDocument();
    if (!$document->loadXML('<order>42')) {
        echo "Заказ не принят: повреждённый XML\n";
    }
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previous);
}
Ожидаемый результат
Заказ не принят: повреждённый XML

true направляет сообщения libxml во внутренний список. Возврат setter — старый режим, который сохраняем в $previous; успех разбора проверяем отдельно по loadXML. В документе отсутствует закрывающий /order. finally очищает нашу диагностику и возвращает прежний режим, даже если дальнейший код выбросит исключение.

Отказ с адресом проблемы в накладнойСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $document = new DOMDocument();
    if (!$document->load(__DIR__ . '/invoice.xml')) {
        $errors = libxml_get_errors();
        echo 'invoice.xml, строка ', $errors[0]->line, ": накладная не принята\n";
    }
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previous);
}
Ожидаемый результат
invoice.xml, строка 2: накладная не принята

Файл открывает invoice, затем order и ошибочно закрывает invoice в строке 2, пока order ещё открыт. Это уже полезная адресация проблемы, без вывода серверного пути и всего документа. Наличие internal_errors не исправляет XML и не перехватывает любые ошибки PHP.

Режим и список ошибок общие для libxml-операций в текущем выполнении PHP. Вложенный helper не может незаметно очистить чужие сообщения, а затем восстановить их этой API: обратной записи списка нет. Определите, кто владеет диагностикой операции. В длительном воркере регулярно очищайте обработанный список, иначе он растёт.

Дальше: libxml_get_errors; libxml_get_last_error; libxml_clear_errors.

libxml_get_errorsПолучить все диагностические сообщения текущей XML-операции, чтобы объяснить поставщику, что именно не разобралось.

PHP5.1+ · libxml

libxml_get_errors(): array

Получить все диагностические сообщения текущей XML-операции, чтобы объяснить поставщику, что именно не разобралось.

Часть вызоваСмысл и границы
АргументыНет. Читает накопленный libxml-список. Для его заполнения заранее включите internal_errors.
РезультатМассив объектов LibXMLError либо пустой массив. Чтение не очищает список и не возвращает объект документа.
ОшибкиСообщения разных операций могут смешаться, если не задать границы очистки. Успех определяется результатом парсера/валидации, не одним count списка.
Список — журнал одной попытки
  1. Очистили предыдущую диагностику
  2. Парсер встретил несовпадающие теги
  3. get_errors → объекты level/code/line/message
  4. Сохраняем нужные сведения, затем clear_errors
Отделить успешный разбор от наличия сообщенийСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $document = new DOMDocument();
    if ($document->loadXML('<order>42</order>')) {
        echo 'Заказ ', $document->documentElement->textContent, ": XML прочитан\n";
    }
    if (libxml_get_errors() === []) { echo "Замечаний парсера нет\n"; }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
Заказ 42: XML прочитан
Замечаний парсера нет

libxml_get_errors не принимает аргументов и возвращает массив сообщений. Пустой массив означает только отсутствие накопленных сообщений: сам факт разбора подтверждён loadXML. Без вызова парсера такой же пустой массив не доказывал бы корректность файла.

Сохранить диагностику попытки до очистки очередиСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $document = new DOMDocument();
    $document->loadXML("<invoice>\n<order>42</invoice>");
    $errors = libxml_get_errors();
    libxml_clear_errors();
    echo 'Отчёт импорта 17: первая проблема в строке ', $errors[0]->line, "\n";
    echo libxml_get_errors() === [] ? "Очередь для следующей попытки пуста\n" : "Остались сообщения\n";
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
Отчёт импорта 17: первая проблема в строке 2
Очередь для следующей попытки пуста

get_errors возвращает PHP-массив объектов LibXMLError. clear_errors очищает очередь движка, но уже полученный $errors остаётся доступен для отчёта. Чтение get_errors само по себе очередь не очищает. После отправки отчёта не накапливайте бесконечно и собственные копии.

message может включать фрагменты входных данных, file — серверный путь. Перед показом в HTML требуется htmlspecialchars, а публичный ответ обычно ограничивают кодом операции и понятной причиной. Точный текст и количество сообщений могут отличаться между версиями libxml; тестируйте структурные свойства и ожидаемое решение импорта.

Дальше: libxml_clear_errors; libxml_get_last_error; libxml_use_internal_errors.

libxml_get_last_errorУзнать последнее XML-сообщение для краткой диагностики, не считая его полной историей ошибки.

PHP5.1+ · libxml

libxml_get_last_error(): LibXMLError|false

Узнать последнее XML-сообщение для краткой диагностики, не считая его полной историей ошибки.

Часть вызоваСмысл и границы
АргументыНет.
ВозвратОбъект последней libxml-ошибки или false. Сначала проверяйте false, затем обращайтесь к ->message/->line.
НюансВPHP8.5.11 с внутренним списком берётся его последний элемент; без него — native last error. Поэтому get_errors и get_last_error не обязаны давать одинаковую картину при разных режимах. Последний элемент может быть последствием первой причины.
Краткая карточка последней проблемы
  1. Есть или нет LibXMLError?
  2. false → сообщений нет
  3. Объект → line/code/message
  4. Для цепочки причин читаем get_errors
Начать отчёт новой попытки без чужой ошибкиСамостоятельный пример
<?php
declare(strict_types=1);

libxml_clear_errors();
$lastError = libxml_get_last_error();
if ($lastError === false) {
    echo "Новая попытка: сообщений XML пока нет\n";
}
Ожидаемый результат
Новая попытка: сообщений XML пока нет

false здесь означает отсутствие последнего сообщения. Разбора ещё не было: зелёную отметку «файл проверен» показывать рано. Сначала проверяем результат парсера, затем при необходимости читаем объект ошибки.

Краткое сообщение после отказа разбораСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $document = new DOMDocument();
    if (!$document->loadXML("<order>\n42")) {
        $error = libxml_get_last_error();
        echo "Импорт заказа отклонён\n";
        if ($error !== false) { echo 'Последняя диагностика: строка ', $error->line, "\n"; }
    }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
Импорт заказа отклонён
Последняя диагностика: строка 2

Сначала проверка !== false, только затем ->line: функция может не вернуть объект. Последнее сообщение не обязательно первопричина. Полную последовательность сообщения разбирайте через get_errors; точные английские формулировки зависят от libxml.

Дальше: libxml_get_errors; libxml_clear_errors.

libxml_clear_errorsЗакончить обработку XML-диагностики, чтобы следующая попытка не унаследовала прежние сообщения.

PHP5.1+ · libxml

libxml_clear_errors(): void

Закончить обработку XML-диагностики, чтобы следующая попытка не унаследовала прежние сообщения.

Часть вызоваСмысл и границы
АргументыНет.
Результатvoid. Очищает внутренний список и native last error. Режим internal_errors не переключает.
Что не делаетНе исправляет повреждённый XML, не очищает сам DOM и не стирает уже сохранённые в переменные копии LibXMLError.
Два документа — два отдельных журнала
  1. Разобрали первый, получили ошибку
  2. Сообщили результат и clear_errors
  3. Второй документ проверяется отдельно
  4. Его диагностика не содержит историю первого
Очистка даёт следующему импорту свой журналСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
try {
    foreach (['<order>', "<invoice>\n<order>"] as $text) {
        libxml_clear_errors();
        $document = new DOMDocument();
        $document->loadXML($text);
        $error = libxml_get_last_error();
        if ($error !== false) { echo 'Отказ текущего документа: строка ', $error->line, "\n"; }
    }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
Отказ текущего документа: строка 1
Отказ текущего документа: строка 2

После первой очистки режим накопления остаётся включённым: следующий документ снова даёт собственную диагностику. clear_errors возвращает void, не исправляет ни первый, ни второй XML. Следующий пример добавляет успешную попытку после отказа.

Отделить повреждённый документ от корректногоСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
try {
    foreach (['<order>', '<order>42</order>'] as $text) {
        libxml_clear_errors();
        $document = new DOMDocument();
        $ok = $document->loadXML($text);
        echo $ok ? "accepted\n" : "rejected\n";
        echo count(libxml_get_errors()) > 0 ? "has diagnostics\n" : "clean\n";
    }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
rejected
has diagnostics
accepted
clean

Здесь accepted означает только синтаксически разобранный XML. Наличие нужных полей, допустимый номер заказа и доверие к отправителю проверяются отдельно. Очистка границ особенно важна в импортах тысяч документов.

Дальше: libxml_get_errors; libxml_get_last_error; libxml_use_internal_errors.

libxml_get_external_entity_loaderСохранить чужой XML-загрузчик перед временной настройкой, чтобы после импорта вернуть прежнее поведение приложения.

PHP8.2+ · libxml

libxml_get_external_entity_loader(): ?callable

Сохранить чужой XML-загрузчик перед временной настройкой, чтобы после импорта вернуть прежнее поведение приложения.

Часть вызоваСмысл и границы
АргументыНет.
ВозвратРанее установленный PHP callback либо null, если пользовательский callback не установлен/сброшен. null не означает запрет загрузки: остаётся стандартный загрузчик.
ВерсииPHP8.2+. Нужен для корректного сохранения/восстановления настройки библиотеки.
Временная политика не должна пережить импорт
  1. Получили прежний callback
  2. Установили свой resolver
  3. Выполнили XML-операцию
  4. finally вернул прежний callback
Сохранить установленную политику загрузки до временного запретаСамостоятельный пример
<?php
declare(strict_types=1);

$original = libxml_get_external_entity_loader();
$resolveLocal = static function (?string $public, ?string $system, array $context) {
    return $system === 'urn:shop:label' ? __DIR__ . '/label.txt' : null;
};
try {
    libxml_set_external_entity_loader($resolveLocal);
    $saved = libxml_get_external_entity_loader();
    libxml_set_external_entity_loader(static fn() => null);
    libxml_set_external_entity_loader($saved);
    $document = new DOMDocument();
    $document->loadXML('<!DOCTYPE product [<!ENTITY label SYSTEM "urn:shop:label">]><product>&label;</product>', LIBXML_NOENT);
    echo 'Восстановленный загрузчик: ', $document->textContent, "\n";
} finally { libxml_set_external_entity_loader($original); }
Ожидаемый результат
Восстановленный загрузчик: Desk

Getter без аргументов сохраняет сам callback, чтобы временная политика не сломала другие XML-операции. После восстановления реальная подстановка читает только известный label.txt. Возврат строки из callback — путь к содержимому, не само содержимое. NOENT включён исключительно в этом контролируемом опыте с заданной зависимостью.

Отличить отсутствие callback от функции, возвращающей nullСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_get_external_entity_loader();
try {
    libxml_set_external_entity_loader(null);
    var_dump(libxml_get_external_entity_loader());
    libxml_set_external_entity_loader(static fn() => null);
    var_dump(is_callable(libxml_get_external_entity_loader()));
} finally { libxml_set_external_entity_loader($previous); }
Ожидаемый результат
NULL
bool(true)

Не вызывайте полученный callback ради проверки: это может открыть ресурс или изменить его состояние. Сохраните и восстановите его как значение.

Дальше: libxml_set_external_entity_loader; libxml_use_internal_errors.

libxml_set_external_entity_loaderРазрешить XML использовать только заранее известную схему или запретить внешние зависимости, вместо произвольного чтения адресов из документа.

PHP5.4+; return true с8.5 · libxml

libxml_set_external_entity_loader(?callable $resolver_function): true

Внешняя зависимость — отдельные данные, на которые ссылается XML. DTD задаёт допустимую структуру, а entity даёт имя подставляемому фрагменту. В наших опытах supplier просит имя urn:shop:order.dtd, приложение разрешает только свой локальный файл; произвольного открытия имён из документа нет.

Разрешить XML использовать только заранее известную схему или запретить внешние зависимости, вместо произвольного чтения адресов из документа.

Часть вызоваСмысл и границы
resolver_functionPHP callback; null сбрасывает пользовательскую замену и возвращает стандартный загрузчик. Callback получает public_id, system_id и context.
public_id / system_idПубличный и системный идентификаторы зависимости. В официальной сигнатуре system_id — string; taggedPHP8.5.11 передаёт null при отсутствующем native URL, поэтому наш callback принимает ?string для обоих.
contextМассив directory, intSubName, extSubURI, extSubSystem; значения могут быть null. Это сведения парсера, не разрешение читать произвольный путь.
Результат callbackПоток resource с нужными байтами, строка пути/URI для открытия либо null — отказ. Возвращённая строка XML не считается содержимым: она будет воспринята как адрес.
Возврат setter / версииВсегда true; объявленный тип true сPHP8.5, раньше bool. Функция доступна с5.4. Установка не проверяет заранее результат будущего вызова callback.
Схема по имени, содержимое из доверенного набора
  1. XML просит urn:shop:order.dtd
  2. Resolver сравнивает точный идентификатор
  3. Знакомый → поток с локальной DTD
  4. Незнакомый → null, без открытия адреса
Запрещённая зависимость означает отказ импортаСамостоятельный пример
<?php
declare(strict_types=1);

$loader = libxml_get_external_entity_loader();
$errors = libxml_use_internal_errors(true);
libxml_clear_errors();
$denied = [];
try {
    libxml_set_external_entity_loader(static function (?string $public, ?string $system, array $context) use (&$denied) {
        $denied[] = $system;
        return null;
    });
    $document = new DOMDocument();
    $document->loadXML('<!DOCTYPE product [<!ENTITY label SYSTEM "urn:shop:unknown">]><product>&label;</product>', LIBXML_NOENT);
    if ($denied !== []) {
        echo 'Импорт отменён: не разрешена зависимость ', $denied[0], "\n";
    }
} finally {
    libxml_set_external_entity_loader($loader);
    libxml_clear_errors(); libxml_use_internal_errors($errors);
}
Ожидаемый результат
Импорт отменён: не разрешена зависимость urn:shop:unknown

return null от callback отказывает в разрешении запрошенной сущности; libxml_set_external_entity_loader(null), напротив, возвращает штатный загрузчик. loadXML может построить дерево с пустым содержимым, поэтому отказ зависимости здесь отдельно запрещает импорт. Идентификатор urn не является адресом сети; чтения внешнего ресурса нет.

Два заказа проверяются одной локальной DTDСамостоятельный пример
<?php
declare(strict_types=1);

$loader = libxml_get_external_entity_loader();
$errors = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    libxml_set_external_entity_loader(static function (?string $public, ?string $system, array $context) {
        return $system === 'urn:shop:order.dtd' ? __DIR__ . '/order.dtd' : null;
    });
    foreach (['<order number="42"><item>Desk</item></order>', '<order number="43"><comment>Desk</comment></order>'] as $xml) {
        libxml_clear_errors();
        $document = new DOMDocument();
        $document->loadXML('<!DOCTYPE order SYSTEM "urn:shop:order.dtd">' . $xml, LIBXML_DTDLOAD);
        $number = $document->documentElement->getAttribute('number');
        echo 'Заказ ', $number, $document->validate() ? ": формат принят\n" : ": нарушена DTD\n";
    }
} finally {
    libxml_set_external_entity_loader($loader);
    libxml_clear_errors(); libxml_use_internal_errors($errors);
}
Ожидаемый результат
Заказ 42: формат принят
Заказ 43: нарушена DTD

DTD — описание допустимой структуры: order должен содержать хотя бы один item, number обязателен. XML с comment синтаксически разбирается, но не проходит правила формата. Callback связывает один известный urn с собственным order.dtd. Внешние адреса не открываются; разрешение на исходный XML и на зависимость — разные решения.

Не возвращайте system_id безусловно и не делайте is_file(system_id) достаточным разрешением: так документ сам выберет читаемый сервером файл. Белый список связывает точные известные ID с доверенными схемами; разрешение относительных include/import тоже должно следовать этой политике. Доступ к схеме может значительно замедлять каждую валидацию; локальный согласованный каталог снимает повторную сетевую зависимость.

Loader может участвовать и в открытии основного документа через load, а не только сущностей. В примере loadXML получает уже готовую строку, поэтому зависимость отделена от чтения источника. Настройка действует шире одного DOM-объекта: сохраняйте и восстанавливайте её; параллельные логические операции в одном процессе не должны без координации менять общую политику.

Дальше: libxml_get_external_entity_loader; libxml_use_internal_errors; libxml_get_errors.

libxml_disable_entity_loaderПонять старую защитную настройку XML и заменить её осознанной политикой загрузки в современном PHP.

PHP5.2.11+; deprecated8.0 · libxml

libxml_disable_entity_loader(bool $disable = true): bool

Понять старую защитную настройку XML и заменить её осознанной политикой загрузки в современном PHP.

Часть вызоваСмысл и границы
disabletrue запрещает загрузку через этот старый переключатель, false разрешает её. Возвращается предыдущее значение.
ВерсииPHP5.2.11+; deprecated с8.0. Не включайте вызов в новый код по старому совету из форума без необходимости.
ГраницаМожет мешать обычной загрузке документов и другим XML-операциям. Сlibxml2.9 внешняя подстановка по умолчанию отключена, но опасные режимы NOENT/DTDLOAD/DTDVALID меняют поведение.
Старый глобальный переключатель — широкое действие
  1. Старый код меняет entity_loader_disabled
  2. Меняется загрузка для разных XML-инструментов
  3. Новый код задаёт нужные опции/загрузчик
  4. Прежнее состояние всегда восстанавливают
Увидеть deprecated в изолированном старом сценарииСамостоятельный пример
<?php
declare(strict_types=1);

$deprecated = 0;
set_error_handler(static function (int $level) use (&$deprecated): bool {
    if ($level !== E_DEPRECATED) { return false; }
    $deprecated++; return true;
});
try {
    $previous = libxml_disable_entity_loader(true);
    libxml_disable_entity_loader($previous);
} finally { restore_error_handler(); }
echo $deprecated, "\n";
Ожидаемый результат
2

Оба вызова наPHP8.5.11 помечены устаревшими. Перехват сделан только для наглядного опыта; рабочая миграция убирает причину deprecated, а не скрывает все сообщения.

Прочитать обычный заказ без устаревшего переключателяСамостоятельный пример
<?php
declare(strict_types=1);

$options = LIBXML_NONET;
if (defined('LIBXML_NO_XXE')) { $options |= constant('LIBXML_NO_XXE'); }
$document = new DOMDocument();
if (!$document->loadXML('<order number="42"><item>Desk</item></order>', $options)) {
    throw new RuntimeException('Заказ не разобран');
}
echo 'Заказ ', $document->documentElement->getAttribute('number'), ': ', $document->textContent, "\n";
Ожидаемый результат
Заказ 42: Desk

Здесь нет DTD, подстановки внешних сущностей и вызова deprecated setter. | объединяет доступные флаги. LIBXML_NO_XXE требует PHP8.4 и libxml2.13+, поэтому проверяем defined; один LIBXML_NONET не закрывает чтение локальных файлов и не задаёт всю политику доступа.

Для требующего сущностей доверенного документа настройте явный resolver. Для недоверенного документа не включайте расширение сущностей ради «починки» ошибки с амперсандом: NOENT означает подстановку entities, а не их запрет. libxml_set_external_entity_loader позволяет возвратить null для запрещённых зависимостей.

Дальше: libxml_set_external_entity_loader; libxml_get_external_entity_loader.

libxml_set_streams_contextЗадать тайм-аут и заголовки PHP-потока, когда XML-библиотека сама открывает документ по HTTP.

PHP5+ · libxml

libxml_set_streams_context(resource $context): void

Результат увидим прямо в XML: служба склада возвращает разные остатки по заголовку X-Warehouse. Контекст — набор настроек открытия потока, который PHP прикрепит к загрузке документа. Локальная служба ниже действительно получает этот заголовок; настройка сама по себе запрос не выполняет.

Задать тайм-аут и заголовки PHP-потока, когда XML-библиотека сама открывает документ по HTTP.

Часть вызоваСмысл и границы
contextРесурс контекста от stream_context_create. Опции выбирают нужный wrapper: http, ssl и другие. Это не CurlHandle.
Возвратvoid. Не выполняет загрузку и не возвращает XML.
Ошибки / версииСPHP8.4 неподходящий ресурс приводит кTypeError сразу; раньше ошибка могла возникнуть только при использовании. Обычный аргумент не-resource тоже не подходит.
ОбластьДокументация говорит о следующей загрузке/записи; вPHP8.5.11 настройка хранится в состоянии libxml и может применяться к последующим операциям. Публичного getter прежнего контекста нет.
Контекст — договор открытия, а не документ
  1. stream_context_create с http.timeout
  2. libxml_set_streams_context сохраняет ресурс
  3. DOMDocument::load открывает адрес
  4. Парсер разбирает полученные байты
Склад из HTTP-заголовка меняет полученный XMLСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/xml-lab.php';
[$server, $base] = startXmlLab();
try {
    $context = stream_context_create(['http' => [
        'header' => "X-Warehouse: north\r\n", 'timeout' => 2.0,
    ]]);
    libxml_set_streams_context($context);
    $document = new DOMDocument();
    if (!$document->load($base . '/stock.xml')) { throw new RuntimeException('Нет складского XML'); }
    echo 'Склад ответа: ', $document->documentElement->getAttribute('warehouse'), "\n";
    echo 'A1 — остаток ', $document->textContent, "\n";
} finally { stopXmlLab($server); }
Ожидаемый результат
Склад ответа: north
A1 — остаток 3

Это настоящий запрос к локальному PHP HTTP-серверу: он читает X-Warehouse и выбирает XML склада north. Без этого заголовка возвращаются default и 0. В архиве даны сервер и полностью локальный helper запуска/остановки; они нужны только лаборатории. Проверено прохождение заголовка, а не наступление тайм-аута. Setter передаёт ресурс контекста и возвращает void; load выполняет запрос отдельно.

Не спутать файл с ресурсом контекстаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
if ($stream === false) { throw new RuntimeException('No stream'); }
try {
    try { libxml_set_streams_context($stream); echo "stream accepted\n"; }
    catch (TypeError $error) { echo "context required\n"; }
} finally { fclose($stream); }
Ожидаемый результат
context required

Проверенный PHP8.5.11 отклоняет обычный файловый поток: нужен именно ресурс контекста. Передавайте результат stream_context_create. То, что оба значения имеют тип resource, ещё не означает взаимозаменяемость их назначения.

Контекст общий, без стандартного публичного способа узнать и восстановить чужое значение. В переиспользуемой библиотеке удобнее отдельно получить ограниченное тело HTTP-клиентом, а затем вызвать loadXML над строкой. Так сетевые ошибки, статус, размер и XML-ошибки обрабатываются раздельно. Если именно libxml должен открывать URL, владелец операции задаёт полный контекст и политику доступа.

HTTP timeout потока относится к ожиданию ввода/вывода и не является универсальным ограничением времени всего импорта. Не передавайте Authorization всем адресам XML-зависимостей; whitelist и отдельные контексты resolver могут потребоваться для каждого разрешённого назначения. Контексты и потоки.

Дальше: libxml_set_external_entity_loader; libxml_use_internal_errors.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.