不从SOAP响应对象调用构造函数

Roc*_*mat 14 php soap soap-client

我正在使用PHP的SOAPClient类与SOAP API进行通信.其中一个选项允许您使用自己的类重新映射WSDL文件中指定的类型:

类映射选项可用于一些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
]);
Run Code Online (Sandbox Code Playgroud)

这是有效的,当我调用时$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];
    }
}
Run Code Online (Sandbox Code Playgroud)

发生了什么事我正在做$api->method('param')->getData()并收到以下错误:

注意:未定义的属性:Result :: $ data

如何在获取SOAP响应时调用我需要的构造函数?我尝试过使用__wakeup(),但这似乎也没有用.

PS我用一个小的解决方法"解决"了它,但我不认为这是理想的.这是我做的:

function getData(){
    if($this->data === NULL){
        parent::__construct();
    }

    return $this->data[$this->name];
}
Run Code Online (Sandbox Code Playgroud)

LSe*_*rni 7

更新:另一种解决方法

您可以将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...
            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;
    }
}
Run Code Online (Sandbox Code Playgroud)

优点是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;
                }
        }
Run Code Online (Sandbox Code Playgroud)

...如果定义了类映射,并且已知,则调用zend_fetch_class().

我认为应该在从节点获取的值之后调用某种ctor()函数,例如在PDO :: fetchObject中完成(参见文件"ext/pdo/pdo_stmt.c").

目前,似乎没有这样做.可能是因为XML树中对象的评估顺序,这使得为构造函数提供适当的参数很棘手,但此时我只是在猜测.

但是,你当时没有"官方"的解决方案是正确的(你不能获得比源代码更多的官方解决方案).

黑客攻击

我试图在PHP源代码中添加一个构造函数运行器,只是为了它的地狱.不幸的是,我似乎需要几个不在我需要的范围内的变量,所以我必须改变几个结构来传递构造函数信息等等,这些结构在SOAP代码中普遍使用.

除了最简单的情况下,对象具有无参数构造函数且没有析构函数,对代码的必要修改对我来说根本不是轻微的.


Ran*_*eed 4

这是已知行为(错误报告)。

正如有人在错误报告中建议的那样(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);
Run Code Online (Sandbox Code Playgroud)