图 1. SOAP 扩展的结构
- 安装的前置条件:在官方的使用手册中可以找到,ext/soap 扩展使用了 GNOME XML 库,因此在安装 SOAP 扩展之前需要安装这个库(需要 2.5.4 以上版本)。
- PHP 是否已安装:
- 如果你想在安装 PHP 的同时加入 SOAP 扩展,那再简单不过了。如果是下载 PHP 源代码自己编译安装的情况,则只需要在编译时的 configure 命令中添加选项 --enable-soap 即可。如果是直接使用二进制文件安装(通常只用于 Windows 平台),安装包中则已经包括了这一扩展,不需要额外安装。
- 而如果需要在已经安装好的 PHP 上添加 SOAP 扩展,需要做的工作就要多一些。在编译 SOAP 扩展的源代码之前需要使用 phpize 命令设置编译环境,然后再使用 configure 命令,之后编译并安装 SOAP 扩展。
extension = php_soap.so |
extension = php_soap.dll |
extension_dir = "/usr/local/php/lib/" |
清单 1.php.ini 中 SOAP 扩展的设置
soap] ; Enables or disables WSDL caching feature. soap.wsdl_cache_enabled=1 ; Sets the directory name where SOAP extension will put cache files. soap.wsdl_cache_dir="C:\xampp\tmp" ; (time to live) Sets the number of second while cached file will be used ; instead of original one. soap.wsdl_cache_ttl=86400 |
图 2. 产品资料查询系统架构
- 产品信息数据库,其中存储了产品代码,CPU 信息,内存容量,屏幕尺寸,硬盘容量等产品信息。
- Web 服务端,它发布一个 Web 服务,响应客户端的查询请求,并将查询结果放入 SOAP 应答中返回给客户端。
- 客户机,它接收浏览器发来的查询条件,以此生成 SOAP 请求发送给 Web 服务端,并接收 SOAP 应答,将其发送到浏览器并显示出来。浏览器的输出如图 3 所示。
图 3. 产品信息查询系统页面
图 4. PDT 中 WSDL 文件的图形化表示
图 5. PDT 中输入的数据类型的图形化表示
图 6. PDT 中输出的数据类型的图形化表示
清单 2. WSDL 源代码
<?xml version="1.0"encoding="UTF-8"standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://soapexample.cn/ProductQuery/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ProductQuery"targetNamespace="http://soapexample.cn/ProductQuery/"> <wsdl:types> <xsd:schema targetNamespace="http://soapexample.cn/ProductQuery/"> <xsd:element name="ProductQueryCode"> <xsd:complexType> <xsd:sequence> <xsd:element name="ProductCode"type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="ProductSpec"> <xsd:complexType> <xsd:sequence> <xsd:element name="ProductCode"type="xsd:string"></xsd:element> <xsd:element name="CPU"type="xsd:string"></xsd:element> <xsd:element name="RAM"type="xsd:string"></xsd:element> <xsd:element name="Screen"type="xsd:string"></xsd:element> <xsd:element name="HDD"type="xsd:string"></xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="QuerySpecRequest"> <wsdl:part element="tns:ProductQueryCode"name="QueryCode"/> </wsdl:message> <wsdl:message name="QuerySpecResponse"> <wsdl:part element="tns:ProductSpec"name="Specification"/> </wsdl:message> <wsdl:portType name="ProductQuery"> <wsdl:operation name="QuerySpec"> <wsdl:input message="tns:QuerySpecRequest"/> <wsdl:output message="tns:QuerySpecResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="ProductQuerySOAP"type="tns:ProductQuery"> <soap:binding style="document"transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="QuerySpec"> <soap:operation soapAction="http://soapexample.cn/ProductQuery//> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="LaptopProduct"> <wsdl:port binding="tns:ProductQuerySOAP"name="ProductQuerySOAP"> <soap:address location="http://soapexample.cn/ProductQueryService.php"/> </wsdl:port> </wsdl:service> </wsdl:definitions> |
清单 3. PHP 类定义代码
<?php class SimpleClass { /** * add two parameters, then return result * * @paraminteger $a1 * @paraminteger $a2 * @returninteger */ function add( $a1, $a2 ) { return $a1 + $a2; } } ?> |
图 7. Zend Studio 生成的 WSDL 文件示例
清单 4. 创建 SoapServer 类的实例(WSDL 模式)
$server = new SoapServer( "./QueryService.wsdl" ); |
清单 5. 创建 SoapServer 类的实例(non-WSDL 模式)
$server = new SoapServer( null, array( "uri" => "http://soapexample.cn/ProductQuery", "encoding" => "ISO-8859-1", "soap_version" => SOAP_1_2 ) ); |
清单 6. 产品信息查询函数
function QuerySpec( $param ) { try{ $conn = getDBConnection(); $result = queryFromDB( $conn, $param->ProductCode ); }catch( Exception $e ){ printf( "ErrorMessage: %s", $e->__toString() ); } return array( "ProductCode" => $result['PRODUCTCODE'], "CPU" => $result['CPU'], "RAM" => $result['RAM'], "Screen" => $result['Screen'], "HDD" => $result['HDD'] ) ; } |
- 函数的名称必须是 WSDL 中已定义的一个操作名称,即添加到 SoapServer 中的函数必须与 WSDL 中定义的操作相对应。
- 输入到函数中的参数是一个类的实例,类的结构与 WSDL 中定义的数据类型相对应。通过访问参数中以元素名字为变量名称的成员变量 ($param->ProductCode),就可以取得 SOAP 请求中的相应数据。对于仅以顺序方式 ( 数据类型定义中只有以 <sequence> 标签包含的简单类型序列 ) 定义的数据类型,那么这个类的实例中仅仅包含简单类型的成员。对于有多于一个层次的数据结构,那么类中还将包含描述下层数据结构的类的示例,以此类推,形成一个多层次的结构。在 SOAP 扩展中,无论是客户端还是服务端接收到的 SOAP 数据包,都会被解析成这种数据结构。
- 函数的返回值则不需要包装成类的结构,使用数组即可。对于 WSDL 模式来说,可以直接使用关联数组,关联数组的键值必须与数据类型定义中的名称相对应。对于更多层次的数据结构,需要在这个数组中加入其他的关联数组来实现层次化的表达。而如果想要采用 non-WSDL 模式,则需要把每个元素使用 SoapParam 类包装构造函数的两个参数为元素名称和元素的值,然后放入数组中。
清单 7. 把定义好的函数加入 Web 服务中
$server->addFunction( "QuerySpec" ); $server->handle(); |
清单 9. 创建 SoapClient 类的实例(non-WSDL 模式)
$client = new SoapClient( null, array( "location" => "http://soapexample.cn/ProductQuery", "uri" => "http://soapexample.cn/ProductQueryService.php", "style" => SOAP_DOCUMENT,"use" => SOAP_LITERAL, "soap_version" => SOAP_1_2, "encoding" => "ISO-8859-1" ) ); |
- 我们可以调用哪些操作,这些操作需要的参数是什么?
- 参数的数据类型定义是什么?
清单 11. Web 服务开放的方法和数据类型示例
Array ( [0] => ProductSpec QuerySpec(ProductQueryCode $QueryCode) ) Array ( [0] => struct ProductQueryCode { string ProductCode; } [1] => struct ProductSpec { string ProductCode; string CPU; string RAM; string Screen; string HDD; } ) |
清单 12. 调用 Web 服务开放的操作
$result = $client->__soapCall('QuerySpec', array( array( "ProductCode" => '1175-PXA') ) ); $result = $client->QuerySpec( array( array( "ProductCode" => '1175-PXA') ) ); |
清单 13. 使用 SOAP 应答中的数据
echo "Product Code:" . $client->ProductCode . "<br />"; echo "Product Code:" . $client->CPU . "<br />"; echo "Product Code:" . $client->RAM . "<br />"; echo "Product Code:" . $client->Screen . "<br />"; echo "Product Code:" . $client->HDD . "<br />"; |
清单 14. 加入异常处理部分的客户端代码
try { $client = new SoapClient('./ProductQuery.wsdl'); $result = $client->__soapCall('QuerySpec', array( array( "ProductCod" => '1175-PXA' ) ) ); echo "Product Code:" . $client->ProductCode . "<br />"; echo "Product Code:" . $client->CPU . "<br />"; echo "Product Code:" . $client->RAM . "<br />"; echo "Product Code:" . $client->Screen . "<br />"; echo "Product Code:" . $client->HDD . "<br />"; } catch (SoapFault $e) { echo $e; } |
清单 15. Web 服务端返回的异常信息:缺少属性
SoapFault exception: [Client] SOAP-ERROR: Encoding: object hasn't 'ProductCode' property in C:\xampp\htdocs\soapTest\GetProductInfo.php:17 Stack trace: #0 C:\xampp\htdocs\soapTest\GetProductInfo.php(17): SoapClient->__soapCall('QuerySpec', Array) #1 {main} |
清单 17. Web 服务端返回的异常信息:产品代码无效
SoapFault exception: [SOAP-ENV:Server] Invalid Product Code! in C:\xampp\htdocs\soapTest\GetProductInfo.php:17 Stack trace: #0 C:\xampp\htdocs\soapTest\GetProductInfo.php(17): SoapClient->__soapCall('QuerySpec', Array) #1 {main} |
清单 19. 跟踪 SOAP 请求和应答
echo "Request :<br/>".htmlspecialchars($client->__getLastRequest())."<br/>"; echo "Response :<br/>".htmlspecialchars($client->__getLastResponse())."<br/>"; |
清单 20. 开启 SOAP 跟踪功能
$client = new SoapClient('./ProductQuery.wsdl' , array( 'trace' => 1 ) ); |
清单 21. 错误的 SOAP 请求和应答
Request: <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://soapexample.cn/ProductQuery/"> <SOAP-ENV:Body> <ns1:ProductQueryCode/> </SOAP-ENV:Body> </SOAP-ENV:Envelope> Response: <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://soapexample.cn/ProductQuery/"> <SOAP-ENV:Body> <ns1:ProductSpec> <ProductCode/> <CPU/> <RAM/> <Screen/> <HDD/> </ns1:ProductSpec> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
清单 22. 正确的 SOAP 请求和应答
Request: <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.ibm.com/ProductQuery/"> <SOAP-ENV:Body> <ns1:ProductQueryCode> <ProductCode>1175-PXA</ProductCode> </ns1:ProductQueryCode> </SOAP-ENV:Body> </SOAP-ENV:Envelope> Response: <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.ibm.com/ProductQuery/"> <SOAP-ENV:Body> <ns1:ProductSpec> <ProductCode>1175-PXA</ProductCode> <CPU>Centrino T9400</CPU> <RAM>3GB DDR3</RAM> <Screen>14.1 inch.</Screen> <HDD>300GB 5400rpm</HDD> </ns1:ProductSpec> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
- PHP 对于某些 SOAP 协议中的元素不能正确解析,例如目前 SoapServer 类并不能处理客户端发来的 SOAP 请求中的 Header 部分,这使得一些基于 Header 的特性无法在 PHP 中得到实现,例如权限验证等。
- 由于 PHP 是弱类型语言,而 SOAP 协议中对类型的定义是比较严格的,所以 PHP 无法仅仅根据代码生成可供使用的 WSDL 文件,只能通过 PHP Doc 之类的机制在注释中声明,从而使辅助工具获得参数的类型。
- PHP 的弱类型性质还造成 SOAP 扩展对类型的检查并不严格,如果服务端的实现中如果返回了类型错误的数据(例如应该返回类型为 integer 的数据,实际上却返回了字符串),则并不会产生异常,而只是将返回的数据解释成 WSDL 中定义的类型,但是这种转换通常是不能得到正确结果的。
- PHP 的文档中对于 SOAP 调用的参数构造介绍很少,关联数组构造方法与 WSDL 中的数据定义的映射关系也不是十分清晰易懂。对于数据类型较为复杂的情况,单纯使用数组构造一个具有很多层次的参数结构也是困难且容易出错的。