PHP使用WSDL格式Soap通信
Posted 兴趣导向工作
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP使用WSDL格式Soap通信相关的知识,希望对你有一定的参考价值。
近期在搞一个项目,甲方只给了一个WSDL文件,让我们实现响应接口。
于是研究了WSDL和SOAP通信相关知识,获益甚深,把我的理解写下来,希望对php新手有帮助。
1.基本概念
soap是简单对象访问协议的缩写,是一种可以基于HTTP协议的访问方式。客户端发送请求,然后调用带参数的服务端函数得到服务端的数据;服务端编写处理函数并响应客户端。
wsdl是网络服务者动态语言的缩写,这里面定义了双方通信时包含的东西。客户端把wsdl文件给服务端,服务端分析wsdl并写出里面的函数,这样两者无论是什么平台、什么语言都可以通信。
我对soap的理解就是类似于POST请求的一种传递参数的方式,只是要求格式比POST更严格。
我认为wsdl也仅仅是一种xml标记文本,跟html文本没有什么区别。
但是两者结合起来就很头疼了。
我是第一次接触soap和wsdl,这两者同时出现,我把他们俩混在一起搞不清。
参看了很多文档才清楚这两者到底是什么。
soap结合wsdl,其实就是把soap通信方式限制的更死,死到你服务端里只能按照wsdl里面规定的函数来写,并严格限制参数和返回结果。
2.我走的弯路
一开始我拿到这个wsdl文档,打开来看密密麻麻的,一共将近200行。
我没搞清是什么东西,连里面的标记符都没有搞懂,就开始在网上搜php与wsdl有关的博客。
搜到一个SoapDiscovery.class.php文件,说可以创建wsdl文件。
我就开始盲目的想创建wsdl文件,并使这个wsdl文件和甲方的一样。
我错误的认为客户端发送wsdl文件给我,我来解析里面是什么,然后我处理后再次封装成wsdl文件发送给客户端。
其实,这个wsdl根本就不用创建,它甚至都不是重点。
这个wsdl文件是双方通信前就规定好的,双方都按照这个wsdl里面的规定访问函数或返回函数等内容。
所以重点不是解析、构建wsdl文件,而是根据这个wsdl写好服务端的函数和返回值。
3.解决问题阶段一
理解wsdl的几个网站:http://staff.ustc.edu.cn/~shizhu/DotNet/WSDLxj.htm 【Web Service描述语言 WSDL 详解】
https://www.ibm.com/developerworks/cn/education/webservices/ws-dewsdl/ws-dewsdl.html 【描述 Web 服务:WSDL】
认真看完上面两个网站,基本就知道wsdl里面讲的是什么了。
我在网上下载了很多关于php与wsdl的实例,
感觉这个例子很好:http://www.cnblogs.com/VipBin/archive/2011/12/07/2279927.html 【在PHP中利用wsdl创建标准webservice】
有了以上的基础。
我开始理解wsdl工作原理了。
客户端client就是发送请求的,服务端service就是接收请求的,再来一个class类处理函数(这里我叫它Robot类),就行了。
上面怎么写客户端与服务端用到了SoapClient,SoapServer两个类。
怎么写Robot类的函数用到了wsdl文件里规定的函数,也就是wsdl知识。
4.解决问题阶段二
通过上面的例子,我可以成功运行。但已加上我Robot类就直接http 500错误,没有任何提示信息,调试及其麻烦。
还好网上有两款很好的软件。
一款是让wsdl文件直观化,直接看到里面定义函数的规则,叫XMLSpy软件。
另一款是SoapUI,不需要写客户端,可以直接加入wsdl文件,访问服务端。
从这两个软件中,我认识到wsdl文件虽然很乱,但只有几个是重点。
一个是wsdl里的soap:address,要定义好访问的服务端的地址。
一个是wsdl里的portType的operation,每一个operation的name就是Robot类中必须写的函数名,
最后一个是wsdl里operation的out,也就是每个函数的返回值的规定。
通过SoapUI,我可以清晰的看到客户端发送和服务端返回的到底是什么内容。
其实里面没有任何wsdl的标记,都是正规的soap标记,所以wsdl根本就没有在通信过程用到,只在通信两端用到。
我们就不需要管wsdl了,只要能实现里面的函数就行了。
5.解决问题阶段三
到了这个阶段,客户端与服务端的函数访问什么的都有了,唯一有问题的就是来回的参数格式问题。
这时不会报http500错误,而是200 OK,但页面不会显示任何东西。因为参数是有问题的。
先说怎么知道参数的格式的。
1 //下面两个看懂,输出就是你要写的类 2 var_dump($client->__getFunctions());//打印暴露的方法 3 print("<br/>"); 4 var_dump($client->__getTypes());//打印对应方法的参数和参数类型 5 print("<br/>");
然后打印出来的参数是下面这样的:
array(7) { [0]=> string(97) "struct standPointInfo { string POINTDES; string STANDPOINT; string STOR_TYPE; string WH_NO; }" [1]=> string(57) "struct webServiceResult { string INFO; string STATUS; }" [2]=> string(79) "struct taskResultInfo { string EXEC_STATE; string PICTURE; string TASK_NO; }" [3]=> string(265) "struct inventoryResultInfo { string CCDD; string CCLX; string CKH; string CW; string GC; string KCLX; double KCSL; string PC; double PDCY; string PDSJ; double PDSL; string RWH; string TSKCBH; string TSKCLX; string WLBH; string WZSFM; string ZHXM; }" [4]=> string(52) "struct standPointInfoArray { standPointInfo item; }" [5]=> string(62) "struct inventoryResultInfoArray { inventoryResultInfo item; }" [6]=> string(37) "struct Exception { string message; }" }
看到上面的参数格式,然后找到合适的php数据类型就行了。
里面有struct类型,但php没有,不用怕,直接当array来用就行了,比如构建参数standPointInfoArray :
1 $standPointInfoArray = array( 2 array( 3 \'POINTDES\' => \'hujun\', 4 \'STANDPOINT\' => \'standPoint\', 5 \'STOR_TYPE\' => \'storType\', 6 \'WH_NO\' => \'whNo1\', 7 ), 8 array( 9 \'POINTDES\' => \'hujun\', 10 \'STANDPOINT\' => \'standPoint\', 11 \'STOR_TYPE\' => \'storType\', 12 \'WH_NO\' => \'whNo2\', 13 ), 14 );
这个参数在wsdl里定义为二维数组,所以我用php也构建了一个二维数组,键名不能改要与wsdl名字一模一样而其必须加上,不然没法传参数。
然后把这个参数扔到客户端的访问函数参数里就行了,不用转什么stdClass和json之类的,多余,直接用array就行了。
客户端参数搞定了,下面看看服务端形参怎么搞。
下面是我得到的wsdl规定的函数:
array(3) { [0]=> string(76) "webServiceResult receiveStandPointInfo(standPointInfoArray $StandPointInfos)" [1]=> string(82) "webServiceResult receiveInventoryResult(inventoryResultInfoArray $InventoryResult)" [2]=> string(66) "webServiceResult receiveTaskResult(taskResultInfo $TaskResultInfo)" }
以receiveStandPointInfo函数为例。
在Robot类里必须写这个函数,而且函数名必须一模一样,不能有一点改变。
然后看形参是standPointInfoArray $StandPointInfos,这个standPointInfoArray类型在PHP很难自定义,但PHP的好处就是可以不用写类型。
所以直接在形成里写变量名就行了,把前面的类型去掉。
函数名如下:
public function receiveStandPointInfo($standPointInfoArray)
这样形参就好了,$standPointInfoArray可以被客户端赋值。
这个时候是最折磨人的,因为你没法知道这个$standPointInfoArray是什么东西。服务端不给打印信息。
只能先猜测为数组,然后用数组的方式调用,直接报错。
Fatal error: Cannot use object of type stdClass as array
上面讲的很清楚,这是个stdClass的类型。
要是一维数组还好访问,直接用$standPointInfo->POINTDES就可以访问了。
但是刚才客户端传递的是二维数组呀,这怎么访问呢?
所以必须知道$standPointInfoArray到底是什么东西。
在网上找到了stdClass的打印信息示例如下:
stdClass Object ( [item] => Array ( [0] => stdClass Object ( [date] => 2008-07-17T01:23:06Z [directory] => 1 [downloadCount] => 0 ) [1] => stdClass Object ( [date] => 2009-11-03T23:03:15Z [directory] => 2 [downloadCount] => 5 ) ) )
这下明朗了,原理这个键名叫item,是Soap协议传输多个复杂类型规定的。
所以可以使用$standPointInfoArray->item[0]->data来访问日期等等。
到这里,参数传递就完成了。
返回的参数构造与发送的参数一样,这里就不写了。
6.源代码
客户端的client.php
1 <?php 2 $client = new SoapClient("robot_origin.wsdl", array(\'trace\'=>true)); 3 try { 4 $parms = array( 5 \'EXEC_STATE\' => "hujun", 6 \'PICTURE\' => \'pictures\', 7 \'TASK_NO\' => \'task_nos\', 8 ); 9 $result = $client->receiveTaskResult($parms); 10 var_dump($result); 11 print("<br/>=======================<br/>"); 12 13 $standPointInfo = array( 14 \'POINTDES\' => \'hujun\', 15 \'STANDPOINT\' => \'standPoint\', 16 \'STOR_TYPE\' => \'storType\', 17 \'WH_NO\' => \'whNo\', 18 ); 19 $standPointInfoArray = array( 20 array( 21 \'POINTDES\' => \'hujun\', 22 \'STANDPOINT\' => \'standPoint\', 23 \'STOR_TYPE\' => \'storType\', 24 \'WH_NO\' => \'whNo1\', 25 ), 26 array( 27 \'POINTDES\' => \'hujun\', 28 \'STANDPOINT\' => \'standPoint\', 29 \'STOR_TYPE\' => \'storType\', 30 \'WH_NO\' => \'whNo2\', 31 ), 32 ); 33 $result = $client->receiveStandPointInfo($standPointInfoArray); 34 var_dump($result); 35 print("<br/>=======================<br/>"); 36 37 //下面两个看懂,输出就是你要写的类 38 var_dump($client->__getFunctions());//打印暴露的方法 39 print("<br/>"); 40 var_dump($client->__getTypes());//打印对应方法的参数和参数类型 41 print("<br/>"); 42 echo("\\nDumping request headers:\\n"); 43 var_dump($client->__getLastRequestHeaders()); 44 echo "<br>"; 45 echo("\\nDumping request:\\n"); 46 var_dump($client->__getLastRequest()); 47 echo "<br>"; 48 echo("\\nDumping response headers:\\n"); 49 var_dump($client->__getLastResponseHeaders()); 50 echo "<br>"; 51 echo("\\nDumping response:\\n"); 52 var_dump($client->__getLastResponse()); 53 } 54 catch (SoapFault $f){ 55 echo "Error Message: {$f->getMessage()}"; 56 } 57 ?>
服务端的service.php
1 <?php 2 include("robot.class.php"); 3 ini_set(\'soap.wsdl_cache_enabled\',\'0\'); //关闭WSDL缓存 4 $objSoapServer = new SoapServer("robot_origin.wsdl");//person.wsdl是刚创建的wsdl文件 5 6 $objSoapServer->setClass("Robot");//注册person类的所有方法 7 $objSoapServer->handle();//处理请求
服务端处理类Robot.class.php
1 <?php 2 class Robot{ 3 public function receiveInventoryResult($inventoryResultInfoArray){ 4 $webServiceResult = array( 5 \'INFO\' => \'info\', 6 \'STATUS\' => \'status\', 7 ); 8 9 $webServiceResult[\'INFO\'] = $inventoryResultInfoArray->item[0]->KCSL; 10 11 return $webServiceResult; 12 } 13 public function receiveStandPointInfo($standPointInfoArray){ 14 $webServiceResult = array( 15 \'INFO\' => \'info\', 16 \'STATUS\' => \'status\', 17 ); 18 if($standPointInfoArray->item[0]->POINTDES){ 19 $webServiceResult[\'INFO\'] = \'we can change it\'; 20 } 21 return $webServiceResult; 22 } 23 24 public function receiveTaskResult($taskResultInfo){ 25 $EXEC_STATE = $taskResultInfo->EXEC_STATE; 26 $PICTURE = $taskResultInfo->PICTURE; 27 $TASK_NO = $taskResultInfo->TASK_NO; 28 29 $webServiceResult = array( 30 \'INFO\' => \'info\', 31 \'STATUS\' => $PICTURE, 32 ); 33 $webServiceResult[\'INFO\'] = $EXEC_STATE; 34 return $webServiceResult; 35 } 36 37 public function Exception($exception){ 38 $this->exception = $exception; 39 return $this->exception; 40 } 41 }
一直是电脑在用的wsdl文件
<?xml version=\'1.0\' encoding=\'utf-8\'?><wsdl:definitions name="RobotWebServiceImplService" targetNamespace="http://robot.server.webService/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://robot.server.webService/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:types> <!------schema才是关键,注意类型的寻址,下面是xs开头的,意思是这些类型到xmlns:xs=里面找------------> <xs:schema attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://robot.server.webService/" xmlns:tns="http://robot.server.webService/" xmlns:xs="http://www.w3.org/2001/XMLSchema"> //------------------------------------------------------------------------------------------------------------ <!--设计一个复杂的数据类型standPointInfo--> <xs:complexType name="standPointInfo"> <!--sequence是顺序限制--> <!--<minOccurs> 指示器可规定某个元素能够出现的最小次数--> <xs:sequence> <xs:element minOccurs="0" name="POINTDES" type="xs:string"></xs:element> <xs:element minOccurs="0" name="STANDPOINT" type="xs:string"></xs:element> <xs:element minOccurs="0" name="STOR_TYPE" type="xs:string"></xs:element> <xs:element minOccurs="0" name="WH_NO" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //-------------------------------1.1.1 output <xs:complexType name="webServiceResult"> <xs:sequence> <xs:element minOccurs="0" name="INFO" type="xs:string"></xs:element> <xs:element minOccurs="0" name="STATUS" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //--------------------------------不需要 <xs:complexType name="taskResultInfo"> <xs:sequence> <xs:element minOccurs="0" name="EXEC_STATE" type="xs:string"></xs:element> <xs:element minOccurs="0" name="PICTURE" type="xs:string"></xs:element> <xs:element minOccurs="0" name="TASK_NO" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //----------------------------------1.2.1 input <xs:complexType name="inventoryResultInfo"> <xs:sequence> <xs:element minOccurs="0" name="CCDD" type="xs:string"></xs:element> <xs:element minOccurs="0" name="CCLX" type="xs:string"></xs:element> <xs:element minOccurs="0" name="CKH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="CW" type="xs:string"></xs:element> <xs:element minOccurs="0" name="GC" type="xs:string"></xs:element> <xs:element minOccurs="0" name="KCLX" type="xs:string"></xs:element> <xs:element minOccurs="0" name="KCSL" type="xs:double"></xs:element> <xs:element minOccurs="0" name="PC" type="xs:string"></xs:element> <xs:element minOccurs="0" name="PDCY" type="xs:double"></xs:element> <xs:element minOccurs="0" name="PDSJ" type="xs:string"></xs:element> <xs:element minOccurs="0" name="PDSL" type="xs:double"></xs:element> <xs:element minOccurs="0" name="RWH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="TSKCBH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="TSKCLX" type="xs:string"></xs:element> <xs:element minOccurs="0" name="WLBH" type="xs:string"></xs:element> <xs:element minOccurs="0" name="WZSFM" type="xs:string"></xs:element> <xs:element minOccurs="0" name="ZHXM" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> //------------------------------------arrary <xs:complexType final="#all" name="standPointInfoArray"> <xs:sequence> <xs:element maxOccurs="unbounded" minOccurs="0" name="item" nillable="true" type="tns:standPointInfo"></xs:element> </xs:sequence> </xs:complexType> //-------------------------------------arrary <xs:complexType final="#all" name="inventoryResultInfoArray"> <xs:sequence> <xs:element maxOccurs="unbounded" minOccurs="0" name="item" nillable="true" type="tns:inventoryResultInfo"></xs:element> </xs:sequence> </xs:complexType> //--------------------------------------异常 <xs:element name="Exception" type="tns:Exception"></xs:element> <xs:complexType name="Exception"> <xs:sequence> <xs:element minOccurs="0" name="message" type="xs:string"></xs:element> </xs:sequence> </xs:complexType> </xs:schema> </wsdl:types> //---------------------------------------message是operation的参数 <!--<message> 元素将数据(数据类型在 <types> 元素中进行定义)分组成一个用于逻辑网络传输的特征符,并将数据绑定到一个名称上--> <!--上面的name用于operation引用,下面的name是声明的实例--> <wsdl:message name="receiveInventoryResult"> <wsdl:part name="InventoryResult" type="tns:inventoryResultInfoArray"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveTaskResult"> <wsdl:part name="TaskResultInfo" type="tns:taskResultInfo"> </wsdl:part> </wsdl:message> <wsdl:message name="receiveStandPointInfoResponse"> 使用php和wsdl查询soap以获得结果