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

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

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

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

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

SimpleXML: читать, проверять и изменять XML

От первого товара из XML до полного API элементов, атрибутов, XPath, пространств имён и обхода дерева.

Получить название товара из файла catalog.xml

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

Один товар: элемент, атрибут и текст

Объект $product даёт доступ к структуре XML. Имя узла и его содержимое — разные части документа.

  • $product → product

    new SimpleXMLElement разбирает строку и создаёт доступ к корневому элементу.

    • Атрибут sku = A1

      $product['sku'] выбирает атрибут; (string) получает текст A1.

    • Дочерний элемент name

      $product->name выбирает элемент; (string) получает текст Desk.

Дерево показывает отношения XML, а не расположение байтов в памяти. Квадратные скобки не превращают объект в обычный массив.

Поставщик выгружает каталог в XML. Наша первая задача — прочитать один товар и получить строку A1: Desk. Затем из этого же формата сделаем список остатков и сохраним обновлённую выгрузку. SimpleXML берёт на себя разбор вложенных тегов: нам не приходится искать границы полей в тексте вручную.

Исходный файл catalog.xml
<catalog>
  <product sku="A1"><name>Desk</name><stock>3</stock></product>
</catalog>
Часть этого файлаСмысл
ДокументВесь файл catalog.xml. Здесь у документа один корневой элемент catalog.
Элемент / узелcatalog содержит элемент product; product содержит name и stock. Узел — общее название части дерева, например элемента или текста.
Атрибут sku="A1"Дополнительное именованное значение в открывающем теге product. Оно относится к этому товару.
Текст Desk и 3Содержимое элементов name и stock. XML хранит текст; количество станет PHP-числом после проверки и преобразования.
</product>Закрывает тот product, который открыт выше. Вложенность важна: теги должны закрываться в обратном порядке.
Где находится название Desk
  1. catalog.xml: корень catalog
  2. его ребёнок product с атрибутом sku=A1
  3. ребёнок product → name → текст Desk
  4. PHP читает этот путь и выводит A1: Desk
Один файл — одна строка товараСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = simplexml_load_file(__DIR__ . '/catalog.xml');
$product = $catalog->product;
echo (string) $product['sku'], ': ', (string) $product->name, "\n";
Ожидаемый результат
A1: Desk

Положите catalog.xml рядом с PHP-файлом. Оба файла входят в пример. Здесь известный исправный учебный файл; обработка чужого повреждённого файла разобрана ниже, до итогового импортёра.

Запись в PHPЧто происходит
$catalog = simplexml_load_file(...)Функция читает файл и разбирает XML; = сохраняет результат в переменной $catalog. Знак $ начинает имя PHP-переменной. Скобки содержат входной аргумент — путь. Точка с запятой завершает действие.
__DIR__ . '/catalog.xml'__DIR__ — папка текущего PHP-файла; точка соединяет её с именем файла. Запуск из другого рабочего каталога не меняет этот путь.
$catalog->productСтрелка выбирает вложенные элементы product. В первом файле такой элемент один; позднее foreach обработает несколько.
$product['sku']Квадратные скобки выбирают XML-атрибут. Это специальный доступ SimpleXML, а не преобразование документа в обычный массив.
$product->name и (string)Стрелка выбирает элемент name, а (string) извлекает из него текст Desk для вывода и строгих сравнений.
echo ... , "\n"Вывести перечисленные через запятую части строки и её окончание. В браузерном HTML перевод строки сам по себе не создаёт тег br.

Для XML, который уже пришёл строкой, есть simplexml_load_string; new SimpleXMLElement($text) тоже разбирает строку, но при повреждённом XML бросает исключение. Переменная, вызов функции и объект объясняют окружающий PHP. Для первого чтения XML не требуется проектировать собственные классы. SimpleXML хранит дерево документа в памяти; большой потоковый импорт обсуждается в XMLReader.

Наличие расширения и настройка окружения

SimpleXML входит в PHP и включён в стандартной сборке по умолчанию; при сборке из исходников его можно отключить --disable-simplexml. Он требует libxml. Современная сборка libxml использует --with-libxml; до PHP 7.4 применялся --enable-libxml. В готовом пакете ОС состав расширений определяет поставщик. Не скачивайте случайную DLL: версия PHP, архитектура и вариант сборки должны совпадать.

Почему CLI работает, а сайт пишет Class not found
  1. Терминал запускает один PHP и его ini
  2. Веб-сервер/FPM может использовать другой бинарник и ini
  3. Проверяем SimpleXML именно в нужном процессе
  4. Подключаем совместимый пакет/сборку и перезапускаем соответствующий процесс
Проверить доступность необходимых возможностейСамостоятельный пример
<?php
declare(strict_types=1);

echo extension_loaded('SimpleXML') ? "SimpleXML доступен\n" : "Нет SimpleXML\n";
echo extension_loaded('libxml') ? "libxml доступен\n" : "Нет libxml\n";
echo class_exists(SimpleXMLIterator::class) ? "Итератор доступен\n" : "Нет итератора\n";
Ожидаемый результат
SimpleXML доступен
libxml доступен
Итератор доступен

В примере показан результат установленного окружения. На другой машине отрицательный ответ — повод проверить сборку и загруженный php.ini, а не переписать XML. DOM нужен отдельно только для примеров взаимодействия с DOM.

У семейства SimpleXML нет собственной отдельной страницы констант или runtime-директив. Аргумент options принимает константы libxml; сетевое чтение также зависит от доступных потоковых обёрток и конфигурации PHP. Диагностику конфигурации изучайте в настройке окружения. Не оставляйте публичную страницу phpinfo для проверки сервера.

Из того же файла — список остатков

Теперь поставщик добавил второй товар. Нужна отдельная строка для каждого product: Desk доступен, Lamp закончился. Ноль остаётся честным остатком, а не пропавшим полем.

Исходный файл 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. foreach выбирает product A1
  2. читает name=Desk и stock=3
  3. следующий product B2 даёт Lamp и 0
  4. после второго товара повторение заканчивается

foreach ($catalog->product as $product) означает «для каждого product временно назвать текущий товар $product». Тело между фигурными скобками выполняется для каждого товара. Сам XML-файл от чтения не изменяется.

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

$catalog = simplexml_load_file(__DIR__ . '/catalog.xml');
foreach ($catalog->product as $product) {
    echo (string) $product['sku'], ': ', (string) $product->name,
        ' — остаток ', (string) $product->stock, "\n";
}
Ожидаемый результат
A1: Desk — остаток 3
B2: Lamp — остаток 0

На этой ступени показываем ровно полученный текст количества. Перед записью в складской учёт добавим проверку формы и диапазона. Отсутствующий stock не следует автоматически считать нулём.

Измените 3 на 7 в файле и повторите запуск: первая строка изменится, вторая останется прежней. Добавьте ещё один product внутрь catalog — цикл выведет его без изменения PHP. Имена с дефисом и пути через промежуточный items рассмотрены в следующей ступени.

Повторяющиеся элементы, атрибуты и имена с дефисом

Каталог — несколько товаров, а не один. XML разрешает повторять product внутри catalog. Выражение $catalog->product — специальное перебираемое представление, не PHP-массив; [0] выбирает первый узел, а foreach проходит все. Если имя тега содержит дефис, обычная запись после стрелки неоднозначна для PHP: заключаем имя в фигурные скобки и кавычки.

Повторения не теряются
  1. catalog содержит product[0]=Desk и product[1]=Lamp
  2. foreach получает каждый product отдельно
  3. ['sku'] читает атрибут текущего товара
  4. ->{'short-name'} читает тег с дефисом
Список товаров с нестандартным именем поляСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog><product sku="A1"><short-name>Desk</short-name></product><product sku="B2"><short-name>Lamp</short-name></product></catalog>');
foreach ($catalog->product as $product) {
    echo (string) $product['sku'], ': ', (string) $product->{'short-name'}, "\n";
}
echo 'Первый: ', (string) $catalog->product[0]->{'short-name'}, "\n";
Ожидаемый результат
A1: Desk
B2: Lamp
Первый: Desk

foreach означает «для каждого товара». Фигурные скобки не меняют XML, а только сообщают PHP полное имя поля. Строка short-name не вычисляется как вычитание name из short.

Цепочка стрелок должна соответствовать фактической структуре: если товар лежит внутри items, нужен ->items->product. Пустой результат часто означает неверный путь или namespace, а не отсутствие данных. Перед обработкой нового поставщика рассмотрите один полный документ и сформулируйте его контракт. SimpleXMLElement::children помогает исследовать один уровень, SimpleXMLElement::attributes — атрибуты.

Отсутствие, пустой текст и ноль: три разных состояния

Количество 0 — корректный остаток; пустой комментарий — корректное отсутствие текста; отсутствие обязательного sku — ошибка формата. Нельзя свести эти случаи к if (!$value). Более того, успешный SimpleXMLElement с пустым корнем может преобразовываться к false. Разбор проверяют через === false, существование элемента — isset на XML-пути, текст — явным приведением и сравнением со строкой.

СитуацияПравильный вопрос
simplexml_load_string вернул falseУдалось ли разобрать XML? Проверка === false.
Тега comment нетПрисутствует ли поле? isset($xml->comment).
Тег comment существует, но пустРавен ли (string) $xml->comment пустой строке?
Тег stock содержит 0Это строка "0", а не отсутствие; валидируем как количество.
Пустой комментарий, нулевой остаток и пропущенная ценаСамостоятельный пример
<?php
declare(strict_types=1);

$product = new SimpleXMLElement('<product><stock>0</stock><comment/></product>');
echo 'Остаток: ', (string) $product->stock, "\n";
if (isset($product->comment) && (string) $product->comment === '') {
    echo "Комментарий передан, но пуст\n";
}
if (!isset($product->price)) {
    echo "Цена не передана: запросить её у поставщика\n";
}
Ожидаемый результат
Остаток: 0
Комментарий передан, но пуст
Цена не передана: запросить её у поставщика

isset проверяет путь в XML, явное сравнение строки отличает пустоту от "0". Если сохранить отсутствующую выборку в отдельную PHP-переменную, isset этой переменной не заменит проверку наличия XML-поля.

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

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

$first = new SimpleXMLElement('<status>paid</status>');
$second = new SimpleXMLElement('<status>paid</status>');
var_dump($first == $second);
var_dump((string) $first === (string) $second);
Ожидаемый результат
bool(false)
bool(true)

Проверка == здесь не доказывает равенство XML-документов. В задаче проверки статуса нужна вторая строка: простое сравнение извлечённого значения.

Передать в сервис обычные значения, а не XML-объекты

Слой импорта должен перевести данные поставщика в понятную структуру приложения: sku остаётся строкой, qty становится целым после проверки, повторяющиеся позиции — списком. Автоматическое json_encode($xml) → json_decode(...) выглядит коротко, но не определяет политику атрибутов, пространств имён, смешанного содержимого и повторяющихся тегов. Явное отображение фиксирует контракт и место отказа.

Граница интеграции
  1. XML item имеет sku="0012", qty="2"
  2. Проверяем обязательное поле и текст количества
  3. Создаём массив [sku => "0012", quantity => 2]
  4. Дальнейшая обработка работает с массивом независимо от XML
Нормализовать позиции поставщикаСамостоятельный пример
<?php
declare(strict_types=1);

$order = new SimpleXMLElement('<order><item sku="0012"><qty>2</qty></item><item sku="A7"><qty>0</qty></item></order>');
$items = [];
foreach ($order->item as $item) {
    if (!isset($item['sku']) || (string) $item['sku'] === '') {
        throw new UnexpectedValueException('Нет артикула');
    }
    $qtyText = (string) $item->qty;
    if (!preg_match('/\A(?:0|[1-9][0-9]{0,3})\z/', $qtyText)) {
        throw new UnexpectedValueException('Количество: целое от 0 до 9999');
    }
    $items[] = ['sku' => (string) $item['sku'], 'quantity' => (int) $qtyText];
}
foreach ($items as $item) { echo $item['sku'], ':', $item['quantity'], "\n"; }
Ожидаемый результат
0012:2
A7:0

Это следующий уровень после минимального чтения. Регулярное выражение разрешает ровно договорённую десятичную форму 0…9999, без пробелов, знака, дроби и ведущих нулей. Ограничение до приведения защищает от переполнения; SKU не приводим к числу. В контракте другого поставщика допустимая форма может быть иной.

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

Ошибки разбора: сообщение человеку и диагностика разработчику

Исходный файл catalog.xml — намеренно ошибочный
<catalog>
  <product sku="A1"><name>Desk</name></item>
</catalog>

Если закрывающий тег не соответствует открывающему, документа как корректного дерева нет. simplexml_load_string/load_file возвращают false и сообщают XML-ошибки; new SimpleXMLElement дополнительно бросает Exception. Внутренний режим libxml сохраняет объекты LibXMLError, где есть code, level, line, column, message и file. Эти сведения помогают найти повреждённый ответ, но не должны бесконтрольно попадать посетителю.

Граница разбора чужого ответа
  1. Установить внутренний режим, сохранив прежний
  2. Разобрать строку и проверить === false
  3. На отказе забрать список ошибок и остановить обработку
  4. finally очищает очередь и восстанавливает прежний режим
Повреждённый каталог: назвать строку и остановить импортСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $catalog = simplexml_load_file(__DIR__ . '/catalog.xml');
    if ($catalog === false) {
        $errors = libxml_get_errors();
        echo "catalog.xml: импорт отменён\n";
        echo 'Проверьте закрывающий тег в строке ', $errors[0]->line, "\n";
    }
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previous);
}
Ожидаемый результат
catalog.xml: импорт отменён
Проверьте закрывающий тег в строке 2

В приложенном файле строка 2 закрывает product тегом item. Исправьте его на /product и повторите запуск: сообщение об отказе исчезнет. Номер строки взят из LibXMLError, а объяснение дефекта дано для этого известного учебного файла. В рабочем сервисе выводите пользователю идентификатор импорта, подробности оставляйте в закрытой диагностике.

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

Отдельно различайте тип ошибки: нет расширения, недоступен источник, некорректен XML, неверен путь/namespace, нет обязательного поля, поле не проходит бизнес-проверку. Заглушка «не работает XML» скрывает разные способы исправления. libxml_get_errors, libxml_clear_errors и исключения раскрывают механизм обработки.

Параметры libxml, кодировки и граница доверия

SimpleXML разбирает весь документ; options — битовая маска настроек парсера, а не разрешение доверять входу. Источник, размер, кодировку и набор допустимых XML-возможностей определяет интеграция. Не передавайте URL из пользовательского запроса в load_file: даже корректный XML может быть получен из запрещённого места. Сначала контролируемый транспорт, затем ограниченный ответ и разбор.

Флаг / свойствоКогда важно
LIBXML_NONETОграничивает сетевой доступ парсера, но не является универсальной изоляцией путей, локальных файлов или всех возможных загрузчиков.
LIBXML_NOENTНесмотря на название, включает подстановку сущностей. Для недоверенного XML не включайте его без специальной необходимости и защиты от XXE.
LIBXML_DTDLOAD / DTDATTR / DTDVALIDЧтение внешнего DTD, добавление DTD-атрибутов, DTD-валидация могут включить обращение к внешним сущностям.
LIBXML_NO_XXEPHP 8.4+ при libxml 2.13+: ограничивает внешние сущности. Проверяйте defined; отсутствие константы не оправдывает небезопасную комбинацию других флагов.
LIBXML_PARSEHUGEОслабляет ограничения глубины/размеров; только при обоснованном доверенном входе. Не средство экономии памяти.
LIBXML_NOCDATA / NOBLANKSПервый сливает CDATA с текстом, второй убирает пустые текстовые узлы. Это изменения модели документа, не обязательные настройки каждого импорта.
LIBXML_NOEMPTYTAGВыходная возможность DOM, не настройка SimpleXML-конструктора; не заставит asXML рисовать парные пустые теги.
CDATA читается как текст, HTML не становится безопаснымСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<product><description><![CDATA[Desk & <b>Lamp</b>]]></description></product>');
$text = (string) $xml->description;
echo $text, "\n";
echo htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), "\n";
Ожидаемый результат
Desk & <b>Lamp</b>
Desk &amp; &lt;b&gt;Lamp&lt;/b&gt;

CDATA позволяет записать символы как текст внутри XML. Она не очищает HTML. Первая строка подходит для консоли, вторая — для безопасного отображения как текста на HTML-странице. Двойной разбор этого текста как HTML — уже другое решение приложения.

XML-декларация encoding описывает исходные байты; произвольная замена её на UTF-8 байты не перекодирует. Извлечённые значения SimpleXML представлены в UTF-8. Если внешняя система действительно присылает другую кодировку, сохраняйте верную декларацию либо выполняйте согласованное преобразование кодировки. Не прогоняйте уже UTF-8 через устаревший utf8_encode. Полный список констант и правила загрузчиков — в семействе libxml.

Элемент определяется URI и локальным именем

У двух поставщиков может быть поле id с разным смыслом. XML namespace отделяет имена: пара URI + локальное имя задаёт элемент, а prefix — лишь короткая запись. xmlns="urn:shop" задаёт namespace по умолчанию для элементов; атрибут sku без префикса остаётся без namespace. URI здесь идентификатор, его не требуется скачивать.

Почему один product находится, а другой нет
  1. product в urn:shop и product без namespace — разные имена
  2. children("urn:shop") выбирает правильную группу
  3. attributes() читает обычный sku без namespace
  4. Для XPath регистрируем собственный s → urn:shop
Соединить элементы и атрибуты разных пространствСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<catalog xmlns="urn:shop" xmlns:sync="urn:sync"><product sku="A1" sync:id="remote-8"><name>Desk</name></product></catalog>');
$product = $xml->children('urn:shop')->product;
echo (string) $product->attributes()['sku'], "\n";
echo (string) $product->attributes('urn:sync')['id'], "\n";
echo (string) $product->children('urn:shop')->name, "\n";
Ожидаемый результат
A1
remote-8
Desk

После children(URI) объект несёт выбранный контекст namespace. Для обычного атрибута явно вызываем attributes() без namespace, затем ['sku']; простые скобки на представлении другого namespace могут выбрать не то. Запись по URI переживёт замену sync на s у поставщика. Если меняется URI версии протокола, меняется контракт; удалять xmlns из строки опасно.

Для исследования различайте SimpleXMLElement::getDocNamespaces (объявления) и SimpleXMLElement::getNamespaces (использование). Для поиска по XPath нужен SimpleXMLElement::registerXPathNamespace у того объекта, на котором запускается запрос.

Изменить XML в памяти и сохранить результат отдельно

Чтение файла не создаёт автоматической синхронизации с ним. Можно поменять поле, добавить строку, удалить ненужный атрибут — исходный файл останется прежним до явной записи. Присваивание простого текста через свойство экранирует его как XML-текст; у addChild правила амперсанда отличаются. Выбирайте API согласно операции.

Изменение и сохранение
  1. В памяти: order status=new, два item
  2. Присвоить status=paid, удалить временный атрибут
  3. Добавить note как обычный текст
  4. asXML возвращает новое представление; запись файла — отдельное действие
Обновить заказ и увидеть всё новое деревоСамостоятельный пример
<?php
declare(strict_types=1);

$order = new SimpleXMLElement('<order draft="1" status="new"><item>A</item><item>B</item></order>');
$order['status'] = 'paid';
unset($order['draft']);
unset($order->item[1]);
$order->addChild('note', htmlspecialchars('Desk & Lamp', ENT_XML1 | ENT_QUOTES, 'UTF-8'));
echo $order->asXML();
Ожидаемый результат
<?xml version="1.0"?>
<order status="paid"><item>A</item><note>Desk &amp; Lamp</note></order>

Сравните весь результат с первой строкой: status изменён, draft удалён, item B удалён, note добавлен. В памяти уже новое дерево; на диске файл сам не меняется. Для файла передайте путь в asXML, как в следующем готовом задании.

Сохранение подробно показывают SimpleXMLElement::asXML и её alias SimpleXMLElement::saveXML. Чтобы передать состояние в очередь/сессию, храните проверенные обычные значения либо XML-строку и разбирайте её заново. Встроенный XML-объект не следует считать переносимым сериализуемым DTO.

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

$order = new SimpleXMLElement('<order><number>42</number></order>');
try { serialize($order); }
catch (Exception $error) { echo "Объект не сериализуется\n"; }
$saved = $order->asXML();
if ($saved === false) { throw new RuntimeException('Не сохранён XML'); }
$restored = new SimpleXMLElement($saved);
echo (string) $restored->number, "\n";
Ожидаемый результат
Объект не сериализуется
42

Строка не сохраняет произвольное состояние PHP-класса и XPath-регистрации; это новый разбор документа. Доверие и ограничения при следующем чтении применяются снова.

SimpleXMLElement: представление дерева, а не массив свойств

SimpleXMLElement представляет XML-элемент/выборку. Класс доступен с PHP 5; в PHP 8 объявляет Stringable, Countable и RecursiveIterator. Поэтому объект поддерживает строковое преобразование, count и foreach. Доступ к именам тегов и атрибутов реализован расширением: var_dump не является точным контрактом преобразования XML в PHP-структуру. Все публичные методы перечислены ниже в карточках; saveXML — alias asXML.

Интерфейс / операцияПрактическое следствие
Stringable / (string)Читаем непосредственный текст, а не рекурсивно весь текст потомков.
Countable / countСчитаем выбранные элементы, а не символы строки.
RecursiveIteratorМожно управлять курсором и организовать спуск по дереву.
НаследованиеСобственный подкласс может добавить предметные методы; функции загрузки принимают class_name.
Связанные представленияЧтение ребёнка создаёт доступ к той же структуре; изменение через него видно в исходном дереве.

В PHP 8.4 изменилось влияние чтения на итератор: getName/asXML и приведение к string больше не перематывают его. Ручной обход начинайте явно с rewind. Дополнительный метод PHP 8.5 SimpleXMLElement::__debugInfo описан по официальному исходнику, хотя отдельной страницы у него пока нет. Не путайте специальную модель XML со своими классами заказов: хранить XML-представление во всех слоях означает связать бизнес-код с форматом поставщика. Для независимого сервиса сделайте явное отображение полей.

SimpleXMLIterator и проход по всей глубине

Само слово Recursive в интерфейсе не означает, что обычный foreach автоматически обойдёт всех потомков. Он проходит один уровень. Для рекурсивного разворачивания нужен управляющий алгоритм, например RecursiveIteratorIterator. SimpleXMLIterator — наследник SimpleXMLElement; с PHP 8 семь его методов курсора перенесены в базовый класс. В новых проектах базового объекта достаточно, исторический класс остаётся доступен и наследует всё его API.

Один уровень и вся глубина
  1. catalog содержит category, внутри неё product
  2. Обычный foreach → только category
  3. RecursiveIteratorIterator SELF_FIRST → category, затем product
  4. LEAVES_ONLY → только листья, промежуточные категории пропускаются
Показать категории с названиями вложенных товаровСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog><category label="Для кабинета"><product>Desk</product><product>Lamp</product></category></catalog>');
$walk = new RecursiveIteratorIterator($catalog, RecursiveIteratorIterator::SELF_FIRST);
foreach ($walk as $name => $node) {
    $label = $name === 'category' ? (string) $node['label'] : (string) $node;
    echo str_repeat('  ', $walk->getDepth()), $label, "\n";
}
Ожидаемый результат
Для кабинета
  Desk
  Lamp

SELF_FIRST сначала выдаёт category, затем её товары. getDepth даёт уровень: str_repeat рисует два пробела для вложенного уровня. Название категории берём из атрибута label, название товара — из его текста. Это дерево элементов, доступных SimpleXML, а не полный перечень комментариев и текстовых узлов DOM.

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

$catalog = new SimpleXMLIterator('<catalog><category><product>Desk</product></category></catalog>');
$walk = new RecursiveIteratorIterator($catalog);
foreach ($walk as $name => $node) { echo $name, '=', (string) $node, "\n"; }
echo is_subclass_of(SimpleXMLIterator::class, SimpleXMLElement::class) ? "Наследник\n" : "Другой тип\n";
Ожидаемый результат
product=Desk
Наследник

Режим по умолчанию LEAVES_ONLY выдаёт листья. Это не сбор всех XML-узлов любого типа: комментарии, текстовые узлы и namespace имеют собственную модель. Для полного контроля над ними переходите к DOM.

Ручной протокол: SimpleXMLElement::rewind → SimpleXMLElement::valid → SimpleXMLElement::key/SimpleXMLElement::current → SimpleXMLElement::next. Спуск: SimpleXMLElement::hasChildren и SimpleXMLElement::getChildren. Не смешивайте изменение курсора вручную с тем же проходом foreach без ясной необходимости.

Когда оставить SimpleXML, а когда выбрать другой XML-инструмент

ЗадачаВыбор и причина
Небольшой ответ сервиса, известные поляSimpleXML: короткие пути, attributes, children и XPath.
Смешанный текст, комментарии, перестановка узлов, схемыDOM: явные типы узлов и операции над структурой; можно совместить через simplexml_import_dom.
Большая последовательная выгрузкаXMLReader: курсор вперёд, обработка ограниченных фрагментов вместо целого дерева.
Создание огромной выходной выгрузкиXMLWriter: последовательная запись, без необходимости держать всю структуру как SimpleXML.
Обычная бизнес-обработка после импортаПроверенные массивы/DTO; формат XML остаётся на границе интеграции.

Переход на XMLReader меняет алгоритм: глобальный XPath по всему документу уже нельзя бездумно выполнять на каждом отдельном фрагменте. Ограничивать нужно и размер одного фрагмента; один огромный product тоже может исчерпать память. Содержимое XML, строки его сериализации и массивы нормализованных данных могут находиться в памяти одновременно. Не обещайте экономию только потому, что файл открыт «потоком».

Разбор XML подтверждает синтаксическую корректность, но не допустимость цены, уникальность артикула, безопасность HTML в description или подлинность поставщика. Эти требования добавляет код интеграции. Регулярное выражение подходит для проверки отдельного извлечённого поля; произвольный вложенный XML им не разбирают.

Полный указатель функций и методов SimpleXML

Начните с simplexml_load_string, если XML уже получен строкой, с simplexml_load_file — для известного локального файла, с simplexml_import_dom — если дерево уже разобрано DOM. Методы работают на полученном объекте.

У SimpleXMLIterator нет отдельного нового набора методов в PHP 8: он наследует опубликованные здесь методы SimpleXMLElement. Настройки разбора и общая диагностика относятся к libxml.

Практика: подготовить локальную ведомость остатков

Есть catalog с product, атрибутом sku и тегом stock. Нужно получить список «артикул:остаток», сохранив нулевые остатки. Сначала проверьте существование sku и stock, затем форму stock по контракту, потом преобразуйте его в число. Вторая задача: поставщик перенёс stock в urn:stock — меняем чтение поля, остальной прикладной массив остаётся прежним.

Предлагаемая последовательность решения
  1. Загрузить и проверить документ
  2. Найти каждый product, отличить отсутствие от 0
  3. Прочитать stock из известного namespace
  4. Проверить целое число в пределах договора
  5. Выдать простой список и сохранить ошибки отдельно
Решение: namespace меняется только на границе чтенияСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog xmlns:s="urn:stock"><product sku="A1"><s:stock>0</s:stock></product><product sku="B2"><s:stock>12</s:stock></product></catalog>');
foreach ($catalog->product as $product) {
    $stockFields = $product->children('urn:stock');
    if (!isset($product['sku']) || (string) $product['sku'] === '' || !isset($stockFields->stock)) {
        throw new UnexpectedValueException('Нет обязательных полей');
    }
    $text = (string) $stockFields->stock;
    if (!preg_match('/\A(?:0|[1-9][0-9]{0,5})\z/', $text)) {
        throw new UnexpectedValueException('Остаток вне формата');
    }
    echo (string) $product['sku'], ':', (int) $text, "\n";
}
Ожидаемый результат
A1:0
B2:12

Здесь договор допускает 0…999999. После проверки int безопасен и на 32-битном PHP. Добавьте отрицательный тест: удалите stock или замените 12 на 12x; импорт должен остановиться, а не молча превратить мусор в 12.

Готовое задание: принять остатки и сохранить только доступные товары

Магазину нужна уменьшенная выгрузка available.xml: товары с нулевым остатком не включаются. Сначала проверим один остаток, затем применим уже знакомый foreach ко всему файлу. Этот этап ничего не записывает в настоящую базу магазина.

Путь товарной записи
  1. catalog.xml: A1 / Desk / 3 и B2 / Lamp / 0
  2. XML-парсер проверяет структуру документа
  3. наша проверка проверяет sku/name/stock
  4. 3 → добавить товар, 0 → пропустить
  5. available.xml содержит только A1; открыть и проверить содержимое

Синтаксическая корректность XML означает, что дерево построено. Число «много» внутри stock синтаксически допустимо, но наш склад принимает только десятичное целое 0…9999. Регулярное выражение ниже проверяет именно эту договорённость, а не XML.

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

foreach (['3', '0', 'много'] as $stock) {
    if (preg_match('/\A(?:0|[1-9][0-9]{0,3})\z/', $stock) !== 1) {
        echo $stock, ": отклонить остаток\n";
        continue;
    }
    echo $stock, (int) $stock > 0 ? ": включить товар\n" : ": пропустить товар\n";
}
Ожидаемый результат
3: включить товар
0: пропустить товар
много: отклонить остаток

0 — отдельная допустимая форма; [1-9] начинает положительное число, [0-9]{0,3} разрешает ещё до трёх цифр. Якоря \A и \z ограничивают всю строку. Приведение (int) выполняем после проверки; правила для другого поставщика могут отличаться.

Теперь соберём эти действия в exportAvailable($input, $output). Два параметра — имена входного и выходного файлов. Функция сначала собирает полностью проверенное дерево, и только после всех товаров сохраняет результат. throw останавливает операцию с понятной причиной; finally возвращает режим диагностики даже при отказе.

catalog.xml → проверка → available.xmlСамостоятельный пример
<?php
declare(strict_types=1);

function exportAvailable(string $input, string $output): void {
    $previous = libxml_use_internal_errors(true);
    libxml_clear_errors();
    try {
        $catalog = simplexml_load_file($input, SimpleXMLElement::class, LIBXML_NONET);
        if ($catalog === false || $catalog->getName() !== 'catalog') {
            throw new UnexpectedValueException('Нужен исправный XML с корнем catalog');
        }
        $result = new SimpleXMLElement('<catalog/>');
        foreach ($catalog->product as $product) {
            $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 . ': нужны sku, name и остаток 0…9999');
            }
            if ((int) $stock === 0) { continue; }
            $item = $result->addChild('product');
            $item->addAttribute('sku', $sku);
            $item->addChild('name', htmlspecialchars($name, ENT_XML1 | ENT_QUOTES, 'UTF-8'));
            $item->addChild('stock', $stock);
        }
        if (!$result->asXML($output)) { throw new RuntimeException('Не удалось записать выгрузку'); }
    } finally {
        libxml_clear_errors();
        libxml_use_internal_errors($previous);
    }
}
exportAvailable(__DIR__ . '/catalog.xml', __DIR__ . '/available.xml');
echo file_get_contents(__DIR__ . '/available.xml');
try {
    exportAvailable(__DIR__ . '/bad-stock.xml', __DIR__ . '/rejected.xml');
} catch (UnexpectedValueException $error) {
    echo 'Отклонено: ', $error->getMessage(), "\n";
}
echo file_exists(__DIR__ . '/rejected.xml') ? "Ошибка: появился файл отказа\n" : "rejected.xml не создан\n";
Ожидаемый результат
<?xml version="1.0"?>
<catalog><product sku="A1"><name>Desk</name><stock>3</stock></product></catalog>
Отклонено: Товар X9: нужны sku, name и остаток 0…9999
rejected.xml не создан

В архиве есть исправный catalog.xml и bad-stock.xml с количеством "много". Успех создаёт available.xml; ошибка до сохранения не создаёт rejected.xml. Это локальная учебная операция: проверка не задаёт полноценную XSD-схему, допустимые namespace/дубликаты SKU здесь не реализованы. Для рабочего импорта добавьте их по договору, ограничьте размер входа, а замену общего файла сделайте через временный файл и согласованную политику записи.

Попробуйте вернуть Lamp остаток 4: в available.xml появится вторая позиция. Если источник применяет default namespace, сначала используйте SimpleXMLElement::children или зарегистрированный префикс SimpleXMLElement::xpath. Для точного редактирования произвольного дерева перейдите к DOM, для последовательной выгрузки большого объёма — к XMLWriter.

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

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

simplexml_load_stringПревратить полученную строку XML в дерево, чтобы прочитать название товара, а не искать теги через explode.

PHP 5+ · SimpleXML

simplexml_load_string(string $data, ?string $class_name = SimpleXMLElement::class, int $options = 0, string $namespace_or_prefix = "", bool $is_prefix = false): SimpleXMLElement|false

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

Часть контрактаЗначение
dataПолный корректный XML-документ в строке, а не имя файла. Декларация encoding должна соответствовать байтам; прочитанный текст SimpleXML предоставляет в UTF-8.
class_nameИмя SimpleXMLElement или его наследника; значение по умолчанию SimpleXMLElement::class, null также выбирает стандартный класс. Неподходящий класс — TypeError; отсутствующий класс — ошибка разрешения имени.
optionsЦелое число: 0 — обычный разбор, несколько флагов libxml объединяют побитовым |. Флаги меняют разбор, не проверяют бизнес-правила; подробнее безопасные границы.
namespace_or_prefix / is_prefixСтрока URI пространства имён либо префикс при is_prefix=true. По умолчанию пустая строка и false. Это выбор пространства для представления узлов, не регистрация префикса XPath.
Возврат и ошибкиSimpleXMLElement при успехе, false при ошибке чтения/разбора. Проверять === false: пустой XML-элемент может приводиться к false. Ошибки XML обычно E_WARNING; внутренний режим libxml собирает их отдельно. Типы аргументов проверяет PHP, некорректные аргументы могут выбросить TypeError/ValueError.
Что получает переменная $catalog
  1. Строка содержит catalog → product → name
  2. simplexml_load_string разбирает разметку и создаёт дерево
  3. Возвращённый объект помещается в $catalog
  4. (string) $catalog->product->name извлекает обычный текст
Прочитать название товараСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = simplexml_load_string('<catalog><product><name>Desk</name></product></catalog>');
if ($catalog === false) { throw new RuntimeException('XML не прочитан'); }
$name = (string) $catalog->product->name;
echo $name, "\n";
Ожидаемый результат
Desk

Строка между кавычками — данные документа. product и name после стрелки — имена вложенных тегов. (string) отделяет текст от XML-представления: $name уже можно передать функции, которая ждёт строку.

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

$catalog = simplexml_load_string('<catalog/>');
if ($catalog === false) {
    echo "Повреждённый XML\n";
} elseif ($catalog->product->count() === 0) {
    echo "Каталог прочитан; товаров нет\n";
}
Ожидаемый результат
Каталог прочитан; товаров нет

Строгое === false проверяет отказ функции. Пустой корректный SimpleXMLElement может преобразовываться в false, поэтому if (!$catalog) ошибочно смешивает два случая. Отсутствие товаров — отдельное решение: здесь показываем пустой каталог, а не удаляем прежние остатки.

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

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $catalog = simplexml_load_string('<catalog><product></catalog>');
    echo $catalog === false ? "Импорт отклонён\n" : "Прочитан\n";
    echo count(libxml_get_errors()) > 0 ? "Есть диагностика\n" : "Нет ошибок\n";
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previous);
}
Ожидаемый результат
Импорт отклонён
Есть диагностика

Сначала останавливаем дальнейшую работу с false, затем изучаем причину. Точный текст ошибок зависит от libxml, поэтому пользовательское сообщение и автоматическая проверка не привязаны к английской формулировке.

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

Продолжение: simplexml_load_file, SimpleXMLElement::__construct.

simplexml_load_fileПрочитать небольшой XML-каталог из известного локального файла одним вызовом.

PHP 5+ · SimpleXML

simplexml_load_file(string $filename, ?string $class_name = SimpleXMLElement::class, int $options = 0, string $namespace_or_prefix = "", bool $is_prefix = false): SimpleXMLElement|false

Прочитать небольшой XML-каталог из известного локального файла одним вызовом.

Часть контрактаЗначение
filenameПуть к XML-файлу. Доступные URL/потоковые обёртки зависят от окружения и настроек. Не передавайте сюда произвольный URL/путь из формы: это граница доступа к файлам и сети.
class_nameИмя SimpleXMLElement или его наследника; значение по умолчанию SimpleXMLElement::class, null также выбирает стандартный класс. Неподходящий класс — TypeError; отсутствующий класс — ошибка разрешения имени.
optionsЦелое число: 0 — обычный разбор, несколько флагов libxml объединяют побитовым |. Флаги меняют разбор, не проверяют бизнес-правила; подробнее безопасные границы.
namespace_or_prefix / is_prefixСтрока URI пространства имён либо префикс при is_prefix=true. По умолчанию пустая строка и false. Это выбор пространства для представления узлов, не регистрация префикса XPath.
Возврат и ошибкиSimpleXMLElement при успехе, false при ошибке чтения/разбора. Проверять === false: пустой XML-элемент может приводиться к false. Ошибки XML обычно E_WARNING; внутренний режим libxml собирает их отдельно. Типы аргументов проверяет PHP, некорректные аргументы могут выбросить TypeError/ValueError.
Файл не становится потоком товаров
  1. Путь catalog.xml выбирает источник
  2. Парсер читает документ и строит целое дерево
  3. foreach перебирает уже разобранные product
  4. Для огромного каталога нужен XMLReader, а не увеличение цикла
Два товара из локального файлаСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = simplexml_load_file(__DIR__ . '/catalog.xml');
if ($catalog === false) { throw new RuntimeException('Каталог недоступен'); }
foreach ($catalog->product as $product) {
    echo (string) $product['sku'], ': ', (string) $product->name, "\n";
}
Ожидаемый результат
A1: Desk
B2: Lamp

__DIR__ — папка этого PHP-файла, точка склеивает путь. foreach по очереди кладёт каждый product в $product. В учебном запуске catalog.xml создаётся рядом; в своём проекте сохраните файл с указанным XML.

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

class InventoryXml extends SimpleXMLElement {
    public function firstAvailability(): string {
        $stock = (string) $this->product[0]->stock;
        return $stock === '0' ? 'закончился' : 'остаток ' . $stock;
    }
}
$catalog = simplexml_load_file(__DIR__ . '/catalog.xml', InventoryXml::class);
echo (string) $catalog->product[0]->name, ': ', $catalog->firstAvailability(), "\n";
Ожидаемый результат
Desk: остаток 3

Второй аргумент задаёт класс результата. InventoryXml наследует чтение XML и добавляет операцию для известного формата; ::class даёт имя класса строкой. Это развитие примера после знакомства с классами. Валидацию произвольного stock оно не заменяет; для обычной выгрузки достаточно отдельной функции без наследника.

Предварительный file_exists не гарантирует успех последующего чтения: файл может измениться, права — отличаться. Всё равно проверяйте результат. Конструирование дерева занимает память сверх размера исходного файла; потоковое чтение само по себе не делает SimpleXML потоковым парсером. Настройку загрузок libxml выполняет libxml_set_streams_context; транспортный тайм-аут не заменяет лимит размера и политику разрешённых адресов.

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

Продолжение: simplexml_load_string, SimpleXMLElement::asXML.

SimpleXMLElement::__constructСоздать XML-объект через new, получая исключение, если документ невозможно разобрать.

PHP 5+ · SimpleXML

new SimpleXMLElement(string $data, int $options = 0, bool $dataIsURL = false, string $namespaceOrPrefix = "", bool $isPrefix = false)

Создать XML-объект через new, получая исключение, если документ невозможно разобрать.

Часть контрактаЗначение
dataXML в строке; при dataIsURL=true — путь/URL. Случайно передать filename без этого флага означает попробовать разобрать имя файла как XML.
optionsФлаги libxml при разборе. Выходные флаги, например LIBXML_NOEMPTYTAG, игнорируются: конструктор не настраивает сериализацию.
dataIsURLfalse по умолчанию. true включает чтение источника, со всеми его правами и сетевыми рисками.
namespaceOrPrefix / isPrefixURI либо префикс при true; по умолчанию пустая строка и false. Имена аргументов отличаются от snake_case параметров функций загрузки.
Результат и отказnew возвращает созданный объект. XML-ошибки дают E_WARNING и дополнительно Exception при невозможности разбора. Внутренний режим libxml убирает вывод предупреждений, но не отменяет исключение.
new и вызов обычной функции
  1. new выбирает класс SimpleXMLElement
  2. Конструктор получает строку data
  3. Успех → объект присваивается $order
  4. Отказ → Exception, присваивание не завершается
Создать XML подтверждения заказаСамостоятельный пример
<?php
declare(strict_types=1);

$order = new SimpleXMLElement('<order number="42" status="accepted"/>');
echo 'Заказ ', (string) $order['number'], ': ', (string) $order['status'], "\n";
echo $order->asXML();
Ожидаемый результат
Заказ 42: accepted
<?xml version="1.0"?>
<order number="42" status="accepted"/>

new создаёт объект из XML-строки: number и status — атрибуты одного корня. asXML показывает, какой документ можно передать следующему этапу. Конструктор не подтверждает заказ в магазине сам по себе — accepted здесь явно заданные учебные данные.

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

$old = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    new SimpleXMLElement('<order>');
} catch (Exception $error) {
    echo "Заказ не принят\n";
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($old);
}
Ожидаемый результат
Заказ не принят

catch выполняется после неудачного new. Не присваиваем повреждённый объект и не продолжаем импорт. Если удобнее явное значение false, используйте функцию загрузки.

LIBXML_PARSEHUGE ослабляет ограничения парсера на глубину/размер текстовых узлов. Это не средство экономии памяти и не универсальный способ «починить» чужой XML. Для недоверенных документов сохраняйте ограничения; как работают исключения.

Продолжение: simplexml_load_string, simplexml_load_file.

simplexml_import_domИспользовать удобное чтение SimpleXML для узла, уже разобранного DOM, без повторного разбора XML-строки.

PHP 5+ · SimpleXML

simplexml_import_dom(object $node, ?string $class_name = SimpleXMLElement::class): ?SimpleXMLElement

Использовать удобное чтение SimpleXML для узла, уже разобранного DOM, без повторного разбора XML-строки.

Часть контрактаЗначение
nodeПоддерживаемый узел документа XML/HTML: обычно DOMDocument или DOMElement; с PHP 8.4 поддерживается современный Dom\Document. Произвольный объект не является DOM-узлом.
class_nameSimpleXMLElement или его наследник; null выбирает стандартный класс. Не меняет класс DOM-объекта.
ВозвратПредставление SimpleXMLElement либо null при неудаче. Два API работают с одним underlying XML-деревом: изменение видно с другой стороны.
Ошибки и версииНеподдерживаемый объект — TypeError с PHP 8.4, ранее ValueError. Не каждый тип DOM-узла является подходящим элементом; неподходящий узел может дать предупреждение и null. Нужны расширения DOM и SimpleXML.
Два окна к одному дереву
  1. DOMDocument владеет деревом order → status
  2. simplexml_import_dom возвращает другой интерфейс к нему
  3. SimpleXML меняет status на paid
  4. DOM читает тот же новый текст, копия документа не создаётся
Прочитать результат существующего DOM-кодаСамостоятельный пример
<?php
declare(strict_types=1);

$dom = new DOMDocument();
if (!$dom->loadXML('<order><status>new</status></order>')) {
    throw new RuntimeException('Нет документа');
}
$order = simplexml_import_dom($dom);
if ($order === null) { throw new RuntimeException('Нет элемента'); }
echo (string) $order->status, "\n";
$order->status = 'paid';
echo $dom->getElementsByTagName('status')->item(0)->textContent, "\n";
Ожидаемый результат
new
paid

Метод loadXML возвращает bool: проверяем его, а не истинность объекта $dom. Импорт сохраняет связь с деревом; это важно, если разные части программы редактируют один документ.

Современный Dom\XMLDocument: PHP 8.4+Самостоятельный пример
<?php
declare(strict_types=1);

$document = Dom\XMLDocument::createFromString('<catalog><product>Desk</product></catalog>');
$catalog = simplexml_import_dom($document);
if ($catalog === null) { throw new RuntimeException('Нет XML'); }
echo (string) $catalog->product, "\n";
try { simplexml_import_dom(new stdClass()); }
catch (TypeError $error) { echo "Нужен DOM-узел\n"; }
Ожидаемый результат
Desk
Нужен DOM-узел

Этот пример требует PHP 8.4+. Нельзя заменить документ объектом с похожими публичными свойствами: контракт проверяет фактический DOM-тип.

Для произвольного HTML сначала нужен HTML-парсер DOM; SimpleXML ожидает корректный XML. Обратное направление — dom_import_simplexml. DOM удобнее для смешанного текста, комментариев, перемещения узлов и точного управления структурой.

Продолжение: simplexml_load_string, SimpleXMLElement::asXML.

SimpleXMLElement::attributesПеречислить свойства, записанные внутри открывающего тега: артикул, валюту или признак доступности.

PHP 5+ · SimpleXML

SimpleXMLElement::attributes(?string $namespaceOrPrefix = null, bool $isPrefix = false): ?SimpleXMLElement

Перечислить свойства, записанные внутри открывающего тега: артикул, валюту или признак доступности.

Часть контрактаЗначение
namespaceOrPrefixnull по умолчанию выбирает обычные атрибуты без пространства имён; строка задаёт URI либо префикс при isPrefix=true.
isPrefixfalse: строка — URI, true: строка — префикс из документа. URI надёжнее, когда поставщик меняет сокращение префикса.
ВозвратПеребираемое XML-представление атрибутов, даже если список пуст; сигнатура также допускает null. Повторный attributes() на представлении списка атрибутов возвращает null. Это не обычный PHP-массив.
ОграниченияDefault namespace элементов не распространяется на атрибуты без префикса. Отладочный var_dump не заменяет чтение через foreach/(string). Числовое значение ещё требуется валидировать.
Атрибуты и текст товара — разные части
  1. product sku="A1" currency="RUB"
  2. attributes() выбирает пары sku/A1 и currency/RUB
  3. foreach даёт имя и XML-представление значения
  4. (string) превращает значение в строку для прикладного кода
Перечислить атрибуты товараСамостоятельный пример
<?php
declare(strict_types=1);

$product = new SimpleXMLElement('<product sku="A1" currency="RUB">Desk</product>');
foreach ($product->attributes() as $name => $value) {
    echo $name, '=', (string) $value, "\n";
}
Ожидаемый результат
sku=A1
currency=RUB

В foreach запись $name => $value означает «имя атрибута и его значение». Текст Desk расположен между тегами, поэтому сюда не попадает. Для заранее известного sku короче $product['sku'].

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

$product = new SimpleXMLElement('<product xmlns="urn:shop" xmlns:sync="urn:sync" id="local-7" sync:id="remote-9"/>');
echo (string) $product->attributes()['id'], "\n";
echo (string) $product->attributes('urn:sync')['id'], "\n";
echo (string) $product->attributes('sync', true)['id'], "\n";
var_dump($product->attributes()->attributes());
Ожидаемый результат
local-7
remote-9
remote-9
NULL

Одинаковое локальное имя id не делает атрибуты одинаковыми: URI различает их смысл. У атрибутов нет своего уровня атрибутов. Не удаляйте xmlns строковой заменой ради упрощения чтения.

PHP.net описывает null для представления атрибута; детали представления существенны: на PHP 8.5.11 attributes()->attributes() даёт null, а attributes()['id']->attributes() может дать пустой XML-объект. Поэтому не используйте этот вызов как определитель типа узла: attributes вызывайте на элементе, значение отдельного атрибута читайте через (string).

Если вход допускает отсутствующий атрибут, сначала проверяйте isset на нужном представлении attributes(); пустая строка и отсутствие — разные состояния. Связь с обычным массивом только синтаксическая: квадратные скобки здесь обслуживает SimpleXML.

Продолжение: SimpleXMLElement::children, SimpleXMLElement::addAttribute.

SimpleXMLElement::childrenПройти непосредственных детей текущего XML-элемента, когда их имена заранее неизвестны или принадлежат отдельному namespace.

PHP 5+ · SimpleXML

SimpleXMLElement::children(?string $namespaceOrPrefix = null, bool $isPrefix = false): ?SimpleXMLElement

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

Часть контрактаЗначение
namespaceOrPrefixНеобязательный URI пространства имён; null — стандартный режим выбора, для явного отсутствия namespace можно задать пустую строку.
isPrefixПри true первый аргумент — префикс из XML; при false — URI. children('s') не эквивалентно children('s', true).
ВозвратSimpleXMLElement, по которому можно пройти foreach, в том числе с нулём детей; сигнатура допускает null. На представлении списка атрибутов возвращается null. Метод предназначен для элемента, не определения типа произвольного XML-представления.
Граница обходаТолько один уровень элементов в выбранном пространстве. Текстовые фрагменты, комментарии и атрибуты не являются этими дочерними элементами. Рекурсивный обход нужно явно организовать.
Один уровень дерева
  1. order содержит number и delivery
  2. children() выбирает number и delivery
  3. Внутренний city не входит в этот проход
  4. delivery->children() нужен для следующего уровня
Посмотреть поля заказаСамостоятельный пример
<?php
declare(strict_types=1);

$order = new SimpleXMLElement('<order><number>42</number><delivery><city>Tula</city></delivery></order>');
foreach ($order->children() as $name => $child) {
    echo $name, "\n";
}
echo (string) $order->delivery->city, "\n";
Ожидаемый результат
number
delivery
Tula

foreach не спускается автоматически во все ветки. Известный путь city можно прочитать цепочкой стрелок, для произвольной глубины пригодятся getChildren/RecursiveIteratorIterator.

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

$product = new SimpleXMLElement('<product xmlns:s="urn:stock"><name>Desk</name><s:available>5</s:available><s:reserved>2</s:reserved></product>');
echo count($product->children('s')), "\n";
foreach ($product->children('urn:stock') as $name => $value) {
    echo $name, '=', (string) $value, "\n";
}
echo (string) $product->children('s', true)->available, "\n";
Ожидаемый результат
0
available=5
reserved=2
5

Первый вызов искал URI s, которого нет. Второй выбирает фактический URI, третий явно трактует s как префикс. Адрес urn:stock — идентификатор: SimpleXML не делает HTTP-запрос, чтобы его открыть.

Продолжение: SimpleXMLElement::attributes, SimpleXMLElement::getChildren, SimpleXMLElement::count.

SimpleXMLElement::countПосчитать выбранные XML-элементы, например строки заказа; не спутать число строк с числом полей в одной строке.

PHP 8+ (актуальный контракт) · SimpleXML

SimpleXMLElement::count(): int

Посчитать выбранные XML-элементы, например строки заказа; не спутать число строк с числом полей в одной строке.

Часть контрактаЗначение
АргументыНет. Важен объект слева от ->: конкретный элемент либо представление группы одноимённых детей.
ВозвратНеотрицательное целое количество элементов текущей выборки/детей. count($xml) использует Countable; это не длина текста и не число всех потомков.
ВерсииВ актуальном справочнике метод относится к PHP 8; с PHP 8 SimpleXMLElement объявляет Countable. Урок и исполняемые контракты рассчитаны на PHP 8.2+.
Ошибки выбораУ элемента item[0] считаются его дочерние поля, у item — одноимённые item. Ноль не говорит, что разбор XML завершился ошибкой.
Три разных вопроса к одному заказу
  1. order → item A и item B: две позиции
  2. item[0] → name и qty: два поля
  3. name → текст Desk: ноль вложенных элементов
Количество товаров для подписи спискаСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog><product sku="A1"><name>Desk</name><stock>3</stock></product><product sku="B2"><name>Lamp</name><stock>0</stock></product></catalog>');
echo 'Товаров в каталоге: ', $catalog->product->count(), "\n";
echo 'Дочерних полей первого товара: ', $catalog->product[0]->count(), "\n";
echo 'Дочерних элементов внутри name: ', $catalog->product[0]->name[0]->count(), "\n";
Ожидаемый результат
Товаров в каталоге: 2
Дочерних полей первого товара: 2
Дочерних элементов внутри name: 0

Для подписи списка считаем выборку product. Индекс [0] выбирает отдельный узел: у первого product считаются вложенные name и stock, атрибут sku не входит. У конкретного name[0] нет дочерних элементов, хотя текст Desk существует. Вызов ->name->count() без [0] считал бы саму выборку name и вернул 1. count не проверяет пустоту текста.

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

$order = new SimpleXMLElement('<order><item sku="A"/><item sku="B"/><item sku="C"/></order>');
$limit = 2;
$items = count($order->item);
echo $items > $limit ? "Слишком много позиций\n" : "Можно обработать\n";
echo 'missing=', count($order->missing), "\n";
Ожидаемый результат
Слишком много позиций
missing=0

Это бизнес-лимит после разбора, а не защита памяти при получении огромного XML. Ограничивать размер входа нужно раньше. Для подсчёта по условию сначала создайте XPath-выборку, проверьте её и вызовите обычный count на массиве.

Продолжение: SimpleXMLElement::children, SimpleXMLElement::xpath.

SimpleXMLElement::getNameУзнать имя выбранного тега, чтобы проверить тип документа или объяснить неизвестное поле импорта.

PHP 5.1.3+ · SimpleXML

SimpleXMLElement::getName(): string

Узнать имя выбранного тега, чтобы проверить тип документа или объяснить неизвестное поле импорта.

Часть контрактаЗначение
АргументыНет.
ВозвратЛокальное имя элемента строкой: для p:product это product. Имя не содержит текст и не идентифицирует namespace.
Версии/контекстPHP 5.1.3+. С PHP 8.4 вызов больше не делает неявный rewind итератора. Не путайте имя объекта с key(), которое относится к текущей позиции итератора.
Имя и значение
  1. <status>paid</status>
  2. getName() даёт status
  3. (string) даёт paid
  4. Проверка корневого имени выбирает обработчик, но не валидирует весь документ
Не принять накладную за каталогСамостоятельный пример
<?php
declare(strict_types=1);

foreach (['<catalog><product>Desk</product></catalog>', '<invoice number="42"/>'] as $text) {
    $xml = new SimpleXMLElement($text);
    if ($xml->getName() !== 'catalog') {
        echo 'Нужен catalog; получен ', $xml->getName(), "\n";
        continue;
    }
    echo 'Каталог принят: ', (string) $xml->product, "\n";
}
Ожидаемый результат
Каталог принят: Desk
Нужен catalog; получен invoice

getName без аргументов читает локальное имя узла. Проверка помогает выбрать обработчик, но не проверяет пространства имён и всю схему: два разных протокола могут иметь корень catalog. Пример ниже добавляет URI формата.

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

$xml = new SimpleXMLElement('<v:catalog xmlns:v="urn:vendor:v2"><v:product/></v:catalog>');
$namespaces = $xml->getNamespaces();
$accepted = $xml->getName() === 'catalog' && in_array('urn:vendor:v2', $namespaces, true);
echo $accepted ? "Формат v2\n" : "Неизвестный формат\n";
Ожидаемый результат
Формат v2

Для данного простого корня учитываем также его используемый URI. Проверка имени сама по себе примет catalog из другого протокола; полноценную схему и обязательные поля проверяют отдельно.

Продолжение: SimpleXMLElement::getNamespaces, SimpleXMLElement::key.

SimpleXMLElement::__toStringПолучить непосредственный текст элемента для сравнения, валидации или вывода.

PHP 5.3+ · SimpleXML

SimpleXMLElement::__toString(): string

Получить непосредственный текст элемента для сравнения, валидации или вывода.

Часть контрактаЗначение
АргументыНет. Обычно удобнее (string) $element или echo $element, которые используют строковое преобразование.
ВозвратТекст, непосредственно находящийся в элементе; текст вложенных элементов не включается. Пустая строка возможна для пустого содержимого и не является отдельным сигналом успешности загрузки.
ВерсииPHP 5.3+. С PHP 8 SimpleXMLElement реализует Stringable; с PHP 8.4 строковое преобразование не перематывает итератор.
ГраницаЭто не XML-разметка, не полная текстовая свёртка смешанного документа и не автоматическая бизнес-конвертация числа/даты/bool.
Текст и разметка
  1. note: текст «Hello », вложенный b: «team», текст «!»
  2. (string) note → «Hello !»
  3. note->b → «team»
  4. asXML() сохраняет разметку; DOM textContent объединяет весь текст
Сохранить артикул 0012 для поиска товараСамостоятельный пример
<?php
declare(strict_types=1);

$product = new SimpleXMLElement('<product><sku>0012</sku><name>Desk</name></product>');
$sku = (string) $product->sku;
$labels = ['0012' => 'Стол письменный'];
echo 'Артикул ', $sku, ': ', $labels[$sku], "\n";
Ожидаемый результат
Артикул 0012: Стол письменный

(string) вызывает строковое представление SimpleXMLElement. Артикул — идентификатор, поэтому сохраняем ведущие нули; (int) превратил бы его в 12 и изменил ключ поиска. Следующий пример объясняет предел: строковое представление не собирает текст всех вложенных тегов.

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

$note = new SimpleXMLElement('<note>Hello <b>team</b>!</note>');
echo (string) $note, "\n";
echo (string) $note->b, "\n";
$element = dom_import_simplexml($note);
echo $element->textContent, "\n";
Ожидаемый результат
Hello !
team
Hello team!

SimpleXML рассчитан прежде всего на структурированные данные. Для произвольного текста с вложенной разметкой DOM точнее выражает задачу. Показать полученный текст в HTML безопасно можно после htmlspecialchars, а не после одного приведения к string.

Продолжение: SimpleXMLElement::asXML, SimpleXMLElement::getName.

SimpleXMLElement::__debugInfoПосмотреть диагностическое представление XML-объекта либо ограничить отладочный вывод собственного подкласса.

PHP 8.5+ · SimpleXML

SimpleXMLElement::__debugInfo(): ?array

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов. Декларирован array|null; штатный метод возвращает массив диагностических свойств, используемый отладочным выводом.
Версия/источникДополнительный метод PHP 8.5, подтверждён tagged PHP source 8.5.0/8.5.11. Отдельной страницы в book.simplexml нет; основной class synopsis на PHP.net пока его не перечисляет.
ГраницаСнимок для отладки не является полным нейтральным преобразованием XML в массив. Namespace, смешанный текст и повторы требуют явной модели. Диагностический вывод может раскрыть данные документа.
ПереопределениеНаследник может вернуть ограниченный набор полей. Это управляет отладочным представлением, но не скрывает исходный XML от другого кода и не заменяет контроль доступа.
Отладочное окно не меняет дерево
  1. Исходный product содержит sku и name
  2. __debugInfo получает представление для просмотра
  3. Прикладное чтение по-прежнему использует attributes/children
  4. Собственный наследник может показывать только безопасный краткий итог
PHP 8.5+: посмотреть известные диагностические поляСамостоятельный пример
<?php
declare(strict_types=1);

$product = new SimpleXMLElement('<product sku="A1"><name>Desk</name></product>');
$snapshot = $product->__debugInfo();
echo $snapshot['@attributes']['sku'], "\n";
echo (string) $snapshot['name'], "\n";
Ожидаемый результат
A1
Desk

@attributes — диагностическое представление атрибутов данного простого документа. Не стройте универсальный импортёр на обещании такой формы: для бизнес-данных явно извлекайте и проверяйте нужные поля.

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

class OrderXml extends SimpleXMLElement {
    public function __debugInfo(): ?array {
        return ['root' => $this->getName(), 'items' => count($this->item)];
    }
}
$order = new OrderXml('<order><item>A</item><item>B</item><internal>fixture-only</internal></order>');
echo json_encode($order->__debugInfo(), JSON_THROW_ON_ERROR), "\n";
echo (string) $order->internal, "\n";
Ожидаемый результат
{"root":"order","items":2}
fixture-only

Отладочная сводка исключила internal, но поле осталось в XML. Это демонстрационные данные без секрета. Реальный журнал должен выбирать безопасные поля явно; скрытие в debugInfo не является защитой хранения.

Метод добавлен сверх официального оглавления, чтобы современный API не остался без объяснения. В PHP 8.4 и ранее диагностический вывод существовал, но этот вызываемый метод в классе не был объявлен.

Продолжение: SimpleXMLElement::__toString, SimpleXMLElement::asXML.

SimpleXMLElement::getDocNamespacesПосмотреть объявления xmlns в документе, в том числе пока не использованные, чтобы разобраться с новым форматом поставщика.

PHP 5.1.2+ · SimpleXML

SimpleXMLElement::getDocNamespaces(bool $recursive = false, bool $fromRoot = true): array|false

Посмотреть объявления xmlns в документе, в том числе пока не использованные, чтобы разобраться с новым форматом поставщика.

Часть контрактаЗначение
recursivefalse — объявления стартового элемента, true — также объявления в его потомках.
fromRoottrue начинает от корня документа, даже если вызов сделан на вложенном узле. false начинает от текущего узла, полезно для исследования отдельной ветки.
ВозвратМассив prefix => URI, default namespace имеет ключ пустая строка. Сигнатура допускает false при неудаче; перед работой с результатом это проверяют.
ГраницаОбъявлено не значит используется. Плоский словарь не хранит всю иерархию областей действия и переопределений одинакового префикса. Не превращайте его в универсальный валидатор структуры.
Разрешённый словарь и реально использованные имена
  1. Корень объявляет a → urn:active и spare → urn:spare
  2. Дочерний элемент использует только a
  3. getDocNamespaces показывает оба объявления
  4. getNamespaces(true) показывает используемый a
Найти объявленные префиксыСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<catalog xmlns:a="urn:active" xmlns:spare="urn:spare"><a:product/></catalog>');
$map = $xml->getDocNamespaces();
if ($map === false) { throw new RuntimeException('Нет списка'); }
foreach ($map as $prefix => $uri) { echo $prefix, '=', $uri, "\n"; }
Ожидаемый результат
a=urn:active
spare=urn:spare

Возвращается обычный PHP-массив, его значения уже строки. Метод лишь исследует объявления; не делает запросов по URI и не регистрирует XPath-префиксы.

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

$xml = new SimpleXMLElement('<catalog xmlns:a="urn:main"><extension xmlns:e="urn:extra"><e:value>9</e:value></extension></catalog>');
$root = $xml->extension[0]->getDocNamespaces(true);
$local = $xml->extension[0]->getDocNamespaces(true, false);
if ($root === false || $local === false) { throw new RuntimeException('Нет списка'); }
echo implode(',', array_keys($root)), "\n";
echo implode(',', array_keys($local)), "\n";
Ожидаемый результат
a,e
e

[0] выбирает конкретный extension, а не представление списка его одноимённых соседей. Первый вызов снова рассматривает весь документ; второй — объявления выбранной ветки. Унаследованное родительское объявление не обязано быть повторно записано на extension. Не теряйте контекст при вырезании фрагментов.

Продолжение: SimpleXMLElement::getNamespaces, SimpleXMLElement::registerXPathNamespace.

SimpleXMLElement::getNamespacesОпределить пространства имён, фактически используемые элементами и атрибутами выбранной ветки.

PHP 5.1.2+ · SimpleXML

SimpleXMLElement::getNamespaces(bool $recursive = false): array

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

Часть контрактаЗначение
recursivefalse — текущий элемент; true включает его потомков. В отличие от getDocNamespaces нет параметра fromRoot: важен объект вызова.
ВозвратМассив префиксов и URI используемых namespace; пустой массив означает отсутствие таких использований в области поиска.
ГраницаЭто сведения об использовании, не все объявления xmlns, не имена тегов и не выборка узлов. При повторном использовании префикса для разных URI плоский результат не заменяет анализ контекста.
Выбор глубины исследования
  1. catalog сам без namespace
  2. product имеет p:sku → urn:product
  3. getNamespaces() у catalog → пусто
  4. getNamespaces(true) → p используется ниже
Узнать, нужен ли namespace при чтении детейСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<catalog xmlns:p="urn:product" xmlns:unused="urn:unused"><p:product/></catalog>');
echo count($xml->getNamespaces()), "\n";
foreach ($xml->getNamespaces(true) as $prefix => $uri) {
    echo $prefix, '=', $uri, "\n";
}
Ожидаемый результат
0
p=urn:product

unused объявлен, но не применён к имени элемента/атрибута. Чтобы прочитать p:product, используем children с URI, а не обычное ->product.

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

$xml = new SimpleXMLElement('<product xmlns:s="urn:sync" s:id="remote-8"><name>Desk</name></product>');
$map = $xml->getNamespaces();
echo $map['s'], "\n";
echo (string) $xml->attributes($map['s'])['id'], "\n";
Ожидаемый результат
urn:sync
remote-8

Исследование помогает понять неизвестный документ. В стабильной интеграции URI лучше хранить в явном контракте формата; автоматически доверять любому присланному URI как нужному протоколу нельзя.

Продолжение: SimpleXMLElement::getDocNamespaces, SimpleXMLElement::attributes.

SimpleXMLElement::registerXPathNamespaceПривязать собственный короткий префикс к известному URI, чтобы XPath работал при смене префикса у поставщика.

PHP 5.1.0+ · SimpleXML

SimpleXMLElement::registerXPathNamespace(string $prefix, string $namespace): bool

Привязать собственный короткий префикс к известному URI, чтобы XPath работал при смене префикса у поставщика.

Часть контрактаЗначение
prefixВыбранный вами префикс для выражения XPath, например shop. Он не обязан совпадать с префиксом внутри XML.
namespaceТочный URI namespace. Несовпадение URI обычно даёт пустую выборку, а не доказательство отсутствия самого тега.
Возвратtrue при успешной регистрации, false при отказе. Регистрация не ищет узлы и не меняет документ.
Область действияКонтекст XPath конкретного объекта. При вызове xpath на другом объекте регистрируйте нужный префикс там. children/attributes используют свои параметры и от регистрации XPath не меняются.
Наше имя s остаётся постоянным
  1. Поставщик пишет v:product, xmlns:v="urn:shop"
  2. Мы регистрируем s → urn:shop
  3. Запрос //s:product выбирает нужный URI + имя
  4. Поставщик меняет v на feed, наш запрос остаётся прежним
Найти тег из default namespaceСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<catalog xmlns="urn:shop"><product>Desk</product></catalog>');
if (!$xml->registerXPathNamespace('s', 'urn:shop')) {
    throw new RuntimeException('Не зарегистрирован prefix');
}
$products = $xml->xpath('/s:catalog/s:product');
if (!is_array($products)) { throw new RuntimeException('Ошибка XPath'); }
echo (string) $products[0], "\n";
echo count($xml->xpath('/catalog/product')), "\n";
Ожидаемый результат
Desk
0

В XPath 1.0 имена без префикса означают элементы без namespace. Даже когда в XML нет двоеточия, xmlns на корне меняет имя каждого такого элемента. Свой s решает задачу без удаления xmlns.

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

$documents = [
    '<catalog xmlns:a="urn:shop"><a:product>Desk</a:product></catalog>',
    '<catalog xmlns:b="urn:shop"><b:product>Lamp</b:product></catalog>',
];
foreach ($documents as $text) {
    $xml = new SimpleXMLElement($text);
    $xml->registerXPathNamespace('s', 'urn:shop');
    $matches = $xml->xpath('/catalog/s:product');
    if (!is_array($matches)) { throw new RuntimeException('Ошибка XPath'); }
    foreach ($matches as $product) { echo (string) $product, "\n"; }
}
Ожидаемый результат
Desk
Lamp

Меняется буква в документе, а смысл URI остаётся. При реальном изменении версии URI код должен осознанно поддержать новый формат: регистрировать старое сокращение недостаточно.

Продолжение: SimpleXMLElement::xpath, SimpleXMLElement::children.

SimpleXMLElement::xpathВыбрать элементы по пути и условию, например все доступные товары, вместо ручного перебора ненужных веток.

PHP 5+ · SimpleXML

SimpleXMLElement::xpath(string $expression): array|null|false

Выбрать элементы по пути и условию, например все доступные товары, вместо ручного перебора ненужных веток.

Часть контрактаЗначение
expressionВыражение XPath 1.0. /catalog/product — путь от корня, product — от текущего узла, //product ищет потомков по документу, .//product — относительно текущей ветки. [@active="1"] фильтрует по атрибуту.
ВозвратМассив SimpleXMLElement. [] — корректный запрос без совпадений; null/false — отказ. Проверять is_array до foreach. Результаты остаются представлениями узлов документа.
Ошибки/версииНеверный XPath или неизвестный префикс обычно вызывает предупреждение. С PHP 8.5 выражение со скалярным результатом (число/bool/строка) даёт E_WARNING и false; раньше молча возвращало []. Для скалярного XPath используйте DOMXPath::evaluate.
БезопасностьНе вставляйте произвольный ввод в XPath-конкатенацию. Это отдельный язык, HTML-экранирование/SQL-параметры его не защищают. Ограничьте запросы известными шаблонами либо выбирайте узлы и сравнивайте значения средствами PHP.
Как выбирается товар
  1. Дерево содержит product active="1" и active="0"
  2. XPath /catalog/product[@active="1"] проверяет каждый подходящий узел
  3. Массив содержит только совпадения
  4. foreach читает/изменяет найденные объекты; пустой массив означает ноль совпадений
Выбрать доступные позицииСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<catalog><product active="1">Desk</product><product active="0">Lamp</product></catalog>');
$products = $xml->xpath('/catalog/product[@active="1"]');
if (!is_array($products)) { throw new RuntimeException('Ошибка запроса'); }
foreach ($products as $product) { echo (string) $product, "\n"; }
Ожидаемый результат
Desk

@active обозначает атрибут, а не дочерний тег active. Кавычки и скобки относятся к XPath. Путь здесь фиксирован в исходном коде, а не построен из пользовательской строки.

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

$xml = new SimpleXMLElement('<catalog><product sku="A&amp;B">Desk</product><product sku="C">Lamp</product></catalog>');
$wantedSku = 'A&B';
foreach ($xml->product as $product) {
    if ((string) $product['sku'] === $wantedSku) {
        echo (string) $product, "\n";
    }
}
Ожидаемый результат
Desk

Пользовательское значение вообще не становится программой XPath. Для маленькой выборки такой проход понятен; при большом документе учитывайте стоимость прохода и подходящий формат хранения.

PHP 8.5+: count(...) не является выборкой узловСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<catalog><product/><product/></catalog>');
$warning = false;
set_error_handler(function (int $level, string $message) use (&$warning): bool {
    $warning = true;
    return true;
});
try { $result = $xml->xpath('count(/catalog/product)'); }
finally { restore_error_handler(); }
var_dump($result === false, $warning);
echo count($xml->xpath('/catalog/product')), "\n";
Ожидаемый результат
bool(true)
bool(true)
2

Этот пример версии 8.5+ намеренно перехватывает предупреждение. Для нашего количества достаточно count массива выборки. Обработчик ограничен try/finally и не скрывает ошибки остальной программы.

XPath-поиск по всему дереву повторно внутри большого цикла может выполнять одну и ту же работу многократно. Сначала выбирайте нужную ветку/список, затем обрабатывайте результат. Это не обещание фиксированной сложности любого выражения: измеряйте свой документ и запросы.

Продолжение: SimpleXMLElement::registerXPathNamespace, SimpleXMLElement::children, SimpleXMLElement::count.

SimpleXMLElement::addChildДобавить новый товар или поле в XML и получить объект именно добавленного ребёнка для дальнейшего заполнения.

PHP 5.1.3+ · SimpleXML

SimpleXMLElement::addChild(string $qualifiedName, ?string $value = null, ?string $namespace = null): ?SimpleXMLElement

Добавить новый товар или поле в XML и получить объект именно добавленного ребёнка для дальнейшего заполнения.

Часть контрактаЗначение
qualifiedNameИмя нового элемента, при namespace можно использовать prefix:name. Имена структуры задаёт контракт формата, не произвольный ввод посетителя.
valueТекст или null для пустого элемента. < и > экранируются автоматически; & требуется экранировать вручную. Это не XML-фрагмент для вставки готового поддерева.
namespaceURI для создаваемого элемента либо null. Префикс и URI задают вместе, если нужен квалифицированный элемент.
Возврат/отказОбъект нового ребёнка либо null при неудаче. Некорректное имя/неподходящий узел могут дать предупреждение; пустое имя вызывает ValueError. Проверять === null, не истинность пустого элемента.
Что возвращает добавление
  1. catalog ещё пуст
  2. addChild("product") добавляет ребёнка и возвращает его
  3. Переменная $product указывает на нового ребёнка
  4. Его name и sku меняют исходный catalog
Добавить товар и увидеть XML карточкиСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog/>');
$product = $catalog->addChild('product');
$product->addChild('name', 'Desk');
$product->addAttribute('sku', 'A1');
echo $catalog->asXML();
Ожидаемый результат
<?xml version="1.0"?>
<catalog><product sku="A1"><name>Desk</name></product></catalog>

addChild("product") создаёт пустой элемент и возвращает именно его: поэтому следующая стрелка добавляет name внутрь нового товара. addChild("name", "Desk") задаёт имя тега и его текст двумя разными аргументами. Добавление в дерево ещё не записывает файл.

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

$catalog = new SimpleXMLElement('<catalog/>');
$name = 'Desk & Lamp <set>';
$product = $catalog->addChild('product');
if ($product === null) { throw new RuntimeException('Не создан товар'); }
$product->addChild('name', htmlspecialchars($name, ENT_XML1 | ENT_QUOTES, 'UTF-8'));
echo (string) $product->name, "\n";
echo $product->name->asXML(), "\n";
Ожидаемый результат
Desk & Lamp <set>
<name>Desk &amp; Lamp &lt;set&gt;</name>

ENT_XML1 выбирает XML-правила сущностей. На входе предполагается корректный UTF-8 и допустимые XML-символы: экранирование не исправляет запрещённые управляющие байты. Для обычного присваивания $product->name = $name предварительно экранировать не нужно.

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

$product = new SimpleXMLElement('<product sku="A1"/>');
$product->addChild('s:available', '5', 'urn:stock');
echo $product->asXML();
Ожидаемый результат
<?xml version="1.0"?>
<product sku="A1"><s:available xmlns:s="urn:stock">5</s:available></product>

Первый аргумент задаёт префикс и локальное имя, второй — остаток текстом, третий — URI пространства имён. В результате видно объявление xmlns:s. Префикс можно переименовать, принадлежность элемента определяется URI urn:stock.

Продолжение: SimpleXMLElement::addAttribute, SimpleXMLElement::asXML.

SimpleXMLElement::addAttributeДобавить короткое служебное значение в открывающий тег: идентификатор заказа, валюту или код источника.

PHP 5.1.3+ · SimpleXML

SimpleXMLElement::addAttribute(string $qualifiedName, string $value, ?string $namespace = null): void

Добавить короткое служебное значение в открывающий тег: идентификатор заказа, валюту или код источника.

Часть контрактаЗначение
qualifiedNameИмя атрибута; для namespace — qualified name с префиксом. Атрибут без префикса не наследует default namespace элемента.
valueСтроковое значение. Метод экранирует XML-значимые символы атрибута; не передавайте заранее HTML-экранированную строку.
namespaceURI namespace либо null. Для непустого URI атрибут должен иметь корректный префикс.
Возврат/ошибкиvoid — ничего полезного не возвращает. Повторное добавление существующего атрибута предупреждает, это не операция обновления. Пустое имя — ValueError. Для обновления обычного атрибута используйте $node['name'] = $value.
Добавление и изменение — два действия
  1. order не содержит source
  2. addAttribute создаёт source="mobile"
  3. Повторное создание не является обновлением
  4. Присваивание order["source"] меняет уже имеющееся значение
Добавить номер и сменить состояние заказаСамостоятельный пример
<?php
declare(strict_types=1);

$order = new SimpleXMLElement('<order/>');
$order->addAttribute('number', '42');
$order->addAttribute('status', 'new');
$order['status'] = 'paid';
echo $order->asXML();
Ожидаемый результат
<?xml version="1.0"?>
<order number="42" status="paid"/>

addAttribute создаёт новый атрибут; name и value передаются отдельными строками. Для изменения существующего status используем квадратные скобки. Возврат метода void не нужно присваивать как новый узел.

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

$order = new SimpleXMLElement('<order/>');
$order->addAttribute('source', 'A&B');
$order->addAttribute('s:id', 'remote9', 'urn:sync');
echo $order->asXML();
Ожидаемый результат
<?xml version="1.0"?>
<order xmlns:s="urn:sync" source="A&amp;B" s:id="remote9"/>

addAttribute сам экранирует амперсанд в значении source. Не передавайте заранее A&B, иначе получите двойное экранирование. URI urn:sync отличает служебный id от обычного id; для чтения нужен attributes("urn:sync").

Имена атрибутов выбирайте из известного набора. Значение «42» не становится проверенным целым только потому, что оно записано в XML; валидация предметной области остаётся в PHP.

Продолжение: SimpleXMLElement::attributes, SimpleXMLElement::addChild.

SimpleXMLElement::asXMLПолучить XML-разметку текущего элемента либо сохранить её в заданный файл; эти два режима возвращают разные типы.

PHP 5+; filename nullable с PHP 8.0 · SimpleXML

SimpleXMLElement::asXML(?string $filename = null): string|bool

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

Часть контрактаЗначение
filenamenull/аргумент пропущен: вернуть XML строкой. Строка пути: записать файл, возможно перезаписав существующий; путь контролирует приложение.
ВозвратБез имени файла: string при успехе, false при отказе. С filename: true/false успешности записи. true — не содержимое документа.
ФорматXML 1.0. Корневой документ может содержать декларацию XML; выбранная вложенная ветка выводится как фрагмент. Побайтовая идентичность исходнику и красивое форматирование не гарантируются.
Ошибки/ограниченияОтказ записи может сопровождаться предупреждением. Метод не предоставляет блокировку, атомарное обновление, транзакцию или проверку схемы. Нет аргумента options для форматирования.
Выбор режима сериализации
  1. Дерево уже изменено в памяти
  2. Вызов без filename → строка XML для передачи
  3. Вызов с filename → байты в файл и bool
  4. Изменение объекта не обновляет ранее прочитанный файл автоматически
XML отдельной позицииСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog><product sku="A1">Desk</product></catalog>');
$text = $catalog->product->asXML();
if ($text === false) { throw new RuntimeException('Не сериализован'); }
echo $text, "\n";
Ожидаемый результат
<product sku="A1">Desk</product>

Вызов сделан на product, поэтому возвращается эта ветка, а не весь catalog. Для простого текста Desk нужен (string), для передачи структуры — XML.

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

$order = new SimpleXMLElement('<order number="42" status="new"/>');
$order['status'] = 'paid';
$path = __DIR__ . '/result.xml';
if (!$order->asXML($path)) {
    throw new RuntimeException('Не удалось сохранить подтверждение');
}
echo file_get_contents($path);
Ожидаемый результат
<?xml version="1.0"?>
<order number="42" status="paid"/>

При переданном имени файла asXML возвращает bool, а XML находится в файле. Без имени возвращается строка XML. Пример пишет только result.xml в своей учебной папке; после ручного запуска файл можно открыть редактором. Повторный запуск заменит именно этот результат.

Сериализация большого дерева дополнительно создаёт строку, если выбран возврат текста. Если нужны управление отступами/прологом и поэтапное создание большой выгрузки, рассмотрите DOM или XMLWriter. XML-строка для просмотра внутри HTML требует отдельного HTML-экранирования.

Продолжение: SimpleXMLElement::__toString, simplexml_load_file, SimpleXMLElement::saveXML.

SimpleXMLElement::saveXMLПолучить XML-разметку текущего элемента либо сохранить её в заданный файл; эти два режима возвращают разные типы.

PHP 5.2+; alias asXML · SimpleXML

SimpleXMLElement::saveXML(?string $filename = null): string|bool

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

Часть контрактаЗначение
filenamenull/аргумент пропущен: вернуть XML строкой. Строка пути: записать файл, возможно перезаписав существующий; путь контролирует приложение.
ВозвратБез имени файла: string при успехе, false при отказе. С filename: true/false успешности записи. true — не содержимое документа.
ФорматXML 1.0. Корневой документ может содержать декларацию XML; выбранная вложенная ветка выводится как фрагмент. Побайтовая идентичность исходнику и красивое форматирование не гарантируются.
Ошибки/ограниченияОтказ записи может сопровождаться предупреждением. Метод не предоставляет блокировку, атомарное обновление, транзакцию или проверку схемы. Нет аргумента options для форматирования.
Выбор режима сериализации
  1. Дерево уже изменено в памяти
  2. Вызов без filename → строка XML для передачи
  3. Вызов с filename → байты в файл и bool
  4. Изменение объекта не обновляет ранее прочитанный файл автоматически

saveXML — точный alias asXML: не другой формат и не отдельная технология хранения. В новом коде удобно выбрать одно название для единообразия. Все параметры, результаты и ограничения asXML применяются здесь тоже.

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

$catalog = new SimpleXMLElement('<catalog><product sku="A1">Desk</product></catalog>');
$text = $catalog->product->saveXML();
if ($text === false) { throw new RuntimeException('Не сериализован'); }
echo $text, "\n";
Ожидаемый результат
<product sku="A1">Desk</product>

Вызов сделан на product, поэтому возвращается эта ветка, а не весь catalog. Для простого текста Desk нужен (string), для передачи структуры — XML.

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

$order = new SimpleXMLElement('<order number="42" status="new"/>');
$order['status'] = 'paid';
$path = __DIR__ . '/result.xml';
if (!$order->saveXML($path)) {
    throw new RuntimeException('Не удалось сохранить подтверждение');
}
echo file_get_contents($path);
Ожидаемый результат
<?xml version="1.0"?>
<order number="42" status="paid"/>

При переданном имени файла saveXML возвращает bool, а XML находится в файле. Без имени возвращается строка XML. Пример пишет только result.xml в своей учебной папке; после ручного запуска файл можно открыть редактором. Повторный запуск заменит именно этот результат.

Сериализация большого дерева дополнительно создаёт строку, если выбран возврат текста. Если нужны управление отступами/прологом и поэтапное создание большой выгрузки, рассмотрите DOM или XMLWriter. XML-строка для просмотра внутри HTML требует отдельного HTML-экранирования.

Продолжение: SimpleXMLElement::__toString, simplexml_load_file, SimpleXMLElement::asXML.

SimpleXMLElement::rewindПодготовить ручной обход списка детей и вернуться к первой позиции после предыдущего прохода.

PHP 8.0+ на SimpleXMLElement · SimpleXML

SimpleXMLElement::rewind(): void

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов; void. Устанавливает внутреннюю позицию на первый доступный элемент, если он есть.
Пустое деревоПосле rewind обязательно valid: первый ребёнок может отсутствовать.
ВерсииС PHP 8.4 getName/asXML и строковое преобразование больше не делают неявный rewind. Пишем rewind явно, не используем чтение имени как способ запуска итератора.
Начать или повторить проход
  1. До rewind курсор не установлен
  2. rewind выбирает первого ребёнка
  3. next передвигает к следующему
  4. Новый rewind возвращает к первому без повторного разбора XML
Вернуться к первой позицииСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><item>Desk</item><item>Lamp</item></order>');
$xml->rewind();
echo (string) $xml->current(), "\n";
$xml->next();
echo (string) $xml->current(), "\n";
$xml->rewind();
echo (string) $xml->current(), "\n";
Ожидаемый результат
Desk
Lamp
Desk

Объект содержит документ и состояние обхода. rewind не загружает файл заново и не отменяет изменения узлов.

PHP 8.4+: сериализация не запускает курсорСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><item>Desk</item></order>');
$xml->asXML();
$xml->getName();
var_dump($xml->valid());
$xml->rewind();
var_dump($xml->valid());
Ожидаемый результат
bool(false)
bool(true)

Пример отмечает изменение 8.4+. После изучения документа курсор по-прежнему не установлен: только явный rewind переводит его к ребёнку.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Продолжение: SimpleXMLElement::valid, SimpleXMLElement::next.

SimpleXMLElement::validУзнать, можно ли читать текущую позицию ручного обхода, не вызывая ошибку на конце списка.

PHP 8.0+ на SimpleXMLElement · SimpleXML

SimpleXMLElement::valid(): bool

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов; true означает установленный текущий элемент, false — курсор не инициализирован или уже закончен.
Что не проверяетЭто не проверка корректности XML, XSD, присутствия атрибута или содержимого товара. Метод относится только к состоянию итератора.
ПорядокСначала rewind, затем valid; после каждого next снова valid перед current/key.
Защита от чтения за концом
  1. rewind → первая позиция
  2. valid=true → можно current
  3. next → конец списка
  4. valid=false → заканчиваем цикл
Печатать позицию только пока курсор указывает на товарСамостоятельный пример
<?php
declare(strict_types=1);

$catalog = new SimpleXMLElement('<catalog><product>Desk</product></catalog>');
$catalog->rewind();
if ($catalog->valid()) {
    echo 'В обработке: ', (string) $catalog->current(), "\n";
}
$catalog->next();
if (!$catalog->valid()) { echo "Все товары обработаны\n"; }
Ожидаемый результат
В обработке: Desk
Все товары обработаны

Курсор — текущая позиция ручного обхода. rewind ставит его на первого ребёнка, valid проверяет наличие текущего ребёнка, next сдвигает дальше. valid не проверяет синтаксис XML и не сообщает, прошёл ли товар бизнес-проверку; для обычного списка короче foreach.

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

$catalog = new SimpleXMLElement('<catalog/>');
$catalog->rewind();
if (!$catalog->valid()) { echo "В выгрузке нет товаров; прежний каталог не меняем\n"; }
while ($catalog->valid()) {
    echo 'Обработан: ', (string) $catalog->current(), "\n";
    $catalog->next();
}
Ожидаемый результат
В выгрузке нет товаров; прежний каталог не меняем

Условие проверяется до первого чтения current. Пустая выгрузка корректна как XML, но её смысл определяет договор: здесь она не является командой удалить каталог. Тело while не выполняется ни разу.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Продолжение: SimpleXMLElement::rewind, SimpleXMLElement::current.

SimpleXMLElement::currentПолучить объект ребёнка на текущей позиции, чтобы читать или изменять именно его.

PHP 8.0+; Error с PHP 8.1 · SimpleXML

SimpleXMLElement::current(): SimpleXMLElement

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов; возвращается SimpleXMLElement текущего элемента, а не его строковый текст.
ОтказС PHP 8.1 неинициализированный/исчерпанный итератор вызывает Error; ранее возвращал null. Проверяйте valid.
Побочный эффектcurrent сам не передвигает курсор. Возвращённый узел связан с исходным документом; присваивание его атрибуту меняет дерево.
Документ, курсор и ребёнок
  1. $order содержит два item
  2. rewind устанавливает курсор на первый
  3. current возвращает этот item
  4. Чтение current повторно не выбирает второй; для него нужен next
Прочитать выбранную позицию дваждыСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><item>Desk</item><item>Lamp</item></order>');
$xml->rewind();
echo (string) $xml->current(), "\n";
echo (string) $xml->current(), "\n";
Ожидаемый результат
Desk
Desk

Курсор остаётся на том же item. Это удобно, когда несколько проверок относятся к одной позиции.

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

$catalog = new SimpleXMLElement('<catalog><product sku="A1">Desk</product></catalog>');
$catalog->rewind();
$product = $catalog->current();
$product['processed'] = 'yes';
echo $product->asXML(), "\n";
$catalog->next();
try { $catalog->current(); }
catch (Error $error) { echo "Текущего товара больше нет: сначала проверяйте valid\n"; }
Ожидаемый результат
<product sku="A1" processed="yes">Desk</product>
Текущего товара больше нет: сначала проверяйте valid

current возвращает представление текущего узла, а не независимую копию: изменение атрибута входит в каталог. Здесь намеренно показана ошибка чтения после конца; рабочий цикл проверяет valid до current.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Продолжение: SimpleXMLElement::valid, SimpleXMLElement::key.

SimpleXMLElement::keyУзнать имя текущего дочернего тега в ручном обходе: ключ здесь не порядковый номер.

PHP 8.0+; Error с PHP 8.1 · SimpleXML

SimpleXMLElement::key(): string

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов; строковое имя тега текущего элемента. Повторяющиеся одноимённые узлы имеют одинаковый ключ.
ОтказС PHP 8.1 Error, если курсор ещё не установлен или исчерпан; до этого false.
РазличияgetName у корневого объекта возвращает имя корня, key после rewind — имя текущего ребёнка. key не является уникальным идентификатором товара.
Почему ключ может повторяться
  1. order содержит item A, item B и comment
  2. Ключи обхода: item, item, comment
  3. Если собрать array[key] без списка, второй item затрёт первый
  4. Для сохранения всех строк используем [] или реальный sku
Различить корень и текущего ребёнкаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><item>Desk</item></order>');
$xml->rewind();
echo $xml->getName(), "\n";
echo $xml->key(), "\n";
Ожидаемый результат
order
item

Оба метода вызываются у $xml, но отвечают о разных уровнях: сам объект и текущая позиция его итератора.

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

$xml = new SimpleXMLElement('<order><item>Desk</item><item>Lamp</item></order>');
$groups = [];
for ($xml->rewind(); $xml->valid(); $xml->next()) {
    $groups[$xml->key()][] = (string) $xml->current();
}
echo implode(', ', $groups['item']), "\n";
Ожидаемый результат
Desk, Lamp

Вторые квадратные скобки [] добавляют в список, сохраняя повторения. array с одним значением на имя тега потерял бы данные. Такая группировка всё ещё не универсальный XML→array: смешанное содержимое/порядок разных тегов требуют другой модели.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Продолжение: SimpleXMLElement::getName, SimpleXMLElement::current.

SimpleXMLElement::nextПередвинуть ручной XML-курсор к следующему элементу после обработки текущего.

PHP 8.0+ на SimpleXMLElement · SimpleXML

SimpleXMLElement::next(): void

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов; void. Новый узел берут через current после проверки valid.
КонецПереход после последнего делает valid=false. Возвращаемое значение next не сигнализирует наличие следующего элемента.
ГраницаНе спускается рекурсивно во вложенные теги. Пропущенный next создаёт бесконечный цикл, два next за итерацию пропускают позицию.
Одна итерация — один шаг
  1. current читает A
  2. Прикладной код обрабатывает A
  3. next устанавливает B
  4. valid решает, продолжать ли обработку
Выбрать вторую позициюСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><item>Desk</item><item>Lamp</item></order>');
$xml->rewind();
$xml->next();
if ($xml->valid()) { echo (string) $xml->current(), "\n"; }
Ожидаемый результат
Lamp

Результат next не печатаем: метод лишь изменил позицию. Документ и содержимое товаров остались прежними.

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

$catalog = new SimpleXMLElement('<catalog><product>Desk</product><product>Lamp</product><product>Chair</product></catalog>');
$catalog->rewind();
for ($done = 0; $done < 2 && $catalog->valid(); $done++) {
    echo 'Подготовлен товар: ', (string) $catalog->current(), "\n";
    $catalog->next();
}
if ($catalog->valid()) { echo 'Следующий товар: ', (string) $catalog->current(), "\n"; }
Ожидаемый результат
Подготовлен товар: Desk
Подготовлен товар: Lamp
Следующий товар: Chair

next перемещает текущую позицию и не возвращает новый товар. Лимит две позиции относится только к обработке; SimpleXML уже держит весь XML в памяти. Для потока большого файла нужен XMLReader, а для продолжения после перезапуска — отдельный устойчивый ключ/протокол.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Продолжение: SimpleXMLElement::valid, SimpleXMLElement::rewind.

SimpleXMLElement::hasChildrenПроверить, есть ли дочерние элементы у текущего ребёнка итератора, прежде чем спускаться глубже.

PHP 8.0+ на SimpleXMLElement · SimpleXML

SimpleXMLElement::hasChildren(): bool

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов; true, если текущий элемент имеет вложенные элементы, иначе false.
ПозицияПроверяется ребёнок, выбранный курсором после rewind/next, а не произвольный узел самого объекта. Без установленной позиции — false.
Что не является ребёнкомПростой текст и атрибуты не являются вложенными элементами. false не означает пустую строку, несуществующий узел или ошибку XML.
Граница списка и вложенной ветки
  1. Курсор на delivery → внутри city: true
  2. Курсор на comment → только текст: false
  3. Ветка требует спуска getChildren
  4. Лист читаем через current и (string)
Отличить ветку от текстового листаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><delivery><city>Tula</city></delivery><comment>Call first</comment></order>');
for ($xml->rewind(); $xml->valid(); $xml->next()) {
    echo $xml->key(), ':', $xml->hasChildren() ? 'branch' : 'leaf', "\n";
}
Ожидаемый результат
delivery:branch
comment:leaf

Вложенный city делает delivery веткой. Текст Call first не делает comment веткой, но значение в нём есть.

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

$xml = new SimpleXMLElement('<response><status>ready</status><details><code>200</code></details></response>');
for ($xml->rewind(); $xml->valid(); $xml->next()) {
    if ($xml->hasChildren()) { echo $xml->key(), " требует разбора\n"; }
    else { echo $xml->key(), '=', (string) $xml->current(), "\n"; }
}
Ожидаемый результат
status=ready
details требует разбора

Если обрабатывать только getChildren, текстовый status легко пропустить. Условия разделяют два вида узлов согласно формату ответа, не превращают все значения в массивы.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Продолжение: SimpleXMLElement::getChildren, SimpleXMLElement::children.

SimpleXMLElement::getChildrenПолучить объект текущего элемента как следующий уровень рекурсивного обхода.

PHP 8.0+ на SimpleXMLElement · SimpleXML

SimpleXMLElement::getChildren(): ?SimpleXMLElement

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

Часть контрактаЗначение
Аргументы/возвратНет аргументов. Объект текущего ребёнка, по которому можно начать обход его детей; null при неинициализированном/законченном итераторе либо итерации атрибутов.
Текстовый листНа установленном текстовом листе результат может быть объектом с нулём детей. hasChildren проверяет наличие следующего уровня, а не просто результат !== null.
Не children()children() выбирает детей объекта вызова и поддерживает namespace. getChildren() относится к текущему ребёнку курсора и входит в контракт RecursiveIterator.
Спуск на один уровень
  1. Внешний $order указывает на корень, курсор на delivery
  2. getChildren возвращает объект delivery
  3. foreach по нему перебирает city и street
  4. Внешний next затем передвигает курсор к следующему ребёнку order
Прочитать поля текущего раздела доставкиСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new SimpleXMLElement('<order><delivery><city>Tula</city><street>Central</street></delivery></order>');
$xml->rewind();
$delivery = $xml->getChildren();
if ($delivery !== null) {
    foreach ($delivery as $name => $value) {
        echo $name, '=', (string) $value, "\n";
    }
}
Ожидаемый результат
city=Tula
street=Central

delivery — объект выбранной ветки. Этот вызов не означает «все потомки документа одним массивом» и не преобразует данные в PHP-массив.

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

$xml = new SimpleXMLElement('<response><status>ready</status></response>');
var_dump($xml->getChildren());
$xml->rewind();
$leaf = $xml->getChildren();
echo (string) $leaf, "\n";
echo count($leaf), "\n";
var_dump($xml->hasChildren());
Ожидаемый результат
NULL
ready
0
bool(false)

До rewind возвращается null. После него объект status существует и содержит ready, но вложенных элементов нет. Это объясняет, зачем RecursiveIterator разделяет hasChildren и getChildren.

Эти семь методов итератора объявлены в SimpleXMLElement с PHP 8.0; ранее находились только в SimpleXMLIterator. В PHP 8.2+ обычный SimpleXMLElement уже реализует RecursiveIterator. Простому импорту обычно достаточно foreach; ручной курсор нужен при поэтапном управлении обходом.

Проход по всей глубине организует RecursiveIteratorIterator; готовый пример — классы и рекурсивный обход.

Продолжение: SimpleXMLElement::hasChildren, SimpleXMLElement::children.

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

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

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

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

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