Глава 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, который открыт выше. Вложенность важна: теги должны закрываться в обратном порядке. |
- catalog.xml: корень catalog
- его ребёнок product с атрибутом sku=A1
- ребёнок product → name → текст Desk
- 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, архитектура и вариант сборки должны совпадать.
- Терминал запускает один PHP и его ini
- Веб-сервер/FPM может использовать другой бинарник и ini
- Проверяем SimpleXML именно в нужном процессе
- Подключаем совместимый пакет/сборку и перезапускаем соответствующий процесс
<?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>
- foreach выбирает product A1
- читает name=Desk и stock=3
- следующий product B2 даёт Lamp и 0
- после второго товара повторение заканчивается
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: заключаем имя в фигурные скобки и кавычки.
- catalog содержит product[0]=Desk и product[1]=Lamp
- foreach получает каждый product отдельно
- ['sku'] читает атрибут текущего товара
- ->{'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(...) выглядит коротко, но не определяет политику атрибутов, пространств имён, смешанного содержимого и повторяющихся тегов. Явное отображение фиксирует контракт и место отказа.
- XML item имеет sku="0012", qty="2"
- Проверяем обязательное поле и текст количества
- Создаём массив [sku => "0012", quantity => 2]
- Дальнейшая обработка работает с массивом независимо от 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. Эти сведения помогают найти повреждённый ответ, но не должны бесконтрольно попадать посетителю.
- Установить внутренний режим, сохранив прежний
- Разобрать строку и проверить === false
- На отказе забрать список ошибок и остановить обработку
- 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_XXE | PHP 8.4+ при libxml 2.13+: ограничивает внешние сущности. Проверяйте defined; отсутствие константы не оправдывает небезопасную комбинацию других флагов. |
| LIBXML_PARSEHUGE | Ослабляет ограничения глубины/размеров; только при обоснованном доверенном входе. Не средство экономии памяти. |
| LIBXML_NOCDATA / NOBLANKS | Первый сливает CDATA с текстом, второй убирает пустые текстовые узлы. Это изменения модели документа, не обязательные настройки каждого импорта. |
| LIBXML_NOEMPTYTAG | Выходная возможность DOM, не настройка SimpleXML-конструктора; не заставит asXML рисовать парные пустые теги. |
<?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 & <b>Lamp</b>
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 в urn:shop и product без namespace — разные имена
- children("urn:shop") выбирает правильную группу
- attributes() читает обычный sku без namespace
- Для 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 согласно операции.
- В памяти: order status=new, два item
- Присвоить status=paid, удалить временный атрибут
- Добавить note как обычный текст
- 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 & 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.
- catalog содержит category, внутри неё product
- Обычный foreach → только category
- RecursiveIteratorIterator SELF_FIRST → category, затем product
- 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
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. Методы работают на полученном объекте.
simplexml_load_stringsimplexml_load_fileSimpleXMLElement::__constructsimplexml_import_domSimpleXMLElement::attributesSimpleXMLElement::childrenSimpleXMLElement::countSimpleXMLElement::getNameSimpleXMLElement::__toStringSimpleXMLElement::__debugInfoSimpleXMLElement::getDocNamespacesSimpleXMLElement::getNamespacesSimpleXMLElement::registerXPathNamespaceSimpleXMLElement::xpathSimpleXMLElement::addChildSimpleXMLElement::addAttributeSimpleXMLElement::asXMLSimpleXMLElement::saveXMLSimpleXMLElement::rewindSimpleXMLElement::validSimpleXMLElement::currentSimpleXMLElement::keySimpleXMLElement::nextSimpleXMLElement::hasChildrenSimpleXMLElement::getChildren
У SimpleXMLIterator нет отдельного нового набора методов в PHP 8: он наследует опубликованные здесь методы SimpleXMLElement. Настройки разбора и общая диагностика относятся к libxml.
Практика: подготовить локальную ведомость остатков
Есть catalog с product, атрибутом sku и тегом stock. Нужно получить список «артикул:остаток», сохранив нулевые остатки. Сначала проверьте существование sku и stock, затем форму stock по контракту, потом преобразуйте его в число. Вторая задача: поставщик перенёс stock в urn:stock — меняем чтение поля, остальной прикладной массив остаётся прежним.
- Загрузить и проверить документ
- Найти каждый product, отличить отсутствие от 0
- Прочитать stock из известного 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 ко всему файлу. Этот этап ничего не записывает в настоящую базу магазина.
- catalog.xml: A1 / Desk / 3 и B2 / Lamp / 0
- XML-парсер проверяет структуру документа
- наша проверка проверяет sku/name/stock
- 3 → добавить товар, 0 → пропустить
- 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 возвращает режим диагностики даже при отказе.
<?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.
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 → product → name
- simplexml_load_string разбирает разметку и создаёт дерево
- Возвращённый объект помещается в $catalog
- (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 уже можно передать функции, которая ждёт строку.
<?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-каталог из известного локального файла одним вызовом.
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. |
- Путь catalog.xml выбирает источник
- Парсер читает документ и строит целое дерево
- foreach перебирает уже разобранные product
- Для огромного каталога нужен 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, получая исключение, если документ невозможно разобрать.
new SimpleXMLElement(string $data, int $options = 0, bool $dataIsURL = false, string $namespaceOrPrefix = "", bool $isPrefix = false)Создать XML-объект через new, получая исключение, если документ невозможно разобрать.
| Часть контракта | Значение |
|---|---|
| data | XML в строке; при dataIsURL=true — путь/URL. Случайно передать filename без этого флага означает попробовать разобрать имя файла как XML. |
| options | Флаги libxml при разборе. Выходные флаги, например LIBXML_NOEMPTYTAG, игнорируются: конструктор не настраивает сериализацию. |
| dataIsURL | false по умолчанию. true включает чтение источника, со всеми его правами и сетевыми рисками. |
| namespaceOrPrefix / isPrefix | URI либо префикс при true; по умолчанию пустая строка и false. Имена аргументов отличаются от snake_case параметров функций загрузки. |
| Результат и отказ | new возвращает созданный объект. XML-ошибки дают E_WARNING и дополнительно Exception при невозможности разбора. Внутренний режим libxml убирает вывод предупреждений, но не отменяет исключение. |
- new выбирает класс SimpleXMLElement
- Конструктор получает строку data
- Успех → объект присваивается $order
- Отказ → Exception, присваивание не завершается
<?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-строки.
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_name | SimpleXMLElement или его наследник; null выбирает стандартный класс. Не меняет класс DOM-объекта. |
| Возврат | Представление SimpleXMLElement либо null при неудаче. Два API работают с одним underlying XML-деревом: изменение видно с другой стороны. |
| Ошибки и версии | Неподдерживаемый объект — TypeError с PHP 8.4, ранее ValueError. Не каждый тип DOM-узла является подходящим элементом; неподходящий узел может дать предупреждение и null. Нужны расширения DOM и SimpleXML. |
- DOMDocument владеет деревом order → status
- simplexml_import_dom возвращает другой интерфейс к нему
- SimpleXML меняет status на paid
- 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. Импорт сохраняет связь с деревом; это важно, если разные части программы редактируют один документ.
<?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Перечислить свойства, записанные внутри открывающего тега: артикул, валюту или признак доступности.
SimpleXMLElement::attributes(?string $namespaceOrPrefix = null, bool $isPrefix = false): ?SimpleXMLElementПеречислить свойства, записанные внутри открывающего тега: артикул, валюту или признак доступности.
| Часть контракта | Значение |
|---|---|
| namespaceOrPrefix | null по умолчанию выбирает обычные атрибуты без пространства имён; строка задаёт URI либо префикс при isPrefix=true. |
| isPrefix | false: строка — URI, true: строка — префикс из документа. URI надёжнее, когда поставщик меняет сокращение префикса. |
| Возврат | Перебираемое XML-представление атрибутов, даже если список пуст; сигнатура также допускает null. Повторный attributes() на представлении списка атрибутов возвращает null. Это не обычный PHP-массив. |
| Ограничения | Default namespace элементов не распространяется на атрибуты без префикса. Отладочный var_dump не заменяет чтение через foreach/(string). Числовое значение ещё требуется валидировать. |
- product sku="A1" currency="RUB"
- attributes() выбирает пары sku/A1 и currency/RUB
- foreach даёт имя и XML-представление значения
- (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'].
<?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.
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-представления. |
| Граница обхода | Только один уровень элементов в выбранном пространстве. Текстовые фрагменты, комментарии и атрибуты не являются этими дочерними элементами. Рекурсивный обход нужно явно организовать. |
- order содержит number и delivery
- children() выбирает number и delivery
- Внутренний city не входит в этот проход
- 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-элементы, например строки заказа; не спутать число строк с числом полей в одной строке.
SimpleXMLElement::count(): intПосчитать выбранные XML-элементы, например строки заказа; не спутать число строк с числом полей в одной строке.
| Часть контракта | Значение |
|---|---|
| Аргументы | Нет. Важен объект слева от ->: конкретный элемент либо представление группы одноимённых детей. |
| Возврат | Неотрицательное целое количество элементов текущей выборки/детей. count($xml) использует Countable; это не длина текста и не число всех потомков. |
| Версии | В актуальном справочнике метод относится к PHP 8; с PHP 8 SimpleXMLElement объявляет Countable. Урок и исполняемые контракты рассчитаны на PHP 8.2+. |
| Ошибки выбора | У элемента item[0] считаются его дочерние поля, у item — одноимённые item. Ноль не говорит, что разбор XML завершился ошибкой. |
- order → item A и item B: две позиции
- item[0] → name и qty: два поля
- 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Узнать имя выбранного тега, чтобы проверить тип документа или объяснить неизвестное поле импорта.
SimpleXMLElement::getName(): stringУзнать имя выбранного тега, чтобы проверить тип документа или объяснить неизвестное поле импорта.
| Часть контракта | Значение |
|---|---|
| Аргументы | Нет. |
| Возврат | Локальное имя элемента строкой: для p:product это product. Имя не содержит текст и не идентифицирует namespace. |
| Версии/контекст | PHP 5.1.3+. С PHP 8.4 вызов больше не делает неявный rewind итератора. Не путайте имя объекта с key(), которое относится к текущей позиции итератора. |
- <status>paid</status>
- getName() даёт status
- (string) даёт paid
- Проверка корневого имени выбирает обработчик, но не валидирует весь документ
<?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 формата.
<?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Получить непосредственный текст элемента для сравнения, валидации или вывода.
SimpleXMLElement::__toString(): stringПолучить непосредственный текст элемента для сравнения, валидации или вывода.
| Часть контракта | Значение |
|---|---|
| Аргументы | Нет. Обычно удобнее (string) $element или echo $element, которые используют строковое преобразование. |
| Возврат | Текст, непосредственно находящийся в элементе; текст вложенных элементов не включается. Пустая строка возможна для пустого содержимого и не является отдельным сигналом успешности загрузки. |
| Версии | PHP 5.3+. С PHP 8 SimpleXMLElement реализует Stringable; с PHP 8.4 строковое преобразование не перематывает итератор. |
| Граница | Это не XML-разметка, не полная текстовая свёртка смешанного документа и не автоматическая бизнес-конвертация числа/даты/bool. |
- note: текст «Hello », вложенный b: «team», текст «!»
- (string) note → «Hello !»
- note->b → «team»
- asXML() сохраняет разметку; DOM textContent объединяет весь текст
<?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-объекта либо ограничить отладочный вывод собственного подкласса.
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 от другого кода и не заменяет контроль доступа. |
- Исходный product содержит sku и name
- __debugInfo получает представление для просмотра
- Прикладное чтение по-прежнему использует attributes/children
- Собственный наследник может показывать только безопасный краткий итог
<?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 в документе, в том числе пока не использованные, чтобы разобраться с новым форматом поставщика.
SimpleXMLElement::getDocNamespaces(bool $recursive = false, bool $fromRoot = true): array|falseПосмотреть объявления xmlns в документе, в том числе пока не использованные, чтобы разобраться с новым форматом поставщика.
| Часть контракта | Значение |
|---|---|
| recursive | false — объявления стартового элемента, true — также объявления в его потомках. |
| fromRoot | true начинает от корня документа, даже если вызов сделан на вложенном узле. false начинает от текущего узла, полезно для исследования отдельной ветки. |
| Возврат | Массив prefix => URI, default namespace имеет ключ пустая строка. Сигнатура допускает false при неудаче; перед работой с результатом это проверяют. |
| Граница | Объявлено не значит используется. Плоский словарь не хранит всю иерархию областей действия и переопределений одинакового префикса. Не превращайте его в универсальный валидатор структуры. |
- Корень объявляет a → urn:active и spare → urn:spare
- Дочерний элемент использует только a
- getDocNamespaces показывает оба объявления
- 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Определить пространства имён, фактически используемые элементами и атрибутами выбранной ветки.
SimpleXMLElement::getNamespaces(bool $recursive = false): arrayОпределить пространства имён, фактически используемые элементами и атрибутами выбранной ветки.
| Часть контракта | Значение |
|---|---|
| recursive | false — текущий элемент; true включает его потомков. В отличие от getDocNamespaces нет параметра fromRoot: важен объект вызова. |
| Возврат | Массив префиксов и URI используемых namespace; пустой массив означает отсутствие таких использований в области поиска. |
| Граница | Это сведения об использовании, не все объявления xmlns, не имена тегов и не выборка узлов. При повторном использовании префикса для разных URI плоский результат не заменяет анализ контекста. |
- catalog сам без namespace
- product имеет p:sku → urn:product
- getNamespaces() у catalog → пусто
- getNamespaces(true) → p используется ниже
<?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 работал при смене префикса у поставщика.
SimpleXMLElement::registerXPathNamespace(string $prefix, string $namespace): boolПривязать собственный короткий префикс к известному URI, чтобы XPath работал при смене префикса у поставщика.
| Часть контракта | Значение |
|---|---|
| prefix | Выбранный вами префикс для выражения XPath, например shop. Он не обязан совпадать с префиксом внутри XML. |
| namespace | Точный URI namespace. Несовпадение URI обычно даёт пустую выборку, а не доказательство отсутствия самого тега. |
| Возврат | true при успешной регистрации, false при отказе. Регистрация не ищет узлы и не меняет документ. |
| Область действия | Контекст XPath конкретного объекта. При вызове xpath на другом объекте регистрируйте нужный префикс там. children/attributes используют свои параметры и от регистрации XPath не меняются. |
- Поставщик пишет v:product, xmlns:v="urn:shop"
- Мы регистрируем s → urn:shop
- Запрос //s:product выбирает нужный URI + имя
- Поставщик меняет v на feed, наш запрос остаётся прежним
<?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Выбрать элементы по пути и условию, например все доступные товары, вместо ручного перебора ненужных веток.
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. |
- Дерево содержит product active="1" и active="0"
- XPath /catalog/product[@active="1"] проверяет каждый подходящий узел
- Массив содержит только совпадения
- 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. Путь здесь фиксирован в исходном коде, а не построен из пользовательской строки.
<?php
declare(strict_types=1);
$xml = new SimpleXMLElement('<catalog><product sku="A&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
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 и получить объект именно добавленного ребёнка для дальнейшего заполнения.
SimpleXMLElement::addChild(string $qualifiedName, ?string $value = null, ?string $namespace = null): ?SimpleXMLElementДобавить новый товар или поле в XML и получить объект именно добавленного ребёнка для дальнейшего заполнения.
| Часть контракта | Значение |
|---|---|
| qualifiedName | Имя нового элемента, при namespace можно использовать prefix:name. Имена структуры задаёт контракт формата, не произвольный ввод посетителя. |
| value | Текст или null для пустого элемента. < и > экранируются автоматически; & требуется экранировать вручную. Это не XML-фрагмент для вставки готового поддерева. |
| namespace | URI для создаваемого элемента либо null. Префикс и URI задают вместе, если нужен квалифицированный элемент. |
| Возврат/отказ | Объект нового ребёнка либо null при неудаче. Некорректное имя/неподходящий узел могут дать предупреждение; пустое имя вызывает ValueError. Проверять === null, не истинность пустого элемента. |
- catalog ещё пуст
- addChild("product") добавляет ребёнка и возвращает его
- Переменная $product указывает на нового ребёнка
- Его name и sku меняют исходный catalog
<?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 & Lamp <set></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Добавить короткое служебное значение в открывающий тег: идентификатор заказа, валюту или код источника.
SimpleXMLElement::addAttribute(string $qualifiedName, string $value, ?string $namespace = null): voidДобавить короткое служебное значение в открывающий тег: идентификатор заказа, валюту или код источника.
| Часть контракта | Значение |
|---|---|
| qualifiedName | Имя атрибута; для namespace — qualified name с префиксом. Атрибут без префикса не наследует default namespace элемента. |
| value | Строковое значение. Метод экранирует XML-значимые символы атрибута; не передавайте заранее HTML-экранированную строку. |
| namespace | URI namespace либо null. Для непустого URI атрибут должен иметь корректный префикс. |
| Возврат/ошибки | void — ничего полезного не возвращает. Повторное добавление существующего атрибута предупреждает, это не операция обновления. Пустое имя — ValueError. Для обновления обычного атрибута используйте $node['name'] = $value. |
- order не содержит source
- addAttribute создаёт source="mobile"
- Повторное создание не является обновлением
- Присваивание 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&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-разметку текущего элемента либо сохранить её в заданный файл; эти два режима возвращают разные типы.
SimpleXMLElement::asXML(?string $filename = null): string|boolПолучить XML-разметку текущего элемента либо сохранить её в заданный файл; эти два режима возвращают разные типы.
| Часть контракта | Значение |
|---|---|
| filename | null/аргумент пропущен: вернуть XML строкой. Строка пути: записать файл, возможно перезаписав существующий; путь контролирует приложение. |
| Возврат | Без имени файла: string при успехе, false при отказе. С filename: true/false успешности записи. true — не содержимое документа. |
| Формат | XML 1.0. Корневой документ может содержать декларацию XML; выбранная вложенная ветка выводится как фрагмент. Побайтовая идентичность исходнику и красивое форматирование не гарантируются. |
| Ошибки/ограничения | Отказ записи может сопровождаться предупреждением. Метод не предоставляет блокировку, атомарное обновление, транзакцию или проверку схемы. Нет аргумента options для форматирования. |
- Дерево уже изменено в памяти
- Вызов без filename → строка XML для передачи
- Вызов с filename → байты в файл и bool
- Изменение объекта не обновляет ранее прочитанный файл автоматически
<?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.
<?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-разметку текущего элемента либо сохранить её в заданный файл; эти два режима возвращают разные типы.
SimpleXMLElement::saveXML(?string $filename = null): string|boolПолучить XML-разметку текущего элемента либо сохранить её в заданный файл; эти два режима возвращают разные типы.
| Часть контракта | Значение |
|---|---|
| filename | null/аргумент пропущен: вернуть XML строкой. Строка пути: записать файл, возможно перезаписав существующий; путь контролирует приложение. |
| Возврат | Без имени файла: string при успехе, false при отказе. С filename: true/false успешности записи. true — не содержимое документа. |
| Формат | XML 1.0. Корневой документ может содержать декларацию XML; выбранная вложенная ветка выводится как фрагмент. Побайтовая идентичность исходнику и красивое форматирование не гарантируются. |
| Ошибки/ограничения | Отказ записи может сопровождаться предупреждением. Метод не предоставляет блокировку, атомарное обновление, транзакцию или проверку схемы. Нет аргумента options для форматирования. |
- Дерево уже изменено в памяти
- Вызов без filename → строка XML для передачи
- Вызов с filename → байты в файл и bool
- Изменение объекта не обновляет ранее прочитанный файл автоматически
saveXML — точный alias asXML: не другой формат и не отдельная технология хранения. В новом коде удобно выбрать одно название для единообразия. Все параметры, результаты и ограничения asXML применяются здесь тоже.
<?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.
<?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Подготовить ручной обход списка детей и вернуться к первой позиции после предыдущего прохода.
SimpleXMLElement::rewind(): voidПодготовить ручной обход списка детей и вернуться к первой позиции после предыдущего прохода.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов; void. Устанавливает внутреннюю позицию на первый доступный элемент, если он есть. |
| Пустое дерево | После rewind обязательно valid: первый ребёнок может отсутствовать. |
| Версии | С PHP 8.4 getName/asXML и строковое преобразование больше не делают неявный rewind. Пишем rewind явно, не используем чтение имени как способ запуска итератора. |
- До rewind курсор не установлен
- rewind выбирает первого ребёнка
- next передвигает к следующему
- Новый 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
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Узнать, можно ли читать текущую позицию ручного обхода, не вызывая ошибку на конце списка.
SimpleXMLElement::valid(): boolУзнать, можно ли читать текущую позицию ручного обхода, не вызывая ошибку на конце списка.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов; true означает установленный текущий элемент, false — курсор не инициализирован или уже закончен. |
| Что не проверяет | Это не проверка корректности XML, XSD, присутствия атрибута или содержимого товара. Метод относится только к состоянию итератора. |
| Порядок | Сначала rewind, затем valid; после каждого next снова valid перед current/key. |
- rewind → первая позиция
- valid=true → можно current
- next → конец списка
- 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Получить объект ребёнка на текущей позиции, чтобы читать или изменять именно его.
SimpleXMLElement::current(): SimpleXMLElementПолучить объект ребёнка на текущей позиции, чтобы читать или изменять именно его.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов; возвращается SimpleXMLElement текущего элемента, а не его строковый текст. |
| Отказ | С PHP 8.1 неинициализированный/исчерпанный итератор вызывает Error; ранее возвращал null. Проверяйте valid. |
| Побочный эффект | current сам не передвигает курсор. Возвращённый узел связан с исходным документом; присваивание его атрибуту меняет дерево. |
- $order содержит два item
- rewind устанавливает курсор на первый
- current возвращает этот item
- Чтение 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Узнать имя текущего дочернего тега в ручном обходе: ключ здесь не порядковый номер.
SimpleXMLElement::key(): stringУзнать имя текущего дочернего тега в ручном обходе: ключ здесь не порядковый номер.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов; строковое имя тега текущего элемента. Повторяющиеся одноимённые узлы имеют одинаковый ключ. |
| Отказ | С PHP 8.1 Error, если курсор ещё не установлен или исчерпан; до этого false. |
| Различия | getName у корневого объекта возвращает имя корня, key после rewind — имя текущего ребёнка. key не является уникальным идентификатором товара. |
- order содержит item A, item B и comment
- Ключи обхода: item, item, comment
- Если собрать array[key] без списка, второй item затрёт первый
- Для сохранения всех строк используем [] или реальный 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-курсор к следующему элементу после обработки текущего.
SimpleXMLElement::next(): voidПередвинуть ручной XML-курсор к следующему элементу после обработки текущего.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов; void. Новый узел берут через current после проверки valid. |
| Конец | Переход после последнего делает valid=false. Возвращаемое значение next не сигнализирует наличие следующего элемента. |
| Граница | Не спускается рекурсивно во вложенные теги. Пропущенный next создаёт бесконечный цикл, два next за итерацию пропускают позицию. |
- current читает A
- Прикладной код обрабатывает A
- next устанавливает B
- 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Проверить, есть ли дочерние элементы у текущего ребёнка итератора, прежде чем спускаться глубже.
SimpleXMLElement::hasChildren(): boolПроверить, есть ли дочерние элементы у текущего ребёнка итератора, прежде чем спускаться глубже.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов; true, если текущий элемент имеет вложенные элементы, иначе false. |
| Позиция | Проверяется ребёнок, выбранный курсором после rewind/next, а не произвольный узел самого объекта. Без установленной позиции — false. |
| Что не является ребёнком | Простой текст и атрибуты не являются вложенными элементами. false не означает пустую строку, несуществующий узел или ошибку XML. |
- Курсор на delivery → внутри city: true
- Курсор на comment → только текст: false
- Ветка требует спуска getChildren
- Лист читаем через 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Получить объект текущего элемента как следующий уровень рекурсивного обхода.
SimpleXMLElement::getChildren(): ?SimpleXMLElementПолучить объект текущего элемента как следующий уровень рекурсивного обхода.
| Часть контракта | Значение |
|---|---|
| Аргументы/возврат | Нет аргументов. Объект текущего ребёнка, по которому можно начать обход его детей; null при неинициализированном/законченном итераторе либо итерации атрибутов. |
| Текстовый лист | На установленном текстовом листе результат может быть объектом с нулём детей. hasChildren проверяет наличие следующего уровня, а не просто результат !== null. |
| Не children() | children() выбирает детей объекта вызова и поддерживает namespace. getChildren() относится к текущему ребёнку курсора и входит в контракт RecursiveIterator. |
- Внешний $order указывает на корень, курсор на delivery
- getChildren возвращает объект delivery
- foreach по нему перебирает city и street
- Внешний 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.