Александр Некрасов · Автономное руководство

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

Разделы этой страницы
  1. XMLReader: большие XML-каталоги, курсор и полный справочник
  2. Получить один товар, затем читать каталог по очереди
  3. Установка и доступные способы открытия
  4. Тот же каталог: остановиться на каждом товаре
  5. XMLReader: состояние, свойства и отсутствие произвольного возврата
  6. Все виды узлов и четыре настройки парсера
  7. Выбрать путь: атрибут, текст или отдельное поддерево
  8. Проверка XML, схемы и бизнес-правил — отдельные решения
  9. Практика: проверяем остатки поставщика по одной записи
  10. Все методы XMLReader
  11. Готовый конвертер: catalog.xml → stock.csv
  12. Функции и методы этой темы
  13. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

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

XMLReader: большие XML-каталоги, курсор и полный справочник

Все методы XMLReader: чтение по узлам, атрибуты, пространства имён, извлечение поддеревьев, схемы и безопасные границы импорта.

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

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

Курсор проходит каталог по одному узлу

Товар читается, проверяется и передаётся дальше без хранения всего дерева.

  1. <catalog>

    Первый read ставит курсор на корневой элемент глубины 0.

  2. <product sku="A"/>

    ELEMENT глубины 1: getAttribute читает артикул A.

  3. Проверить запись A

    Тип узла, пространство имён, обязательные поля и значения.

  4. Следующий товар B

    read/next двигают вперёд; после полного конца отдельно проверяют диагностику.

Это не произвольное перемещение по дереву. Один гигантский фрагмент или накопление всех результатов снова потребуют много памяти.

Когда каталог велик, магазину удобно последовательно брать один товар, подготовить строку ведомости и перейти к следующему. XMLReader хранит текущее положение чтения — курсор. Первый результат будет маленьким: из product.xml получим строку A1: Desk. После этого превратим каталог в CSV, не собирая все товары в PHP-массив.

Исходный файл product.xml
<product sku="A1">Desk</product>
Три состояния одного читателя
  1. Открыли product.xml: текущего узла ещё нет
  2. read() переместил курсор на открывающий product
  3. 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, которым исполняется сайт.

Убедиться, что reader и дерево DOM доступныСамостоятельный пример
<?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.
Файл/URIXMLReader::open либо XMLReader::fromUri с PHP 8.4.
Уже открытый PHP-streamXMLReader::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>
Читатель движется вперёд
  1. catalog — пропустить контейнер
  2. product A1 — вывести сведения об этой записи
  3. name/текст/stock/закрытие product — пропустить в этом проходе
  4. product B2 — вывести вторую запись
  5. 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; их нельзя использовать как редактируемые поля документа.

СвойствоТипЧто означает
attributeCountintЧисло атрибутов текущего элемента. Это не количество дочерних элементов.
baseURIstringБазовый URI узла; учитывает документ и xml:base. Не является разрешением автоматически читать любые относительные зависимости.
depthintГлубина текущего узла, корень имеет 0. Атрибут и текст имеют свою глубину; сначала смотрите nodeType.
hasAttributesboolЕсть ли у текущего узла атрибуты. Их значения ещё могут быть пустыми.
hasValueboolИмеет ли данный вид узла текстовое value. Это не проверка заполненности всех потомков элемента.
isDefaultboolЯвляется ли выбранный атрибут значением по умолчанию из DTD.
isEmptyElementboolВстретилась ли самозакрывающаяся запись вроде <product/>. Она отличается от пары <product></product>, даже если содержимое обоих пустое.
localNamestringЛокальное имя без префикса: для s:product это product.
namestringПолное имя, например s:product. Префикс сам по себе не определяет смысл пространства.
namespaceURIstringURI пространства текущего узла; сравнивайте вместе с localName.
nodeTypeintКонстанта XMLReader, описывающая текущий вид узла. Не подменяйте числовыми константами DOM.
prefixstringПрефикс текущего имени: s у s:product; может быть пустым.
valuestringТекстовое значение текущего узла/атрибута; на открывающем элементе не означает весь текст потомков.
xmlLangstringДействующее значение 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_TYPEDOCTYPE/объявление типа документа.
XMLReader::DOC_FRAGMENTФрагмент документа.
XMLReader::NOTATIONОбъявление notation.
XMLReader::WHITESPACEПробельный узел.
XMLReader::SIGNIFICANT_WHITESPACEЗначимые пробелы; нельзя автоматически обрезать всё через trim.
XMLReader::END_ELEMENTЗакрывающий элемент.
XMLReader::END_ENTITYОкончание сущности.
XMLReader::XML_DECLARATIONXML-декларация. Не все возможные виды обязательно встретятся при обычном проходе.
Настройка XMLReaderДействие
XMLReader::LOADDTDЗагрузить DTD без обязательной валидации.
XMLReader::DEFAULTATTRSЗагрузить DTD и добавить значения атрибутов по умолчанию, без обязательной валидации.
XMLReader::VALIDATEЗагрузить DTD и проверять по ней при чтении.
XMLReader::SUBST_ENTITIESПодставлять сущности и разворачивать ссылки на них. Может влиять на внешние чтения и ресурсы.

Настройки выбирают после загрузки входа, до первого read. Работа с DTD и entities требует ограничения внешних зависимостей. Для обычного каталога не включайте их без потребности. Флаги libxml и защита границы внешних чтений.

Выбрать путь: атрибут, текст или отдельное поддерево

Один товар — несколько доступных представлений
  1. ELEMENT product: узнаём имя/глубину/namespace
  2. getAttribute("sku"): артикул строкой
  3. readString: объединённый текст
  4. 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, схемы и бизнес-правил — отдельные решения

Документ проходит несколько уровней
  1. Парсер: корректные теги и кодировка
  2. Схема: нужные элементы и типы
  3. Приложение: существующий SKU и допустимый остаток
  4. Публикация: только после успешного конца импорта

Возврат false у read/next может означать как конец, так и отказ. Включите внутренние сообщения libxml в пределах операции, очистите прошлый буфер, после полного чтения оцените сообщения и восстановите прежний режим. Схему выбирает приложение и подключает до чтения; isValid до конца не обещает, что непрочитанный хвост верен.

Для большого импорта полезна промежуточная таблица или версия каталога: сначала загрузить и проверить всё, затем переключить видимую версию. При прямых частичных обновлениях ошибка в конце оставит смешанные данные. Не держите бесконечную транзакцию без оценки блокировок/журнала; пакетную стратегию определяет база и задача. Транзакции и границы операции.

Практика: проверяем остатки поставщика по одной записи

Формат примера — catalog с непосредственными детьми product без вложенных элементов. sku обязателен; qty должен быть целой строкой от 0 до 100000. Здесь мы только показываем проверенные значения, а в сервисе отправляли бы их в промежуточное хранилище. Лимит файла и политика внешних источников должны действовать ещё до открытия.

Проверить XML, положение записи и значения атрибутовСамостоятельный пример
<?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 до полного успешного чтения.

Ограничиваем накопление и точку принятия
  1. Читаем один product из catalog.xml
  2. Проверяем sku/name/stock, сохраняем 0
  3. Пишем одну CSV-строку в stock.pending.csv
  4. Дочитываем весь XML и проверяем диагностику
  5. Закрываем поток, переименовываем в 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 или сохраняйте отдельные проверенные записи.

Проверенный отказ: неверный остаток не создаёт итоговый CSVСамостоятельный пример
<?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, который уже находится в строке.

PHP 8.4+ · xmlreader

public static XMLReader::fromString(string $source, ?string $encoding = null, int $flags = 0): static

Начать последовательное чтение XML, который уже находится в строке.

Часть вызоваДоговор и границы
sourceСтрока XML, не путь и не URL. Пустая строка — ValueError.
encodingnull — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8.
flagsБитовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения.
ВозвратНовый объект вызываемого класса (PHP 8.4+). Ошибка создания — Error; ошибка XML может обнаружиться позже, при read.
Движение данных и курсора
  1. XML-строка в памяти
  2. fromString создаёт reader
  3. read ставит на первый узел
  4. 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 для последовательного чтения.

PHP 8.4+ · xmlreader

public static XMLReader::fromUri(string $uri, ?string $encoding = null, int $flags = 0): static

Открыть файл или разрешённый URI для последовательного чтения.

Часть вызоваДоговор и границы
uriНепустой путь/URI. Это источник XML, а не XML-текст. Адрес посетителя нельзя использовать без политики доступа.
encodingnull — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8.
flagsБитовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения.
ВозвратНовый объект вызываемого класса; невозможность открыть источник вызывает Error. libxml также может оставить диагностику.
Движение данных и курсора
  1. Локальный catalog.xml
  2. Reader открывает источник
  3. read доставляет узлы по мере чтения
  4. 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-потока.

PHP 8.4+ · xmlreader

public static XMLReader::fromStream(resource $stream, ?string $encoding = null, int $flags = 0, ?string $documentUri = null): static

Читать XML из уже открытого PHP-потока.

Часть вызоваДоговор и границы
streamЧитаемый stream resource. Чтение начинается с его текущего положения; неверный вид ресурса — TypeError.
encodingnull — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с PHP 8.4 вызывает ValueError. Читаемые строки выдаются в UTF-8.
flagsБитовая маска LIBXML_*. Флаги внешних сущностей/DTD требуют явной политики, а NONET не запрещает локальные чтения.
documentUriНеобязательный базовый URI документа для относительных ссылок; не означает отдельную загрузку XML по этому адресу.
Возврат и владениеНовый объект. Reader удерживает ссылку на поток, но close не закрывает ресурс, которым ещё владеет вызывающий код. Поток закрывайте после reader.
Движение данных и курсора
  1. Приложение открыло поток
  2. Настроило/переместило его указатель
  3. fromStream читает с текущего места
  4. Сначала 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 с двумя формами вызова.

PHP 5.1+ · xmlreader

XMLReader::open(string $uri, ?string $encoding = null, int $flags = 0): XMLReader|false; $reader->open(...): bool

Загрузить источник по пути через совместимый API с двумя формами вызова.

Часть вызоваДоговор и границы
uriНепустой путь/URI, а не XML-текст.
encodingnull — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с 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.
Движение данных и курсора
  1. new XMLReader создаёт пустой курсор
  2. open(...) загружает вход
  3. true подтверждает настройку входа
  4. 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 с двумя формами вызова.

PHP 5.1+ · xmlreader

XMLReader::XML(string $source, ?string $encoding = null, int $flags = 0): XMLReader|false; $reader->XML(...): bool

Загрузить XML-строку через совместимый API с двумя формами вызова.

Часть вызоваДоговор и границы
sourceНепустой XML-текст, а не имя файла.
encodingnull — использовать сведения документа/автоопределение; явное имя выбирает исходную кодировку. Неверное имя с 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.
Движение данных и курсора
  1. new XMLReader создаёт пустой курсор
  2. XML(...) загружает вход
  3. true подтверждает настройку входа
  4. 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.

PHP 5.1+ · xmlreader

public XMLReader::close(): true

Завершить чтение и освободить входные ресурсы reader.

Часть вызоваДоговор и границы
ПараметрыНет.
РезультатВсегда true. Это не результат проверки XML и не подтверждение завершённого импорта.
После вызоваСтарый курсор больше не читает документ; сначала загрузите новый вход. Свой внешний stream закрывает его владелец.
Движение данных и курсора
  1. Reader закончил работу либо возник отказ
  2. finally вызывает close
  3. Связанные ресурсы reader освобождены
  4. При повторном использовании загрузить новый вход
Завершить читателя при досрочно найденном товареСамостоятельный пример
<?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Получить атрибут по его полному имени.

PHP 5.1+ · xmlreader

public XMLReader::getAttribute(string $name): ?string

Получить атрибут по его полному имени.

Часть вызоваДоговор и границы
nameНепустое имя атрибута; для имени с префиксом оно должно совпадать с записью документа. Пустое имя вызывает ValueError.
РезультатСтрока, в том числе пустая, либо null при отсутствии атрибута/подходящего элемента. Курсор не передвигается.
Нюанс документацииДля PHP 8 используйте актуальный nullable-договор. Старое упоминание empty string в описании getAttributeNo/getAttributeNs не является признаком отсутствия.
Движение данных и курсора
  1. Курсор на открывающем product
  2. getAttribute выбирает атрибут
  3. Возвращается строка A
  4. Курсор остаётся на 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Получить атрибут по его позиции на текущем элементе.

PHP 5.1+ · xmlreader

public XMLReader::getAttributeNo(int $index): ?string

Получить атрибут по его позиции на текущем элементе.

Часть вызоваДоговор и границы
indexИндекс с нуля. Не используйте порядок атрибутов как бизнес-договор: отправитель может его изменить.
РезультатСтрока, в том числе пустая, либо null при отсутствии атрибута/подходящего элемента. Курсор не передвигается.
Нюанс документацииДля PHP 8 используйте актуальный nullable-договор. Старое упоминание empty string в описании getAttributeNo/getAttributeNs не является признаком отсутствия.
Движение данных и курсора
  1. Курсор на открывающем product
  2. getAttributeNo выбирает атрибут
  3. Возвращается строка A
  4. Курсор остаётся на 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 пространства.

PHP 5.1+ · xmlreader

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 не является признаком отсутствия.
Движение данных и курсора
  1. Курсор на открывающем product
  2. getAttributeNs выбирает атрибут
  3. Возвращается строка A
  4. Курсор остаётся на 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.

PHP 5.1+ · xmlreader

public XMLReader::lookupNamespace(string $prefix): ?string

Узнать URI, которому соответствует префикс в текущей области XML.

Часть вызоваДоговор и границы
prefixНепустой префикс, например s. Пустая строка в PHP 8.5.11 вызывает ValueError; URI самого элемента доступен через namespaceURI.
ВозвратСтрока URI либо null, если префикс не определён в текущей области. С PHP 8.0 false больше не возвращается.
ОбластьОдин и тот же префикс может быть переопределён во вложенном элементе. URI — идентификатор, его не нужно автоматически скачивать.
Движение данных и курсора
  1. На элементе действует xmlns:s="urn:shop"
  2. lookupNamespace("s")
  3. Получаем urn:shop
  4. Сравниваем смысл пространства, не написание префикса
Получить определение префиксаСамостоятельный пример
<?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Переместить курсор с элемента на выбранный атрибут.

PHP 5.1+ · xmlreader

public XMLReader::moveToAttribute(string $name): bool

Переместить курсор с элемента на выбранный атрибут.

Часть вызоваДоговор и границы
nameНепустое полное имя атрибута. Не значение: передают sku, а не A.
Результатtrue — атрибут выбран, name/value описывают его; false — перейти не удалось. Это не поиск по всем элементам документа.
После работыВернуться к содержащему элементу методом moveToElement перед продолжением чтения.
Движение данных и курсора
  1. Курсор на product
  2. moveToAttribute выбирает sku
  3. value теперь A
  4. 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 относится к содержимому узла и не заменяет этот способ чтения атрибута.

Не читать старое value после неудачного переходаСамостоятельный пример
<?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Переместить курсор с элемента на выбранный атрибут.

PHP 5.1+ · xmlreader

public XMLReader::moveToAttributeNo(int $index): bool

Переместить курсор с элемента на выбранный атрибут.

Часть вызоваДоговор и границы
indexПозиция с нуля. У отрицательного или отсутствующего индекса нет подходящего атрибута.
Результатtrue — атрибут выбран, name/value описывают его; false — перейти не удалось. Это не поиск по всем элементам документа.
После работыВернуться к содержащему элементу методом moveToElement перед продолжением чтения.
Движение данных и курсора
  1. Курсор на product
  2. moveToAttributeNo выбирает sku
  3. value теперь A
  4. 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 относится к содержимому узла и не заменяет этот способ чтения атрибута.

Не читать старое value после неудачного переходаСамостоятельный пример
<?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Переместить курсор с элемента на выбранный атрибут.

PHP 5.1+ · xmlreader

public XMLReader::moveToAttributeNs(string $name, string $namespace): bool

Переместить курсор с элемента на выбранный атрибут.

Часть вызоваДоговор и границы
nameНепустое локальное имя без префикса.
namespaceНепустой URI пространства; пустое имя/URI вызывает ValueError.
Результатtrue — атрибут выбран, name/value описывают его; false — перейти не удалось. Это не поиск по всем элементам документа.
После работыВернуться к содержащему элементу методом moveToElement перед продолжением чтения.
Движение данных и курсора
  1. Курсор на product
  2. moveToAttributeNs выбирает sku
  3. value теперь A
  4. 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 относится к содержимому узла и не заменяет этот способ чтения атрибута.

Не читать старое value после неудачного переходаСамостоятельный пример
<?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Перейти к первому атрибуту текущего элемента.

PHP 5.1+ · xmlreader

public XMLReader::moveToFirstAttribute(): bool

Перейти к первому атрибуту текущего элемента.

Часть вызоваДоговор и границы
ПараметрыНет. Работает относительно уже выбранного элемента.
Результатtrue — выбран первый атрибут; false — подходящего атрибута нет. Объявления namespace тоже могут входить в список атрибутов.
Движение данных и курсора
  1. product имеет sku и qty
  2. moveToFirstAttribute выбирает первый
  3. name/value читаются у атрибута
  4. 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Перебрать атрибуты, оставаясь в пределах текущего элемента.

PHP 5.1+ · xmlreader

public XMLReader::moveToNextAttribute(): bool

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

Часть вызоваДоговор и границы
ПараметрыНет.
ПоложениеС атрибута переходит к следующему; с элемента — к первому атрибуту.
Результатtrue при успешном переходе; false при конце списка/отсутствии атрибутов. После обхода явно вернитесь moveToElement.
Движение данных и курсора
  1. Курсор на product
  2. Следующий атрибут: sku=A
  3. Следующий атрибут: qty=2
  4. 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Вернуться с атрибута на элемент, которому он принадлежит.

PHP 5.1+ · xmlreader

public XMLReader::moveToElement(): bool

Вернуться с атрибута на элемент, которому он принадлежит.

Часть вызоваДоговор и границы
ПараметрыНет.
Результатtrue при возврате с атрибута, false если переход не выполнен. Это не общий подъём на родителя в дереве.
ОграничениеИз вложенного элемента нельзя этим методом вернуться к его родителю или предыдущему товару. Reader идёт вперёд.
Движение данных и курсора
  1. Был выбран product/@sku
  2. Закончили обработку значения
  3. moveToElement → product
  4. Теперь можно читать содержимое 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 и узнать, что находится в этом месте.

PHP 5.1+ · xmlreader

public XMLReader::read(): bool

Перейти к следующему узлу XML и узнать, что находится в этом месте.

Часть вызоваДоговор и границы
ПараметрыНет. Вход уже должен быть загружен; иначе Error.
Результатtrue — текущий узел доступен; false — конец либо ошибка разбора. Одного false недостаточно для вывода «весь XML правильный».
ПорядокВстречаются открывающие элементы, текст, комментарии, закрывающие элементы и другие виды. Сравнивайте nodeType вместе с именем.
Движение данных и курсора
  1. До первого read: узла ещё нет
  2. read → открывающий price
  3. read → текст 7
  4. read → закрывающий price
  5. 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 выдаёт узлы, а не строки текстового файла. Один узел может занимать много строк, а одна строка — содержать много узлов.

Не принять повреждённый XML за успешное завершениеСамостоятельный пример
<?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Пропустить текущее поддерево и перейти дальше, при необходимости к заданному локальному имени.

PHP 5.1+ · xmlreader

public XMLReader::next(?string $name = null): bool

Пропустить текущее поддерево и перейти дальше, при необходимости к заданному локальному имени.

Часть вызоваДоговор и границы
namenull — следующий узел за поддеревом. Строка — искать совпадение localName при последовательном пропуске; это не XPath и не проверка namespace. Nullable с PHP 8.0.
Результатtrue — переход выполнен; false — подходящего узла нет или ошибка. Когда узла нет, курсор уходит к концу, а не остаётся на прошлом товаре.
ГраницаФильтр имени не гарантирует ту же глубину/родителя/namespace. Проверяйте nodeType, depth и namespaceURI по своему договору.
Движение данных и курсора
  1. Курсор на product A с вложенной details
  2. next("product") пропускает его детей
  3. Курсор на следующем product B
  4. Обрабатываем 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Получить объединённый текст текущего элемента без его разметки.

PHP 5.2+ · xmlreader

public XMLReader::readString(): string

Получить объединённый текст текущего элемента без его разметки.

Часть вызоваДоговор и границы
ПараметрыНет. Сначала встаньте на нужный узел.
РезультатСтрока UTF-8. Пустая строка может означать пустое содержимое или отказ; анализируйте контекст и libxml-диагностику.
ГраницыМетод не предназначен для перехода к следующему товару. XML-фрагмент — сериализация дерева, не побайтовая копия исходника: кавычки, пустые теги и объявления namespace могут оформиться иначе.
ПамятьСоздаётся строка содержимого выбранного узла. Если выбрать весь корень огромного каталога, поточный выигрыш будет потерян. Требует libxml 2.6.20+; все современные поддерживаемые сборки выше этой границы.
Движение данных и курсора
  1. Курсор на description
  2. Внутри: Hello <b>Tea</b>!
  3. readString выбирает текст/разметку
  4. Приложение получает отдельную строку
Различить текст и XML-разметку описанияСамостоятельный пример
<?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-содержимое текущего узла без его внешнего тега.

PHP 5.2+ · xmlreader

public XMLReader::readInnerXml(): string

Получить XML-содержимое текущего узла без его внешнего тега.

Часть вызоваДоговор и границы
ПараметрыНет. Сначала встаньте на нужный узел.
РезультатСтрока UTF-8. Пустая строка может означать пустое содержимое или отказ; анализируйте контекст и libxml-диагностику.
ГраницыМетод не предназначен для перехода к следующему товару. XML-фрагмент — сериализация дерева, не побайтовая копия исходника: кавычки, пустые теги и объявления namespace могут оформиться иначе.
ПамятьСоздаётся строка содержимого выбранного узла. Если выбрать весь корень огромного каталога, поточный выигрыш будет потерян. Требует libxml 2.6.20+; все современные поддерживаемые сборки выше этой границы.
Движение данных и курсора
  1. Курсор на description
  2. Внутри: Hello <b>Tea</b>!
  3. readInnerXml выбирает текст/разметку
  4. Приложение получает отдельную строку
Различить текст и XML-разметку описанияСамостоятельный пример
<?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-фрагмент вместе с текущим внешним элементом.

PHP 5.2+ · xmlreader

public XMLReader::readOuterXml(): string

Получить XML-фрагмент вместе с текущим внешним элементом.

Часть вызоваДоговор и границы
ПараметрыНет. Сначала встаньте на нужный узел.
РезультатСтрока UTF-8. Пустая строка может означать пустое содержимое или отказ; анализируйте контекст и libxml-диагностику.
ГраницыМетод не предназначен для перехода к следующему товару. XML-фрагмент — сериализация дерева, не побайтовая копия исходника: кавычки, пустые теги и объявления namespace могут оформиться иначе.
ПамятьСоздаётся строка содержимого выбранного узла. Если выбрать весь корень огромного каталога, поточный выигрыш будет потерян. Требует libxml 2.6.20+; все современные поддерживаемые сборки выше этой границы.
Движение данных и курсора
  1. Курсор на description
  2. Внутри: Hello <b>Tea</b>!
  3. readOuterXml выбирает текст/разметку
  4. Приложение получает отдельную строку
Различить текст и XML-разметку описанияСамостоятельный пример
<?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-объект.

PHP 5.1+ · xmlreader

public XMLReader::expand(?DOMNode $baseNode = null): DOMNode|false

Получить копию текущего поддерева как DOM-объект.

Часть вызоваДоговор и границы
baseNodeDOMNode, чей owner document станет документом копии; обычно передают DOMDocument. Не задаёт место вставки. null — без указанного целевого документа.
РезультатDOMNode подходящего вида либо false с диагностикой при отказе. При незагруженном входе или отсутствии DOM — Error.
СовместимостьЭто старый DOMNode API, не новый Dom\Node. Копируется всё выбранное поддерево; большой узел может занять много памяти.
Движение данных и курсора
  1. XMLReader нашёл product A
  2. expand($document) копирует только этот товар
  3. DOM позволяет пройти его детей/сделать запрос
  4. 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 нужен для ограничения общего накопления.

Изменить DOM-копию для отдельной выгрузкиСамостоятельный пример
<?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-парсера.

PHP 5.1+ · xmlreader

public XMLReader::getParserProperty(int $property): bool

Узнать, включён ли конкретный режим XML-парсера.

Часть вызоваДоговор и границы
propertyОдна из XMLReader::LOADDTD, DEFAULTATTRS, VALIDATE, SUBST_ENTITIES. Это отдельные переключатели, не битовая маска LIBXML_*.
Результатtrue — режим включён, false — выключен. Не трактуйте false как ошибку чтения настройки.
ОшибкиНеизвестный property — ValueError; без загруженного входа — Error.
Движение данных и курсора
  1. Вход XML уже загружен
  2. getParserProperty(VALIDATE)
  3. false: DTD-валидация выключена
  4. Это не заключение о правильности XML
Показать оператору, включена ли DTD-проверкаСамостоятельный пример
<?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Включить или выключить отдельное поведение парсера до начала чтения.

PHP 5.1+ · xmlreader

public XMLReader::setParserProperty(int $property, bool $value): bool

Включить или выключить отдельное поведение парсера до начала чтения.

Часть вызоваДоговор и границы
propertyОдна из четырёх XMLReader-констант настроек.
valuetrue включает, false выключает.
КогдаПосле загрузки входа, до первого read. Возвращает true при успехе; неверная/неприменимая настройка может вызвать ValueError, незагруженный reader — Error.
Внешние данныеLOADDTD, DEFAULTATTRS, VALIDATE и SUBST_ENTITIES могут включать работу с зависимостями. Нужна политика внешних чтений и лимитов.
Движение данных и курсора
  1. Создать reader с XML
  2. Выбрать режим до первого read
  3. Разбирать документ с этим режимом
  4. Проверять данные и диагностику отдельно
Разрешить подстановку известной внутренней сущностиСамостоятельный пример
<?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. Это не рекомендация включать подстановку для любых документов посетителя: внешняя сущность может запросить файл или адрес.

Получить атрибут по умолчанию из известной DTDСамостоятельный пример
<?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Узнать результат включённой проверки по схеме на прочитанной части документа.

PHP 5.1+ · xmlreader

public XMLReader::isValid(): bool

Узнать результат включённой проверки по схеме на прочитанной части документа.

Часть вызоваДоговор и границы
ПараметрыНет. До чтения включают DTD-валидацию или задают XSD/RelaxNG.
Результатtrue — текущая проверка не обнаружила несоответствия; false — невалидность либо отсутствие включённой проверки.
ГраницаНе проверяет непрочитанный хвост. Чтобы принять весь документ, прочитайте его до конца, проверьте диагностику и итоговый isValid.
Движение данных и курсора
  1. Установить известную схему
  2. read последовательно проверяет XML
  3. Ошибка может обнаружиться в последней записи
  4. Принимать весь импорт только после завершения
Не объявлять синтаксически прочитанный XML проверенным по схемеСамостоятельный пример
<?php
declare(strict_types=1);

$reader = XMLReader::fromString('<product sku="A1"/>');
while ($reader->read()) {}
if (!$reader->isValid()) {
    echo "Подтверждения схемы нет: сначала подключите договор каталога\n";
}
$reader->close();
Ожидаемый результат
Подтверждения схемы нет: сначала подключите договор каталога

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

Принять файл с товарами по локально заданной DTDСамостоятельный пример
<?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-схему из файла для проверки входного договора.

PHP 5.2+ · xmlreader

public XMLReader::setSchema(?string $filename): bool

Подключить XSD-схему из файла для проверки входного договора.

Часть вызоваДоговор и границы
filenameПуть/URI доверенной схемы, а не текст XML. null отключает соответствующую схему; пустая строка — ValueError.
КогдаПосле открытия входа, до первого read. Настройка схемы не подтверждает успешное прохождение всей проверки.
Результатtrue — схема подключена; false и warning при отказе загрузки/разбора схемы или отсутствии поддержки schemas в libxml. Неверное состояние может вызвать Error.
ДовериеСхему выбирает приложение. Не берите произвольный путь/URI от посетителя; импортируемые схемой зависимости тоже требуют контроля.
Движение данных и курсора
  1. Вход содержит quantity=2
  2. setSchema подключает договор
  3. read проверяет узлы по мере поступления
  4. Итоговый 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.

Корректный 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-схему из файла для проверки входного договора.

PHP 5.1+ · xmlreader

public XMLReader::setRelaxNGSchema(?string $filename): bool

Подключить RelaxNG-схему из файла для проверки входного договора.

Часть вызоваДоговор и границы
filenameПуть/URI доверенной схемы, а не текст XML. null отключает соответствующую схему; пустая строка — ValueError.
КогдаПосле открытия входа, до первого read. Настройка схемы не подтверждает успешное прохождение всей проверки.
Результатtrue — схема подключена; false и warning при отказе загрузки/разбора схемы или отсутствии поддержки schemas в libxml. Неверное состояние может вызвать Error.
ДовериеСхему выбирает приложение. Не берите произвольный путь/URI от посетителя; импортируемые схемой зависимости тоже требуют контроля.
Движение данных и курсора
  1. Вход содержит product с sku
  2. setRelaxNGSchema подключает договор
  3. read проверяет узлы по мере поступления
  4. Итоговый 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.

Корректный 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-схему из строки для проверки входного договора.

PHP 5.1+ · xmlreader

public XMLReader::setRelaxNGSchemaSource(?string $source): bool

Подключить RelaxNG-схему из строки для проверки входного договора.

Часть вызоваДоговор и границы
sourceТекст RelaxNG, а не путь. null отключает соответствующую схему; пустая строка — ValueError.
КогдаПосле открытия входа, до первого read. Настройка схемы не подтверждает успешное прохождение всей проверки.
Результатtrue — схема подключена; false и warning при отказе загрузки/разбора схемы или отсутствии поддержки schemas в libxml. Неверное состояние может вызвать Error.
ДовериеСхему выбирает приложение. Не берите произвольный путь/URI от посетителя; импортируемые схемой зависимости тоже требуют контроля.
Движение данных и курсора
  1. Вход содержит product с sku
  2. setRelaxNGSchemaSource подключает договор
  3. read проверяет узлы по мере поступления
  4. Итоговый 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.

Корректный 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.

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

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

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

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

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