Как стать автором
Обновить

SoapServer на PHP. Пусть array всегда будет Map

Время на прочтение4 мин
Количество просмотров2.6K
Во время работы над серверной частью одного iphone приложения, всплыла любопытная особенность Zend_Soap_Server. Приводила она к спонтанно (на первый взгляд) возникающим ошибкам при возвращении php-ных массивов. У нас выявление и отладка заняли несколько человеко-часов, и, возможно, данная статья позволит кому-то те же самые несколько часов сэкономить.

Стоит отметить, что Zend_Soap_Server — это несложная обертка над встроенным SoapServer, и описанный ниже эффект наблюдается не только при использовании ZF, но и при работе с SoapServer напрямую.

Метод, в котором мистическим образом возникала ошибка, занимается тем, что находит цены на отели по заданным клиентом параметрам. Возвращаются они вместе с некоторой дополнительной информацией и итоговая структура выдачи выглядит примерно так:
<?php
   $result = array(
       'info' => array(
           'key_1' => 'value_1',
           ...,
           'key_n' => 'value_n'
       ),
       'prices' => array(
           id_1 => price_1,
           ...
           id_m => price_m
       )
   );
   return $result;
?>

Из важных особенностей — цены на сервере упорядочиваются по возрастанию; id_k — это целые неотрицательные числа, (идентификаторы отелей); если по заданным критериям невозможно найти ни одной актуальной цены, то возвращается сообщение об ошибке (другая структура).

Все проблемы, как выяснилось из анализа логов запросов, были связаны с массивом prices. В подавляющем большинстве случаев он не был массивом в “классическом” понимании. То есть его ключи не были идущими подряд целыми, начинающимися с 0. Подобные данные SoapServer считает (справедливо) типом Map и конвертирует (если залезть в raw xml ответа) к виду
<item>
	<key xsi:type="xsd:string">prices</key>
	<value xsi:type="ns2:Map">
		<item>
			<key xsi:type="xsd:int">100</key>
			<value xsi:type="xsd:int">150</value>
		</item>
		<item>
			<key xsi:type="xsd:int">2</key>
			<value xsi:type="xsd:int">300</value>
		</item>
		<item>
			<key xsi:type="xsd:int">1078</key>
			<value xsi:type="xsd:int">306</value>
		</item>
	</value>
</item>


И лишь изредка prices оказывались действительно “классическим” массивом. Например, такая ситуация возникала, когда единственным доступным вариантом был отель с id = 0. Подобные данные SoapSever считает (опять же, справедливо) типом Array и приводит к виду
<item>
	<key xsi:type="xsd:string">prices</key>
	<value enc:itemType="xsd:int" enc:arraySize="1" xsi:type="enc:Array">
		<item xsi:type="xsd:int">420</item>
	</value>
</item>


Возможно, ошибка проявилась бы раньше, если бы метод возвращал аналогичную структуру для “пустых” результатов. Эта ситуация более распространена, логично выделяется как отдельный случай при тестировании, а SoapServer пустой массив тоже считает типом Array:
<item>
	<key xsi:type="xsd:string">prices</key>
	<value enc:itemType="xsd:anyType" enc:arraySize="0" xsi:type="enc:Array"/>
</item>


Таким образом, в зависимости от данных, наш сервер возвращал клиенту разные типы — в основном Map, но иногда Array. А клиент всегда ожидал увидеть Map и падал при получении Array. Проблема была выявлена, но пока еще не решена.

В качестве возможных вариантов решения были предложены
1) изменение на стороне клиента — чтобы он умел разбирать оба случая;
2) добавить в массив prices фейковый строковый ключ и пропускать его на клиенте при разборе;
3) добиться того, чтобы сервер всегда выдавал тип Map, менее “кривым” способом, чем пункт 2.

Третий вариант был сочтен предпочтительным и после качественного и не слишком короткого гугления решение было найдено на stackoverflow.
Массив prices нужно обернуть в SoapVar с указанием типа APACHE_MAP

<?php
   $result = ...;
   $result['prices'] = new SoapVar($result['prices'], APACHE_MAP);
   return $result;
?>

и только после этого возвращать клиенту. При наличии такой обертки SoapServer уже не смотрит на реальные данные для определения типа, а всегда возвращает Map — и для пустых массивов:
<item>
	<key xsi:type="xsd:string">prices</key>
	<value xsi:type="ns2:Map"/>
</item>

и для “классических” непустых:
<item>
	<key xsi:type="xsd:string">prices</key>
	<value xsi:type="ns2:Map">
		<item>
			<key xsi:type="xsd:int">0</key>
			<value xsi:type="xsd:int">100</value>
		</item>
		<item>
			<key xsi:type="xsd:int">1</key>
			<value xsi:type="xsd:int">200</value>
		</item>
		<item>
			<key xsi:type="xsd:int">2</key>
			<value xsi:type="xsd:int">300</value>
		</item>
	</value>
</item>


Несмотря на итоговую простоту решения и то, что оно было найдено методом Google'а, сама ситуация показалась мне интересной, а ошибка вполне типичной и достойной описания, что и привело к написанию этой статьи.

Для полноты информации: софт, работающий на сервере: PHP 5.3, Zend Framework 1.11, на других версиях я не проверял, хотя предполагаю, что все должно быть аналогично.
Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+6
Комментарии2

Публикации

Истории

Работа

PHP программист
179 вакансий

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
26 октября
ProIT Network Fest
Санкт-Петербург
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань