如何使用PHP Soap消费OTA WSDL?请求异常问题求助
咱们直接针对你遇到的两个核心问题来拆解解决:
问题1:数组生成错误的<item>/<key>/<value>标签
PHP的SoapClient在处理关联数组时,如果没有明确指定元素的命名空间和结构,会默认把关联数组序列成item/key/value这种通用结构,但OTA的WSDL要求严格遵循它的Schema定义,所以得调整数组的构建方式,或者用SoapVar来明确指定每个元素的类型和命名空间。
修复方案:使用正确的数组结构+SoapVar指定命名空间
你需要确保每个嵌套元素都符合OTA Schema的要求,并且给关键元素指定对应的命名空间(也就是http://www.opentravel.org/OTA/2003/05)。试试下面的代码:
$client = new \SoapClient('myservice?wsdl', array('trace' => true)); // 构建RequestorID,用SoapVar指定命名空间和元素名 $requestorId = new \SoapVar([ 'ID' => 'myID', 'MessagePassword' => 'myPASSWORD', 'CompanyName' => 'WEB001' ], SOAP_ENC_OBJECT, null, null, 'RequestorID', 'http://www.opentravel.org/OTA/2003/05'); $source = new \SoapVar([$requestorId], SOAP_ENC_OBJECT, null, null, 'Source', 'http://www.opentravel.org/OTA/2003/05'); $pos = new \SoapVar([$source], SOAP_ENC_OBJECT, null, null, 'POS', 'http://www.opentravel.org/OTA/2003/05'); // 构建VehRentalCore的属性和子元素 $pickUpLocation = new \SoapVar([], SOAP_ENC_OBJECT, null, null, 'PickUpLocation', 'http://www.opentravel.org/OTA/2003/05'); $pickUpLocation->LocationCode = 'CODE'; $returnLocation = new \SoapVar([], SOAP_ENC_OBJECT, null, null, 'ReturnLocation', 'http://www.opentravel.org/OTA/2003/05'); $returnLocation->LocationCode = 'CODE'; $vehRentalCore = new \StdClass(); $vehRentalCore->PickUpDateTime = '2018-05-25T08:30:00+00:00'; $vehRentalCore->ReturnDateTime = '2018-05-26T08:30:00+00:00'; $vehRentalCore->PickUpLocation = $pickUpLocation; $vehRentalCore->ReturnLocation = $returnLocation; $vehAvailRQCore = new \SoapVar([$vehRentalCore], SOAP_ENC_OBJECT, null, null, 'VehAvailRQCore', 'http://www.opentravel.org/OTA/2003/05'); // 组装最终的请求参数 $request = new \SoapVar([ 'POS' => $pos, 'VehAvailRQCore' => $vehAvailRQCore ], SOAP_ENC_OBJECT, null, null, 'OTA_VehAvailRateRQ', 'http://www.opentravel.org/OTA/2003/05'); $result = $client->__soapCall('OTA_VehAvailRate', ['OTA_VehAvailRateRQ' => $request]);
这样做的核心是用SoapVar明确告诉SoapClient每个元素的命名空间和元素名称,避免它自动生成通用的item结构。
问题2:使用XSD_ANYXML时的Cannot find dispatch method错误
这个错误是因为你直接把XML字符串作为参数传递,但没有把它包裹在对应方法的参数键下,而且XSD_ANYXML的使用需要确保命名空间正确匹配。
修复方案:正确包裹XML到方法参数中
调整你的代码,把XML放在对应方法的参数数组里,并且确保命名空间正确:
$client = new \SoapClient('myservice?wsdl', array('trace' => true)); $data = '<OTA_VehAvailRateRQ xmlns="http://www.opentravel.org/OTA/2003/05"> <POS> <Source> <RequestorID ID="myID" MessagePassword="myPASSWORD"> <CompanyName>WEB001</CompanyName> </RequestorID> </Source> </POS> <VehAvailRQCore> <VehRentalCore PickUpDateTime="2018-05-25T08:30:00+00:00" ReturnDateTime="2018-05-26T08:30:00+00:00"> <PickUpLocation LocationCode="APR" /> <ReturnLocation LocationCode="APR" /> </VehRentalCore> </VehAvailRQCore> </OTA_VehAvailRateRQ>'; // 用SoapVar包装XML $soapBody = new \SoapVar($data, \XSD_ANYXML); // 关键:把SoapVar放在对应方法的参数键下,匹配WSDL里的参数定义 $result = $client->__soapCall('OTA_VehAvailRate', ['OTA_VehAvailRateRQ' => $soapBody]);
注意XML里必须加上xmlns="http://www.opentravel.org/OTA/2003/05"命名空间,否则服务端无法识别元素归属。
额外建议:使用WSDL生成实体类
对于复杂的OTA Schema,手动构建数组和SoapVar很容易出错,推荐使用工具(比如wsdl2phpgenerator或者IDE自带的WSDL生成功能)把OTA的WSDL生成对应的PHP实体类,这样你可以直接实例化类来构建请求,结构更清晰,也不容易出错。
比如用wsdl2phpgenerator的命令:
wsdl2phpgenerator --wsdl=myservice?wsdl --output-dir=./ota_classes
生成后直接用类来构建请求:
$request = new OTA_VehAvailRateRQ(); // 给每个属性赋值,比如$request->POS = new POS(); 以此类推 $result = $client->OTA_VehAvailRate($request);
内容的提问来源于stack exchange,提问作者Paul

