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

时间:2016-01-14 21:24:39

标签: php soap soap-client

我正在使用PHP的SOAPClient class与SOAP API进行通信。其中一个选项允许您使用自己的类重新映射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()并发生以下错误:

  

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

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

P.S。我用一个小的解决方法“解决”了它,但我不认为这是理想的。这是我做的:

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

    return $this->data[$this->name];
}

3 个答案:

答案 0 :(得分:6)

更新:另一种解决方法

您可以将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;
    }
}

优点是ActiveSOAPClient类不需要有关于您自己的逻辑的任何信息,并且您的逻辑不需要更改。

问题

我认为这种行为是故意的或已知的错误(即必须有一些原因或问题),因为在手册页中它是already noted as of seven years ago

我查看了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()函数,例如,在PDO :: fetchObject中(参见文件“ext / pdo / pdo_stmt.c”)。

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

然而,当时没有“官方”解决方案是正确的(你不能获得比源代码更多官方)。

黑客攻击

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

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

答案 1 :(得分:4)

这是已知行为(bug report)。

正如某人在错误报告( miceleparkip at web dot de )中所建议的那样:

  

这不是错误。这很正常。

     

在服务器端创建soap对象。所以构造函数只是在服务器上调用。

我赞同她的立场。

同一个错误报告中的后续评论( 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 :(得分:0)

您可以使用__get和__set。

写一个类似

的类
class Test
{
    private $container = [];

    public function __get($key)
    {
        return $this->container[$key] ?? null;
    }

    public function __set($key, $value)
    {
        $this->container[$key] = $value;
    }
}

您可以根据需要在按键上执行其他“魔术”操作