Constructor not being called from SOAP response object Constructor not being called from SOAP response object php php

Constructor not being called from SOAP response object


Update: another workaround

You can wrap SoapClient in another class that will invoke the constructors properly. To save trouble, the class checks whether it's needed or not. Still not ideal, but I think it's simpler this way.

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...            if (false !== array_search(get_class($ret), $this->_classmap, true)) {                // ...then assume it's an incomplete object and try activating it.                $ret->__construct();            }        }        return $ret;    }}

The advantage is that the ActiveSOAPClient class does not need to have any information about your own logic, and your logic does not need to change.

The Problem

I think this behaviour is intentional or a known bug (i.e., there must be some reason or problem behind), because in the manual page it is already noted as of seven years ago.

I checked out the source code from PHP 5.5.6. As far as I can read the code in 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;                }        }

...if a class map is defined, and is known, zend_fetch_class() is invoked.

I believe that some kind of ctor() function should be called afterwards on the values fetched from the node, as is done e.g. in PDO::fetchObject (see file "ext/pdo/pdo_stmt.c").

Currently, this does not seem to be done. Possibly it's because of the order of evaluation of objects in the XML tree, which makes supplying appropriate arguments to the constructor tricky, but at this point I'm just guessing.

However, you are correct in there being no "official" solution at the time (you can't get much more official than the source code).

Hacking the source

I've tried to add a constructor-runner in the PHP source code, just for the hell of it. Unfortunately, I seem to need several variables that are not in the scope where I need them, so I'd have to change a couple of structures to pass constructor information and so on around, and those structures are used ubiquitously in the SOAP code.

Except perhaps in the simplest case of an object with parameter-less constructor and no destructor, the necessary modifications to the code don't look to me minor at all.


This is known behaviour (bug report).

As someoned advised in the bug report (miceleparkip at web dot de):

This is not a bug. It's quite normal.

The soap object is created on the server side. So the constructor is just called on the server.

I share her position.

A subsequent comment (php at hotblocks dot nl) in the same bug report disagrees :

The server doesn't create objects, it sends XML. The client decodes that XML and creates the objects.

While this is indisputably true from a technical pont of view, the "abstract" object is arguably created at server side. Whether it is first converted into XML then reconstructed at client side is a low-level concern that the application layer needs not be aware of.

If your application needs objects with more features than those provided by the server, I would create a local class that takes the object created by the SOAPClient as a constructor argument:

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);


you can use __get and __set.

Write a class like

class Test{    private $container = [];    public function __get($key)    {        return $this->container[$key] ?? null;    }    public function __set($key, $value)    {        $this->container[$key] = $value;    }}

You can perform additional "magic" on the keys where needed