PHP SoapClient未调用映射类__set魔术方法,如何将null转为空数组?
问题:SoapClient classmap映射类的__set()未触发,无法将null转为空数组
根据PHP官方文档,使用SoapClient的classmap选项映射的类,构造函数不会被调用,但__get()和__set()魔术方法会生效。但实际测试中,__set()和构造函数均未触发。
WSDL定义了THpSvcWTableauClesRubriques复杂类型,对应PHP类中试图通过__set()将null转为空数组[],但当SOAP服务返回无元素数组对应的null时,转换失败。
WSDL类型定义
<xsd:simpleType name="THpSvcWCleRubrique"> <xsd:restriction base="xsd:unsignedInt"/> </xsd:simpleType> ... <xsd:complexType name="THpSvcWTableauClesRubriques"> <xsd:sequence> <xsd:element name="THpSvcWCleRubrique" type="hp:THpSvcWCleRubrique" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType>
PHP映射类
class THpSvcWTableauClesRubriques { /** @var int[] */ private $THpSvcWCleRubrique; public function __get($name) { if ($name === "THpSvcWCleRubrique") { return $this->THpSvcWCleRubrique; } } public function __set($name, $value) { if ($name === "THpSvcWCleRubrique") { $this->THpSvcWCleRubrique = $value ?? []; } } }
SoapClient实例化代码
$this->soapClient = new \SoapClient($this->url, [ "login" => $this->username, "password" => $this->password, "soap_version" => SOAP_1_2, "features" => SOAP_SINGLE_ELEMENT_ARRAYS, "classmap" => [ "THpSvcWTableauClesRubriques" => THpSvcWTableauClesRubriques::class ] ]);
当前PHP版本:7.3.7
解决方案
1. __set()未触发的核心原因
SoapClient在处理classmap映射类时,是通过ReflectionClass直接给类的私有属性赋值,完全绕过了__set()魔术方法——__set()仅在直接访问未定义/不可访问属性时才会被调用,这里的属性赋值逻辑被底层跳过了。
2. 可行解决方法
方法一:通过typemap自定义类型解析(推荐)
借助typemap实现自定义的XML到对象的转换逻辑,直接在构造函数中完成null转空数组的处理:
// 修改映射类,添加构造函数处理值 class THpSvcWTableauClesRubriques { /** @var int[] */ public $THpSvcWCleRubrique; public function __construct($data = null) { $this->THpSvcWCleRubrique = $data['THpSvcWCleRubrique'] ?? []; } } // 实例化SoapClient时添加typemap配置 $this->soapClient = new \SoapClient($this->url, [ "login" => $this->username, "password" => $this->password, "soap_version" => SOAP_1_2, "features" => SOAP_SINGLE_ELEMENT_ARRAYS, "classmap" => [ "THpSvcWTableauClesRubriques" => THpSvcWTableauClesRubriques::class ], "typemap" => [ [ "type_name" => "THpSvcWTableauClesRubriques", "type_ns" => "http://你的WSDL命名空间", // 从WSDL的targetNamespace获取 "from_xml" => function($xml) { $dom = new DOMDocument(); $dom->loadXML($xml); $values = []; foreach ($dom->getElementsByTagName('THpSvcWCleRubrique') as $node) { $values[] = (int)$node->nodeValue; } return new THpSvcWTableauClesRubriques(['THpSvcWCleRubrique' => $values]); } ] ] ]);
方法二:在__get()中做兜底处理
既然__set()无法触发,直接在获取属性时返回默认值,确保始终返回数组类型:
class THpSvcWTableauClesRubriques { /** @var int[] */ private $THpSvcWCleRubrique; public function __get($name) { if ($name === "THpSvcWCleRubrique") { return $this->THpSvcWCleRubrique ?? []; } return null; } // 保留__set()用于手动赋值场景 public function __set($name, $value) { if ($name === "THpSvcWCleRubrique") { $this->THpSvcWCleRubrique = $value ?? []; } } }
方法三:实现ArrayAccess接口
让映射类实现ArrayAccess接口,通过数组式访问的逻辑处理null值:
class THpSvcWTableauClesRubriques implements ArrayAccess { /** @var int[] */ private $THpSvcWCleRubrique = []; public function offsetExists($offset) { return $offset === 'THpSvcWCleRubrique'; } public function offsetGet($offset) { return $this->THpSvcWCleRubrique; } public function offsetSet($offset, $value) { if ($offset === 'THpSvcWCleRubrique') { $this->THpSvcWCleRubrique = $value ?? []; } } public function offsetUnset($offset) { if ($offset === 'THpSvcWCleRubrique') { $this->THpSvcWCleRubrique = []; } } }
注:PHP 7.3版本的SoapClient确实存在直接操作映射类属性的行为,升级到PHP 7.4+可能会有不同表现,但以上方法在7.3版本均可正常生效。
内容的提问来源于stack exchange,提问作者kagmole
相关产品推荐
相关产品推荐

