Глава 116 · От основ к практике
XMLReader: большие XML-каталоги, курсор и полный справочник
Все методы XMLReader: чтение по узлам, атрибуты, пространства имён, извлечение поддеревьев, схемы и безопасные границы импорта.
Получить один товар, затем читать каталог по очереди
Курсор проходит каталог по одному узлу
Товар читается, проверяется и передаётся дальше без хранения всего дерева.
- <catalog>
Первый read ставит курсор на корневой элемент глубины 0.
- <product sku="A"/>
ELEMENT глубины 1: getAttribute читает артикул A.
- Проверить запись A
Тип узла, пространство имён, обязательные поля и значения.
- Следующий товар B
read/next двигают вперёд; после полного конца отдельно проверяют диагностику.
Это не произвольное перемещение по дереву. Один гигантский фрагмент или накопление всех результатов снова потребуют много памяти.
Когда каталог велик, магазину удобно последовательно брать один товар, подготовить строку ведомости и перейти к следующему. XMLReader хранит текущее положение чтения — курсор. Первый результат будет маленьким: из product.xml получим строку A1: Desk. После этого превратим каталог в CSV, не собирая все товары в PHP-массив.
product.xml<product sku="A1">Desk</product>- Открыли product.xml: текущего узла ещё нет
- read() переместил курсор на открывающий product
- getAttribute("sku") читает A1, readString() получает Desk
<?php
declare(strict_types=1);
$reader = new XMLReader();
$reader->open(__DIR__ . '/product.xml');
$reader->read();
echo $reader->getAttribute('sku'), ': ', $reader->readString(), "\n";
$reader->close();
Ожидаемый результат
A1: Desk
Файл приложен и заранее корректен. Здесь open только подключает вход, read начинает движение по XML; close освобождает читателя. Проверки отказов добавим после понимания позиции. Для PHP8.4+ fromUri создаёт уже открытый объект, а эта форма работает и в PHP8.2/8.3.
| Запись | Что означает |
|---|---|
| new XMLReader() | Создать объект чтения; он пока не связан с документом. Переменная $reader сохраняет этот объект. |
| open(__DIR__ . '/product.xml') | Открыть файл рядом с PHP-примером. Точка соединяет папку и имя, а не выбирает XML-узел. |
| read() | Продвинуть курсор к следующему узлу. Первый вызов здесь находит открывающий product; следующий обычный read нашёл бы текст Desk. |
| getAttribute('sku') | Прочитать атрибут текущего элемента по имени; курсор остаётся на product. |
| readString() | Получить текст текущего элемента и его потомков без XML-тегов. Для структурированных полей name/stock выбирают отдельные элементы. |
| close() | Закончить чтение и освободить вход reader. Это не подтверждает, что весь документ проверен. |
В терминах XML документ — весь файл, элемент product — часть дерева, sku — атрибут, Desk — текст. Эти различия подробно введены на небольшом каталоге. Курсор напоминает закладку, но не умеет произвольно вернуться назад. Парсер читает и буферизует данные; потоковое чтение не означает нулевую память. Глубина depth — уровень вложенности, а не номер строки.
Установка и доступные способы открытия
XMLReader входит в исходники PHP, включён по умолчанию и требует libxml. При собственной сборке отключается --disable-xmlreader. libxml также включена по умолчанию: в старых инструкциях до PHP 7.4 встречается --enable-libxml, в новых --with-libxml. Способ включения готового модуля зависит от пакета PHP и SAPI; проверяйте именно тот PHP, которым исполняется сайт.
<?php
declare(strict_types=1);
var_dump(extension_loaded('xmlreader'), class_exists(XMLReader::class));
var_dump(extension_loaded('libxml'), class_exists(DOMDocument::class));
Ожидаемый результат
bool(true)
bool(true)
bool(true)
bool(true)
DOM нужен для expand; последовательное чтение и общий XML-движок имеют разные роли. На проверенном стенде PHP 8.5.11 используется libxml 2.11.9.
| Вход | Способ |
|---|---|
| XML уже в переменной | XMLReader::XML для PHP 8.2/8.3; XMLReader::fromString с PHP 8.4. |
| Файл/URI | XMLReader::open либо XMLReader::fromUri с PHP 8.4. |
| Уже открытый PHP-stream | XMLReader::fromStream с PHP 8.4: положение и владение потоком остаются важными. |
Все извлечённые строки libxml отдаёт в UTF-8, даже если исходник объявлен в другой поддерживаемой кодировке. Не выполняйте повторную перекодировку «на всякий случай». Передача encoding — указание исходной кодировки, а не выбор формата результата.
Тот же каталог: остановиться на каждом товаре
Вернёмся к catalog.xml из SimpleXML: повторяются product, у каждого есть name и stock. До полей товара нужно пройти корневой catalog. while ($reader->read()) повторяет тело, пока очередной узел доступен; условие внутри выбирает только открывающие product.
catalog.xml<catalog>
<product sku="A1"><name>Desk</name><stock>3</stock></product>
<product sku="B2"><name>Lamp</name><stock>0</stock></product>
</catalog>
- catalog — пропустить контейнер
- product A1 — вывести сведения об этой записи
- name/текст/stock/закрытие product — пропустить в этом проходе
- product B2 — вывести вторую запись
- false после завершения — чтение закончено либо есть ошибка
<?php
declare(strict_types=1);
$reader = new XMLReader();
if (!$reader->open(__DIR__ . '/catalog.xml', null, LIBXML_NONET)) {
throw new RuntimeException('Каталог не открыт');
}
try {
while ($reader->read()) {
if ($reader->nodeType !== XMLReader::ELEMENT || $reader->name !== 'product') { continue; }
$product = new SimpleXMLElement($reader->readOuterXml());
echo (string) $product['sku'], ': ', (string) $product->name,
' — остаток ', (string) $product->stock, "\n";
}
} finally { $reader->close(); }
Ожидаемый результат
A1: Desk — остаток 3
B2: Lamp — остаток 0
readOuterXml создаёт строку только выбранного product, SimpleXML удобно читает его поля. read не следует считать «следующим товаром»: он проходит и текст, и закрывающие теги, поэтому фильтруем ELEMENT. Здесь известный локальный формат; в готовом конвертере ниже добавлены проверка полей и диагностика полного чтения.
Это сочетание двух инструментов: XMLReader ограничивает накопление всего файла, SimpleXML разбирает одну запись. Один гигантский product всё ещё создаст большую строку и дерево. Если product вложен в другие product, нужно дополнительно проверять depth; для namespace сравнивают localName вместе с namespaceURI.
XMLReader: состояние, свойства и отсутствие произвольного возврата
XMLReader — объект с текущим состоянием чтения. Присваивание $other = $reader не создаёт независимый курсор: обе переменные обращаются к тому же объекту. Для отдельного прохода открывают отдельный reader. После close нужно загрузить новый вход. Виртуальные публичные свойства читают состояние libxml; их нельзя использовать как редактируемые поля документа.
| Свойство | Тип | Что означает |
|---|---|---|
| attributeCount | int | Число атрибутов текущего элемента. Это не количество дочерних элементов. |
| baseURI | string | Базовый URI узла; учитывает документ и xml:base. Не является разрешением автоматически читать любые относительные зависимости. |
| depth | int | Глубина текущего узла, корень имеет 0. Атрибут и текст имеют свою глубину; сначала смотрите nodeType. |
| hasAttributes | bool | Есть ли у текущего узла атрибуты. Их значения ещё могут быть пустыми. |
| hasValue | bool | Имеет ли данный вид узла текстовое value. Это не проверка заполненности всех потомков элемента. |
| isDefault | bool | Является ли выбранный атрибут значением по умолчанию из DTD. |
| isEmptyElement | bool | Встретилась ли самозакрывающаяся запись вроде <product/>. Она отличается от пары <product></product>, даже если содержимое обоих пустое. |
| localName | string | Локальное имя без префикса: для s:product это product. |
| name | string | Полное имя, например s:product. Префикс сам по себе не определяет смысл пространства. |
| namespaceURI | string | URI пространства текущего узла; сравнивайте вместе с localName. |
| nodeType | int | Константа XMLReader, описывающая текущий вид узла. Не подменяйте числовыми константами DOM. |
| prefix | string | Префикс текущего имени: s у s:product; может быть пустым. |
| value | string | Текстовое значение текущего узла/атрибута; на открывающем элементе не означает весь текст потомков. |
| xmlLang | string | Действующее значение xml:lang, унаследованное из соответствующей области документа. |
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog xmlns:s="urn:shop"><s:product sku="A"/></catalog>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->localName === 'product') {
echo $reader->name, '/', $reader->localName, '/', $reader->prefix, "\n";
echo $reader->namespaceURI, '/', $reader->depth, "\n";
}
}
$reader->close();
Ожидаемый результат
s:product/product/s
urn:shop/1
Если поставщик заменит s на shop, name изменится, а пара localName + namespaceURI сохранит смысл. Глубина 1 означает прямого ребёнка корня, не первую строку файла.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><product/><product></product></catalog>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'product') {
var_dump($reader->isEmptyElement);
}
}
$reader->close();
Ожидаемый результат
bool(true)
bool(false)
Самозакрывающаяся форма не создаёт обычную пару событий начала и закрытия. При ручном сборе записи надо учитывать isEmptyElement, иначе код будет ждать закрывающий узел, которого нет.
Все виды узлов и четыре настройки парсера
Сначала выбирают семейство константы. nodeType отвечает на вопрос «на каком узле я стою», а get/setParserProperty управляют поведением разбора. Их числовые значения не надо смешивать с константами DOM или флагами LIBXML_*. С PHP 8.4 константы XMLReader имеют явно объявленный тип int.
| Константа XMLReader | Значение |
|---|---|
| XMLReader::NONE | Нет текущего узла. |
| XMLReader::ELEMENT | Открывающий элемент. |
| XMLReader::ATTRIBUTE | Атрибут, выбранный отдельным переходом. |
| XMLReader::TEXT | Обычный текст. |
| XMLReader::CDATA | Текст секции CDATA. |
| XMLReader::ENTITY_REF | Ссылка на сущность. |
| XMLReader::ENTITY | Объявление сущности. |
| XMLReader::PI | Инструкция обработки, например <?job ...?>; не PHP-вызов. |
| XMLReader::COMMENT | Комментарий XML. |
| XMLReader::DOC | Узел документа. |
| XMLReader::DOC_TYPE | DOCTYPE/объявление типа документа. |
| XMLReader::DOC_FRAGMENT | Фрагмент документа. |
| XMLReader::NOTATION | Объявление notation. |
| XMLReader::WHITESPACE | Пробельный узел. |
| XMLReader::SIGNIFICANT_WHITESPACE | Значимые пробелы; нельзя автоматически обрезать всё через trim. |
| XMLReader::END_ELEMENT | Закрывающий элемент. |
| XMLReader::END_ENTITY | Окончание сущности. |
| XMLReader::XML_DECLARATION | XML-декларация. Не все возможные виды обязательно встретятся при обычном проходе. |
| Настройка XMLReader | Действие |
|---|---|
| XMLReader::LOADDTD | Загрузить DTD без обязательной валидации. |
| XMLReader::DEFAULTATTRS | Загрузить DTD и добавить значения атрибутов по умолчанию, без обязательной валидации. |
| XMLReader::VALIDATE | Загрузить DTD и проверять по ней при чтении. |
| XMLReader::SUBST_ENTITIES | Подставлять сущности и разворачивать ссылки на них. Может влиять на внешние чтения и ресурсы. |
Настройки выбирают после загрузки входа, до первого read. Работа с DTD и entities требует ограничения внешних зависимостей. Для обычного каталога не включайте их без потребности. Флаги libxml и защита границы внешних чтений.
Выбрать путь: атрибут, текст или отдельное поддерево
- ELEMENT product: узнаём имя/глубину/namespace
- getAttribute("sku"): артикул строкой
- readString: объединённый текст
- readOuterXml/expand: разметка или DOM одной записи
| Потребность | Выбор и ограничение |
|---|---|
| Два атрибута простого товара | XMLReader::getAttribute без создания второго дерева. |
| Неизвестный набор атрибутов | XMLReader::moveToFirstAttribute / XMLReader::moveToNextAttribute, затем XMLReader::moveToElement. |
| Текст заголовка | XMLReader::readString; он склеивает текст потомков, не сохраняя структуру полей. |
| Сложный товар со вложенными вариантами | XMLReader::expand для отдельного DOM-поддерева либо XMLReader::readOuterXml и явный повторный разбор маленького фрагмента. |
| Ненужная ветвь каталога | XMLReader::next пропускает поддерево, но не отменяет чтение/обработку его байтов движком. |
Не вызывайте readOuterXml на корне гигабайтного каталога, если цель — снизить расход памяти: получите большую строку целиком. Следите также за массивами результатов, очередью SQL и журналом ошибок — они могут расти независимо от reader. Если нужен произвольный поиск назад/вперёд по небольшому документу, SimpleXML или DOM могут сделать код понятнее.
Проверка XML, схемы и бизнес-правил — отдельные решения
- Парсер: корректные теги и кодировка
- Схема: нужные элементы и типы
- Приложение: существующий SKU и допустимый остаток
- Публикация: только после успешного конца импорта
Возврат false у read/next может означать как конец, так и отказ. Включите внутренние сообщения libxml в пределах операции, очистите прошлый буфер, после полного чтения оцените сообщения и восстановите прежний режим. Схему выбирает приложение и подключает до чтения; isValid до конца не обещает, что непрочитанный хвост верен.
Для большого импорта полезна промежуточная таблица или версия каталога: сначала загрузить и проверить всё, затем переключить видимую версию. При прямых частичных обновлениях ошибка в конце оставит смешанные данные. Не держите бесконечную транзакцию без оценки блокировок/журнала; пакетную стратегию определяет база и задача. Транзакции и границы операции.
Практика: проверяем остатки поставщика по одной записи
Формат примера — catalog с непосредственными детьми product без вложенных элементов. sku обязателен; qty должен быть целой строкой от 0 до 100000. Здесь мы только показываем проверенные значения, а в сервисе отправляли бы их в промежуточное хранилище. Лимит файла и политика внешних источников должны действовать ещё до открытия.
<?php
declare(strict_types=1);
$xml = '<catalog><product sku="A" qty="0"/><product sku="B" qty="12"/></catalog>';
$previous = libxml_use_internal_errors(true); libxml_clear_errors();
$reader = XMLReader::fromString($xml);
try {
while ($reader->read()) {
if ($reader->nodeType !== XMLReader::ELEMENT || $reader->localName !== 'product') { continue; }
if ($reader->depth !== 1 || $reader->namespaceURI !== '' || !$reader->isEmptyElement) {
throw new RuntimeException('Unexpected product structure');
}
$sku = $reader->getAttribute('sku');
$rawQuantity = $reader->getAttribute('qty');
if ($sku === null || $sku === '' || $rawQuantity === null) { throw new RuntimeException('Missing field'); }
$quantity = filter_var($rawQuantity, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 100000]]);
if ($quantity === false) { throw new RuntimeException('Invalid quantity'); }
echo $sku, ': ', $quantity, "\n";
}
if (libxml_get_errors() !== []) { throw new RuntimeException('Invalid XML'); }
} finally { $reader->close(); libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
A: 0
B: 12
Ноль не отбрасывается: проверка использует === false. Это ограниченный пример проверки записей, не полная схема catalog: корень, неизвестные элементы, дубликаты SKU и требование хотя бы одной записи задаются отдельно, например схемой и логикой импорта. До финальной проверки вывод здесь диагностический, а не опубликованный каталог.
Упражнение: замените qty="12" на qty="many". Ожидается исключение Invalid quantity, а не остаток 0 после приведения. Затем добавьте одинаковый sku второй раз и определите политику дубликатов. Для большого файла проверяйте уникальность в промежуточной таблице с ограничением, чтобы не держать все ключи в PHP-массиве.
Дальше: проверка значений, работа с массивами, разбор вложенной записи.
Все методы XMLReader
| Метод | Для чего нужен |
|---|---|
| XMLReader::fromString | Начать последовательное чтение XML, который уже находится в строке. |
| XMLReader::fromUri | Открыть файл или разрешённый URI для последовательного чтения. |
| XMLReader::fromStream | Читать XML из уже открытого PHP-потока. |
| XMLReader::open | Загрузить источник по пути через совместимый API с двумя формами вызова. |
| XMLReader::XML | Загрузить XML-строку через совместимый API с двумя формами вызова. |
| XMLReader::close | Завершить чтение и освободить входные ресурсы reader. |
| XMLReader::getAttribute | Получить атрибут по его полному имени. |
| XMLReader::getAttributeNo | Получить атрибут по его позиции на текущем элементе. |
| XMLReader::getAttributeNs | Получить атрибут по локальному имени и URI пространства. |
| XMLReader::lookupNamespace | Узнать URI, которому соответствует префикс в текущей области XML. |
| XMLReader::moveToAttribute | Переместить курсор с элемента на выбранный атрибут. |
| XMLReader::moveToAttributeNo | Переместить курсор с элемента на выбранный атрибут. |
| XMLReader::moveToAttributeNs | Переместить курсор с элемента на выбранный атрибут. |
| XMLReader::moveToFirstAttribute | Перейти к первому атрибуту текущего элемента. |
| XMLReader::moveToNextAttribute | Перебрать атрибуты, оставаясь в пределах текущего элемента. |
| XMLReader::moveToElement | Вернуться с атрибута на элемент, которому он принадлежит. |
| XMLReader::read | Перейти к следующему узлу XML и узнать, что находится в этом месте. |
| XMLReader::next | Пропустить текущее поддерево и перейти дальше, при необходимости к заданному локальному имени. |
| XMLReader::readString | Получить объединённый текст текущего элемента без его разметки. |
| XMLReader::readInnerXml | Получить XML-содержимое текущего узла без его внешнего тега. |
| XMLReader::readOuterXml | Получить XML-фрагмент вместе с текущим внешним элементом. |
| XMLReader::expand | Получить копию текущего поддерева как DOM-объект. |
| XMLReader::getParserProperty | Узнать, включён ли конкретный режим XML-парсера. |
| XMLReader::setParserProperty | Включить или выключить отдельное поведение парсера до начала чтения. |
| XMLReader::isValid | Узнать результат включённой проверки по схеме на прочитанной части документа. |
| XMLReader::setSchema | Подключить XSD-схему из файла для проверки входного договора. |
| XMLReader::setRelaxNGSchema | Подключить RelaxNG-схему из файла для проверки входного договора. |
| XMLReader::setRelaxNGSchemaSource | Подключить RelaxNG-схему из строки для проверки входного договора. |
Готовый конвертер: catalog.xml → stock.csv
Нужен файл ведомости для коллеги: заголовок sku,name,stock и по одной строке на товар. Уже умеем выбирать product и читать его поля; добавим запись CSV и отложим появление итогового stock.csv до полного успешного чтения.
- Читаем один product из catalog.xml
- Проверяем sku/name/stock, сохраняем 0
- Пишем одну CSV-строку в stock.pending.csv
- Дочитываем весь XML и проверяем диагностику
- Закрываем поток, переименовываем в stock.csv, показываем содержимое
CSV — таблица в текстовом файле; fputcsv сам оформляет разделители и кавычки. Пятый аргумент '' отключает собственный escape-символ PHP, шестой "\n" задаёт окончание строки. Ниже каждый товар сразу уходит в файл, список всех товаров в памяти не собирается.
<?php
declare(strict_types=1);
$old = libxml_use_internal_errors(true);
libxml_clear_errors();
$reader = new XMLReader();
$pending = __DIR__ . '/stock.pending.csv';
$output = fopen($pending, 'wb');
if ($output === false) { throw new RuntimeException('Ведомость не открыта'); }
try {
if (!$reader->open(__DIR__ . '/catalog.xml', null, LIBXML_NONET)) {
throw new RuntimeException('Каталог не открыт');
}
fputcsv($output, ['sku', 'name', 'stock'], ',', '"', '', "\n");
while ($reader->read()) {
if ($reader->nodeType !== XMLReader::ELEMENT || $reader->localName !== 'product') { continue; }
if ($reader->depth !== 1 || $reader->namespaceURI !== '') { throw new UnexpectedValueException('Неверное положение product'); }
$product = new SimpleXMLElement($reader->readOuterXml());
$sku = (string) $product['sku'];
$name = (string) $product->name;
$stock = (string) $product->stock;
if ($sku === '' || $name === '' || preg_match('/\A(?:0|[1-9][0-9]{0,3})\z/', $stock) !== 1) {
throw new UnexpectedValueException('Неверные поля товара ' . $sku);
}
if (fputcsv($output, [$sku, $name, $stock], ',', '"', '', "\n") === false) {
throw new RuntimeException('CSV не записан');
}
}
if (libxml_get_errors() !== []) { throw new UnexpectedValueException('XML повреждён'); }
} finally {
$reader->close(); fclose($output);
libxml_clear_errors(); libxml_use_internal_errors($old);
}
if (!rename($pending, __DIR__ . '/stock.csv')) { throw new RuntimeException('Ведомость не завершена'); }
echo file_get_contents(__DIR__ . '/stock.csv');
Ожидаемый результат
sku,name,stock
A1,Desk,3
B2,Lamp,0
Скопируйте оба файла в новую учебную папку. Успешное выполнение оставляет stock.csv; при исключении до rename остаётся только промежуточный файл, который не считается опубликованной ведомостью. Это ограниченный конвертер согласованного формата, не полная схема: неизвестные элементы/корень/дубликаты SKU должны быть дополнительно проверены по договору. Замена общего файла на сервере потребует своей политики блокировок и восстановления. Для произвольных полей при открытии CSV в офисной программе отдельно учитывайте интерпретацию формул.
Измените stock на «много»: будет отказ вместо тихого приведения к нулю. Увеличьте число товаров в fixture — PHP-код останется тем же. Если нужен произвольный возврат к уже прочитанному узлу, XMLReader для этого не предназначен: разбирайте небольшой документ как DOM или сохраняйте отдельные проверенные записи.
<?php
declare(strict_types=1);
try {
require __DIR__ . '/converter.php';
} catch (UnexpectedValueException $error) {
echo 'Импорт отклонён: ', $error->getMessage(), "\n";
}
echo file_exists(__DIR__ . '/stock.csv') ? "Итоговый файл существует\n" : "stock.csv не создан\n";
Ожидаемый результат
Импорт отклонён: Неверные поля товара A1
stock.csv не создан
converter.php в архиве содержит ровно конвертер выше, а catalog.xml — тот же формат с остатком «много». require исполняет файл, catch показывает заранее предусмотренный отказ. Отдельная учебная папка подтверждает отсутствие нового итогового файла; уже существовавший результат здесь не моделируется.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
XMLReader::fromStringНачать последовательное чтение XML, который уже находится в строке.
public static XMLReader::fromString(string $source, ?string $encoding = null, int $flags = 0): staticНачать последовательное чтение XML, который уже находится в строке.
| Часть вызова | Договор и границы |
|---|---|
| source | Строка XML, не путь и не URL. Пустая строка — ValueError. |
| encoding | null — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8. |
| flags | Битовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения. |
| Возврат | Новый объект вызываемого класса (PHP 8.4+). Ошибка создания — Error; ошибка XML может обнаружиться позже, при read. |
- XML-строка в памяти
- fromString создаёт reader
- read ставит на первый узел
- name/value дают сведения
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<price>7</price>');
$reader->read();
echo $reader->name, ': ', $reader->readString(), "\n";
$reader->close();
Ожидаемый результат
price: 7
readString читает текст элемента. Полученная 7 пока строка; допустимость цены проверяет код приложения.
<?php
declare(strict_types=1);
try {
XMLReader::fromString('<price/>', 'not-an-encoding');
} catch (ValueError $error) { echo "invalid encoding\n"; }
Ожидаемый результат
invalid encoding
Ошибка настройки не равна повреждению XML. Внешнему клиенту не нужно показывать внутренний текст исключения; сохраните ограниченную диагностику в журнал.
Строка уже целиком занимает память. Для большого файла лучше XMLReader::fromUri либо XMLReader::fromStream, чтобы не читать его целиком через file_get_contents перед поточным разбором.
Продолжить: XMLReader::XML; XMLReader::fromUri; XMLReader::read.
XMLReader::fromUriОткрыть файл или разрешённый URI для последовательного чтения.
public static XMLReader::fromUri(string $uri, ?string $encoding = null, int $flags = 0): staticОткрыть файл или разрешённый URI для последовательного чтения.
| Часть вызова | Договор и границы |
|---|---|
| uri | Непустой путь/URI. Это источник XML, а не XML-текст. Адрес посетителя нельзя использовать без политики доступа. |
| encoding | null — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8. |
| flags | Битовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения. |
| Возврат | Новый объект вызываемого класса; невозможность открыть источник вызывает Error. libxml также может оставить диагностику. |
- Локальный catalog.xml
- Reader открывает источник
- read доставляет узлы по мере чтения
- close завершает работу
<?php
declare(strict_types=1);
$reader = XMLReader::fromUri(__DIR__ . '/catalog.xml');
try {
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'product') {
echo $reader->getAttribute('sku'), ': ', $reader->readString(), "\n";
}
}
} finally { $reader->close(); }
Ожидаемый результат
A1: Desk
B2: Lamp
fromUri сPHP8.4 возвращает новый reader. Товары здесь имеют текст прямо внутри product; readString не годится для раздельных name/stock, потому что объединяет весь текст потомков. Проверка ELEMENT исключает повторное чтение закрывающего product.
<?php
declare(strict_types=1);
try { XMLReader::fromUri(''); }
catch (ValueError $error) { echo "empty source path\n"; }
Ожидаемый результат
empty source path
Пустой адрес — ошибка аргумента, а не пустой каталог. Не подменяйте отсутствие файла успешным импортом нуля товаров.
Продолжить: XMLReader::open; XMLReader::fromStream; XMLReader::close.
XMLReader::fromStreamЧитать XML из уже открытого PHP-потока.
public static XMLReader::fromStream(resource $stream, ?string $encoding = null, int $flags = 0, ?string $documentUri = null): staticЧитать XML из уже открытого PHP-потока.
| Часть вызова | Договор и границы |
|---|---|
| stream | Читаемый stream resource. Чтение начинается с его текущего положения; неверный вид ресурса — TypeError. |
| encoding | null — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8. |
| flags | Битовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения. |
| documentUri | Необязательный базовый URI документа для относительных ссылок; не означает отдельную загрузку XML по этому адресу. |
| Возврат и владение | Новый объект. Reader удерживает ссылку на поток, но close не закрывает ресурс, которым ещё владеет вызывающий код. Поток закрывайте после reader. |
- Приложение открыло поток
- Настроило/переместило его указатель
- fromStream читает с текущего места
- Сначала close reader, потом fclose владельца
<?php
declare(strict_types=1);
$stream = fopen('php://temp', 'w+b');
fwrite($stream, '<order>42</order>');
rewind($stream);
$reader = XMLReader::fromStream($stream);
try { $reader->read(); echo 'Заказ № ', $reader->readString(), "\n"; }
finally { $reader->close(); }
rewind($stream);
echo 'Исходный поток после close: ', stream_get_contents($stream), "\n";
fclose($stream);
Ожидаемый результат
Заказ № 42
Исходный поток после close: <order>42</order>
Только что записанный поток находится в конце — rewind ставит его в начало перед передачей читателю. close завершает reader, но наш ресурс остаётся доступен: это доказано повторным чтением исходных байтов. Его fclose выполняет владелец. Возможность rewind у сетевого потока не гарантирована.
<?php
declare(strict_types=1);
$stream = fopen('php://temp', 'w+b');
if ($stream === false) { throw new RuntimeException('No stream'); }
fwrite($stream, "DATA\n<order>43</order>");
fseek($stream, 5);
$reader = XMLReader::fromStream($stream, documentUri: 'urn:shop:import');
try { $reader->read(); echo $reader->readString(), "\n"; }
finally { $reader->close(); fclose($stream); }
Ожидаемый результат
43
Пять байтов пропускаются только потому, что это известный формат учебного контейнера. Случайный мусор перед XML не надо молча «чинить» поиском первого знака <. Для сетевого потока возможность seek не гарантирована.
Продолжить: XMLReader::fromString; XMLReader::fromUri; XMLReader::close.
XMLReader::openЗагрузить источник по пути через совместимый API с двумя формами вызова.
XMLReader::open(string $uri, ?string $encoding = null, int $flags = 0): XMLReader|false; $reader->open(...): boolЗагрузить источник по пути через совместимый API с двумя формами вызова.
| Часть вызова | Договор и границы |
|---|---|
| uri | Непустой путь/URI, а не XML-текст. |
| encoding | null — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8. |
| flags | Битовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения. |
| Статический вызов | Возвращает XMLReader либо false при отказе. До PHP 8.0 такой вызов выдавал deprecated; с 8.0 поддерживается. |
| Вызов у объекта | Меняет вход текущего reader, освобождая прежнее состояние. Возвращает bool. Не присваивайте этот bool вместо reader. |
| Ошибки | Отказ создания может вернуть false с warning; неверные аргументы/кодировка могут выбросить исключение. XML проверяется по мере read. |
- new XMLReader создаёт пустой курсор
- open(...) загружает вход
- true подтверждает настройку входа
- read начинает разбор узлов
<?php
declare(strict_types=1);
$reader = new XMLReader();
if (!$reader->open(__DIR__ . '/order.xml')) { throw new RuntimeException('Cannot load XML'); }
$reader->read();
echo $reader->name, ': ', $reader->readString(), "\n";
$reader->close();
Ожидаемый результат
order: 42
Возвращаемое true не содержит сам XML и не доказывает корректность всего документа. Объект остаётся в $reader.
<?php
declare(strict_types=1);
$reader = XMLReader::open(__DIR__ . '/order.xml');
if ($reader === false) { throw new RuntimeException('Cannot load XML'); }
try { $reader->read(); echo $reader->readString(), "\n"; }
finally { $reader->close(); }
Ожидаемый результат
42
В PHP 8.4+ для нового кода фабрика fromUri делает договор однозначнее: объект либо исключение. Старые формы нужны при сопровождении и совместимости с PHP 8.2/8.3.
Продолжить: XMLReader::fromUri; XMLReader::read; XMLReader::close.
XMLReader::XMLЗагрузить XML-строку через совместимый API с двумя формами вызова.
XMLReader::XML(string $source, ?string $encoding = null, int $flags = 0): XMLReader|false; $reader->XML(...): boolЗагрузить XML-строку через совместимый API с двумя формами вызова.
| Часть вызова | Договор и границы |
|---|---|
| source | Непустой XML-текст, а не имя файла. |
| encoding | null — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8. |
| flags | Битовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения. |
| Статический вызов | Возвращает XMLReader либо false при отказе. До PHP 8.0 такой вызов выдавал deprecated; с 8.0 поддерживается. |
| Вызов у объекта | Меняет вход текущего reader, освобождая прежнее состояние. Возвращает bool. Не присваивайте этот bool вместо reader. |
| Ошибки | Отказ создания может вернуть false с warning; неверные аргументы/кодировка могут выбросить исключение. XML проверяется по мере read. |
- new XMLReader создаёт пустой курсор
- XML(...) загружает вход
- true подтверждает настройку входа
- read начинает разбор узлов
<?php
declare(strict_types=1);
$reader = new XMLReader();
if (!$reader->XML('<order>42</order>')) { throw new RuntimeException('Cannot load XML'); }
$reader->read();
echo $reader->name, ': ', $reader->readString(), "\n";
$reader->close();
Ожидаемый результат
order: 42
Возвращаемое true не содержит сам XML и не доказывает корректность всего документа. Объект остаётся в $reader.
<?php
declare(strict_types=1);
$reader = XMLReader::XML('<order>42</order>');
if ($reader === false) { throw new RuntimeException('Cannot load XML'); }
try { $reader->read(); echo $reader->readString(), "\n"; }
finally { $reader->close(); }
Ожидаемый результат
42
В PHP 8.4+ для нового кода фабрика fromString делает договор однозначнее: объект либо исключение. Старые формы нужны при сопровождении и совместимости с PHP 8.2/8.3.
Продолжить: XMLReader::fromString; XMLReader::read; XMLReader::close.
XMLReader::closeЗавершить чтение и освободить входные ресурсы reader.
public XMLReader::close(): trueЗавершить чтение и освободить входные ресурсы reader.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. |
| Результат | Всегда true. Это не результат проверки XML и не подтверждение завершённого импорта. |
| После вызова | Старый курсор больше не читает документ; сначала загрузите новый вход. Свой внешний stream закрывает его владелец. |
- Reader закончил работу либо возник отказ
- finally вызывает close
- Связанные ресурсы reader освобождены
- При повторном использовании загрузить новый вход
<?php
declare(strict_types=1);
$reader = XMLReader::XML('<catalog><product sku="A1">Desk</product><product sku="B2">Lamp</product></catalog>');
if ($reader === false) { throw new RuntimeException('Нет читателя'); }
try {
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'product') {
echo 'Первый товар: ', $reader->readString(), "\n";
break;
}
}
} finally { $reader->close(); }
echo "Читатель закрыт после первого товара\n";
Ожидаемый результат
Первый товар: Desk
Читатель закрыт после первого товара
Для задачи предварительного просмотра хватает первого товара. close освобождает вход даже после break; его true не означает проверку непрочитанного хвоста. Полный импорт должен дочитать и оценить диагностику. Для поиска назад проще новое чтение либо дерево.
<?php
declare(strict_types=1);
$reader = new XMLReader();
$reader->XML('<order>42</order>');
$reader->read();
echo 'Заказ: ', $reader->readString(), "\n";
$reader->close();
$reader->XML('<shipment>SH-42</shipment>');
$reader->read();
echo 'Отправление: ', $reader->readString(), "\n";
$reader->close();
Ожидаемый результат
Заказ: 42
Отправление: SH-42
Это два независимых документа, а не продолжение старого курсора. После close задаём новый вход XML и снова вызываем read. В крупном приложении отдельный reader на документ часто понятнее повторного использования.
Продолжить: XMLReader::read; XMLReader::XML; XMLReader::fromStream.
XMLReader::getAttributeПолучить атрибут по его полному имени.
public XMLReader::getAttribute(string $name): ?stringПолучить атрибут по его полному имени.
| Часть вызова | Договор и границы |
|---|---|
| name | Непустое имя атрибута; для имени с префиксом оно должно совпадать с записью документа. Пустое имя вызывает ValueError. |
| Результат | Строка, в том числе пустая, либо null при отсутствии атрибута/подходящего элемента. Курсор не передвигается. |
| Нюанс документации | Для PHP 8 используйте актуальный nullable-договор. Старое упоминание empty string в описании getAttributeNo/getAttributeNs не является признаком отсутствия. |
- Курсор на открывающем product
- getAttribute выбирает атрибут
- Возвращается строка A
- Курсор остаётся на product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A"/>');
$reader->read();
echo $reader->getAttribute('sku'), "\n";
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
A
product
Сначала read ставит на элемент. Дополнительный read перед получением атрибута мог бы уже увести курсор внутрь или за пределы нужного товара.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku=""/>');
$reader->read();
$empty = $reader->getAttribute('sku');
$missing = $reader->getAttribute('missing');
if ($empty === '') { echo "Артикул передан пустым: заполнить значение\n"; }
if ($missing === null) { echo "Искомого атрибута нет: проверить имя и формат\n"; }
$reader->close();
Ожидаемый результат
Артикул передан пустым: заполнить значение
Искомого атрибута нет: проверить имя и формат
get-метод не перемещает курсор. Пустая строка и null приводят к разным подсказкам: у значения нет текста либо выбранного атрибута вообще нет. В обоих случаях складской импорт здесь должен отклонить товар, а не придумать новый артикул.
Продолжить: XMLReader::getAttributeNo; XMLReader::getAttributeNs; XMLReader::moveToAttribute.
XMLReader::getAttributeNoПолучить атрибут по его позиции на текущем элементе.
public XMLReader::getAttributeNo(int $index): ?stringПолучить атрибут по его позиции на текущем элементе.
| Часть вызова | Договор и границы |
|---|---|
| index | Индекс с нуля. Не используйте порядок атрибутов как бизнес-договор: отправитель может его изменить. |
| Результат | Строка, в том числе пустая, либо null при отсутствии атрибута/подходящего элемента. Курсор не передвигается. |
| Нюанс документации | Для PHP 8 используйте актуальный nullable-договор. Старое упоминание empty string в описании getAttributeNo/getAttributeNs не является признаком отсутствия. |
- Курсор на открывающем product
- getAttributeNo выбирает атрибут
- Возвращается строка A
- Курсор остаётся на product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A"/>');
$reader->read();
echo $reader->getAttributeNo(0), "\n";
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
A
product
Сначала read ставит на элемент. Дополнительный read перед получением атрибута мог бы уже увести курсор внутрь или за пределы нужного товара.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku=""/>');
$reader->read();
$empty = $reader->getAttributeNo(0);
$missing = $reader->getAttributeNo(5);
if ($empty === '') { echo "Артикул передан пустым: заполнить значение\n"; }
if ($missing === null) { echo "Искомого атрибута нет: проверить имя и формат\n"; }
$reader->close();
Ожидаемый результат
Артикул передан пустым: заполнить значение
Искомого атрибута нет: проверить имя и формат
get-метод не перемещает курсор. Пустая строка и null приводят к разным подсказкам: у значения нет текста либо выбранного атрибута вообще нет. В обоих случаях складской импорт здесь должен отклонить товар, а не придумать новый артикул.
Продолжить: XMLReader::getAttribute; XMLReader::getAttributeNs; XMLReader::moveToAttribute.
XMLReader::getAttributeNsПолучить атрибут по локальному имени и URI пространства.
public XMLReader::getAttributeNs(string $name, string $namespace): ?stringПолучить атрибут по локальному имени и URI пространства.
| Часть вызова | Договор и границы |
|---|---|
| name | Непустое локальное имя без префикса, например sku. |
| namespace | Непустой URI пространства, не буквенный префикс s. Пустой аргумент вызывает ValueError; для обычного атрибута без namespace используйте getAttribute. |
| Результат | Строка, в том числе пустая, либо null при отсутствии атрибута/подходящего элемента. Курсор не передвигается. |
| Нюанс документации | Для PHP 8 используйте актуальный nullable-договор. Старое упоминание empty string в описании getAttributeNo/getAttributeNs не является признаком отсутствия. |
- Курсор на открывающем product
- getAttributeNs выбирает атрибут
- Возвращается строка A
- Курсор остаётся на product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product xmlns:s="urn:shop" s:sku="A"/>');
$reader->read();
echo $reader->getAttributeNs('sku', 'urn:shop'), "\n";
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
A
product
Сначала read ставит на элемент. Дополнительный read перед получением атрибута мог бы уже увести курсор внутрь или за пределы нужного товара.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product xmlns:s="urn:shop" s:sku=""/>');
$reader->read();
$empty = $reader->getAttributeNs('sku', 'urn:shop');
$missing = $reader->getAttributeNs('missing', 'urn:shop');
if ($empty === '') { echo "Артикул передан пустым: заполнить значение\n"; }
if ($missing === null) { echo "Искомого атрибута нет: проверить имя и формат\n"; }
$reader->close();
Ожидаемый результат
Артикул передан пустым: заполнить значение
Искомого атрибута нет: проверить имя и формат
get-метод не перемещает курсор. Пустая строка и null приводят к разным подсказкам: у значения нет текста либо выбранного атрибута вообще нет. В обоих случаях складской импорт здесь должен отклонить товар, а не придумать новый артикул.
Продолжить: XMLReader::getAttribute; XMLReader::getAttributeNo; XMLReader::moveToAttribute.
XMLReader::lookupNamespaceУзнать URI, которому соответствует префикс в текущей области XML.
public XMLReader::lookupNamespace(string $prefix): ?stringУзнать URI, которому соответствует префикс в текущей области XML.
| Часть вызова | Договор и границы |
|---|---|
| prefix | Непустой префикс, например s. Пустая строка в PHP 8.5.11 вызывает ValueError; URI самого элемента доступен через namespaceURI. |
| Возврат | Строка URI либо null, если префикс не определён в текущей области. С PHP 8.0 false больше не возвращается. |
| Область | Один и тот же префикс может быть переопределён во вложенном элементе. URI — идентификатор, его не нужно автоматически скачивать. |
- На элементе действует xmlns:s="urn:shop"
- lookupNamespace("s")
- Получаем urn:shop
- Сравниваем смысл пространства, не написание префикса
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<s:product xmlns:s="urn:shop"/>');
$reader->read();
echo $reader->lookupNamespace('s'), "\n";
var_dump($reader->lookupNamespace('unknown'));
$reader->close();
Ожидаемый результат
urn:shop
NULL
Неизвестный префикс не подменяется пустым namespace. Сравнивайте результат с null до использования.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<root xmlns:s="urn:one"><child xmlns:s="urn:two"/></root>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) {
echo $reader->name, ': ', $reader->lookupNamespace('s'), "\n";
}
}
$reader->close();
Ожидаемый результат
root: urn:one
child: urn:two
Кеш вида «s всегда означает urn:one» для всего документа был бы ошибочным. Анализируйте namespace в месте текущего узла.
Продолжить: XMLReader::getAttributeNs; XMLReader::read.
XMLReader::moveToAttributeПереместить курсор с элемента на выбранный атрибут.
public XMLReader::moveToAttribute(string $name): boolПереместить курсор с элемента на выбранный атрибут.
| Часть вызова | Договор и границы |
|---|---|
| name | Непустое полное имя атрибута. Не значение: передают sku, а не A. |
| Результат | true — атрибут выбран, name/value описывают его; false — перейти не удалось. Это не поиск по всем элементам документа. |
| После работы | Вернуться к содержащему элементу методом moveToElement перед продолжением чтения. |
- Курсор на product
- moveToAttribute выбирает sku
- value теперь A
- moveToElement возвращает к product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A"/>');
$reader->read();
if ($reader->moveToAttribute('sku')) { echo $reader->localName, ': ', $reader->value, "\n"; }
$reader->moveToElement();
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
sku: A
product
Значение атрибута читается через value. readString относится к содержимому узла и не заменяет этот способ чтения атрибута.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A"/>');
$reader->read();
if (!$reader->moveToAttribute('unknown')) { echo "attribute not found\n"; }
$reader->close();
Ожидаемый результат
attribute not found
Всегда проверяйте bool. Чтение текущих полей после false может дать данные прежнего положения, а не искомого атрибута.
Продолжить: XMLReader::moveToAttributeNo; XMLReader::moveToAttributeNs; XMLReader::moveToElement.
XMLReader::moveToAttributeNoПереместить курсор с элемента на выбранный атрибут.
public XMLReader::moveToAttributeNo(int $index): boolПереместить курсор с элемента на выбранный атрибут.
| Часть вызова | Договор и границы |
|---|---|
| index | Позиция с нуля. У отрицательного или отсутствующего индекса нет подходящего атрибута. |
| Результат | true — атрибут выбран, name/value описывают его; false — перейти не удалось. Это не поиск по всем элементам документа. |
| После работы | Вернуться к содержащему элементу методом moveToElement перед продолжением чтения. |
- Курсор на product
- moveToAttributeNo выбирает sku
- value теперь A
- moveToElement возвращает к product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A"/>');
$reader->read();
if ($reader->moveToAttributeNo(0)) { echo $reader->localName, ': ', $reader->value, "\n"; }
$reader->moveToElement();
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
sku: A
product
Значение атрибута читается через value. readString относится к содержимому узла и не заменяет этот способ чтения атрибута.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A"/>');
$reader->read();
if (!$reader->moveToAttributeNo(4)) { echo "attribute not found\n"; }
$reader->close();
Ожидаемый результат
attribute not found
Всегда проверяйте bool. Чтение текущих полей после false может дать данные прежнего положения, а не искомого атрибута.
Продолжить: XMLReader::moveToAttribute; XMLReader::moveToAttributeNs; XMLReader::moveToElement.
XMLReader::moveToAttributeNsПереместить курсор с элемента на выбранный атрибут.
public XMLReader::moveToAttributeNs(string $name, string $namespace): boolПереместить курсор с элемента на выбранный атрибут.
| Часть вызова | Договор и границы |
|---|---|
| name | Непустое локальное имя без префикса. |
| namespace | Непустой URI пространства; пустое имя/URI вызывает ValueError. |
| Результат | true — атрибут выбран, name/value описывают его; false — перейти не удалось. Это не поиск по всем элементам документа. |
| После работы | Вернуться к содержащему элементу методом moveToElement перед продолжением чтения. |
- Курсор на product
- moveToAttributeNs выбирает sku
- value теперь A
- moveToElement возвращает к product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product xmlns:s="urn:shop" s:sku="A"/>');
$reader->read();
if ($reader->moveToAttributeNs('sku', 'urn:shop')) { echo $reader->localName, ': ', $reader->value, "\n"; }
$reader->moveToElement();
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
sku: A
product
Значение атрибута читается через value. readString относится к содержимому узла и не заменяет этот способ чтения атрибута.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product xmlns:s="urn:shop" s:sku="A"/>');
$reader->read();
if (!$reader->moveToAttributeNs('sku', 'urn:other')) { echo "attribute not found\n"; }
$reader->close();
Ожидаемый результат
attribute not found
Всегда проверяйте bool. Чтение текущих полей после false может дать данные прежнего положения, а не искомого атрибута.
Продолжить: XMLReader::moveToAttribute; XMLReader::moveToAttributeNo; XMLReader::moveToElement.
XMLReader::moveToFirstAttributeПерейти к первому атрибуту текущего элемента.
public XMLReader::moveToFirstAttribute(): boolПерейти к первому атрибуту текущего элемента.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. Работает относительно уже выбранного элемента. |
| Результат | true — выбран первый атрибут; false — подходящего атрибута нет. Объявления namespace тоже могут входить в список атрибутов. |
- product имеет sku и qty
- moveToFirstAttribute выбирает первый
- name/value читаются у атрибута
- moveToNextAttribute продолжает обход
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A" qty="2"/>');
$reader->read();
if ($reader->moveToFirstAttribute()) { echo $reader->name, '=', $reader->value, "\n"; }
$reader->moveToElement(); $reader->close();
Ожидаемый результат
sku=A
Для изучения входного договора полезно видеть имена. В бизнес-логике выбирают атрибут по согласованному имени, а не позиции.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product/>');
$reader->read();
if (!$reader->moveToFirstAttribute()) {
echo "У product нет атрибутов: нельзя определить sku\n";
}
$reader->close();
Ожидаемый результат
У product нет атрибутов: нельзя определить sku
false означает отсутствие подходящего атрибута, а не ошибку XML. Следующий шаг зависит от договора: для нашего каталога sku обязателен. Если нужен только один известный атрибут, getAttribute("sku") проще полного обхода.
Продолжить: XMLReader::moveToNextAttribute; XMLReader::moveToElement; XMLReader::getAttribute.
XMLReader::moveToNextAttributeПеребрать атрибуты, оставаясь в пределах текущего элемента.
public XMLReader::moveToNextAttribute(): boolПеребрать атрибуты, оставаясь в пределах текущего элемента.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. |
| Положение | С атрибута переходит к следующему; с элемента — к первому атрибуту. |
| Результат | true при успешном переходе; false при конце списка/отсутствии атрибутов. После обхода явно вернитесь moveToElement. |
- Курсор на product
- Следующий атрибут: sku=A
- Следующий атрибут: qty=2
- false завершает список, не весь XML
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A" qty="2"/>');
$reader->read();
$attributes = [];
while ($reader->moveToNextAttribute()) { $attributes[$reader->name] = $reader->value; }
$reader->moveToElement();
echo json_encode($attributes, JSON_THROW_ON_ERROR), "\n";
$reader->close();
Ожидаемый результат
{"sku":"A","qty":"2"}
Значения остались строками. Дальше qty можно проверить как целое, а sku — по ограничениям поставщика; сам XMLReader этого не делает.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product xmlns:s="urn:shop" s:sku="A"/>');
$reader->read();
while ($reader->moveToNextAttribute()) {
if ($reader->namespaceURI === 'urn:shop') {
echo $reader->localName, ': ', $reader->value, "\n";
}
}
$reader->moveToElement(); $reader->close();
Ожидаемый результат
sku: A
Здесь фильтруется URI атрибута, поэтому служебное объявление xmlns не попадает в бизнес-поля. Префикс s отправитель может заменить другим, сохранив URI.
Продолжить: XMLReader::moveToFirstAttribute; XMLReader::moveToElement; XMLReader::getAttributeNs.
XMLReader::moveToElementВернуться с атрибута на элемент, которому он принадлежит.
public XMLReader::moveToElement(): boolВернуться с атрибута на элемент, которому он принадлежит.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. |
| Результат | true при возврате с атрибута, false если переход не выполнен. Это не общий подъём на родителя в дереве. |
| Ограничение | Из вложенного элемента нельзя этим методом вернуться к его родителю или предыдущему товару. Reader идёт вперёд. |
- Был выбран product/@sku
- Закончили обработку значения
- moveToElement → product
- Теперь можно читать содержимое product
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A">Tea</product>');
$reader->read();
$reader->moveToAttribute('sku'); echo $reader->value, "\n";
$reader->moveToElement(); echo $reader->readString(), "\n";
$reader->close();
Ожидаемый результат
A
Tea
Сначала value означало артикул, затем readString читает текст товара. Поля reader всегда относятся к текущему положению.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><product/></catalog>');
$reader->read(); $reader->read();
var_dump($reader->moveToElement());
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
bool(false)
product
Курсор уже стоял на элементе product. Для произвольного движения вверх/назад требуется дерево DOM или сохранённый отдельный фрагмент.
Продолжить: XMLReader::moveToAttribute; XMLReader::read; XMLReader::expand.
XMLReader::readПерейти к следующему узлу XML и узнать, что находится в этом месте.
public XMLReader::read(): boolПерейти к следующему узлу XML и узнать, что находится в этом месте.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. Вход уже должен быть загружен; иначе Error. |
| Результат | true — текущий узел доступен; false — конец либо ошибка разбора. Одного false недостаточно для вывода «весь XML правильный». |
| Порядок | Встречаются открывающие элементы, текст, комментарии, закрывающие элементы и другие виды. Сравнивайте nodeType вместе с именем. |
- До первого read: узла ещё нет
- read → открывающий price
- read → текст 7
- read → закрывающий price
- read → false
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<price>7</price>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) { echo 'start: ', $reader->name, "\n"; }
elseif ($reader->nodeType === XMLReader::TEXT) { echo 'text: ', $reader->value, "\n"; }
elseif ($reader->nodeType === XMLReader::END_ELEMENT) { echo 'end: ', $reader->name, "\n"; }
}
$reader->close();
Ожидаемый результат
start: price
text: 7
end: price
XMLReader выдаёт узлы, а не строки текстового файла. Один узел может занимать много строк, а одна строка — содержать много узлов.
<?php
declare(strict_types=1);
$old = libxml_use_internal_errors(true);
libxml_clear_errors();
$reader = XMLReader::fromString('<catalog><product></catalog>');
try {
while ($reader->read()) { /* Здесь могла бы идти обработка узлов. */ }
echo libxml_get_errors() === [] ? "accepted\n" : "invalid XML\n";
} finally {
$reader->close(); libxml_clear_errors(); libxml_use_internal_errors($old);
}
Ожидаемый результат
invalid XML
Для важного импорта сначала складывайте результаты в промежуточное хранилище и публикуйте после полного успешного разбора. Иначе ошибка в конце файла оставит наполовину обновлённый каталог. Производственная политика может различать warning/error; здесь учебная строгая политика отклоняет любую диагностику.
Продолжить: XMLReader::next; XMLReader::getAttribute; XMLReader::close.
XMLReader::nextПропустить текущее поддерево и перейти дальше, при необходимости к заданному локальному имени.
public XMLReader::next(?string $name = null): boolПропустить текущее поддерево и перейти дальше, при необходимости к заданному локальному имени.
| Часть вызова | Договор и границы |
|---|---|
| name | null — следующий узел за поддеревом. Строка — искать совпадение localName при последовательном пропуске; это не XPath и не проверка namespace. Nullable с PHP 8.0. |
| Результат | true — переход выполнен; false — подходящего узла нет или ошибка. Когда узла нет, курсор уходит к концу, а не остаётся на прошлом товаре. |
| Граница | Фильтр имени не гарантирует ту же глубину/родителя/namespace. Проверяйте nodeType, depth и namespaceURI по своему договору. |
- Курсор на product A с вложенной details
- next("product") пропускает его детей
- Курсор на следующем product B
- Обрабатываем B сразу, не вызывая лишний read
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><product sku="A"><details/></product><product sku="B"/></catalog>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->localName === 'product' && $reader->depth === 1) { break; }
}
while ($reader->nodeType === XMLReader::ELEMENT && $reader->localName === 'product' && $reader->depth === 1) {
echo $reader->getAttribute('sku'), "\n";
if (!$reader->next('product')) { break; }
}
$reader->close();
Ожидаемый результат
A
B
После next курсор уже находится на следующем товаре. Безусловный read в начале второго цикла мог бы пропустить его открывающий тег. Проверка глубины удерживает выбранную форму каталога.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><history><event/><event/></history><product sku="A"/></catalog>');
$reader->read(); $reader->read();
echo $reader->name, "\n";
if ($reader->next()) { echo $reader->name, "\n"; }
$reader->close();
Ожидаемый результат
history
product
Дочерние event не выдаются приложению по одному. Сам парсер всё равно должен прочитать байты и учитывать структуру; next не является бесплатным произвольным seek по XML.
Продолжить: XMLReader::read; XMLReader::readOuterXml; XMLReader::moveToNextAttribute.
XMLReader::readStringПолучить объединённый текст текущего элемента без его разметки.
public XMLReader::readString(): stringПолучить объединённый текст текущего элемента без его разметки.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. Сначала встаньте на нужный узел. |
| Результат | Строка UTF-8. Пустая строка может означать пустое содержимое или отказ; анализируйте контекст и libxml-диагностику. |
| Границы | Метод не предназначен для перехода к следующему товару. XML-фрагмент — сериализация дерева, не побайтовая копия исходника: кавычки, пустые теги и объявления namespace могут оформиться иначе. |
| Память | Создаётся строка содержимого выбранного узла. Если выбрать весь корень огромного каталога, поточный выигрыш будет потерян. Требует libxml 2.6.20+; все современные поддерживаемые сборки выше этой границы. |
- Курсор на description
- Внутри: Hello <b>Tea</b>!
- readString выбирает текст/разметку
- Приложение получает отдельную строку
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<description>Hello <b>Tea</b>!</description>');
$reader->read();
echo $reader->readString(), "\n";
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
Hello Tea!
description
Вложенное b не попадает в текст, но его содержимое Tea сохраняется. Для вывода в HTML нужен осознанный договор: XML/сырой текст не становятся безопасным HTML автоматически.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><name>Tea</name><name>Coffee</name></catalog>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'name') {
echo $reader->readString(), "\n";
}
}
$reader->close();
Ожидаемый результат
Tea
Coffee
Строка одного элемента обрабатывается и отпускается. Если складывать все строки в массив, общий объём памяти снова вырастет вместе с файлом. readString объединяет текст потомков и не сохраняет границы полей; для структурированных данных читайте отдельные элементы.
Продолжить: XMLReader::readInnerXml; XMLReader::readOuterXml; XMLReader::expand; XMLReader::next.
XMLReader::readInnerXmlПолучить XML-содержимое текущего узла без его внешнего тега.
public XMLReader::readInnerXml(): stringПолучить XML-содержимое текущего узла без его внешнего тега.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. Сначала встаньте на нужный узел. |
| Результат | Строка UTF-8. Пустая строка может означать пустое содержимое или отказ; анализируйте контекст и libxml-диагностику. |
| Границы | Метод не предназначен для перехода к следующему товару. XML-фрагмент — сериализация дерева, не побайтовая копия исходника: кавычки, пустые теги и объявления namespace могут оформиться иначе. |
| Память | Создаётся строка содержимого выбранного узла. Если выбрать весь корень огромного каталога, поточный выигрыш будет потерян. Требует libxml 2.6.20+; все современные поддерживаемые сборки выше этой границы. |
- Курсор на description
- Внутри: Hello <b>Tea</b>!
- readInnerXml выбирает текст/разметку
- Приложение получает отдельную строку
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<description>Hello <b>Tea</b>!</description>');
$reader->read();
echo $reader->readInnerXml(), "\n";
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
Hello <b>Tea</b>!
description
Вложенное b сохраняется как разметка. Для вывода в HTML нужен осознанный договор: XML/сырой текст не становятся безопасным HTML автоматически.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><name>Tea</name><name>Coffee</name></catalog>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'name') {
echo $reader->readInnerXml(), "\n";
}
}
$reader->close();
Ожидаемый результат
Tea
Coffee
Строка одного элемента обрабатывается и отпускается. Если складывать все строки в массив, общий объём памяти снова вырастет вместе с файлом. readString объединяет текст потомков и не сохраняет границы полей; для структурированных данных читайте отдельные элементы.
Продолжить: XMLReader::readString; XMLReader::readOuterXml; XMLReader::expand; XMLReader::next.
XMLReader::readOuterXmlПолучить XML-фрагмент вместе с текущим внешним элементом.
public XMLReader::readOuterXml(): stringПолучить XML-фрагмент вместе с текущим внешним элементом.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. Сначала встаньте на нужный узел. |
| Результат | Строка UTF-8. Пустая строка может означать пустое содержимое или отказ; анализируйте контекст и libxml-диагностику. |
| Границы | Метод не предназначен для перехода к следующему товару. XML-фрагмент — сериализация дерева, не побайтовая копия исходника: кавычки, пустые теги и объявления namespace могут оформиться иначе. |
| Память | Создаётся строка содержимого выбранного узла. Если выбрать весь корень огромного каталога, поточный выигрыш будет потерян. Требует libxml 2.6.20+; все современные поддерживаемые сборки выше этой границы. |
- Курсор на description
- Внутри: Hello <b>Tea</b>!
- readOuterXml выбирает текст/разметку
- Приложение получает отдельную строку
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<description>Hello <b>Tea</b>!</description>');
$reader->read();
echo $reader->readOuterXml(), "\n";
echo $reader->name, "\n";
$reader->close();
Ожидаемый результат
<description>Hello <b>Tea</b>!</description>
description
Вложенное b сохраняется как разметка. Для вывода в HTML нужен осознанный договор: XML/сырой текст не становятся безопасным HTML автоматически.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><name>Tea</name><name>Coffee</name></catalog>');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'name') {
echo $reader->readOuterXml(), "\n";
}
}
$reader->close();
Ожидаемый результат
<name>Tea</name>
<name>Coffee</name>
Строка одного элемента обрабатывается и отпускается. Если складывать все строки в массив, общий объём памяти снова вырастет вместе с файлом. readString объединяет текст потомков и не сохраняет границы полей; для структурированных данных читайте отдельные элементы.
Продолжить: XMLReader::readString; XMLReader::readInnerXml; XMLReader::expand; XMLReader::next.
XMLReader::expandПолучить копию текущего поддерева как DOM-объект.
public XMLReader::expand(?DOMNode $baseNode = null): DOMNode|falseПолучить копию текущего поддерева как DOM-объект.
| Часть вызова | Договор и границы |
|---|---|
| baseNode | DOMNode, чей owner document станет документом копии; обычно передают DOMDocument. Не задаёт место вставки. null — без указанного целевого документа. |
| Результат | DOMNode подходящего вида либо false с диагностикой при отказе. При незагруженном входе или отсутствии DOM — Error. |
| Совместимость | Это старый DOMNode API, не новый Dom\Node. Копируется всё выбранное поддерево; большой узел может занять много памяти. |
- XMLReader нашёл product A
- expand($document) копирует только этот товар
- DOM позволяет пройти его детей/сделать запрос
- next/read продолжает исходный поток отдельно
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<catalog><product sku="A"><name>Tea</name></product></catalog>');
$reader->read(); $reader->read();
$document = new DOMDocument();
$product = $reader->expand($document);
if (!$product instanceof DOMElement) { throw new RuntimeException('Expected product'); }
echo $product->getAttribute('sku'), ': ', $product->getElementsByTagName('name')->item(0)->textContent, "\n";
$reader->close();
Ожидаемый результат
A: Tea
Вместо дерева всего каталога создаётся дерево одной записи. Это удобно, если товар сложный, а XMLReader нужен для ограничения общего накопления.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A1">Desk</product>');
$reader->read();
$document = new DOMDocument();
$copy = $reader->expand($document);
if (!$copy instanceof DOMElement) { throw new RuntimeException('Нет элемента товара'); }
$copy->setAttribute('available', 'yes');
$document->appendChild($copy);
echo 'Новый документ: ', $document->saveXML($copy), "\n";
echo 'Исходная запись: ', $reader->readOuterXml(), "\n";
$reader->close();
Ожидаемый результат
Новый документ: <product sku="A1" available="yes">Desk</product>
Исходная запись: <product sku="A1">Desk</product>
expand копирует поддерево в контекст owner document, но не вставляет его: appendChild сделан явно. Видно, что атрибут добавлен только в новый документ. Это старый DOMNode API, не новый Dom\Node PHP8.4.
Продолжить: XMLReader::readOuterXml; XMLReader::next; XMLReader::read.
XMLReader::getParserPropertyУзнать, включён ли конкретный режим XML-парсера.
public XMLReader::getParserProperty(int $property): boolУзнать, включён ли конкретный режим XML-парсера.
| Часть вызова | Договор и границы |
|---|---|
| property | Одна из XMLReader::LOADDTD, DEFAULTATTRS, VALIDATE, SUBST_ENTITIES. Это отдельные переключатели, не битовая маска LIBXML_*. |
| Результат | true — режим включён, false — выключен. Не трактуйте false как ошибку чтения настройки. |
| Ошибки | Неизвестный property — ValueError; без загруженного входа — Error. |
- Вход XML уже загружен
- getParserProperty(VALIDATE)
- false: DTD-валидация выключена
- Это не заключение о правильности XML
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product/>');
echo $reader->getParserProperty(XMLReader::VALIDATE)
? "DTD-проверка включена\n" : "DTD-проверка не включена: схему ещё не задавали\n";
$reader->close();
Ожидаемый результат
DTD-проверка не включена: схему ещё не задавали
Первый аргумент — конкретная XMLReader-константа настройки, не LIBXML-флаг. false означает выключенный режим, а не отказ функции. Это отчёт о настройке; результат проверки документа даёт другой метод isValid.
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product/>');
try { $reader->getParserProperty(9999); }
catch (ValueError $error) { echo "unknown parser property\n"; }
finally { $reader->close(); }
Ожидаемый результат
unknown parser property
Константы разных библиотек нельзя подставлять друг вместо друга только потому, что обе имеют тип int.
Продолжить: XMLReader::setParserProperty; XMLReader::isValid.
XMLReader::setParserPropertyВключить или выключить отдельное поведение парсера до начала чтения.
public XMLReader::setParserProperty(int $property, bool $value): boolВключить или выключить отдельное поведение парсера до начала чтения.
| Часть вызова | Договор и границы |
|---|---|
| property | Одна из четырёх XMLReader-констант настроек. |
| value | true включает, false выключает. |
| Когда | После загрузки входа, до первого read. Возвращает true при успехе; неверная/неприменимая настройка может вызвать ValueError, незагруженный reader — Error. |
| Внешние данные | LOADDTD, DEFAULTATTRS, VALIDATE и SUBST_ENTITIES могут включать работу с зависимостями. Нужна политика внешних чтений и лимитов. |
- Создать reader с XML
- Выбрать режим до первого read
- Разбирать документ с этим режимом
- Проверять данные и диагностику отдельно
<?php
declare(strict_types=1);
$xml = '<!DOCTYPE product [<!ENTITY shop "Tea">]><product>&shop;</product>';
$reader = XMLReader::fromString($xml);
var_dump($reader->setParserProperty(XMLReader::SUBST_ENTITIES, true));
while ($reader->read()) {
if ($reader->nodeType === XMLReader::TEXT) { echo $reader->value, "\n"; }
}
$reader->close();
Ожидаемый результат
bool(true)
Tea
Сущность объявлена внутри полностью известного учебного XML. Это не рекомендация включать подстановку для любых документов посетителя: внешняя сущность может запросить файл или адрес.
<?php
declare(strict_types=1);
$xml = '<!DOCTYPE product [<!ELEMENT product EMPTY><!ATTLIST product currency CDATA "RUB">]><product/>';
$reader = XMLReader::fromString($xml);
$reader->setParserProperty(XMLReader::DEFAULTATTRS, true);
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) { echo $reader->getAttribute('currency'), "\n"; }
}
$reader->close();
Ожидаемый результат
RUB
Атрибут появился благодаря DTD, хотя его не было в открывающем теге. Если различие важно для импорта, учитывайте isDefault на выбранном атрибуте. Пример не делает внешних чтений.
Продолжить: XMLReader::getParserProperty; XMLReader::isValid; XMLReader::setSchema.
XMLReader::isValidУзнать результат включённой проверки по схеме на прочитанной части документа.
public XMLReader::isValid(): boolУзнать результат включённой проверки по схеме на прочитанной части документа.
| Часть вызова | Договор и границы |
|---|---|
| Параметры | Нет. До чтения включают DTD-валидацию или задают XSD/RelaxNG. |
| Результат | true — текущая проверка не обнаружила несоответствия; false — невалидность либо отсутствие включённой проверки. |
| Граница | Не проверяет непрочитанный хвост. Чтобы принять весь документ, прочитайте его до конца, проверьте диагностику и итоговый isValid. |
- Установить известную схему
- read последовательно проверяет XML
- Ошибка может обнаружиться в последней записи
- Принимать весь импорт только после завершения
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A1"/>');
while ($reader->read()) {}
if (!$reader->isValid()) {
echo "Подтверждения схемы нет: сначала подключите договор каталога\n";
}
$reader->close();
Ожидаемый результат
Подтверждения схемы нет: сначала подключите договор каталога
В этом примере схема вообще не назначалась: false не обвиняет угловые скобки в ошибке. Схему подключают до чтения, а итог проверяют после всего документа. При нарушении подключённой схемы этот же false имеет другую причину — нужна диагностика.
<?php
declare(strict_types=1);
$xml = '<!DOCTYPE catalog [<!ELEMENT catalog (product*)><!ELEMENT product EMPTY><!ATTLIST product sku CDATA #REQUIRED>]><catalog><product sku="A1"/></catalog>';
$reader = XMLReader::fromString($xml);
$reader->setParserProperty(XMLReader::VALIDATE, true);
while ($reader->read()) {}
echo $reader->isValid() ? "Каталог соответствует DTD: у товаров есть sku\n" : "Каталог нарушает DTD\n";
$reader->close();
Ожидаемый результат
Каталог соответствует DTD: у товаров есть sku
DTD объявлена прямо в известном документе: catalog допускает повторения product, у каждого требуется sku. Атрибут может быть пустым текстом и всё же пройти эту DTD — заполненность и существование артикула остаются прикладными проверками. Внешних зависимостей нет.
Продолжить: XMLReader::setSchema; XMLReader::setRelaxNGSchema; XMLReader::setParserProperty.
XMLReader::setSchemaПодключить XSD-схему из файла для проверки входного договора.
public XMLReader::setSchema(?string $filename): boolПодключить XSD-схему из файла для проверки входного договора.
| Часть вызова | Договор и границы |
|---|---|
| filename | Путь/URI доверенной схемы, а не текст XML. null отключает соответствующую схему; пустая строка — ValueError. |
| Когда | После открытия входа, до первого read. Настройка схемы не подтверждает успешное прохождение всей проверки. |
| Результат | true — схема подключена; false и warning при отказе загрузки/разбора схемы или отсутствии поддержки schemas в libxml. Неверное состояние может вызвать Error. |
| Доверие | Схему выбирает приложение. Не берите произвольный путь/URI от посетителя; импортируемые схемой зависимости тоже требуют контроля. |
- Вход содержит quantity=2
- setSchema подключает договор
- read проверяет узлы по мере поступления
- Итоговый isValid отличает допустимый документ
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<quantity>2</quantity>');
if (!$reader->setSchema(__DIR__ . '/quantity.xsd')) { throw new RuntimeException('Схема не подключена'); }
while ($reader->read()) {}
echo $reader->isValid() ? "Количество 2 принято\n" : "Формат не принят\n";
$reader->close();
Ожидаемый результат
Количество 2 принято
Сначала назначаем схему, затем полностью читаем, только потом принимаем документ. XSD требует quantity типа positiveInteger. Своя схема входит в пример; приложение выбирает её независимо от XML.
<?php
declare(strict_types=1);
$old = libxml_use_internal_errors(true);
libxml_clear_errors();
$reader = XMLReader::fromString('<quantity>0</quantity>');
try {
if (!$reader->setSchema(__DIR__ . '/quantity.xsd')) { throw new RuntimeException('Схема не подключена'); }
while ($reader->read()) {}
if (!$reader->isValid()) { echo "Количество 0 запрещено: схема требует положительное целое\n"; }
} finally { $reader->close(); libxml_clear_errors(); libxml_use_internal_errors($old); }
Ожидаемый результат
Количество 0 запрещено: схема требует положительное целое
Угловые скобки здесь правильные. Отказ относится к правилам известной схемы. Причина вывода описывает именно подготовленный fixture; для произвольного файла нужна его диагностика. Следующая попытка не наследует сообщения этой проверки.
После успешного разбора и схемы остаются бизнес-проверки: допустимый товар, срок, валюта, права и защита от повторного импорта. Диагностика и внешние зависимости libxml объясняет отдельные уровни отказа.
Продолжить: XMLReader::isValid; XMLReader::setRelaxNGSchema; XMLReader::setRelaxNGSchemaSource.
XMLReader::setRelaxNGSchemaПодключить RelaxNG-схему из файла для проверки входного договора.
public XMLReader::setRelaxNGSchema(?string $filename): boolПодключить RelaxNG-схему из файла для проверки входного договора.
| Часть вызова | Договор и границы |
|---|---|
| filename | Путь/URI доверенной схемы, а не текст XML. null отключает соответствующую схему; пустая строка — ValueError. |
| Когда | После открытия входа, до первого read. Настройка схемы не подтверждает успешное прохождение всей проверки. |
| Результат | true — схема подключена; false и warning при отказе загрузки/разбора схемы или отсутствии поддержки schemas в libxml. Неверное состояние может вызвать Error. |
| Доверие | Схему выбирает приложение. Не берите произвольный путь/URI от посетителя; импортируемые схемой зависимости тоже требуют контроля. |
- Вход содержит product с sku
- setRelaxNGSchema подключает договор
- read проверяет узлы по мере поступления
- Итоговый isValid отличает допустимый документ
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A1"/>');
if (!$reader->setRelaxNGSchema(__DIR__ . '/product.rng')) { throw new RuntimeException('Схема не подключена'); }
while ($reader->read()) {}
echo $reader->isValid() ? "Товар A1 соответствует формату\n" : "Формат не принят\n";
$reader->close();
Ожидаемый результат
Товар A1 соответствует формату
Сначала назначаем схему, затем полностью читаем, только потом принимаем документ. RelaxNG требует product с атрибутом sku и без дочернего содержимого. Своя схема входит в пример; приложение выбирает её независимо от XML.
<?php
declare(strict_types=1);
$old = libxml_use_internal_errors(true);
libxml_clear_errors();
$reader = XMLReader::fromString('<product/>');
try {
if (!$reader->setRelaxNGSchema(__DIR__ . '/product.rng')) { throw new RuntimeException('Схема не подключена'); }
while ($reader->read()) {}
if (!$reader->isValid()) { echo "Товар отклонён: схема требует атрибут sku\n"; }
} finally { $reader->close(); libxml_clear_errors(); libxml_use_internal_errors($old); }
Ожидаемый результат
Товар отклонён: схема требует атрибут sku
Угловые скобки здесь правильные. Отказ относится к правилам известной схемы. Причина вывода описывает именно подготовленный fixture; для произвольного файла нужна его диагностика. Следующая попытка не наследует сообщения этой проверки.
После успешного разбора и схемы остаются бизнес-проверки: допустимый товар, срок, валюта, права и защита от повторного импорта. Диагностика и внешние зависимости libxml объясняет отдельные уровни отказа.
Продолжить: XMLReader::isValid; XMLReader::setSchema; XMLReader::setRelaxNGSchemaSource.
XMLReader::setRelaxNGSchemaSourceПодключить RelaxNG-схему из строки для проверки входного договора.
public XMLReader::setRelaxNGSchemaSource(?string $source): boolПодключить RelaxNG-схему из строки для проверки входного договора.
| Часть вызова | Договор и границы |
|---|---|
| source | Текст RelaxNG, а не путь. null отключает соответствующую схему; пустая строка — ValueError. |
| Когда | После открытия входа, до первого read. Настройка схемы не подтверждает успешное прохождение всей проверки. |
| Результат | true — схема подключена; false и warning при отказе загрузки/разбора схемы или отсутствии поддержки schemas в libxml. Неверное состояние может вызвать Error. |
| Доверие | Схему выбирает приложение. Не берите произвольный путь/URI от посетителя; импортируемые схемой зависимости тоже требуют контроля. |
- Вход содержит product с sku
- setRelaxNGSchemaSource подключает договор
- read проверяет узлы по мере поступления
- Итоговый isValid отличает допустимый документ
<?php
declare(strict_types=1);
$reader = XMLReader::fromString('<product sku="A1"/>');
if (!$reader->setRelaxNGSchemaSource('<element xmlns="http://relaxng.org/ns/structure/1.0" name="product"><attribute name="sku"><text/></attribute><empty/></element>')) { throw new RuntimeException('Схема не подключена'); }
while ($reader->read()) {}
echo $reader->isValid() ? "Товар A1 соответствует формату\n" : "Формат не принят\n";
$reader->close();
Ожидаемый результат
Товар A1 соответствует формату
Сначала назначаем схему, затем полностью читаем, только потом принимаем документ. RelaxNG требует product с атрибутом sku и без дочернего содержимого. Своя схема входит в пример; приложение выбирает её независимо от XML.
<?php
declare(strict_types=1);
$old = libxml_use_internal_errors(true);
libxml_clear_errors();
$reader = XMLReader::fromString('<product/>');
try {
if (!$reader->setRelaxNGSchemaSource('<element xmlns="http://relaxng.org/ns/structure/1.0" name="product"><attribute name="sku"><text/></attribute><empty/></element>')) { throw new RuntimeException('Схема не подключена'); }
while ($reader->read()) {}
if (!$reader->isValid()) { echo "Товар отклонён: схема требует атрибут sku\n"; }
} finally { $reader->close(); libxml_clear_errors(); libxml_use_internal_errors($old); }
Ожидаемый результат
Товар отклонён: схема требует атрибут sku
Угловые скобки здесь правильные. Отказ относится к правилам известной схемы. Причина вывода описывает именно подготовленный fixture; для произвольного файла нужна его диагностика. Следующая попытка не наследует сообщения этой проверки.
После успешного разбора и схемы остаются бизнес-проверки: допустимый товар, срок, валюта, права и защита от повторного импорта. Диагностика и внешние зависимости libxml объясняет отдельные уровни отказа.
Продолжить: XMLReader::isValid; XMLReader::setSchema; XMLReader::setRelaxNGSchema.