Веб

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

Сначала разберёмся, что у вас под капотом. Ваш var_dump показывает, что $xml->product->prices->default->price — не ассоциативный массив, а indexed array из объектов SimpleXMLElement. Каждый элемент массива выглядит так:

php
[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 целиком, чтобы избежать проблем с массивами. Вот готовый сниппет:

php
$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] и каст к строке.

Ещё вариант покороче, если знать точный путь:

php
$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(). Пробегаем массив и проверяем условие:

php
$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:

php
$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 — и цена ваша.


Источники

  1. PHP: SimpleXMLElement::attributes - Manual
  2. PHP: SimpleXMLElement::xpath - Manual
  3. Habr Q&A - Get attribute value by attribute
  4. Stack Overflow - SimpleXML get element content based on attribute value
  5. W3Schools - PHP SimpleXML - Get Node/Attribute Values
  6. Stack Overflow - Get attribute value in SimpleXML object
  7. GeeksforGeeks - PHP SimpleXMLElement XPath() Function
  8. Tutorialspoint - PHP SimpleXMLElement::attributes() Function
  9. RU Stack Overflow - Парсим XML с атрибутами

Заключение

Для php xml и парсинг xml php идеально подойдёт foreach или XPath — оба дают цену 1090 по RUB без лишнего гемора. Выберите array_filter, если любите функционалку, как в JS. Протестируйте код выше на вашем XML, добавьте проверки — и проблема решена. Если структура изменится, XPath легко адаптировать. Удачи в проекте!

Авторы
Проверено модерацией
Модерация