PHP SimpleXML и XPath: получить цену по валюте RUB
Как получить цену 1090 из XML по атрибуту currency=RUB с помощью PHP SimpleXML. Примеры через XPath и foreach, приведение типов и обработка ошибок. Советы.
Как найти значение в ассоциативном массиве?
Ниже приведен код моей xml-ки. Мне нужно получить значение 1090, опираясь на RUB.
[“product”]=>
object(SimpleXMLElement)#7567 (39) {
[“section_id”]=>
string(5) “22304”
[“pwyw”]=>
string(1) “0”
[“prices”]=>
object(SimpleXMLElement)#7168 (2) {
[“default”]=>
object(SimpleXMLElement)#5681 (1) {
[“price”]=>
array(6) {
[0]=>
object(SimpleXMLElement)#5688 (2) {
[“currency”]=>
string(3) “RUB”
[“price”]=>
string(4) “1090”
}
[1]=>
object(SimpleXMLElement)#5687 (2) {
[“currency”]=>
string(3) “USD”
[“price”]=>
string(5) “15.76”
}
}
}
}
}
Как это можно сделать?
Я пытался сделать вот так:
$price = $xml->product->prices->default->price->xpath(‘price[@currency=“RUB”]’);
Но это не работает. Подскажите, пожалуйста, как получить нужное мне значение?
Я понимаю, что “$xml->product->prices->default->price” - это массив. Так вот, хочется что-то на подобии как в JS воспользоваться filter(), но только надо на PHP. Ну или другое какое-то элегантное, красивое решение. Заранее спасибо каждому!
В вашем XML с помощью php simplexml легко вытащить цену 1090 по currency=“RUB”, поскольку $xml->product->prices->default->price — это индексированный массив объектов SimpleXMLElement. Вместо неработающего xpath на массиве используйте цикл foreach с проверкой $item['currency'] или xpath php от корня документа: $xml->xpath('.//price[@currency="RUB"]/price'). Это элегантно заменит JS-подобный filter() — получите строку “(string)$result[0]” и готово.
Содержание
- Структура вашего XML в SimpleXML
- Почему не сработал XPath
- Решение 1: XPath от корня
- Решение 2: Цикл foreach
- Элегантный filter: array_filter в PHP
- Лучшие практики парсинга XML
- Источники
- Заключение
Структура вашего XML в SimpleXML
Сначала разберёмся, что у вас под капотом. Ваш var_dump показывает, что $xml->product->prices->default->price — не ассоциативный массив, а indexed array из объектов SimpleXMLElement. Каждый элемент массива выглядит так:
[0] => object(SimpleXMLElement)#5688 (2) {
["currency"] => string(3) "RUB"
["price"] => string(4) "1090"
}
Это типичная структура после simplexml_load_file или simplexml_load_string в php xml. Атрибуты доступны как $item['currency'], а вложенные элементы — как $item->price. Но! SimpleXML лениво загружает данные, так что прямой доступ требует приведения к строке: (string)$item['currency']. Без этого PHP видит объект, а не строку — вот почему сравнения иногда глючат.
А что если массив большой, как у вас (6 элементов)? Цикл или фильтр — идеально. Но давайте сначала поймём ошибку.
Почему не сработал XPath
Ваш код $xml->product->prices->default->price->xpath('price[@currency="RUB"]') падает, потому что $price — массив, а не объект SimpleXMLElement с методом xpath(). Массивы в PHP не имеют таких методов.
Согласно официальной документации PHP, xpath() работает только на объектах SimpleXMLElement или их контейнерах. Вы пытаетесь вызвать его на array — отсюда null или ошибка. Плюс селектор price[@currency="RUB"] ищет вложенный <price> внутри <price>, а у вас атрибут currency прямо на нём же.
Коротко: контекст неверный. Переходим к рабочим вариантам — они проще, чем кажутся.
Решение 1: XPath от корня
XPath php — мощный инструмент для парсинг xml php, особенно если структура сложная. Запускайте его от $xml целиком, чтобы избежать проблем с массивами. Вот готовый сниппет:
$xml = simplexml_load_string($yourXmlString); // или load_file
$results = $xml->xpath('//prices/default/price[@currency="RUB"]/price');
if (!empty($results)) {
$price = (string) $results[0]; // "1090"
echo $price;
}
Почему это работает? //prices/default/price[@currency="RUB"] находит нужный <price> по атрибуту, а /price тянет вложенное значение. xpath() возвращает массив SimpleXMLElement — берите [0] и каст к строке.
Ещё вариант покороче, если знать точный путь:
$priceNode = $xml->product->prices->default->price;
$results = $priceNode->xpath('@currency="RUB" ? price : null'); // Но лучше полный путь выше.
Из примера на Stack Overflow видно: для атрибутов используйте ./price[@currency="RUB"]/@price, если хотите сам атрибут цены. Тестируйте — для вашего случая первый вариант идеален.
Решение 2: Цикл foreach
Если XPath пугает (хотя он быстрый), то foreach — ваш JS filter(). Пробегаем массив и проверяем условие:
$prices = $xml->product->prices->default->price; // Массив!
foreach ($prices as $item) {
if ((string) $item['currency'] === 'RUB') {
$price = (string) $item->price; // "1090"
break; // Нашли — выходим
}
}
echo $price ?? 'Не найдено';
Просто? Да. (string) обязательно — иначе сравниваешь объект со строкой. Такой подход из Habr Q&A: там точно так же тянут цену по атрибуту. Работает на любом php xml, без regex или хаков.
А если элементов мало (как ваши 6)? Производительность не страдает. Плюс легко добавить логику: суммировать цены или фильтровать по нескольким валютам.
Элегантный filter: array_filter в PHP
Вы просили “как filter() в JS” — вот php xml в массив с array_filter. Сначала преобразуем SimpleXML-массив в обычный array:
$prices = $xml->product->prices->default->price;
$rubPrices = array_filter($prices, function($item) {
return (string) $item['currency'] === 'RUB';
});
if (!empty($rubPrices)) {
$price = (string) reset($rubPrices)->price; // Первый (и единственный) — "1090"
}
array_filter возвращает массив подходящих объектов. reset() берёт первый. Элегантно? Абсолютно! Это ближе к функциональному стилю JS. Из W3Schools примера видно: комбинируйте с array_values для чистоты.
Но учтите: если RUB несколько раз — соберёт все. Для одного — супер.
Лучшие практики парсинга XML
Парсинг php simplexml — не rocket science, но есть нюансы:
- Всегда кастите к строке:
(string)$nodeили$node->__toString(). Иначе — объекты везде. - Обработка ошибок:
if (empty($results)) { throw new Exception('Цена не найдена'); } - XPath vs foreach: XPath быстрее на больших XML (O(1) поиск), foreach — проще для отладки.
- Альтернативы: Для мега-XML — DOMDocument + XPath. Но SimpleXML хватит для 99% случаев.
- php simplexml load file или string: Загружайте с
LIBXML_NOCDATAфлагом для чистоты.
Из PHP docs по attributes: атрибуты — как массив, но с приведением. А в русском Stack Overflow — foreach с attributes() для namespace.
Тестируйте на реальном XML — и цена ваша.
Источники
- PHP: SimpleXMLElement::attributes - Manual
- PHP: SimpleXMLElement::xpath - Manual
- Habr Q&A - Get attribute value by attribute
- Stack Overflow - SimpleXML get element content based on attribute value
- W3Schools - PHP SimpleXML - Get Node/Attribute Values
- Stack Overflow - Get attribute value in SimpleXML object
- GeeksforGeeks - PHP SimpleXMLElement XPath() Function
- Tutorialspoint - PHP SimpleXMLElement::attributes() Function
- RU Stack Overflow - Парсим XML с атрибутами
Заключение
Для php xml и парсинг xml php идеально подойдёт foreach или XPath — оба дают цену 1090 по RUB без лишнего гемора. Выберите array_filter, если любите функционалку, как в JS. Протестируйте код выше на вашем XML, добавьте проверки — и проблема решена. Если структура изменится, XPath легко адаптировать. Удачи в проекте!