我正在使用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];
}
答案 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;
}
}
您可以根据需要在按键上执行其他“魔术”操作