Конструктор, не вызываемый из объекта ответа SOAP
Я общаюсь с SOAP API, используя PHP класс SOAPClient. Один из вариантов позволяет переназначить типы, указанные в файле WSDL, с помощью собственных классов:
Опция classmap может использоваться для сопоставления некоторых типов WSDL с классами PHP. Этот параметр должен быть массивом с типами WSDL в качестве ключей и имен классов PHP в качестве значений.
Я создаю свой клиент как таковой:
$api = new SOAPClient('http://example.com/soap.wsdl', [
'location' => 'http://example.com/soap/endpoint',
'soap_version' => SOAP_1_2,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
'cache_wsdl' => WSDL_CACHE_BOTH,
'classmap' => [
'APIResultObject' => 'Result'
],
# TODO: Set for debug only?
'trace' => TRUE,
'exceptions' => TRUE
]);
Это работает, и когда я вызываю $api->method('param')
, я возвращаю объект Result
назад (вместо объекта StdClass
). Проблема в том, что метод Result::__construct()
никогда не вызывается, поэтому некоторые частные свойства Result
никогда не устанавливаются.
Здесь Result
:
class DataClass{
protected $data;
function __construct(){
$this->data = ['a' => 0, 'b' => 1, 'c' => 2];
}
}
class Result extends DataClass{
public $value, $name, $quantity;
function __construct(array $values){
parent::__construct();
foreach(['value', 'name', 'quantity'] as $var){
$this->$var = isset($values[$var]) ? $values[$var] : NULL;
}
}
function getData(){
return $this->data[$this->name];
}
}
Что происходит, я делаю $api->method('param')->getData()
и получаю следующую ошибку:
Примечание: Undefined свойство: Результат:: $datap >
Как я могу вызвать функцию конструктора, которая мне нужна при получении ответа SOAP? Я попытался использовать __wakeup()
, но это тоже не сработало.
P.S. Я "решил" его с небольшим обходным решением, но я не думаю, что он идеален. Вот что я сделал:
function getData(){
if($this->data === NULL){
parent::__construct();
}
return $this->data[$this->name];
}
Ответы
Ответ 1
Это известное поведение (отчет об ошибке).
Как сообщается в отчете об ошибке (miceleparkip at web dot de):
Это не ошибка. Это нормально.
Мыльный объект создается на стороне сервера. Поэтому конструктор просто вызывается на сервере.
Я разделяю ее позицию.
Следующий комментарий (php at hotblocks dot nl) в том же отчете об ошибке не согласен:
Сервер не создает объекты, он отправляет XML. Клиент декодирует этот XML и создает объекты.
Несмотря на то, что это бесспорно верно с техническим Понта зрения, "абстрактный" объект, возможно, создаются на стороне сервера. Независимо от того, преобразуется ли он сначала в XML, а затем реконструируется на стороне клиента, это проблема низкого уровня, о которой не должен знать уровень приложения.
Если вашему приложению нужны объекты с большим количеством функций, чем те, которые предоставляются сервером, я бы создал локальный класс, который принимает объект, созданный SOAPClient
как аргумент конструктора:
class MySoapResultClass {
// whatever
}
class LocalApplicationClass {
public function __construct(MySoapResultClass $soapResult) {
// your local initialization code
$this->data = ['a' => 0, 'b' => 1, 'c' => 2];
// then either extract your data from $soapResult,
// or just store a reference to it
}
public function getData(){
return $this->data[$this->name];
}
}
$api = new SOAPClient(...);
$soapResult = $api->method('param');
$myUsefulObject = new LocalApplicationClass($soapResult);
Ответ 2
Обновление: другое обходное решение
Вы можете перенести SoapClient в другой класс, который будет правильно использовать конструкторы. Чтобы сохранить проблемы, класс должен сам проверять, нужно ли это или нет. Все еще не идеально, но я думаю, что это проще.
class ActiveSOAPClient extends SoapClient {
// Intercept all calls to class.
public function __call($func, $params) {
// Pass it to parent class.
$ret = parent::__call($func, $params);
// If the answer is an object...
if (is_object($ret)
// Taboo functions that will pass unhindered.
// && (!in_array($func, [ ARRAY OF EXCLUDED_FUNCTIONS ]))
) {
// ...and the object is in the auto classmap...
$key = array_search(get_class($ret), $this->_classmap, true);
if ($key !== false) {
// ...then assume it an incomplete object and try activating it.
$ret->__construct();
}
}
return $ret;
}
}
Преимущество состоит в том, что класс ActiveSOAPClient
не должен иметь никакой информации о вашей собственной логике, и ваша логика не нуждается в изменении.
Проблема
Я думаю, что это поведение преднамеренное или известная ошибка (т.е. должна быть какая-то причина или проблема), потому что на странице руководства это уже отмечено от семи лет назад.
Я проверил исходный код с PHP 5.5.6. Насколько я могу прочитать код в php_encoding.c,
/* Struct encode/decode */
static zval *to_zval_object_ex(encodeTypePtr type, xmlNodePtr data, zend_class_entry *pce TSRMLS_DC)
{
zval *ret;
xmlNodePtr trav;
sdlPtr sdl;
sdlTypePtr sdlType = type->sdl_type;
zend_class_entry *ce = ZEND_STANDARD_CLASS_DEF_PTR;
zval *redo_any = NULL;
if (pce) {
ce = pce;
} else if (SOAP_GLOBAL(class_map) && type->type_str) {
zval **classname;
zend_class_entry *tmp;
if (zend_hash_find(SOAP_GLOBAL(class_map), type->type_str, strlen(type->type_str)+1, (void**)&classname) == SUCCESS &&
Z_TYPE_PP(classname) == IS_STRING &&
(tmp = zend_fetch_class(Z_STRVAL_PP(classname), Z_STRLEN_PP(classname), ZEND_FETCH_CLASS_AUTO TSRMLS_CC)) != NULL) {
ce = tmp;
}
}
... если определено отображение класса и известно, вызывается zend_fetch_class().
Я считаю, что некоторая функция ctor() должна вызываться впоследствии по значениям, полученным из node, как это сделано, например, в PDO:: fetchObject (см. файл "ext/pdo/pdo_stmt.c" ).
В настоящее время это, похоже, не выполняется. Возможно, это связано с порядком оценки объектов в дереве XML, что затрудняет предоставление конструкторам подходящих аргументов, но на данный момент я просто догадываюсь.
Однако вы правы в том, что в то время не было "официального" решения (вы не можете получить гораздо более официальный, чем исходный код).
Hack (серверная сторона)
Я попытался добавить конструктора-бегуна в исходный код PHP, просто для черта его. К сожалению, мне, похоже, нужны несколько переменных, которые не входят в область, в которой они мне нужны, поэтому мне пришлось бы изменить пару структур для передачи информации о конструкторах и т.д., И эти структуры используются повсеместно в коде SOAP.
За исключением, возможно, в простейшем случае объекта с конструктором без параметров и без деструктора, необходимые изменения в коде не выглядят вообще незначительными.