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

Дерево руководства
Глава 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 говорит, что файл закончился.
<?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. Для нескольких полей добавим обработчики начала/конца; для ненадёжного входа отложим публикацию результата до успешного завершения.
- Регистрация showStatus ещё ничего не печатает
- xml_parse читает
и встречает Готово - Парсер вызывает showStatus(parser, "Готово")
- Функция печатает текст и возвращает управление парсеру
- Парсер завершает документ, выполняется 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).
- Проверить extension_loaded(xml)
- XML_SAX_IMPL показывает libxml или expat
- Выбрать совместимые обработчики; у libxml нет end-namespace события
<?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, без пользовательского конструктора и публичных методов для чтения узлов. Нельзя клонировать, сериализовать или добавлять динамические свойства. Собственное состояние импорта храните рядом: в переменных замыкания или в вашем классе.
- xml_parser_create создаёт непрозрачный XMLParser
- Обработчики получают его как первый аргумент и хранят свои данные отдельно
- После финального разбора удаляются ненужные ссылки; для следующего документа создаётся новый parser
<?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_*.
<?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-договор.
- xml_set_element_handler сохраняет opened и closed, ничего не печатает
- xml_parse начинает чтение: opened(parser,order,[])
- opened(parser,item,[]) → closed(parser,item)
- closed(parser,order) → возврат 1 из xml_parse
- Выполняется следующая строка «После 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» это склеит слова.
- Начало title: пустая строка
- Получено Paper → дописать
- Получено & и pen → дописать, а не заменить
- Конец 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, '& pen</title>', true);
echo $text, "
";
Ожидаемый результат
Paper & pen
Оператор .= дописывает кусочек. Присваивание = оставляло бы только последнюю порцию. Даже один цельный вход может вызвать callback несколько раз — на сущностях, Unicode и внутренних границах; размер события не является размером элемента.
<?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 категории. Событие сообщает имя, но не создаёт за вас массив полного пути. Храните стек имён/контекстов, если нужно различать места. Минимальная глубина ниже помогает сначала увидеть порядок событий.
- catalog: глубина 0 → 1
- item: глубина 1 → 2
- title: глубина 2 → 3
- Закрытия уменьшают глубину в обратном порядке
<?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_FOLDING | bool; исходно включена | Поднимает имена элементов/атрибутов в верхний регистр, не значения. Отключайте, чтобы сохранять идентичность XML-имён/URI. |
| XML_OPTION_TARGET_ENCODING | string; UTF-8 по умолчанию | UTF-8, ISO-8859-1 или US-ASCII для строк, передаваемых обработчикам/массивам. Непредставимые символы могут стать вопросительными знаками. |
| XML_OPTION_SKIP_TAGSTART | int; исходно 0 | Отбрасывает начало имени элемента в результатах/handlers, не объявляет namespace. В реализации смещение байтовое: применяйте только к заведомому ASCII-префиксу, не к произвольному UTF-8. |
| XML_OPTION_SKIP_WHITE | bool; исходно выключена | При xml_parse_into_struct пропускает записи/значения только с пробельным содержимым. Не вырезает пробелы внутри текста и не подавляет ваш character callback. |
| XML_OPTION_PARSE_HUGE | bool, PHP 8.4+, libxml | Ослабляет защитные ограничения libxml, в том числе для больших текстовых узлов. Нужен заранее ограниченный вход. Не универсальная опция ускорения и не замена потоковому чтению; нельзя менять внутри callback. |
| XML_SAX_IMPL | string-константа, не опция | libxml либо expat: фактическая реализация, важная для диагностики и поддержки событий. |
- Создать объект
- До parse задать CASE_FOLDING=0 и нужные параметры
- В обработчики/структуру приходят согласованные имена и кодировка
<?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.
<?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.
- XML содержит UTF-8 café
- Parser декодирует XML
- TARGET_ENCODING=US-ASCII не представляет é
- В callback приходит caf?; оставьте UTF-8, чтобы сохранить значение
<?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 означает цельный элемент без дочерних элементов, даже если текст многострочный.
- index[id][0] даёт номер записи id
- values[номер] содержит tag/type/level/value
- value этой записи — строка 42
<?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. Неизвестные теги не выводятся, их текст сохраняется; если целые поддеревья нужно скрывать, добавьте отдельную политику пропуска.
- Parser декодирует A & B до текста A & B
- Карта разрешает strong как HTML-тег
- htmlspecialchars возвращает безопасный HTML-текст
- Готовая HTML-строка выдаётся только после успешного final
<?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 & B</strong></text>', true) !== 1) {
throw new RuntimeException('Ошибка XML');
}
echo $html, "
";
Ожидаемый результат
Buy <strong>A & B</strong>
Ключи карты совпадают с включённым CASE_FOLDING. Только известные имена превращаются в HTML-теги; исходные атрибуты не копируются. Текст после XML-декодирования экранируется заново для HTML. Это узкий преобразователь известного XML-формата, не универсальный HTML-санитайзер.
DTD, внешние сущности, нотации и PI
DTD может объявить имя, которое ссылается на внешние данные. Парсер событиями передаёт декларации/ссылки, а ваше приложение определяет политику. Для простого импорта обычно достаточно отказа от внешней ссылки. Нельзя использовать имя файла/URL из входного XML как безусловное разрешение прочитать его. Декларация NOTATION описывает формат NDATA-ресурса; она не проверяет реальные байты файла.
- Parser встречает ссылку на SYSTEM urn:shop:terms
- Callback сверяет ID с фиксированным разрешённым набором
- Неизвестный ID → false и ошибка; известный → свои ограниченные данные
- Отдельный 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 с заранее известным содержимым.
<?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.
- new OrderItems создаёт пустой список skus
- Callable [$items,opened] привязывает метод к этому объекту
- Parser вызывает метод для каждого ITEM
- После 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>
- Открытие item sku=A1 → current содержит артикул и пустое имя
- Текст Desk дописывается в current[name]
- Закрытие item → готовая строка переносится в rows
- Успешное завершение целого 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 & lamp: обработчик получит обычный &, а CSV сохранит готовое значение. Для редактирования исходного XML событийный подход неудобен: используйте DOM.
Повреждённый заказ не создаёт ведомость
<?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 закрывает файл даже при исключении; он не подавляет ошибку.
<?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 и не равна неправильному аргументу функции. Сообщения полезны для технического журнала; пользователю дают понятное объяснение без полного входного документа, который может содержать закрытые данные.
- xml_parse читает <item></root>
- Возвращает 0, parser сохраняет причину и позицию
- Приложение получает код/описание/строку и отклоняет попытку
<?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_PI | XML-декларация находится в недопустимой позиции. |
| 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_FOLDING | bool; исходно включена | Поднимает имена элементов/атрибутов в верхний регистр, не значения. Отключайте, чтобы сохранять идентичность XML-имён/URI. |
| XML_OPTION_TARGET_ENCODING | string; UTF-8 по умолчанию | UTF-8, ISO-8859-1 или US-ASCII для строк, передаваемых обработчикам/массивам. Непредставимые символы могут стать вопросительными знаками. |
| XML_OPTION_SKIP_TAGSTART | int; исходно 0 | Отбрасывает начало имени элемента в результатах/handlers, не объявляет namespace. В реализации смещение байтовое: применяйте только к заведомому ASCII-префиксу, не к произвольному UTF-8. |
| XML_OPTION_SKIP_WHITE | bool; исходно выключена | При xml_parse_into_struct пропускает записи/значения только с пробельным содержимым. Не вырезает пробелы внутри текста и не подавляет ваш character callback. |
| XML_OPTION_PARSE_HUGE | bool, PHP 8.4+, libxml | Ослабляет защитные ограничения libxml, в том числе для больших текстовых узлов. Нужен заранее ограниченный вход. Не универсальная опция ускорения и не замена потоковому чтению; нельзя менять внутри callback. |
| XML_SAX_IMPL | string-константа, не опция | 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 соединяет строки переводом строки для вывода.
- Первый title → Buy today now
- Второй title → Second title, новый пустой буфер
- Разрыв внутри today не меняет два названия
- Только успешный 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 он не раскрывает частичный результат повреждённого документа.
xml_parser_create()xml_parser_create_ns()xml_parse()xml_parse_into_struct()xml_parser_set_option()xml_parser_get_option()xml_parser_free()xml_set_element_handler()xml_set_character_data_handler()xml_set_processing_instruction_handler()xml_set_default_handler()xml_set_start_namespace_decl_handler()xml_set_end_namespace_decl_handler()xml_set_external_entity_ref_handler()xml_set_notation_decl_handler()xml_set_unparsed_entity_decl_handler()xml_set_object()xml_get_error_code()xml_error_string()xml_get_current_line_number()xml_get_current_column_number()xml_get_current_byte_index()
Реальные затруднения и выбор соседнего инструмента
- Обсуждение: Текст, особенно 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.
- Объект создан без данных
- xml_parse получает <status>ready</status> и final=true
- Результат 1 означает успешное завершение разбора
<?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.
<?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.
- Объявлены s=urn:shop и c=urn:crm
- Парсер раскрывает s:id и c:id
- Обработчик видит urn:shop|id и urn:crm|id
<?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 не путались с разделителем.
<?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, а не ресурс. |
| $data | XML-байты или следующая порция в правильном порядке; может быть пустой строкой при финализации. |
| $is_final | true только для последней части документа, false для промежуточных. |
Возвращает 1 при успехе текущего шага, 0 при ошибке. Подробности берут через xml_get_error_code и функции позиции. Это синхронный вызов: обработчики выполняются внутри него. Нельзя рекурсивно вызвать xml_parse на том же parser из его callback — Error. Для нового документа/повторной попытки создайте новый объект. Обычный return false из start/text/end не является командой остановки; явное исключение обработчика распространяется отдельно.
- Передать <order> с final=false
- Передать оставшиеся части тому же объекту
- Пустой 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 не делает массив безграничным.
- order становится записью open
- id с текстом 42 становится complete
- index[id][0] указывает на запись values, откуда читается value
<?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.
- Исходный тег Item и атрибут sku
- CASE_FOLDING=0 сохраняет имена
- Обработчик получает 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.
<?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, а не ресурс. |
| $option | CASE_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.
- Создать parser со стандартными настройками
- Прочитать CASE_FOLDING и TARGET_ENCODING
- Использовать значения для диагностики/согласования поведения
<?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. Исторические циклические ссылки объект↔ресурс требовали явного разрыва; современный код управляет обычными ссылками и освобождает владельца после окончания работы.
- Исторический free получает XMLParser
- В PHP 8 объект остаётся открытым, в 8.5 возникает deprecation
- Современный код удаляет ненужные ссылки после финализации
<?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 сообщение об устаревании отсутствует.
<?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_handler | function (XMLParser $parser, string $name, array $attributes): void — имя и ассоциативный массив атрибутов. |
| $end_handler | function (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-синтаксиса.
- Регистрация opened/closed сохраняет callable
- xml_parse читает <order> и вызывает opened(parser,order,[])
- Для <item/> вызывает opened и closed
- После </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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- <title> начинает сбор значения
- Callbacks дают Paper , затем & и pen либо иные части
- Каждый $data дописывается через .=
- Конец 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, '& pen</title>', true);
echo $text, "
";
Ожидаемый результат
Paper & pen
Оператор .= дописывает кусочек. Присваивание = оставляло бы только последнюю порцию. Даже один цельный вход может вызвать callback несколько раз — на сущностях, Unicode и внутренних границах; размер события не является размером элемента.
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- В документе <?shop mode="compact"?>
- Парсер вызывает callback с target=shop и data=mode="compact"
- Приложение принимает только известную комбинацию
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- Элементы и текст направлены своим обработчикам
- Комментарий попадает в default
- Приложение собирает диагностическую строку отдельно от бизнес-данных
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- xmlns:s объявляет связь s → urn:shop
- Callback namespace получает два значения
- Затем start элемента получает urn:shop|item
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- Функция успешно сохраняет callback
- Expat может вызвать его после закрытия элемента
- libxml такого события не выдаёт; используйте стек element-событий
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- XML содержит &terms; с SYSTEM ID
- Парсер вызывает callback с urn:shop:terms
- Приложение выбирает только собственный разрешённый текст
- Отдельный 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 с заранее известным содержимым.
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- DTD объявляет jpeg как image/jpeg
- Callback получает name и идентификаторы
- Приложение сохраняет метаданные, не скачивая ресурс
<?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, а не ресурс. |
| $handler | Callable нужной сигнатуры либо null для сброса. Аргументы callback передаёт парсер, не регистрационная функция. |
| Callback | function (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-синтаксиса.
- DTD объявляет NOTATION jpeg
- ENTITY cover получает SYSTEM cover.jpg и NDATA jpeg
- Callback сохраняет метаданные; атрибут image=cover может на них ссылаться
<?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: свой старый класс так больше не называйте.
- Старый код привязывает объект и строку opened отдельно
- Современный callable сразу содержит объект и метод
- Парсер вызывает 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_*.
- xml_parse вернул 0
- xml_get_error_code читает причину из того же parser
- 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.
- Из parser получен код причины
- xml_error_string ищет описание
- Приложение добавляет 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 и после ошибки; это положение обнаружения, не обещание точного места первопричины.
- xml_parse дошёл до item или обнаружил неверное закрытие
- Getter читает позицию того же объекта parser
- Приложение сохраняет отметку вместе с номером записи/ошибкой
<?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.
- xml_parse дошёл до item или обнаружил неверное закрытие
- Getter читает позицию того же объекта parser
- Приложение сохраняет отметку вместе с номером записи/ошибкой
<?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 представлению даже при другой входной кодировке. Это не номер символа и не безопасное смещение для резки исходного файла другой кодировки.
- xml_parse дошёл до item или обнаружил неверное закрытие
- Getter читает позицию того же объекта parser
- Приложение сохраняет отметку вместе с номером записи/ошибкой
<?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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


