При работе над одним порталом у нас возникла задача, по обмену данными между порталом и несколькими другими программами написанными на .NET. Разработчики этих программ настаивали сделать взаимодействие при помощи SOAP, мы согласились.
В этой статье хотелось бы раскрыть проблемы возникшие при разработке SOAP сервера на Zend Framework с использованием Zend_Soap_AutoDiscover.
Так как портал был полностью реализован на Zend Framework мы и решили реализовать SOAP сервер на нем. Так же мы решили использовать AutoDiscover что бы не писать wsdl файл в ручную, так как планировалось что в наш сервер будет добавляться различный функционал в будущем. И не хотелось тратить время на описание этих функций в wsdl файле. Заглянув в ман мы нашли код нужный для создания SOAP сервера.
-
$autodiscover = new Zend_Soap_AutoDiscover();
-
$autodiscover->setClass('ExcampleService');
-
$autodiscover->handle();
-
} else {
-
// pointing to the current file here
-
$soap = new Zend_Soap_Server("http://example.com/soap.php?wsdl");
-
$soap->setClass('ExcampleService');
-
$soap->handle();
-
}
Как же все просто подумали мы, но .NET отказался работать с нашим сервером. Мы стали искать проблемы почему не работает наш сервер с .NET программами, когда клиент на PHP успешно обменивался данными с ним.
Проблема первая.
Как мы знаем PHP не типизированный язык, когда .NET типизированный и это первая проблема. Потому что при генерации wsdl AutoDiscover не может определить типы возвращаемых данных. Для решения этой проблемы нужно указывать тип возвращаемых данных в описании функций.
Пример описания:
-
class ExcampleService {
-
/**
-
* Return true if record active, else if record inactive
-
*
-
* @param integer $recordId
-
* @return boolean
-
*/
-
public function isActiveRecord($recordId) {
-
$recordsTable = new Model_Records();
-
return $recordsTable->isActiveRecord($recordId);
-
}
-
}
При просмотре wsdl выяснилось что типы простых данных указываются, но если возвращается массив то тип его элементов не указывается. Воспользовавшись поисковиками мы нашли решение. Возвращать не массив а объект с массивом других объектов. Типы всех свойств должны быть указаны в описании этих объектов.
Пример описания классов возвращаемых объектов:
-
class SRecord {
-
/** @var int */
-
public $id;
-
/** @var string */
-
public $text;
-
}
-
class SRecordsArray {
-
/** @var SRecord[] */
-
public $records;
-
}
И код самого класса ExcampleService:
-
class ExcampleService {
-
/**
-
* Return true if record active, else if record inactive
-
*
-
* @param integer $recordId
-
* @return boolean
-
*/
-
public function isActiveRecord($recordId) {
-
$recordsTable = new Model_Records();
-
return $recordsTable->isActiveRecord($recordId);
-
}
-
/**
-
* Return list of records
-
*
-
* @return SRecordsArray
-
*/
-
public function getList() {
-
$recordsTable = new Model_Records();
-
$records = $recordsTable -> getList();
-
foreach ($records as $record) {
-
$oSrecord = new SRecord();
-
$oSrecord->id = $record->id;
-
$oSrecord->text = $record->text;
-
$result[] = $oSrecord;
-
}
-
$oSrecordsArray = new SRecordsArray();
-
$oSrecordsArray->records = $result;
-
return $oSrecordsArray;
-
}
-
}
Сохраняем изменения запускаем программу .NET и видим что она снова отказывается работать.
Проблема вторая.
Проблема в том что .NET поддерживает только один стандарт SOAP WS-I а AutoDiscover по умолчанию использует стандарт XSD поэтому нам нужно привести wsdl к стандарту WS-I. Для этого нам нужно доработать первоначальный код следующим образом.
-
$autodiscover = new Zend_Soap_AutoDiscover('Zend_Soap_Wsdl_Strategy_ArrayOfTypeSequence');
-
$autodiscover->setClass('ExcampleService');
-
$autodiscover->handle();
-
} else {
-
// pointing to the current file here
-
$soap = new Zend_Soap_Server("http://example.com/soap.php?wsdl");
-
$soap->setClass('ExcampleService');
-
$soap->handle();
-
}
Сохраняем изменения, обновляем сервер, запускаем программу на .NET и что мы видим, программа выполняет все методы сервера но получает от сервера всегда только null.
Проблема третья.
Последняя проблема связанна с получением и передачей данных. Для решения этой проблемы мы написали специальный класс который трансформирует входящие и исходящие данные.
Исходный код класса:
-
class Service_Soap
-
{
-
protected $_service;
-
public function __construct()
-
{
-
$this->_service = new ExcampleService();
-
}
-
public function __call($name, $arguments)
-
{
-
foreach($arguments[0] as $property=>$value){
-
$params[$property] = $value;
-
}
-
}
-
}
-
}
И доработали код создания сервера
-
$autodiscover = new Zend_Soap_AutoDiscover('Zend_Soap_Wsdl_Strategy_ArrayOfTypeSequence');
-
$autodiscover->setClass('ExcampleService');
-
$autodiscover->handle();
-
} else {
-
// pointing to the current file here
-
$soap = new Zend_Soap_Server("http://example.com/soap.php?wsdl");
-
//$soap->setClass('ExcampleService');
-
$modelSoap = new Service_Soap();
-
$soap->setObject($modelSoap);
-
$soap->handle();
-
}
После сохранения программа на .NET заработала и портал стал обмениваться данными с ней.
Надеюсь что этот пост поможет кому то при разработке SOAP сервера. И он уже быстро справится с проблемами описанными в статье, нам решение этих проблем заняло достаточно времени.
Спасибо всем за внимание и удачи вам!