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

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

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

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

Глава 119 · С нуля → справочник

XML Parser: события, обработчики и полный справочник

Все функции и константы SAX-парсера: от первого callback до порционного импорта, состояний, пространств имён и диагностики.

Файл доставки → понятный статус

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

Кто вызывает обработчик статуса

Регистрация сохраняет функцию. Сам вызов произойдёт внутри xml_parse, когда парсер встретит текст.

  1. Ваш код регистрирует showStatus

    xml_set_character_data_handler сохраняет callable; тело функции пока не выполняется.

  2. Ваш код вызывает xml_parse

    Вход: <status>Готово</status>, final=true. Парсер начинает читать XML.

  3. Парсер вызывает showStatus

    В $parser попадает текущий объект, в $data — кусочек текста «Готово»; выполняется echo.

  4. Возврат в парсер, затем в ваш код

    После обработки событий xml_parse возвращает 1. Следующая строка выводит «Ответ разобран».

Callback работает синхронно внутри xml_parse. Текст может прийти несколькими вызовами: размер события не совпадает с размером элемента.

Служба доставки оставила файл status.xml. Получим из него слово «Готово». Событийный парсер читает XML и вызывает нашу функцию, когда встречает текст. Такую вызываемую им функцию называют обработчиком, или callback. Это полезно, когда большой документ нужно обрабатывать по частям без готового дерева в памяти.

Исходный файл status.xml
<status>Готово</status>

function showStatus($parser, $data) объявляет функцию с двумя параметрами: объектом текущего разбора и текстовым кусочком. Фигурные скобки ограничивают её действия; echo $data печатает полученный текст. Имя showStatus передаём без круглых скобок: пока только назначаем исполнителя. file_get_contents читает байты файла, __DIR__ обозначает папку примера. Значение true в третьем аргументе parse говорит, что файл закончился.

Прочитать текст из настоящего status.xmlСамостоятельный пример
<?php
declare(strict_types=1);

function showStatus($parser, $data) {
    echo $data;
}
$parser = xml_parser_create();
xml_set_character_data_handler($parser, 'showStatus');
xml_parse($parser, file_get_contents(__DIR__ . '/status.xml'), true);
echo "\n";
Ожидаемый результат
Готово

Учебный файл входит в архив и заранее корректен. Объект $parser хранит состояние, а $data содержит очередной текст. Здесь нужен только один status. Для нескольких полей добавим обработчики начала/конца; для ненадёжного входа отложим публикацию результата до успешного завершения.

Порядок исполнения
  1. Регистрация showStatus ещё ничего не печатает
  2. xml_parse читает и встречает Готово
  3. Парсер вызывает showStatus(parser, "Готово")
  4. Функция печатает текст и возвращает управление парсеру
  5. Парсер завершает документ, выполняется echo перевода строки

SAX — название подхода «события при чтении». Он не создаёт дерево документа и не проверяет договор по XSD/DTD. Callback работает прямо внутри xml_parse, без отдельного потока. Текст может прийти несколькими кусочками даже при одном вызове parse. Следующий шаг: как накопить поле.

Установка, реализация, настройки и история ресурсов

Расширение xml включено по умолчанию при сборке PHP и может отключаться --disable-xml. Windows-поставка PHP имеет встроенную поддержку. В Unix-пакетах состав уточняют по пакету своей сборки. По умолчанию используется слой совместимости Expat API поверх libxml; опция сборки --with-expat выбирает Expat. С PHP 7.4 прежнее --with-libexpat-dir переименовано, каталог больше не аргумент, встроенная копия Expat удалена. Для стандартной сборки требуется libxml (--with-libxml; до 7.4 --enable-libxml).

Сборка определяет часть поведения
  1. Проверить extension_loaded(xml)
  2. XML_SAX_IMPL показывает libxml или expat
  3. Выбрать совместимые обработчики; у libxml нет end-namespace события
Проверить доступность и выбранный SAX backendСамостоятельный пример
<?php
echo extension_loaded('xml') ? "Расширение xml доступно
" : "Нужно расширение xml
";
echo 'SAX:', XML_SAX_IMPL, "
";
$parser = xml_parser_create();
echo get_class($parser), "
";
Ожидаемый результат
Расширение xml доступно
SAX:libxml
XMLParser

Ожидаемый вывод относится к среде примеров PHP 8.5.11/libxml. Другая сборка может использовать Expat. Сверяйте CLI/FPM/веб-SAPI отдельно: у них могут отличаться сборка и настройки.

Собственных директив php.ini у xml нет; параметры задаются каждому парсеру через xml_parser_set_option. Общие лимиты процесса, размер входа и ограничения backend остаются. До PHP 8 create-функции возвращали xml-ресурс; теперь это объект XMLParser. Старое is_resource больше не подходит. xml_parser_free с 8.0 не освобождает объект, а с 8.5 устарела.

XMLParser: объект состояния, а не дерево XML

Объект хранит текущий разбор, параметры и зарегистрированные callbacks. В PHP 8 класс final, без пользовательского конструктора и публичных методов для чтения узлов. Нельзя клонировать, сериализовать или добавлять динамические свойства. Собственное состояние импорта храните рядом: в переменных замыкания или в вашем классе.

Владение состоянием
  1. xml_parser_create создаёт непрозрачный XMLParser
  2. Обработчики получают его как первый аргумент и хранят свои данные отдельно
  3. После финального разбора удаляются ненужные ссылки; для следующего документа создаётся новый parser
Создание через фабрику вместо new XMLParserСамостоятельный пример
<?php
try {
    $parser = new XMLParser();
} catch (Error $error) {
    echo "Используйте xml_parser_create
";
}
$parser = xml_parser_create();
var_dump($parser instanceof XMLParser);
Ожидаемый результат
Используйте xml_parser_create
bool(true)

XMLParser — непрозрачный встроенный final-класс. Не создают его напрямую, не наследуют и не используют как массив полей документа. Им управляют функции xml_*.

Для двух ответов API — два независимых парсераСамостоятельный пример
<?php
foreach (['<status>paid</status>', '<status>sent</status>'] as $xml) {
    $parser = xml_parser_create('UTF-8');
    echo xml_parse($parser, $xml, true) === 1 ? "XML принят
" : "Ошибка XML
";
    unset($parser);
}
Ожидаемый результат
XML принят
XML принят

Один объект содержит состояние одного документа. Новый ответ разбирают новым парсером; не пытайтесь использовать завершённый документ как начало следующего.

Не храните объект под собственным глобальным классом XMLParser: с PHP 8 имя уже занято. Ссылка callback на объект и обратная ссылка на parser образуют цикл; освобождайте завершённый импорт и не накапливайте такие владельцы без необходимости.

Регистрация, реальный вызов и возврат управления

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Точный маршрут для
  1. xml_set_element_handler сохраняет opened и closed, ничего не печатает
  2. xml_parse начинает чтение: opened(parser,order,[])
  3. opened(parser,item,[]) → closed(parser,item)
  4. closed(parser,order) → возврат 1 из xml_parse
  5. Выполняется следующая строка «После parse»
Увидеть, кто вызывает функции начала и конца тегаСамостоятельный пример
<?php
function opened($parser, $name, $attributes) {
    echo 'start:', $name, "
";
}
function closed($parser, $name) {
    echo 'end:', $name, "
";
}
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, 'opened', 'closed');
echo "До parse
";
$ok = xml_parse($parser, '<order><item/></order>', true);
echo 'После parse:', $ok, "
";
Ожидаемый результат
До parse
start:order
start:item
end:item
end:order
После parse:1

При регистрации передаём имена без (). Вызовы opened/closed делает xml_parse, пока читает документ. Даже самозакрывающийся item создаёт события начала и конца. function лишь объявляет обработчик; регистрация его тоже не исполняет.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

Передайте имя существующей функции без (), либо сам callable. Запись xml_set_...($parser, opened()) сначала немедленно вызвала бы opened без нужных аргументов и передала бы её результат — это другая операция. Для состояния замыкания use (&$count) захватывает внешний счётчик; для объекта подходит [$collector, 'opened']. См. замыкания и захват переменных.

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

Ни один размер буфера не гарантирует один character callback на элемент. Сущности, многобайтовые символы и внутренняя работа parser создают дополнительные границы. Приложение открывает буфер на нужном start, дописывает каждый $data через .= и фиксирует значение на end. Не делайте trim каждой части: в «Buy <b>today</b> now» это склеит слова.

Сбор названия без потери частей
  1. Начало title: пустая строка
  2. Получено Paper → дописать
  3. Получено & и pen → дописать, а не заменить
  4. Конец title: значение Paper & pen готово
Склеить текст, пришедший несколькими событиямиСамостоятельный пример
<?php
$text = '';
$parser = xml_parser_create();
xml_set_character_data_handler($parser, function ($p, $data) use (&$text) {
    $text .= $data;
});
xml_parse($parser, '<title>Paper ', false);
xml_parse($parser, '&amp; pen</title>', true);
echo $text, "
";
Ожидаемый результат
Paper & pen

Оператор .= дописывает кусочек. Присваивание = оставляло бы только последнюю порцию. Даже один цельный вход может вызвать callback несколько раз — на сущностях, Unicode и внутренних границах; размер события не является размером элемента.

Собирать title, сохраняя пробелы вокруг вложенного выделенияСамостоятельный пример
<?php
$depth = 0;
$titleDepth = null;
$title = '';
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$depth, &$titleDepth, &$title) {
        $depth++;
        if ($name === 'title') { $titleDepth = $depth; $title = ''; }
    },
    function ($p, $name) use (&$depth, &$titleDepth, &$title) {
        if ($titleDepth === $depth) { echo $title, "
"; $titleDepth = null; }
        $depth--;
    }
);
xml_set_character_data_handler($parser, function ($p, $data) use (&$titleDepth, &$title) {
    if ($titleDepth !== null) { $title .= $data; }
});
xml_parse($parser, '<item><title>Buy <b>today</b> now</title><qty>2</qty></item>', true);
Ожидаемый результат
Buy today now

titleDepth запоминает глубину выбранного title. Текст вложенного b включён, qty после закрытия title — нет. Не делайте trim каждого кусочка: исчезнут нужные разделяющие пробелы. Сценарий предполагает, что title не вложены друг в друга; для произвольной схемы нужен стек буферов.

Для простого поля достаточно флага/глубины. Для произвольной вложенности нужен стек контекстов; одинаковое имя item в другом пути не обязательно означает товар. Пробелы между тегами тоже являются текстом. В callback решайте это по схеме, а не удалением всех пустых на вид строк.

Структура и путь: одного имени тега иногда мало

В ответе сервиса может быть title товара и title категории. Событие сообщает имя, но не создаёт за вас массив полного пути. Храните стек имён/контекстов, если нужно различать места. Минимальная глубина ниже помогает сначала увидеть порядок событий.

Стек вложенности
  1. catalog: глубина 0 → 1
  2. item: глубина 1 → 2
  3. title: глубина 2 → 3
  4. Закрытия уменьшают глубину в обратном порядке
Показать дерево элементов по глубине событийСамостоятельный пример
<?php
$depth = 0;
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$depth) {
        echo str_repeat('  ', $depth), $name, "
";
        $depth++;
    },
    function ($p, $name) use (&$depth) { $depth--; }
);
xml_parse($parser, '<catalog><item><title>Pen</title></item></catalog>', true);
Ожидаемый результат
catalog
  item
    title

Каждый start увеличивает глубину, каждый end уменьшает. str_repeat повторяет два пробела. Для отображения в HTML экранируйте имена/текст и используйте подходящий элемент, а не вставляйте XML как HTML.

Не сохраняйте весь текст в глобальный один буфер, если разбираете несколько полей. После ошибки callback мог уже изменить состояние; используйте промежуточный результат и завершайте импорт только после final=true без ошибки.

Все настройки и регистр имён

КонстантаТип и исходное состояниеПоведение
XML_OPTION_CASE_FOLDINGbool; исходно включенаПоднимает имена элементов/атрибутов в верхний регистр, не значения. Отключайте, чтобы сохранять идентичность XML-имён/URI.
XML_OPTION_TARGET_ENCODINGstring; UTF-8 по умолчаниюUTF-8, ISO-8859-1 или US-ASCII для строк, передаваемых обработчикам/массивам. Непредставимые символы могут стать вопросительными знаками.
XML_OPTION_SKIP_TAGSTARTint; исходно 0Отбрасывает начало имени элемента в результатах/handlers, не объявляет namespace. В реализации смещение байтовое: применяйте только к заведомому ASCII-префиксу, не к произвольному UTF-8.
XML_OPTION_SKIP_WHITEbool; исходно выключенаПри xml_parse_into_struct пропускает записи/значения только с пробельным содержимым. Не вырезает пробелы внутри текста и не подавляет ваш character callback.
XML_OPTION_PARSE_HUGEbool, PHP 8.4+, libxmlОслабляет защитные ограничения libxml, в том числе для больших текстовых узлов. Нужен заранее ограниченный вход. Не универсальная опция ускорения и не замена потоковому чтению; нельзя менять внутри callback.
XML_SAX_IMPLstring-константа, не опцияlibxml либо expat: фактическая реализация, важная для диагностики и поддержки событий.
Настройки действуют на выбранный parser
  1. Создать объект
  2. До parse задать CASE_FOLDING=0 и нужные параметры
  3. В обработчики/структуру приходят согласованные имена и кодировка
Оставить исходный регистр имёнСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    echo $name, ':', $attrs['sku'], "
";
}, null);
xml_parse($parser, '<Item sku="aB"/>', true);
Ожидаемый результат
Item:aB

По умолчанию имена тегов/атрибутов становятся прописными; значения атрибутов не меняются. Значение 0 работает и в базовых версиях до изменения bool-договора PHP 8.3.

Упростить согласованный плоский XML перед чтением массиваСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, 2);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
$values = [];
xml_parse_into_struct($parser, "<x_root>
<x_item>A</x_item>
</x_root>", $values);
foreach ($values as $row) { echo $row['tag'], ':', $row['type'], "
"; }
Ожидаемый результат
root:open
item:complete
root:close

В договорённом формате префикс x_ удалён ровно на два ASCII-байта. Это не решение для XML namespaces: там нужен create_ns. Пробельные значения массива пропущены, вложенность сохранена.

XML чувствителен к регистру: выключение/включение case folding меняет отображение имён для вашего кода, но не делает вход <Item></item> корректным. С PHP 8.3 get_option возвращает bool для логических опций; для примеров PHP 8.2 допустимы целые 0/1. Get для SKIP_* поддерживается с 7.1.24/7.2.12/7.3.0. При старом libxml <2.7 на PHP 7 огромный режим мог быть неотключаемым; это историческая граница, а не настройка современных примеров.

Входная кодировка и строки обработчиков — разные границы

Внутреннее представление основано на UTF-8. Страницы create-функций описывают автоопределение входа и выход UTF-8 по умолчанию; старая обзорная страница character encoding ещё называет ISO-8859-1 default и смешивает эти роли. Для PHP 8.5.11 используем актуальные create-контракты и точную реализацию, а не старое значение default. Кодировки целевых строк: UTF-8, ISO-8859-1, US-ASCII; неправильное имя — ошибка аргумента. У сборки Expat явно заданная кодировка передаётся также Expat при создании, поэтому переносимость проверяют на своём backend.

Корректность входа не гарантирует сохранность при смене выхода
  1. XML содержит UTF-8 café
  2. Parser декодирует XML
  3. TARGET_ENCODING=US-ASCII не представляет é
  4. В callback приходит caf?; оставьте UTF-8, чтобы сохранить значение
Выходной US-ASCII теряет непредставимые символыСамостоятельный пример
<?php
$text = '';
$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'US-ASCII');
xml_set_character_data_handler($parser, function ($p, $data) use (&$text) { $text .= $data; });
xml_parse($parser, '<name>café</name>', true);
echo $text, "
";
Ожидаемый результат
caf?

Вход корректен, но выбранный выходной набор US-ASCII не содержит é. Лучше сохранять UTF-8. Указание UTF-8 не исправляет строку с произвольными неизвестными байтами.

Обзор PHP перечисляет ограниченный переносимый набор source encodings и не обещает UTF-16. Более широкая поддержка входа конкретным libxml не означает, что строку UTF-16 можно задать как target encoding функции. Для интеграции закрепите кодировку поставщика, допустимые преобразования и проверку ошибок. Индекс байта, выдаваемый parser, может относиться к UTF-8 представлению, поэтому нельзя слепо резать исходные байты другой кодировки по этому индексу.

Плоский массив: open, complete, cdata и close

Для небольшого ответа xml_parse_into_struct собирает список записей и необязательный индекс. Это не DOM и не универсальный вложенный массив «имя → значение». У одинакового имени может быть несколько записей; у open/close нет обязательного value. complete означает цельный элемент без дочерних элементов, даже если текст многострочный.

Как читается $values[$index["id"][0]]["value"]
  1. index[id][0] даёт номер записи id
  2. values[номер] содержит tag/type/level/value
  3. value этой записи — строка 42
XML превращается в список событий, а не вложенный массивСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
$values = [];
$index = [];
$status = xml_parse_into_struct($parser, '<order><id>42</id></order>', $values, $index);
if ($status !== 1) { throw new RuntimeException('Ошибка XML'); }
foreach ($values as $row) {
    echo $row['tag'], ':', $row['type'], ':', $row['level'], "
";
}
echo $values[$index['id'][0]]['value'], "
";
Ожидаемый результат
order:open:1
id:complete:2
order:close:1
42

values — плоская последовательность, index хранит номера её записей для каждого имени. & в сигнатуре означает, что функция заполняет переданные переменные; при вызове знак & не пишется.

Два одноимённых товара не теряютсяСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
$values = $index = [];
$status = xml_parse_into_struct($parser, '<catalog><item sku="A">2</item><item sku="B">7</item></catalog>', $values, $index);
if ($status !== 1) { throw new RuntimeException('Ошибка XML'); }
foreach ($index['item'] as $position) {
    $row = $values[$position];
    echo $row['attributes']['sku'], '=', $row['value'], "
";
}
Ожидаемый результат
A=2
B=7

Атрибуты находятся внутри attributes, не рядом с tag. Нельзя присваивать все товары одному ключу item: последний затрёт предыдущие. Сложное дерево требует обработки open/close/complete/cdata, а не предположения о фиксированной паре индексов.

cdata в плоском результате означает текстовую запись между узлами, не гарантию буквальной секции CDATA во входе. Для смешанного содержимого сохраняйте порядок всех записей. Для бизнес-объектов сопоставляйте разрешённые поля явно; не назначайте произвольные свойства из чужих имён. Полное накопление массива увеличивает память, а глубина представления имеет отдельный предел.

Практика: безопасное отображение известного XML-формата

Если сервис присылает разметку описания, нельзя просто печатать текст callback: он уже содержит декодированные < и &. Преобразователь ниже разрешает только два известных тега, отбрасывает исходные атрибуты и отдельно экранирует текст для HTML. Неизвестные теги не выводятся, их текст сохраняется; если целые поддеревья нужно скрывать, добавьте отдельную политику пропуска.

XML и HTML — разные контексты
  1. Parser декодирует A &amp; B до текста A & B
  2. Карта разрешает strong как HTML-тег
  3. htmlspecialchars возвращает безопасный HTML-текст
  4. Готовая HTML-строка выдаётся только после успешного final
Преобразовать разрешённые XML-теги в ограниченный HTMLСамостоятельный пример
<?php
$html = '';
$map = ['EM' => 'em', 'STRONG' => 'strong'];
$parser = xml_parser_create();
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use ($map, &$html) {
        if (isset($map[$name])) { $html .= '<' . $map[$name] . '>'; }
    },
    function ($p, $name) use ($map, &$html) {
        if (isset($map[$name])) { $html .= '</' . $map[$name] . '>'; }
    }
);
xml_set_character_data_handler($parser, function ($p, $data) use (&$html) {
    $html .= htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
});
if (xml_parse($parser, '<text>Buy <strong>A &amp; B</strong></text>', true) !== 1) {
    throw new RuntimeException('Ошибка XML');
}
echo $html, "
";
Ожидаемый результат
Buy <strong>A &amp; B</strong>

Ключи карты совпадают с включённым CASE_FOLDING. Только известные имена превращаются в HTML-теги; исходные атрибуты не копируются. Текст после XML-декодирования экранируется заново для HTML. Это узкий преобразователь известного XML-формата, не универсальный HTML-санитайзер.

DTD, внешние сущности, нотации и PI

DTD может объявить имя, которое ссылается на внешние данные. Парсер событиями передаёт декларации/ссылки, а ваше приложение определяет политику. Для простого импорта обычно достаточно отказа от внешней ссылки. Нельзя использовать имя файла/URL из входного XML как безусловное разрешение прочитать его. Декларация NOTATION описывает формат NDATA-ресурса; она не проверяет реальные байты файла.

Разрешение зависит от вашей политики
  1. Parser встречает ссылку на SYSTEM urn:shop:terms
  2. Callback сверяет ID с фиксированным разрешённым набором
  3. Неизвестный ID → false и ошибка; известный → свои ограниченные данные
  4. Отдельный parser проверяет данные; никакого произвольного чтения
Отклонить заказ с неизвестной внешней зависимостьюСамостоятельный пример
<?php
declare(strict_types=1);

$parser = xml_parser_create();
xml_set_external_entity_ref_handler($parser, function ($p, $open, $base, $system, $public) {
    echo 'Неизвестная зависимость: ', $system, "\n";
    return false;
});
$xml = '<!DOCTYPE order [<!ENTITY terms SYSTEM "urn:shop:unknown">]><order>&terms;</order>';
if (xml_parse($parser, $xml, true) !== 1
    && xml_get_error_code($parser) === XML_ERROR_EXTERNAL_ENTITY_HANDLING) {
    echo "Заказ отклонён: согласуйте словарь условий с поставщиком\n";
}
Ожидаемый результат
Неизвестная зависимость: urn:shop:unknown
Заказ отклонён: согласуйте словарь условий с поставщиком

false у этого callback имеет специальный смысл: отказ обработки внешней сущности. Сеть и файлы не открываются. Следующий пример разрешает только один собственный ID с заранее известным содержимым.

Разрешить только известный ID и разобрать собственные данныеСамостоятельный пример
<?php
$known = ['urn:shop:terms' => '<terms>Prepaid</terms>'];
$terms = '';
$parser = xml_parser_create();
xml_set_external_entity_ref_handler($parser, function ($p, $open, $base, $system, $public) use ($known, &$terms) {
    if (!isset($known[$system])) { return false; }
    $child = xml_parser_create();
    xml_set_character_data_handler($child, function ($inner, $data) use (&$terms) { $terms .= $data; });
    return xml_parse($child, $known[$system], true) === 1;
});
$xml = '<!DOCTYPE order [<!ENTITY terms SYSTEM "urn:shop:terms">]><order>&terms;</order>';
$ok = xml_parse($parser, $xml, true);
if ($ok !== 1) { throw new RuntimeException('Ошибка внешней сущности'); }
echo $terms, "
";
Ожидаемый результат
Prepaid

ID сопоставляется с известным содержимым, не с произвольным путём из XML. Новый parser нужен потому, что исходный сейчас активен. true не вставляет строку автоматически в обычный text-handler родителя: приложение само сохраняет полученные данные. Пример намеренно разрешает одну фиксированную зависимость и не устанавливает такой загрузчик ребёнку.

Исторический пример PHP показывает исполнение текста PI через eval и доверие по владельцу файла. В современном импорте эти приёмы не используем: PI остаётся строкой, а зависимости выбираются по явному договору. Наши примеры не ходят во внешнюю сеть. Против огромных внутренних сущностей/глубины также нужны лимиты входа, времени и записей; один запрет внешней ссылки не является всей защитой XML. Связанные настройки: libxml.

Объект обработчика вместо глобальных переменных

Счётчик или собираемая запись должны переживать много callbacks. Можно захватить переменную замыканием; когда состояния больше, собственный объект объединяет его с методами. Это практический смысл класса обработчика: два одновременных импорта получают два независимых состояния. Он не наследует встроенный XMLParser.

Данные принадлежат конкретному импорту
  1. new OrderItems создаёт пустой список skus
  2. Callable [$items,opened] привязывает метод к этому объекту
  3. Parser вызывает метод для каждого ITEM
  4. После final читаем $items->skus
Собрать позиции конкретного заказа в объектеСамостоятельный пример
<?php
declare(strict_types=1);

class OrderItems {
    public array $skus = [];
    public function opened($parser, $name, $attributes): void {
        if ($name === 'ITEM') { $this->skus[] = $attributes['SKU']; }
    }
}
$items = new OrderItems();
$parser = xml_parser_create();
xml_set_element_handler($parser, [$items, 'opened'], null);
if (xml_parse($parser, '<order><item sku="A1"/><item sku="B2"/></order>', true) !== 1) {
    throw new RuntimeException('Повреждён заказ');
}
echo 'К комплектации: ', implode(', ', $items->skus), "\n";
Ожидаемый результат
К комплектации: A1, B2

Класс объединяет список SKU и действие добавления. public позволяет читать список; $this обозначает данный OrderItems. Callable [$items, opened] связывает метод с этим объектом. Новый импорт получает другой объект. В учебном XML SKU известны; полноценный обработчик ниже проверяет их наличие.

Старая связка xml_set_object + строка метода устарела с 8.4. Прямой callable проверяется сразу и явно показывает получателя. Подробнее: классы и объекты, xml_set_object().

Заказ order.xml → ведомость picking.csv

Комплектовщику нужны артикул и название каждой позиции. Сначала повторите короткий пример накопления text-parts; теперь используем два буфера: current для открытой позиции и rows для завершённых. Вход в этой задаче — небольшой согласованный заказ, item не вложены друг в друга. Для полного договора структуры и обязательности полей нужна отдельная проверка, например XSD.

Исходный файл order.xml
<order number="42"><item sku="A1">Desk</item><item sku="B2">Lamp</item></order>
Позиция проходит три стадии
  1. Открытие item sku=A1 → current содержит артикул и пустое имя
  2. Текст Desk дописывается в current[name]
  3. Закрытие item → готовая строка переносится в rows
  4. Успешное завершение целого XML → создаётся ведомость CSV

current = null означает, что товар ещё не открыт. [] добавляет запись в массив, use (&...) разделяет эти переменные между обработчиками. Сбрасываем current после каждого товара. fputcsv записывает строку с правилами CSV; пустой пятый аргумент отключает особую обработку обратной косой черты. Ни одна строка ведомости не создаётся до успешного final.

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

$rows = [];
$current = null;
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$current) {
        if ($name === 'item') {
            if ($current !== null || !isset($attrs['sku']) || $attrs['sku'] === '') {
                throw new RuntimeException('Некорректная позиция заказа');
            }
            $current = ['sku' => $attrs['sku'], 'name' => ''];
        }
    },
    function ($p, $name) use (&$current, &$rows) {
        if ($name === 'item') {
            if ($current === null || trim($current['name']) === '') {
                throw new RuntimeException('Нет названия товара');
            }
            $rows[] = $current;
            $current = null;
        }
    }
);
xml_set_character_data_handler($parser, function ($p, $part) use (&$current) {
    if ($current !== null) { $current['name'] .= $part; }
});
$input = file_get_contents(__DIR__ . '/order.xml');
if ($input === false || strlen($input) > 4096) { throw new RuntimeException('Не удалось прочитать учебный заказ'); }
if (xml_parse($parser, $input, true) !== 1) { throw new RuntimeException('XML заказа повреждён'); }
$file = fopen(__DIR__ . '/picking.csv', 'wb');
if ($file === false) { throw new RuntimeException('Не удалось создать ведомость'); }
try {
    fputcsv($file, ['sku', 'name'], ',', '"', '', "\n");
    foreach ($rows as $row) { fputcsv($file, $row, ',', '"', '', "\n"); }
} finally { fclose($file); }
echo file_get_contents(__DIR__ . '/picking.csv');
Ожидаемый результат
sku,name
A1,Desk
B2,Lamp

Весь пример и исходный order.xml находятся в отдельной учебной папке. Разбор работает с UTF-8. rows здесь намеренно мал; для большого файла пишите во временную ведомость и переименовывайте её только после final — общий приём показан у XMLReader. Ошибки диска при CSV-записи в production нужно также проверять.

Измените Desk на «Desk & lamp» с XML-записью Desk &amp; lamp: обработчик получит обычный &, а CSV сохранит готовое значение. Для редактирования исходного XML событийный подход неудобен: используйте DOM.

Повреждённый заказ не создаёт ведомость

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

try { require __DIR__ . '/converter.php'; }
catch (RuntimeException $error) { echo $error->getMessage(), "\n"; }
echo file_exists(__DIR__ . '/picking.csv') ? "Ведомость создана\n" : "Ведомости нет: исправьте заказ и повторите\n";
Ожидаемый результат
XML заказа повреждён
Ведомости нет: исправьте заказ и повторите

converter.php — тот же полный обработчик предыдущего примера; меняется только входной файл. Ошибка появляется после закрытия item, когда временная строка уже собрана. Но вывод в picking.csv находится после успешного завершения, поэтому частичный заказ не опубликован.

Инкрементальный разбор, ограничения и частичные эффекты

Большой файл читают блоками, но все блоки идут одному parser. Ни граница тега, ни граница UTF-8-символа не обязаны совпасть с размером чтения; внутреннее состояние переносится между вызовами. Внешнее приложение контролирует объём/время, глубину и число записей. В примере finally закрывает файл даже при исключении; он не подавляет ошибку.

Один parser на весь файл
  1. fread даёт очередные 8 байт
  2. xml_parse(...,false) продолжает состояние и вызывает callbacks
  3. Лимиты и ошибки прекращают попытку, частичный результат не подтверждён
  4. EOF → xml_parse("",true) → подтверждение результата
Прочитать файл кусочками и подтвердить только целый импортСамостоятельный пример
<?php
$stream = fopen(__DIR__ . '/stock.xml', 'rb');
if ($stream === false) { throw new RuntimeException('Нет файла'); }
$parser = xml_parser_create();
$count = 0;
$depth = 0;
xml_set_external_entity_ref_handler($parser, function ($p, $open, $base, $system, $public) { return false; });
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$count, &$depth) {
        $depth++;
        if ($depth > 10) { throw new RuntimeException('Слишком большая глубина'); }
        if ($name === 'ITEM') {
            if (!isset($attrs['SKU'])) { throw new RuntimeException('Нет SKU'); }
            $count++;
            if ($count > 100) { throw new RuntimeException('Слишком много товаров'); }
        }
    },
    function ($p, $name) use (&$depth) { $depth--; }
);
$bytes = 0;
try {
    while (!feof($stream)) {
        $chunk = fread($stream, 8);
        if ($chunk === false) { throw new RuntimeException('Ошибка чтения'); }
        if ($chunk === '') {
            if (feof($stream)) { break; }
            throw new RuntimeException('Чтение не продвигается');
        }
        $bytes += strlen($chunk);
        if ($bytes > 4096) { throw new RuntimeException('Вход слишком большой'); }
        if (xml_parse($parser, $chunk, false) !== 1) { throw new RuntimeException('Повреждён XML'); }
    }
    if (xml_parse($parser, '', true) !== 1) { throw new RuntimeException('XML не завершён'); }
} finally { fclose($stream); }
echo 'Подтверждено товаров:', $count, "
";
Ожидаемый результат
Подтверждено товаров:2

В примере локальный обычный файл и нарочно маленькие порции по 8 байт. while повторяет чтение до EOF, === false отличает ошибку, strlen считает байты. Лимиты демонстрационные, не универсальные production-значения. Счётчик публикуется только после final; реальная БД требует транзакции/пакетной стадии, чтобы не принять половину импорта.

PARSE_HUGE не заменяет лимиты и не ускоряет автоматически обработчики. Если callback сохраняет все записи в массив, память всё равно растёт. Временная таблица/транзакция/отложенная фиксация помогают не публиковать частичный результат. Нельзя рекурсивно кормить тот же parser новым документом из его callback. См. потоки, транзакции.

Ошибки и все XML_ERROR_* константы

Сначала проверяют результат parse, затем читают код и позицию того же объекта. Ошибка XML не равна исключению в callback и не равна неправильному аргументу функции. Сообщения полезны для технического журнала; пользователю дают понятное объяснение без полного входного документа, который может содержать закрытые данные.

Повреждённое закрытие
  1. xml_parse читает <item></root>
  2. Возвращает 0, parser сохраняет причину и позицию
  3. Приложение получает код/описание/строку и отклоняет попытку
Указать поставщику повреждённую строку файлаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = file_get_contents(__DIR__ . '/broken-order.xml');
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true) !== 1) {
    $line = xml_get_current_line_number($parser);
    echo 'broken-order.xml, строка ', $line, ': ',
        xml_error_string(xml_get_error_code($parser)) ?? 'Unknown', "\n";
    echo explode("\n", $xml)[$line - 1], "\n";
    echo 'Колонка: ', xml_get_current_column_number($parser),
        '; байт: ', xml_get_current_byte_index($parser), "\n";
    echo "Заказ отклонён: закрывайте item перед order\n";
}
Ожидаемый результат
broken-order.xml, строка 2: Mismatched tag
<item></order>
Колонка: 15; байт: 22
Заказ отклонён: закрывайте item перед order

Здесь короткий учебный файл целиком безопасен для показа. line указывает место обнаружения; вычитаем 1 для индекса массива строк. Колонка и байт — диагностическая позиция backend, не всегда первый неверный символ. В журнале реального сервиса не публикуйте закрытые данные документа.

Константа intСмысл
XML_ERROR_NONEОшибки разбора нет.
XML_ERROR_NO_MEMORYНедостаточно памяти для операции парсера.
XML_ERROR_SYNTAXСинтаксическая ошибка XML, не выделенная в более конкретную категорию.
XML_ERROR_NO_ELEMENTSНет корневого элемента/пустой документ.
XML_ERROR_INVALID_TOKENНедопустимый XML-токен или символ.
XML_ERROR_UNCLOSED_TOKENОборванный тег/другая незавершённая конструкция.
XML_ERROR_PARTIAL_CHARВход закончился внутри многобайтового символа.
XML_ERROR_TAG_MISMATCHЗакрывающий тег не совпадает с открытым.
XML_ERROR_DUPLICATE_ATTRIBUTEАтрибут повторён в одном элементе.
XML_ERROR_JUNK_AFTER_DOC_ELEMENTЛишнее содержимое после корневого элемента.
XML_ERROR_PARAM_ENTITY_REFНедопустимая ссылка на параметрическую сущность.
XML_ERROR_UNDEFINED_ENTITYИспользована необъявленная сущность.
XML_ERROR_RECURSIVE_ENTITY_REFРекурсивная ссылка сущностей.
XML_ERROR_ASYNC_ENTITYРазметка/границы сущности не образуют корректно вложенную структуру.
XML_ERROR_BAD_CHAR_REFНедопустимая числовая ссылка на XML-символ.
XML_ERROR_BINARY_ENTITY_REFНеразбираемая сущность использована как разобранный текст.
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REFВнешняя сущность недопустимо использована в атрибуте.
XML_ERROR_MISPLACED_XML_PIXML-декларация находится в недопустимой позиции.
XML_ERROR_UNKNOWN_ENCODINGНеизвестная входная кодировка документа.
XML_ERROR_INCORRECT_ENCODINGЗаявленная кодировка не согласована с данными.
XML_ERROR_UNCLOSED_CDATA_SECTIONВход закончился до закрытия CDATA.
XML_ERROR_EXTERNAL_ENTITY_HANDLINGОбработка внешней сущности завершилась отказом.

Не закрепляйте числовые значения констант и точные английские тексты в бизнес-правилах. Backend может выдавать разные низкоуровневые причины/позиции для одного повреждения. Для своей категории «невалидный XML» достаточно parse !== 1; подробную причину сохраняют отдельно. XML_ERROR_NONE относится к коду, тогда как xml_parse возвращает 1 для успеха. Индекс байта начинается с 0 и не равен номеру Unicode-символа.

Полный список остальных констант расширения

КонстантаТип/версияДля чего
XML_OPTION_CASE_FOLDINGbool; исходно включенаПоднимает имена элементов/атрибутов в верхний регистр, не значения. Отключайте, чтобы сохранять идентичность XML-имён/URI.
XML_OPTION_TARGET_ENCODINGstring; UTF-8 по умолчаниюUTF-8, ISO-8859-1 или US-ASCII для строк, передаваемых обработчикам/массивам. Непредставимые символы могут стать вопросительными знаками.
XML_OPTION_SKIP_TAGSTARTint; исходно 0Отбрасывает начало имени элемента в результатах/handlers, не объявляет namespace. В реализации смещение байтовое: применяйте только к заведомому ASCII-префиксу, не к произвольному UTF-8.
XML_OPTION_SKIP_WHITEbool; исходно выключенаПри xml_parse_into_struct пропускает записи/значения только с пробельным содержимым. Не вырезает пробелы внутри текста и не подавляет ваш character callback.
XML_OPTION_PARSE_HUGEbool, PHP 8.4+, libxmlОслабляет защитные ограничения libxml, в том числе для больших текстовых узлов. Нужен заранее ограниченный вход. Не универсальная опция ускорения и не замена потоковому чтению; нельзя менять внутри callback.
XML_SAX_IMPLstring-константа, не опцияlibxml либо expat: фактическая реализация, важная для диагностики и поддержки событий.

Вместе с 22 XML_ERROR_* из предыдущего раздела это 28 публичных констант текущего расширения. XML_SAX_IMPL — строка; XML_OPTION_* — целые идентификаторы опций, а не битовые флаги: их не объединяют оператором |. Передаются по одной в get/set_option. Константы существуют только при наличии расширения; PARSE_HUGE — с PHP 8.4.

Упражнение и полный указатель функций

Возьмите пример text-scope и добавьте второй title. Убедитесь, что буфер очищается на каждом его начале, а значение сохраняется на конце. Затем разбейте XML-строку в произвольном месте и передайте порции одному parser, после них выполните final. Результат должен остаться тем же: именно это проверяет правильность накопления, а не выбранный размер fread. В решении $titles[] = $title добавляет готовую строку в массив; implode соединяет строки переводом строки для вывода.

Самопроверка обработчика
  1. Первый title → Buy today now
  2. Второй title → Second title, новый пустой буфер
  3. Разрыв внутри today не меняет два названия
  4. Только успешный final разрешает вывести результат
Решение: два названия и разрыв внутри словаСамостоятельный пример
<?php
$titles = [];
$title = '';
$inTitle = false;
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$inTitle, &$title) {
        if ($name === 'title') { $inTitle = true; $title = ''; }
    },
    function ($p, $name) use (&$inTitle, &$title, &$titles) {
        if ($name === 'title') { $titles[] = $title; $inTitle = false; }
    }
);
xml_set_character_data_handler($parser, function ($p, $data) use (&$inTitle, &$title) {
    if ($inTitle) { $title .= $data; }
});
$chunks = ['<catalog><title>Buy <b>to', 'day</b> now</title><title>Second title</title></catalog>'];
foreach ($chunks as $chunk) {
    if (xml_parse($parser, $chunk, false) !== 1) { throw new RuntimeException('Ошибка XML'); }
}
if (xml_parse($parser, '', true) !== 1) { throw new RuntimeException('XML не завершён'); }
echo implode("\n", $titles), "\n";
Ожидаемый результат
Buy today now
Second title

Для схемы без title внутри title хватает флага inTitle. Массив titles накапливает готовые названия, а строка title очищается на каждом новом поле. Разрыв внутри today не меняет результат. Вывод происходит после финализации; в отличие от демонстрационного echo в callback он не раскрывает частичный результат повреждённого документа.

Реальные затруднения и выбор соседнего инструмента

  • Обсуждение: Текст, особенно Unicode, приходит несколькими callback: накапливаем до конца элемента.
  • Обсуждение: Смысл финальной порции: успех промежуточного шага не подтверждает завершённость документа.
  • Обсуждение: CASE_FOLDING включён по умолчанию; явно выбираем договор имён.
  • Обсуждение: Атрибуты лежат в attributes, а регистр ключей следует опции парсера.
  • Обсуждение: complete относится к представлению целого элемента без детей, не к неполной загрузке строки.
  • Обсуждение: HTML-сущности не становятся XML-сущностями автоматически; проверяем ошибку, не принимаем частичный массив.
  • Обсуждение: XMLParser стал встроенным именем класса PHP 8; переименовываем свой старый класс и мигрируем callbacks.
  • Обсуждение: Обработчику не нужна ссылка &$parser; связывание состояния делаем явно через объект/замыкание.

Когда удобнее самому двигаться по текущему узлу — XMLReader. Когда нужно создать XML — XMLWriter. Для редактируемого дерева используют DOM, для небольшого простого чтения — SimpleXML. SAX callback требует явного состояния, зато не заставляет сохранять всё дерево. Это выбор формы работы, а не обещание, что одна библиотека всегда быстрее.

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

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

xml_parser_createСоздать состояние для последовательного разбора XML.

PHP 4; примеры PHP 8.2+ · xml

xml_parser_create(?string $encoding = null): XMLParser

Создать состояние для последовательного разбора XML.

ПараметрЗначение
$encodingКодировка данных, передаваемых обработчикам: UTF-8 (по умолчанию), ISO-8859-1 или US-ASCII. null — стандартная; пустая строка включает автоопределение по начальному входу.

С PHP 8.0 возвращает XMLParser; раньше ресурс или false. Неизвестное имя кодировки вызывает ValueError. В текущей libxml-реализации вход определяется по XML, а параметр задаёт выход обработчиков; при сборке с Expat явно заданная кодировка влияет и на создание Expat-парсера. Для переносимых UTF-8 интеграций используйте UTF-8 вход с корректной декларацией и проверяйте результат. Старую формулировку обзорной страницы о default ISO-8859-1 нельзя переносить на PHP 8.5: здесь default UTF-8.

События и данные этого примера
  1. Объект создан без данных
  2. xml_parse получает <status>ready</status> и final=true
  3. Результат 1 означает успешное завершение разбора
Передать поставщику решение о корректности XMLСамостоятельный пример
<?php
declare(strict_types=1);

$parser = xml_parser_create();
$status = xml_parse($parser, '<order number="42"/>', true);
echo $status === 1 ? "Заказ 42: XML принят\n" : "Заказ 42: исправьте XML\n";
Ожидаемый результат
Заказ 42: XML принят

create создаёт пустое состояние; parse получает конкретный документ и признак конца. Число 1 означает синтаксически завершённый XML, но ещё не подтверждает оплату, наличие товара или соответствие XSD.

Для двух ответов API — два независимых парсераСамостоятельный пример
<?php
foreach (['<status>paid</status>', '<status>sent</status>'] as $xml) {
    $parser = xml_parser_create('UTF-8');
    echo xml_parse($parser, $xml, true) === 1 ? "XML принят
" : "Ошибка XML
";
    unset($parser);
}
Ожидаемый результат
XML принят
XML принят

Один объект содержит состояние одного документа. Новый ответ разбирают новым парсером; не пытайтесь использовать завершённый документ как начало следующего.

Связанные операции: xml_parse(), xml_parser_create_ns(), xml_parser_set_option().

xml_parser_create_nsСоздать парсер, который различает элементы по namespace URI и локальному имени.

PHP 4.0.5; примеры PHP 8.2+ · xml

xml_parser_create_ns(?string $encoding = null, string $separator = ":"): XMLParser

Создать парсер, который различает элементы по namespace URI и локальному имени.

ПараметрЗначение
$encodingТе же кодировки и правила, что у xml_parser_create.
$separatorРазделитель раскрытого имени, по умолчанию :. Используйте один ASCII-символ, отсутствующий в ожидаемых URI; реализации используют один байт, не произвольный длинный разделитель.

Возвращает XMLParser, nullable encoding и объектный результат с PHP 8.0. Неверная кодировка — ValueError. Отключите CASE_FOLDING, если нужны исходные имена/URI. Это XML namespaces, не пространства имён классов PHP; URI обозначает словарь, а не команду загрузки URL.

События и данные этого примера
  1. Объявлены s=urn:shop и c=urn:crm
  2. Парсер раскрывает s:id и c:id
  3. Обработчик видит urn:shop|id и urn:crm|id
Два id из разных XML-пространствСамостоятельный пример
<?php
$parser = xml_parser_create_ns('UTF-8', '|');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    echo $name, "
";
}, null);
xml_parse($parser, '<root xmlns:s="urn:shop" xmlns:c="urn:crm"><s:id/><c:id/></root>', true);
Ожидаемый результат
root
urn:shop|id
urn:crm|id

Имена передаются как URI + разделитель + локальное имя. Префиксы s/c не являются устойчивой идентичностью поля. | выбран, чтобы двоеточия URI не путались с разделителем.

Default namespace у элемента и обычный атрибутСамостоятельный пример
<?php
$parser = xml_parser_create_ns('UTF-8', '|');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    echo $name, ':', $attrs['id'], "
";
}, null);
xml_parse($parser, '<item xmlns="urn:shop" id="7"/>', true);
Ожидаемый результат
urn:shop|item:7

Default namespace относится к элементу, но не к непрефиксному атрибуту id. Обычный xml_parser_create не даёт такого раскрытия namespace.

Связанные операции: xml_set_start_namespace_decl_handler(), xml_set_end_namespace_decl_handler(), xml_parser_create().

xml_parseПередать XML целиком или очередную часть и вызвать зарегистрированные обработчики.

PHP 4; примеры PHP 8.2+ · xml

xml_parse(XMLParser $parser, string $data, bool $is_final = false): int

Передать XML целиком или очередную часть и вызвать зарегистрированные обработчики.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$dataXML-байты или следующая порция в правильном порядке; может быть пустой строкой при финализации.
$is_finaltrue только для последней части документа, false для промежуточных.

Возвращает 1 при успехе текущего шага, 0 при ошибке. Подробности берут через xml_get_error_code и функции позиции. Это синхронный вызов: обработчики выполняются внутри него. Нельзя рекурсивно вызвать xml_parse на том же parser из его callback — Error. Для нового документа/повторной попытки создайте новый объект. Обычный return false из start/text/end не является командой остановки; явное исключение обработчика распространяется отдельно.

События и данные этого примера
  1. Передать <order> с final=false
  2. Передать оставшиеся части тому же объекту
  3. Пустой final=true проверяет закрытые конструкции; только затем импорт завершён
Собрать поле из трёх частей и вывести после финализацииСамостоятельный пример
<?php
declare(strict_types=1);

$text = '';
$parser = xml_parser_create();
xml_set_character_data_handler($parser, function ($p, $part) use (&$text) {
    $text .= $part;
});
foreach (['<status>От', 'прав', 'лен</status>'] as $chunk) {
    if (xml_parse($parser, $chunk, false) !== 1) {
        throw new RuntimeException('Повреждён ответ доставки');
    }
}
if (xml_parse($parser, '', true) !== 1) {
    throw new RuntimeException('Ответ не завершён');
}
echo 'Доставка: ', $text, "\n";
Ожидаемый результат
Доставка: Отправлен

Все три части идут одному parser. .= дописывает текст; use (&$text) позволяет обработчику менять внешнюю переменную. final=true проверяет, что документ закончен. Только после этого выводим собранный статус.

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

$parser = xml_parser_create();
if (xml_parse($parser, '<status>Отправлен', false) === 1) {
    echo "Часть получена; ждём продолжение\n";
}
if (xml_parse($parser, '', true) !== 1) {
    echo "Ответ оборван: статус не обновлён\n";
}
Ожидаемый результат
Часть получена; ждём продолжение
Ответ оборван: статус не обновлён

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

Связанные операции: xml_get_error_code(), xml_error_string(), xml_set_element_handler(), xml_parse_into_struct().

xml_parse_into_structПолучить плоское представление небольшого XML и индекс его элементов.

PHP 4; примеры PHP 8.2+ · xml

xml_parse_into_struct(XMLParser $parser, string $data, array &$values, array &$index = null): int|false

Получить плоское представление небольшого XML и индекс его элементов.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$dataПолный XML-документ в одной строке; функция сама выполняет финализацию.
$valuesВыходной массив записей tag/type/level, иногда value/attributes; функция заменяет содержимое переменной.
$indexНеобязательный выходной массив: имя тега → список индексов в values, а не объектных ссылок.

Обычный результат 1/0 (не true/false); в сигнатуре также false, например при запрещённом рекурсивном вызове с предупреждением. При неуспехе arrays могут содержать частичный результат — не принимайте его как весь импорт. Типы: open — начало узла с детьми; close — конец; complete — цельный узел без дочерних элементов; cdata — текст между узлами, не обязательно исходная CDATA-секция. level начинается с 1. Вся структура хранится в памяти; для большого ввода используйте callbacks/порции. В PHP 8.5 представление имеет внутренний предел глубины 255; PARSE_HUGE не делает массив безграничным.

События и данные этого примера
  1. order становится записью open
  2. id с текстом 42 становится complete
  3. index[id][0] указывает на запись values, откуда читается value
XML превращается в список событий, а не вложенный массивСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
$values = [];
$index = [];
$status = xml_parse_into_struct($parser, '<order><id>42</id></order>', $values, $index);
if ($status !== 1) { throw new RuntimeException('Ошибка XML'); }
foreach ($values as $row) {
    echo $row['tag'], ':', $row['type'], ':', $row['level'], "
";
}
echo $values[$index['id'][0]]['value'], "
";
Ожидаемый результат
order:open:1
id:complete:2
order:close:1
42

values — плоская последовательность, index хранит номера её записей для каждого имени. & в сигнатуре означает, что функция заполняет переданные переменные; при вызове знак & не пишется.

Два одноимённых товара не теряютсяСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
$values = $index = [];
$status = xml_parse_into_struct($parser, '<catalog><item sku="A">2</item><item sku="B">7</item></catalog>', $values, $index);
if ($status !== 1) { throw new RuntimeException('Ошибка XML'); }
foreach ($index['item'] as $position) {
    $row = $values[$position];
    echo $row['attributes']['sku'], '=', $row['value'], "
";
}
Ожидаемый результат
A=2
B=7

Атрибуты находятся внутри attributes, не рядом с tag. Нельзя присваивать все товары одному ключу item: последний затрёт предыдущие. Сложное дерево требует обработки open/close/complete/cdata, а не предположения о фиксированной паре индексов.

Связанные операции: xml_parse(), xml_parser_set_option().

xml_parser_set_optionНастроить конкретный парсер до обработки данных.

PHP 4; примеры PHP 8.2+ · xml

xml_parser_set_option(XMLParser $parser, int $option, string|int|bool $value): bool

Настроить конкретный парсер до обработки данных.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$optionОдна из пяти XML_OPTION_*; полный договор в таблице настроек главы.
$valueЗначение типа нужной опции: bool, целое смещение либо строка кодировки.

Возвращает true/false; неизвестная опция — ValueError с PHP 8.0, неизвестная целевая кодировка — ValueError. В PHP 8.5.11 неподходящий тип value вызывает предупреждение, а не гарантированно TypeError; передавайте правильные типы. SKIP_TAGSTART вне 0..INT_MAX — предупреждение и false. PARSE_HUGE нельзя менять внутри активного разбора — Error. С PHP 8.3 bool принят официально, CASE_FOLDING/SKIP_WHITE стали bool-опциями; PARSE_HUGE добавлена в 8.4.

События и данные этого примера
  1. Исходный тег Item и атрибут sku
  2. CASE_FOLDING=0 сохраняет имена
  3. Обработчик получает Item, sku и неизменённое значение aB
Оставить исходный регистр имёнСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    echo $name, ':', $attrs['sku'], "
";
}, null);
xml_parse($parser, '<Item sku="aB"/>', true);
Ожидаемый результат
Item:aB

По умолчанию имена тегов/атрибутов становятся прописными; значения атрибутов не меняются. Значение 0 работает и в базовых версиях до изменения bool-договора PHP 8.3.

Неизвестную опцию нельзя принять за falseСамостоятельный пример
<?php
$parser = xml_parser_create();
try {
    xml_parser_set_option($parser, -100, 1);
} catch (ValueError $error) {
    echo "Неизвестная опция
";
}
try {
    xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'NOT-A-CHARSET');
} catch (ValueError $error) {
    echo "Кодировка не поддерживается
";
}
Ожидаемый результат
Неизвестная опция
Кодировка не поддерживается

Идентификаторы опций — константы, а не произвольные числа. Ошибка аргумента не является ошибкой XML-документа и не читается через xml_get_error_code.

Связанные операции: xml_parser_get_option(), xml_parse_into_struct().

xml_parser_get_optionУзнать значение настройки без изменения состояния разбора.

PHP 4; примеры PHP 8.2+ · xml

xml_parser_get_option(XMLParser $parser, int $option): string|int|bool

Узнать значение настройки без изменения состояния разбора.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$optionCASE_FOLDING, TARGET_ENCODING, SKIP_TAGSTART, SKIP_WHITE; PARSE_HUGE с PHP 8.4.

Возвращает строку кодировки, целое смещение или bool. Неизвестная опция — ValueError с PHP 8.0; раньше warning/false. Поддержка get для SKIP_TAGSTART/SKIP_WHITE добавлена в 7.1.24/7.2.12/7.3.0; bool-возврат логических опций — с 8.3.

События и данные этого примера
  1. Создать parser со стандартными настройками
  2. Прочитать CASE_FOLDING и TARGET_ENCODING
  3. Использовать значения для диагностики/согласования поведения
Прочитать исходную настройку без измененияСамостоятельный пример
<?php
$parser = xml_parser_create();
echo xml_parser_get_option($parser, XML_OPTION_CASE_FOLDING) ? "Регистр меняется
" : "Регистр сохранён
";
echo xml_parser_get_option($parser, XML_OPTION_TARGET_ENCODING), "
";
Ожидаемый результат
Регистр меняется
UTF-8

Получаем настройку именно этого объекта. С PHP 8.3 логические опции возвращают bool; раньше это могло быть целое 0/1.

Временно изменить настройку ещё не запущенного парсераСамостоятельный пример
<?php
$parser = xml_parser_create();
$previous = xml_parser_get_option($parser, XML_OPTION_CASE_FOLDING);
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
echo xml_parser_get_option($parser, XML_OPTION_CASE_FOLDING) ? "on
" : "off
";
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, $previous);
echo xml_parser_get_option($parser, XML_OPTION_CASE_FOLDING) ? "on
" : "off
";
Ожидаемый результат
off
on

Сохранение/возврат опции не отменяет уже выполненный разбор. Опции лучше определять до первого xml_parse, чтобы значения имён не менялись посреди импорта.

Связанные операции: xml_parser_set_option().

xml_parser_freeПонять старое освобождение XML-ресурса и убрать ненужный вызов при миграции.

PHP 4; no-op PHP 8.0; deprecated PHP 8.5 · xml

xml_parser_free(XMLParser $parser): bool

Понять старое освобождение XML-ресурса и убрать ненужный вызов при миграции.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.

С PHP 8.0 эффекта освобождения нет; с PHP 8.5 функция deprecated. В обычном неактивном состоянии возвращает true, но это не закрытие XMLParser. В PHP 8.5.11 вызов во время callback дополнительно даёт warning/false. Исторические циклические ссылки объект↔ресурс требовали явного разрыва; современный код управляет обычными ссылками и освобождает владельца после окончания работы.

События и данные этого примера
  1. Исторический free получает XMLParser
  2. В PHP 8 объект остаётся открытым, в 8.5 возникает deprecation
  3. Современный код удаляет ненужные ссылки после финализации
Исторический вызов на PHP 8.5 только сообщает об устареванииСамостоятельный пример
<?php
$parser = xml_parser_create();
set_error_handler(function ($severity, $message) {
    if ($severity === E_DEPRECATED) { echo "Устаревший вызов
"; return true; }
    return false;
});
try {
    var_dump(xml_parser_free($parser));
} finally {
    restore_error_handler();
}
var_dump(xml_parse($parser, '<ok/>', true));
Ожидаемый результат
Устаревший вызов
bool(true)
int(1)

Учебный обработчик принимает только ожидаемую deprecation. Объект остаётся пригодным: с PHP 8.0 free не освобождает его. На PHP 8.2–8.4 сообщение об устаревании отсутствует.

Современное завершение владения без xml_parser_freeСамостоятельный пример
<?php
$parser = xml_parser_create();
var_dump(xml_parse($parser, '<ok/>', true));
unset($parser);
echo isset($parser) ? "Переменная есть
" : "Ссылка удалена
";
Ожидаемый результат
int(1)
Ссылка удалена

unset удаляет эту ссылку, объект освобождается после исчезновения всех ссылок. Обработчики/замыкания могут удерживать другие объекты; не храните завершённые парсеры бесконечно.

Связанные операции: xml_parse(), xml_parser_create().

xml_set_element_handlerНазначить действия на открытие и закрытие XML-элемента.

PHP 4; примеры PHP 8.2+ · xml

xml_set_element_handler(XMLParser $parser, callable|string|null $start_handler, callable|string|null $end_handler): true

Назначить действия на открытие и закрытие XML-элемента.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$start_handlerfunction (XMLParser $parser, string $name, array $attributes): void — имя и ассоциативный массив атрибутов.
$end_handlerfunction (XMLParser $parser, string $name): void — имя закрываемого элемента. Любой из двух может быть null.

Имена тегов и ключи атрибутов подчиняются CASE_FOLDING; значения атрибутов не меняют регистр. Если атрибутов нет, массив пуст. Порядок их обхода соответствует порядку в документе, но бизнес-логика обычно обращается по имени. Для вложенности храните глубину/стек. Обработчик не получает текст — его назначают отдельно.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. Регистрация opened/closed сохраняет callable
  2. xml_parse читает <order> и вызывает opened(parser,order,[])
  3. Для <item/> вызывает opened и closed
  4. После </order> xml_parse возвращает 1, программа идёт дальше
Увидеть, кто вызывает функции начала и конца тегаСамостоятельный пример
<?php
function opened($parser, $name, $attributes) {
    echo 'start:', $name, "
";
}
function closed($parser, $name) {
    echo 'end:', $name, "
";
}
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, 'opened', 'closed');
echo "До parse
";
$ok = xml_parse($parser, '<order><item/></order>', true);
echo 'После parse:', $ok, "
";
Ожидаемый результат
До parse
start:order
start:item
end:item
end:order
После parse:1

При регистрации передаём имена без (). Вызовы opened/closed делает xml_parse, пока читает документ. Даже самозакрывающийся item создаёт события начала и конца. function лишь объявляет обработчик; регистрация его тоже не исполняет.

Получить артикулы и посчитать товарыСамостоятельный пример
<?php
$count = 0;
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser, function ($p, $name, $attributes) use (&$count) {
    if ($name === 'item') {
        echo $attributes['sku'], "
";
        $count++;
    }
}, null);
xml_parse($parser, '<catalog><item sku="A"/><item sku="B"/></catalog>', true);
echo 'Товаров:', $count, "
";
Ожидаемый результат
A
B
Товаров:2

Замыкание function(...) получает аргументы от парсера. use (&$count) даёт ему доступ к внешнему счётчику с сохранением изменений; без & была бы захвачена отдельная копия. null отключает ненужный end-handler. В реальном импорте наличие sku проверяют до чтения ключа.

Связанные операции: xml_set_character_data_handler(), xml_parse().

xml_set_character_data_handlerПолучать текстовые кусочки и собрать значение поля без потери данных.

PHP 4; примеры PHP 8.2+ · xml

xml_set_character_data_handler(XMLParser $parser, callable|string|null $handler): true

Получать текстовые кусочки и собрать значение поля без потери данных.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string $data): void. $data — очередной текст, не обязательно весь элемент.

События включают пробельное содержимое между тегами и текст CDATA. Сущности декодируются по правилам разбора. Не ожидайте один вызов на элемент или на порцию xml_parse. Приложение хранит текущий путь/глубину и накапливает .= до закрывающего события. XML_OPTION_SKIP_WHITE не отключает пользовательский character callback в PHP 8.5.11: эта фильтрация применяется к структуре values.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. <title> начинает сбор значения
  2. Callbacks дают Paper , затем & и pen либо иные части
  3. Каждый $data дописывается через .=
  4. Конец title фиксирует целое значение Paper & pen
Склеить текст, пришедший несколькими событиямиСамостоятельный пример
<?php
$text = '';
$parser = xml_parser_create();
xml_set_character_data_handler($parser, function ($p, $data) use (&$text) {
    $text .= $data;
});
xml_parse($parser, '<title>Paper ', false);
xml_parse($parser, '&amp; pen</title>', true);
echo $text, "
";
Ожидаемый результат
Paper & pen

Оператор .= дописывает кусочек. Присваивание = оставляло бы только последнюю порцию. Даже один цельный вход может вызвать callback несколько раз — на сущностях, Unicode и внутренних границах; размер события не является размером элемента.

Собирать title, сохраняя пробелы вокруг вложенного выделенияСамостоятельный пример
<?php
$depth = 0;
$titleDepth = null;
$title = '';
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$depth, &$titleDepth, &$title) {
        $depth++;
        if ($name === 'title') { $titleDepth = $depth; $title = ''; }
    },
    function ($p, $name) use (&$depth, &$titleDepth, &$title) {
        if ($titleDepth === $depth) { echo $title, "
"; $titleDepth = null; }
        $depth--;
    }
);
xml_set_character_data_handler($parser, function ($p, $data) use (&$titleDepth, &$title) {
    if ($titleDepth !== null) { $title .= $data; }
});
xml_parse($parser, '<item><title>Buy <b>today</b> now</title><qty>2</qty></item>', true);
Ожидаемый результат
Buy today now

titleDepth запоминает глубину выбранного title. Текст вложенного b включён, qty после закрытия title — нет. Не делайте trim каждого кусочка: исчезнут нужные разделяющие пробелы. Сценарий предполагает, что title не вложены друг в друга; для произвольной схемы нужен стек буферов.

Связанные операции: xml_set_element_handler(), xml_parse(), xml_parser_set_option().

xml_set_processing_instruction_handlerРазобрать метаданные PI, если они нужны договору обмена.

PHP 4; примеры PHP 8.2+ · xml

xml_set_processing_instruction_handler(XMLParser $parser, callable|string|null $handler): true

Разобрать метаданные PI, если они нужны договору обмена.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string $target, string $data): void; target — имя инструкции, data — её текст без границ.

Применение PI зависит от программы; XML-парсер не выполняет содержащийся текст. Имена, начинающиеся с XML, зарезервированы стандартом. Не переносите eval из старых демонстраций XML в обработку интеграционных документов.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. В документе <?shop mode="compact"?>
  2. Парсер вызывает callback с target=shop и data=mode="compact"
  3. Приложение принимает только известную комбинацию
Прочитать служебную инструкцию без её исполненияСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_set_processing_instruction_handler($parser, function ($p, $target, $data) {
    echo $target, ':', $data, "
";
});
xml_parse($parser, '<?shop mode="compact"?><order/>', true);
Ожидаемый результат
shop:mode="compact"

<?shop ...?> — инструкция обработки. Парсер передаёт имя shop и содержимое как строки; он не исполняет программу, не запускает оболочку и не загружает файлы.

Отобрать известный тип метаданныхСамостоятельный пример
<?php
$modes = [];
$parser = xml_parser_create();
xml_set_processing_instruction_handler($parser, function ($p, $target, $data) use (&$modes) {
    if ($target === 'shop' && $data === 'mode="compact"') { $modes[] = 'compact'; }
});
xml_parse($parser, '<?unknown ignore?><?shop mode="compact"?><order/>', true);
echo implode(',', $modes), "
";
Ожидаемый результат
compact

Разрешённый смысл определяет приложение. Чужую PI нельзя передавать eval. XML-декларация <?xml ...?> — отдельная часть формата, не произвольная исполняемая PI; ?> внутри содержимого завершает инструкцию.

Связанные операции: xml_set_default_handler(), xml_parse().

xml_set_default_handlerПолучать части XML, для которых нет другого обработчика, например комментарии.

PHP 4; примеры PHP 8.2+ · xml

xml_set_default_handler(XMLParser $parser, callable|string|null $handler): true

Получать части XML, для которых нет другого обработчика, например комментарии.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string $data): void; кусок декларации/комментария/других данных.

Точное разбиение и выдача деклараций зависят от SAX-реализации. Default не является побайтовой копией входа. Наличие обработчика влияет на доставку внутренних сущностей; если нужны только значения полей, начните с element/character handlers. Не выводите диагностические фрагменты посетителю без HTML-экранирования.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. Элементы и текст направлены своим обработчикам
  2. Комментарий попадает в default
  3. Приложение собирает диагностическую строку отдельно от бизнес-данных
Увидеть комментарий, не выдаваемый text-handlerСамостоятельный пример
<?php
$parts = '';
$parser = xml_parser_create();
xml_set_default_handler($parser, function ($p, $data) use (&$parts) { $parts .= $data; });
xml_set_element_handler($parser, function ($p, $n, $a) {}, function ($p, $n) {});
xml_set_character_data_handler($parser, function ($p, $data) {});
xml_parse($parser, '<order><!--batch 42--></order>', true);
echo $parts, "
";
Ожидаемый результат
<!--batch 42-->

Специальные обработчики забирают элементы/обычный текст; default получает оставшееся. Части тоже накапливаются, число вызовов не фиксируется.

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

$comments = '';
$parser = xml_parser_create();
xml_set_default_handler($parser, function ($p, $part) use (&$comments) { $comments .= $part; });
xml_set_default_handler($parser, null);
if (xml_parse($parser, '<order><!--internal note--></order>', true) !== 1) {
    throw new RuntimeException('Повреждён заказ');
}
echo $comments === '' ? "Заказ прочитан; комментарий не попал в отчёт\n" : $comments;
Ожидаемый результат
Заказ прочитан; комментарий не попал в отчёт

null отменяет именно регистрацию обработчика. Комментарий остаётся в XML, но этот сборщик его уже не получает. Пустая строка для сброса устарела с PHP 8.4.

Связанные операции: xml_set_character_data_handler(), xml_set_processing_instruction_handler().

xml_set_start_namespace_decl_handlerПолучать объявления namespace до события открывающего элемента.

PHP 4; примеры PHP 8.2+ · xml

xml_set_start_namespace_decl_handler(XMLParser $parser, callable|string|null $handler): true

Получать объявления namespace до события открывающего элемента.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string|false $prefix, string $uri): void; false означает отсутствие префикса.

Нужен namespace-aware parser от xml_parser_create_ns. Для каждого объявления в начальном теге событие идёт перед его start-handler. Если требуется стек областей, учитывайте, что парная end-namespace функция не вызывается libxml-реализацией.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. xmlns:s объявляет связь s → urn:shop
  2. Callback namespace получает два значения
  3. Затем start элемента получает urn:shop|item
Объявление namespace приходит раньше элементаСамостоятельный пример
<?php
$parser = xml_parser_create_ns('UTF-8', '|');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_start_namespace_decl_handler($parser, function ($p, $prefix, $uri) {
    echo 'ns:', $prefix === false ? '(default)' : $prefix, '=', $uri, "
";
});
xml_set_element_handler($parser, function ($p, $name, $attrs) { echo 'tag:', $name, "
"; }, null);
xml_parse($parser, '<s:item xmlns:s="urn:shop"/>', true);
Ожидаемый результат
ns:s=urn:shop
tag:urn:shop|item

Сначала парсер сообщает объявленную связь, затем вызывает start элемента. Обработчик получает префикс и URI отдельно.

Пространство по умолчанию не имеет префиксаСамостоятельный пример
<?php
$parser = xml_parser_create_ns();
xml_set_start_namespace_decl_handler($parser, function ($p, $prefix, $uri) {
    echo $prefix === false ? 'default' : $prefix;
    echo '=', $uri, "
";
});
xml_parse($parser, '<item xmlns="urn:shop"/>', true);
Ожидаемый результат
default=urn:shop

Для отсутствующего префикса контракт допускает false; не требуйте string во всех параметрах callback. namespace URI — имя словаря, а не адрес для автоматического чтения.

Связанные операции: xml_parser_create_ns(), xml_set_end_namespace_decl_handler().

xml_set_end_namespace_decl_handlerНазначить callback выхода из области namespace в поддерживающей это реализации.

PHP 4.0.5; libxml не генерирует событие · xml

xml_set_end_namespace_decl_handler(XMLParser $parser, callable|string|null $handler): true

Назначить callback выхода из области namespace в поддерживающей это реализации.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string|false $prefix): void; false — default namespace.

Регистрация успешна и возвращает true даже в libxml, где событие вообще не вызывается. В Expat событие предусмотрено после end-handler элемента, объявившего пространство, отдельно для каждого объявления. Проверяйте XML_SAX_IMPL; не стройте переносимую логику очистки только на этом callback.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. Функция успешно сохраняет callback
  2. Expat может вызвать его после закрытия элемента
  3. libxml такого события не выдаёт; используйте стек element-событий
Честно проверить поддержку закрывающего namespace-событияСамостоятельный пример
<?php
$ends = [];
$parser = xml_parser_create_ns();
xml_set_end_namespace_decl_handler($parser, function ($p, $prefix) use (&$ends) { $ends[] = $prefix; });
xml_parse($parser, '<s:item xmlns:s="urn:shop"/>', true);
if (XML_SAX_IMPL === 'libxml') {
    echo count($ends) === 0 ? "libxml: событие не вызывается
" : "Неожиданный результат
";
} else {
    echo 'Expat: событий=', count($ends), "
";
}
Ожидаемый результат
libxml: событие не вызывается

Ожидаемый вывод относится к используемой сборке PHP 8.5.11 с libxml. В Expat событие предусмотрено после end-element; это не проверка Expat на данной машине.

Область элемента можно отслеживать обычной парой событийСамостоятельный пример
<?php
$depth = 0;
$parser = xml_parser_create_ns('UTF-8', '|');
xml_set_end_namespace_decl_handler($parser, null);
xml_set_element_handler($parser,
    function ($p, $name, $attrs) use (&$depth) { $depth++; },
    function ($p, $name) use (&$depth) { $depth--; }
);
xml_parse($parser, '<s:root xmlns:s="urn:shop"><s:item/></s:root>', true);
echo 'Глубина после документа:', $depth, "
";
Ожидаемый результат
Глубина после документа:0

Для собственного namespace-стека связывайте объявления с глубиной и снимайте их по end-element, а не ожидайте неподдерживаемое событие. Здесь показан минимальный счётчик области, не полная таблица повторных объявлений.

Связанные операции: xml_set_start_namespace_decl_handler(), xml_set_element_handler().

xml_set_external_entity_ref_handlerЗадать явную политику обработки ссылок на внешние разобранные сущности.

PHP 4; примеры PHP 8.2+ · xml

xml_set_external_entity_ref_handler(XMLParser $parser, callable|string|null $handler): true

Задать явную политику обработки ссылок на внешние разобранные сущности.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string $open_entity_names, string|false $base, string $system_id, string|false $public_id): bool.
$open_entity_namesСписок открытых имён сущностей, разделённых пробелами; содержит текущую.
$baseБаза разрешения относительного system_id; может отсутствовать (false/пустая строка в реализации).
$system_idСистемный идентификатор из декларации; не доверенный путь.
$public_idПубличный ID с XML-нормализацией пробелов; отсутствие может представляться false/пустой строкой.

Функция регистрации возвращает true. Сам callback должен вернуть true при обработке или false при отказе; отказ прекращает разбор с XML_ERROR_EXTERNAL_ENTITY_HANDLING. В libxml это учитывается с PHP 7.3, раньше bool игнорировался. Не следуйте автоматически URL/путям из XML. Ограничивайте разрешённые ID, объём и число зависимостей; при разборе произвольного внешнего фрагмента отдельным парсером нужно учитывать его контекст/namespace, а здесь ожидается полный известный документ.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. XML содержит &terms; с SYSTEM ID
  2. Парсер вызывает callback с urn:shop:terms
  3. Приложение выбирает только собственный разрешённый текст
  4. Отдельный parser проверяет его; bool возвращается исходному разбору
Отклонить заказ с неизвестной внешней зависимостьюСамостоятельный пример
<?php
declare(strict_types=1);

$parser = xml_parser_create();
xml_set_external_entity_ref_handler($parser, function ($p, $open, $base, $system, $public) {
    echo 'Неизвестная зависимость: ', $system, "\n";
    return false;
});
$xml = '<!DOCTYPE order [<!ENTITY terms SYSTEM "urn:shop:unknown">]><order>&terms;</order>';
if (xml_parse($parser, $xml, true) !== 1
    && xml_get_error_code($parser) === XML_ERROR_EXTERNAL_ENTITY_HANDLING) {
    echo "Заказ отклонён: согласуйте словарь условий с поставщиком\n";
}
Ожидаемый результат
Неизвестная зависимость: urn:shop:unknown
Заказ отклонён: согласуйте словарь условий с поставщиком

false у этого callback имеет специальный смысл: отказ обработки внешней сущности. Сеть и файлы не открываются. Следующий пример разрешает только один собственный ID с заранее известным содержимым.

Разрешить только известный ID и разобрать собственные данныеСамостоятельный пример
<?php
$known = ['urn:shop:terms' => '<terms>Prepaid</terms>'];
$terms = '';
$parser = xml_parser_create();
xml_set_external_entity_ref_handler($parser, function ($p, $open, $base, $system, $public) use ($known, &$terms) {
    if (!isset($known[$system])) { return false; }
    $child = xml_parser_create();
    xml_set_character_data_handler($child, function ($inner, $data) use (&$terms) { $terms .= $data; });
    return xml_parse($child, $known[$system], true) === 1;
});
$xml = '<!DOCTYPE order [<!ENTITY terms SYSTEM "urn:shop:terms">]><order>&terms;</order>';
$ok = xml_parse($parser, $xml, true);
if ($ok !== 1) { throw new RuntimeException('Ошибка внешней сущности'); }
echo $terms, "
";
Ожидаемый результат
Prepaid

ID сопоставляется с известным содержимым, не с произвольным путём из XML. Новый parser нужен потому, что исходный сейчас активен. true не вставляет строку автоматически в обычный text-handler родителя: приложение само сохраняет полученные данные. Пример намеренно разрешает одну фиксированную зависимость и не устанавливает такой загрузчик ребёнку.

Связанные операции: xml_set_default_handler(), xml_parse(), xml_get_error_code().

xml_set_notation_decl_handlerЧитать объявления NOTATION из DTD, если формат обмена использует не-XML вложения.

PHP 4; примеры PHP 8.2+ · xml

xml_set_notation_decl_handler(XMLParser $parser, callable|string|null $handler): true

Читать объявления NOTATION из DTD, если формат обмена использует не-XML вложения.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string $notation_name, string|false $base, string|false $system_id, string|false $public_id): void.
$notation_nameИмя нотации, на которое может ссылаться NDATA.
$baseБаза для system_id, если существует.
$system_idСистемный ID; при PUBLIC-only может отсутствовать. Страница PHP показывает string, но tagged реализация передаёт false для отсутствующего значения.
$public_idПубличный ID либо отсутствие.

Событие относится к декларации, а не к использованию файла. NOTATION имеет форму SYSTEM либо PUBLIC (с допустимым системным ID по XML-правилам). Дополнительную загрузку и проверку ресурса решает приложение; функции для автоматического скачивания здесь нет.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. DTD объявляет jpeg как image/jpeg
  2. Callback получает name и идентификаторы
  3. Приложение сохраняет метаданные, не скачивая ресурс
Получить декларацию формата внешнего изображенияСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_set_notation_decl_handler($parser, function ($p, $name, $base, $system, $public) {
    echo $name, '=', $system, "
";
});
xml_parse($parser, '<!DOCTYPE item [<!NOTATION jpeg SYSTEM "image/jpeg">]><item/>', true);
Ожидаемый результат
jpeg=image/jpeg

NOTATION обозначает формат неразбираемого ресурса. Этот callback сообщает объявление; он не загружает изображение и не проверяет его MIME по содержимому.

Сохранить публичный идентификатор нотацииСамостоятельный пример
<?php
$names = [];
$parser = xml_parser_create();
xml_set_notation_decl_handler($parser, function ($p, $name, $base, $system, $public) use (&$names) {
    $names[$name] = $public;
});
xml_parse($parser, '<!DOCTYPE item [<!NOTATION drawing PUBLIC "-//Example//Drawing//EN">]><item/>', true);
echo $names['drawing'], "
";
Ожидаемый результат
-//Example//Drawing//EN

У PUBLIC-нотации системный ID может отсутствовать. Поэтому callback в примере не навязывает string всем необязательным параметрам; нижняя библиотека передаёт отсутствие как false.

Связанные операции: xml_set_unparsed_entity_decl_handler(), xml_set_external_entity_ref_handler().

xml_set_unparsed_entity_decl_handlerПолучать декларации внешних NDATA-сущностей без попытки разобрать их содержимое как XML.

PHP 4; примеры PHP 8.2+ · xml

xml_set_unparsed_entity_decl_handler(XMLParser $parser, callable|string|null $handler): true

Получать декларации внешних NDATA-сущностей без попытки разобрать их содержимое как XML.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$handlerCallable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция.
Callbackfunction (XMLParser $parser, string $entity_name, string|false $base, string $system_id, string|false $public_id, string|false $notation_name): void.
$entity_nameИмя сущности.
$baseБаза разрешения system_id либо отсутствие.
$system_id / $public_idСистемный и необязательный публичный идентификаторы из декларации.
$notation_nameИмя связанной NOTATION; это формат ресурса, а не содержимое файла.

Вызывается на объявлении ENTITY ... NDATA ..., а не на каждом чтении атрибута с её именем. Отличается от external_entity_ref_handler: последний обрабатывает ссылку на внешнюю разобранную общую сущность. Регистрация не включает загрузку файлов.

Обработчик — функция, которую вызовет парсер. Вы передаёте её в настройку, но не вызываете в этот момент. Внутри xml_parse() парсер встречает событие, подставляет значения в параметры обработчика, выполняет его тело и продолжает чтение XML. Обычный return из обработчика возвращает управление парсеру, а не выходит из всей программы. Обычные обработчики ничего не возвращают; только обработчик внешней сущности имеет специальный bool-договор.

Регистрация возвращает true, но ещё не проверяет XML. Передавайте callable: имя объявленной функции, замыкание или [$object, 'method']; null отключает/сбрасывает обработчик. С PHP 8.4 проверка callable происходит при регистрации; пустая строка для сброса и простое имя метода через xml_set_object устарели. Неверный callable даёт ошибку аргумента. В параметры обработчика не добавляйте &$parser: объект передаётся без требования ссылки. Обработчик может выбросить исключение — это ошибка вашего кода/политики, отдельная от кода XML-синтаксиса.

События и данные этого примера
  1. DTD объявляет NOTATION jpeg
  2. ENTITY cover получает SYSTEM cover.jpg и NDATA jpeg
  3. Callback сохраняет метаданные; атрибут image=cover может на них ссылаться
Узнать, что logo — неразбираемое внешнее вложениеСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, function ($p, $name, $base, $system, $public, $notation) {
    echo $name, ':', $system, ':', $notation, "
";
});
$xml = '<!DOCTYPE item [<!NOTATION jpeg SYSTEM "image/jpeg"><!ENTITY logo SYSTEM "logo.jpg" NDATA jpeg>]><item/>';
var_dump(xml_parse($parser, $xml, true));
Ожидаемый результат
logo:logo.jpg:jpeg
int(1)

NDATA связывает внешнюю сущность с нотацией. logo.jpg не открывается: читается декларация. Неразбираемую сущность нельзя вставить в текст документа обычной ссылкой &logo;.

Сопоставить объявление с атрибутом документаСамостоятельный пример
<?php
$entities = [];
$selected = '';
$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, function ($p, $name, $base, $system, $public, $notation) use (&$entities) {
    $entities[$name] = ['system' => $system, 'notation' => $notation];
});
xml_set_element_handler($parser, function ($p, $name, $attrs) use (&$selected) {
    if ($name === 'ITEM') { $selected = $attrs['IMAGE']; }
}, null);
$xml = '<!DOCTYPE item [<!NOTATION jpeg SYSTEM "image/jpeg"><!ENTITY cover SYSTEM "cover.jpg" NDATA jpeg><!ATTLIST item image ENTITY #IMPLIED>]><item image="cover"/>';
if (xml_parse($parser, $xml, true) !== 1) { throw new RuntimeException('Ошибка XML'); }
echo $entities[$selected]['system'], "
";
Ожидаемый результат
cover.jpg

Имя сущности приходит в атрибуте IMAGE и связывается с таблицей объявлений. Реальное приложение проверит существование ключей и разрешённые пути. XML Parser не проводит полную DTD-валидацию такой бизнес-связи.

Связанные операции: xml_set_notation_decl_handler(), xml_set_external_entity_ref_handler().

xml_set_objectРазобраться со старым способом привязки строковых имён обработчиков к объекту.

PHP 4; deprecated PHP 8.4 · xml

xml_set_object(XMLParser $parser, object $object): true

Разобраться со старым способом привязки строковых имён обработчиков к объекту.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.
$objectОбъект, в котором старые строковые имена обработчиков трактуются как методы.

Всегда true при успешном назначении, но deprecated с PHP 8.4. Передавайте прямые method-callables в xml_set_*_handler, а не настраивайте скрытый объект. Если старый объект заменяют, уже зарегистрированные методы должны существовать у нового — в PHP 8.5 возможен ValueError. PHP 8 занял глобальное имя класса XMLParser: свой старый класс так больше не называйте.

События и данные этого примера
  1. Старый код привязывает объект и строку opened отдельно
  2. Современный callable сразу содержит объект и метод
  3. Парсер вызывает opened; $this->count сохраняет число событий
Собрать позиции конкретного заказа в объектеСамостоятельный пример
<?php
declare(strict_types=1);

class OrderItems {
    public array $skus = [];
    public function opened($parser, $name, $attributes): void {
        if ($name === 'ITEM') { $this->skus[] = $attributes['SKU']; }
    }
}
$items = new OrderItems();
$parser = xml_parser_create();
xml_set_element_handler($parser, [$items, 'opened'], null);
if (xml_parse($parser, '<order><item sku="A1"/><item sku="B2"/></order>', true) !== 1) {
    throw new RuntimeException('Повреждён заказ');
}
echo 'К комплектации: ', implode(', ', $items->skus), "\n";
Ожидаемый результат
К комплектации: A1, B2

Класс объединяет список SKU и действие добавления. public позволяет читать список; $this обозначает данный OrderItems. Callable [$items, opened] связывает метод с этим объектом. Новый импорт получает другой объект. В учебном XML SKU известны; полноценный обработчик ниже проверяет их наличие.

Распознать устаревшую регистрацию при миграцииСамостоятельный пример
<?php
class LegacyCounter {
    public $count = 0;
    public function opened($p, $name, $attrs) { $this->count++; }
}
$counter = new LegacyCounter();
$parser = xml_parser_create();
$deprecated = false;
set_error_handler(function ($severity, $message) use (&$deprecated) {
    if ($severity === E_DEPRECATED) { $deprecated = true; return true; }
    return false;
});
try {
    xml_set_object($parser, $counter);
    xml_set_element_handler($parser, 'opened', null);
} finally { restore_error_handler(); }
xml_parse($parser, '<item/>', true);
echo $deprecated ? "Нужен callable вместо старой связки
" : "Старое окружение
";
echo $counter->count, "
";
Ожидаемый результат
Нужен callable вместо старой связки
1

Фактический результат PHP 8.5: код ещё работает с deprecation, которую пример специально перехватывает. Исправление — предыдущий пример. В PHP 8.4 проверка строкового метода происходит сразу, поэтому даже старый порядок требует сначала xml_set_object.

Связанные операции: xml_set_element_handler(), xml_set_character_data_handler().

xml_get_error_codeПолучить причину неуспешного разбора или XML_ERROR_NONE.

PHP 4; примеры PHP 8.2+ · xml

xml_get_error_code(XMLParser $parser): int

Получить причину неуспешного разбора или XML_ERROR_NONE.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.

Возвращает код состояния parser. Сравнивайте с XML_ERROR_* по имени, не зашивайте числа. Набор констант общий, но соответствие низкоуровневым ошибкам/доступность конкретного кода зависит от реализации. Исключение вашего callback не обязано стать отдельным XML_ERROR_*.

События и данные этого примера
  1. xml_parse вернул 0
  2. xml_get_error_code читает причину из того же parser
  3. xml_error_string и позиция дополняют отчёт
Отличить успех от ошибки закрывающего тегаСамостоятельный пример
<?php
foreach (['<order/>', '<order></item>'] as $xml) {
    $parser = xml_parser_create();
    $status = xml_parse($parser, $xml, true);
    $code = xml_get_error_code($parser);
    echo $status === 1 && $code === XML_ERROR_NONE ? "OK
" : "Ошибка XML
";
}
Ожидаемый результат
OK
Ошибка XML

Код не равен возвращаемому xml_parse: parse даёт 1/0, getter — причину. При ошибке обработчики могли уже получить часть документа.

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

$xml = file_get_contents(__DIR__ . '/broken-order.xml');
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true) !== 1) {
    $line = xml_get_current_line_number($parser);
    echo 'broken-order.xml, строка ', $line, ': ',
        xml_error_string(xml_get_error_code($parser)) ?? 'Unknown', "\n";
    echo explode("\n", $xml)[$line - 1], "\n";
    echo 'Колонка: ', xml_get_current_column_number($parser),
        '; байт: ', xml_get_current_byte_index($parser), "\n";
    echo "Заказ отклонён: закрывайте item перед order\n";
}
Ожидаемый результат
broken-order.xml, строка 2: Mismatched tag
<item></order>
Колонка: 15; байт: 22
Заказ отклонён: закрывайте item перед order

Здесь короткий учебный файл целиком безопасен для показа. line указывает место обнаружения; вычитаем 1 для индекса массива строк. Колонка и байт — диагностическая позиция backend, не всегда первый неверный символ. В журнале реального сервиса не публикуйте закрытые данные документа.

Связанные операции: xml_error_string(), xml_parse().

xml_error_stringПреобразовать XML-код ошибки в диагностическое описание.

PHP 4; примеры PHP 8.2+ · xml

xml_error_string(int $error_code): ?string

Преобразовать XML-код ошибки в диагностическое описание.

ПараметрЗначение
$error_codeЦелое значение из xml_get_error_code либо известная XML_ERROR_* константа.

Возвращает текст, а если описание не найдено — null либо Unknown в зависимости от реализации. Не гарантирует локализацию и стабильную формулировку. Символ ? у string означает возможность null.

События и данные этого примера
  1. Из parser получен код причины
  2. xml_error_string ищет описание
  3. Приложение добавляет fallback и позицию ошибки
Показать конкретную причину несовпадения теговСамостоятельный пример
<?php
declare(strict_types=1);

$parser = xml_parser_create();
xml_parse($parser, '<order></item>', true);
echo xml_error_string(xml_get_error_code($parser)), "\n";
Ожидаемый результат
Mismatched tag

Передаём именно код, полученный от parser, как требует контракт xml_error_string. В нашей libxml-сборке подстановка XML_ERROR_TAG_MISMATCH напрямую дала другое описание: набор экспортированных констант нельзя считать универсальной таблицей номеров backend. Английская формулировка также может отличаться. Здесь сопоставьте открывающий order и закрывающий item.

Не предполагать наличие текста для любого целогоСамостоятельный пример
<?php
$description = xml_error_string(-100);
if ($description === null || $description === 'Unknown') {
    echo "Неизвестный код ошибки
";
} else {
    echo $description, "
";
}
Ожидаемый результат
Неизвестный код ошибки

По официальному договору неизвестный код может дать null или Unknown. Строка не является устойчивым идентификатором ошибки.

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

$xml = file_get_contents(__DIR__ . '/broken-order.xml');
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true) !== 1) {
    $line = xml_get_current_line_number($parser);
    echo 'broken-order.xml, строка ', $line, ': ',
        xml_error_string(xml_get_error_code($parser)) ?? 'Unknown', "\n";
    echo explode("\n", $xml)[$line - 1], "\n";
    echo 'Колонка: ', xml_get_current_column_number($parser),
        '; байт: ', xml_get_current_byte_index($parser), "\n";
    echo "Заказ отклонён: закрывайте item перед order\n";
}
Ожидаемый результат
broken-order.xml, строка 2: Mismatched tag
<item></order>
Колонка: 15; байт: 22
Заказ отклонён: закрывайте item перед order

Здесь короткий учебный файл целиком безопасен для показа. line указывает место обнаружения; вычитаем 1 для индекса массива строк. Колонка и байт — диагностическая позиция backend, не всегда первый неверный символ. В журнале реального сервиса не публикуйте закрытые данные документа.

Связанные операции: xml_get_error_code(), xml_get_current_line_number().

xml_get_current_line_numberУзнать строку, на которой парсер находится в данный момент.

PHP 4; примеры PHP 8.2+ · xml

xml_get_current_line_number(XMLParser $parser): int

Узнать строку, на которой парсер находится в данный момент.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.

Возвращает int с текущей строкой. Удобен при callback и после ошибки; это положение обнаружения, не обещание точного места первопричины.

События и данные этого примера
  1. xml_parse дошёл до item или обнаружил неверное закрытие
  2. Getter читает позицию того же объекта parser
  3. Приложение сохраняет отметку вместе с номером записи/ошибкой
Запомнить позицию в момент начала товараСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    if ($name === 'ITEM') {
        echo 'Строка:', xml_get_current_line_number($p), "
";
        echo 'Колонка:', xml_get_current_column_number($p), "
";
        echo 'Байт:', xml_get_current_byte_index($p), "
";
    }
}, null);
xml_parse($parser, "<root>
<item/>
</root>", true);
Ожидаемый результат
Строка:2
Колонка:6
Байт:12

Getter внутри callback связывает бизнес-событие с позицией парсера. Она может указывать после части прочитанного тега, а не на его <; сохраните её как диагностическую отметку, не как универсальную границу substr.

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

$xml = file_get_contents(__DIR__ . '/broken-order.xml');
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true) !== 1) {
    $line = xml_get_current_line_number($parser);
    echo 'broken-order.xml, строка ', $line, ': ',
        xml_error_string(xml_get_error_code($parser)) ?? 'Unknown', "\n";
    echo explode("\n", $xml)[$line - 1], "\n";
    echo 'Колонка: ', xml_get_current_column_number($parser),
        '; байт: ', xml_get_current_byte_index($parser), "\n";
    echo "Заказ отклонён: закрывайте item перед order\n";
}
Ожидаемый результат
broken-order.xml, строка 2: Mismatched tag
<item></order>
Колонка: 15; байт: 22
Заказ отклонён: закрывайте item перед order

Здесь короткий учебный файл целиком безопасен для показа. line указывает место обнаружения; вычитаем 1 для индекса массива строк. Колонка и байт — диагностическая позиция backend, не всегда первый неверный символ. В журнале реального сервиса не публикуйте закрытые данные документа.

Связанные операции: xml_get_current_column_number(), xml_get_current_byte_index(), xml_get_error_code().

xml_get_current_column_numberУточнить положение на текущей строке разбора.

PHP 4; примеры PHP 8.2+ · xml

xml_get_current_column_number(XMLParser $parser): int

Уточнить положение на текущей строке разбора.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.

Возвращает int с колонкой текущего состояния. Не трактуйте колонку как универсальное число Unicode-символов для всех SAX-реализаций; учитывайте кодировку и конкретный backend.

События и данные этого примера
  1. xml_parse дошёл до item или обнаружил неверное закрытие
  2. Getter читает позицию того же объекта parser
  3. Приложение сохраняет отметку вместе с номером записи/ошибкой
Запомнить позицию в момент начала товараСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    if ($name === 'ITEM') {
        echo 'Строка:', xml_get_current_line_number($p), "
";
        echo 'Колонка:', xml_get_current_column_number($p), "
";
        echo 'Байт:', xml_get_current_byte_index($p), "
";
    }
}, null);
xml_parse($parser, "<root>
<item/>
</root>", true);
Ожидаемый результат
Строка:2
Колонка:6
Байт:12

Getter внутри callback связывает бизнес-событие с позицией парсера. Она может указывать после части прочитанного тега, а не на его <; сохраните её как диагностическую отметку, не как универсальную границу substr.

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

$xml = file_get_contents(__DIR__ . '/broken-order.xml');
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true) !== 1) {
    $line = xml_get_current_line_number($parser);
    echo 'broken-order.xml, строка ', $line, ': ',
        xml_error_string(xml_get_error_code($parser)) ?? 'Unknown', "\n";
    echo explode("\n", $xml)[$line - 1], "\n";
    echo 'Колонка: ', xml_get_current_column_number($parser),
        '; байт: ', xml_get_current_byte_index($parser), "\n";
    echo "Заказ отклонён: закрывайте item перед order\n";
}
Ожидаемый результат
broken-order.xml, строка 2: Mismatched tag
<item></order>
Колонка: 15; байт: 22
Заказ отклонён: закрывайте item перед order

Здесь короткий учебный файл целиком безопасен для показа. line указывает место обнаружения; вычитаем 1 для индекса массива строк. Колонка и байт — диагностическая позиция backend, не всегда первый неверный символ. В журнале реального сервиса не публикуйте закрытые данные документа.

Связанные операции: xml_get_current_line_number(), xml_get_current_byte_index(), xml_get_error_code().

xml_get_current_byte_indexПолучить текущий байтовый индекс для технической диагностики.

PHP 4; примеры PHP 8.2+ · xml

xml_get_current_byte_index(XMLParser $parser): int

Получить текущий байтовый индекс для технической диагностики.

ПараметрЗначение
$parserОбъект, полученный xml_parser_create()/xml_parser_create_ns(). С PHP 8.0 это XMLParser, а не ресурс.

Возвращает int, индекс начинается с 0. Официальная документация предупреждает: индекс относится к UTF-8 представлению даже при другой входной кодировке. Это не номер символа и не безопасное смещение для резки исходного файла другой кодировки.

События и данные этого примера
  1. xml_parse дошёл до item или обнаружил неверное закрытие
  2. Getter читает позицию того же объекта parser
  3. Приложение сохраняет отметку вместе с номером записи/ошибкой
Запомнить позицию в момент начала товараСамостоятельный пример
<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, function ($p, $name, $attrs) {
    if ($name === 'ITEM') {
        echo 'Строка:', xml_get_current_line_number($p), "
";
        echo 'Колонка:', xml_get_current_column_number($p), "
";
        echo 'Байт:', xml_get_current_byte_index($p), "
";
    }
}, null);
xml_parse($parser, "<root>
<item/>
</root>", true);
Ожидаемый результат
Строка:2
Колонка:6
Байт:12

Getter внутри callback связывает бизнес-событие с позицией парсера. Она может указывать после части прочитанного тега, а не на его <; сохраните её как диагностическую отметку, не как универсальную границу substr.

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

$xml = file_get_contents(__DIR__ . '/broken-order.xml');
$parser = xml_parser_create();
if (xml_parse($parser, $xml, true) !== 1) {
    $line = xml_get_current_line_number($parser);
    echo 'broken-order.xml, строка ', $line, ': ',
        xml_error_string(xml_get_error_code($parser)) ?? 'Unknown', "\n";
    echo explode("\n", $xml)[$line - 1], "\n";
    echo 'Колонка: ', xml_get_current_column_number($parser),
        '; байт: ', xml_get_current_byte_index($parser), "\n";
    echo "Заказ отклонён: закрывайте item перед order\n";
}
Ожидаемый результат
broken-order.xml, строка 2: Mismatched tag
<item></order>
Колонка: 15; байт: 22
Заказ отклонён: закрывайте item перед order

Здесь короткий учебный файл целиком безопасен для показа. line указывает место обнаружения; вычитаем 1 для индекса массива строк. Колонка и байт — диагностическая позиция backend, не всегда первый неверный символ. В журнале реального сервиса не публикуйте закрытые данные документа.

Связанные операции: xml_get_current_line_number(), xml_get_current_column_number(), xml_get_error_code().

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

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

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

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

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