使用 PHP 开发基于 Web 服务的应用程序

PHP SOAP 扩展php

SOAP 的全称为简单对象访问协议 (Simple Object Access Protocol)。它是一种基于 XML 的,可扩展的通讯协议。SOAP 提供了一种标准,使得运行在不一样平台上并使用不一样的编程语言编写的应用程序能够互相进行通讯。SOAP 的可扩展性和平台无关性使得它被普遍用做 Web 服务的通讯协议。html

因为 Java 语言提供了对 SOAP 的良好支持,一般基于 Web 服务的应用程序使用 Java 语言编写。对于广大的 PHP 程序员来讲,可能会有一点小小的不满 – PHP 的较早版本根本没有对 SOAP 的直接支持,只能经过 PEAR(the PHP Extension and Application Repository) 中的 SOAP 库或者第三方产品 NuSOAP 来开发 Web 服务。不过最近的版本已经改变了这一情况。自 PHP 5 开始新增了内置的 SOAP 扩展 (ext/soap),今后咱们不须要下载额外的扩展库或是代码包来开发基于 SOAP 的应用程序了。接下来让咱们来看看 SOAP 扩展中都有哪些内容。程序员

回页首web

PHP 5 中的 SOAP 扩展 (ext/soap)数据库

PHP 5 最先发布的版本 5.0.0 中就已经提供了 SOAP 扩展,不过当时的 PHP 手册中声明这个扩展是试验性 (experimental) 的。实际上当时的版本已经实现了比较完善的功能,也没有必要为此而担忧。目前这个扩展还在不断地完善,早期版本中的大部分 bug 都已经获得了修正,目前最新的版本 (5.3.0) 中已经提供了比较完整的对 SOAP 的支持,并且咱们有理由相信,之后的版本还会更好。编程

SOAP 扩展库结构数组

ext/soap 中包括六个预约义的类,经过这些类,咱们能够建立 Web 服务端 (SoapServer 类 ),客户端 (SoapClient 类 ),处理 SOAP 请求和应答 (SoapHeader, SoapParam, SoapVar 类 ),诊断错误 (SoapFault 类 )。这些类之间的联系如图 1 所示:浏览器


图 1. SOAP 扩展的结构
图 1. SOAP 扩展的结构 

SOAP 服务类 SoapServer缓存

SoapServer 类用来开发 Web 服务端应用程序。这个类中包含建立,设置和操纵 Web 服务的函数。有两种方式能够向 Web 服务中添加操做 (Operation)。一种方式是直接添加已定义的函数,另外一种方式是添加已定义好的类,从而将该类的公有成员函数添加到 Web 服务中。服务器

另外一个须要说明的特性是,PHP 支持两种 Web 服务的模式:WSDL 模式和 non-WSDL 模式,为了便于理解,咱们首先从 Web 服务的两种实现模式开始提及。

PHP 中 Web 服务的两种模式:WSDL 模式和 non-WSDL 模式

对于 Web 服务来讲,主要有两种实现模式 – 契约先行 (Contract First) 模式和代码先行 (Code Fist) 模式。

契约先行模式的实现中,首要工做是定义针对这个 Web 服务的借口的 WSDL(Web Services Description Language,Web 服务描述语言 ) 文件。WSDL 文件中描述了 Web 服务的位置,可提供的操做集,以及其余一些属性。WSDL 文件也就是 Web 服务的“契约”。“契约”订立以后,再据此进行服务器端和客户端的应用程序开发。这种模式对应上节所说的 WSDL 模式。咱们后文中介绍的例子就是使用这一模式实现的。

与契约先行模式不一样,代码先行模式中,第一步工做是实现 Web 服务端,而后根据服务端的实现,用某种方法(自动生成或手工编写)生成 WSDL 文件。可是因为 PHP 自己并无提供从 Web 服务实现代码中生成 WSDL 文件的方法,所以就要以 non-WSDL 模式链接服务端,即不经过 WSDL 文件建立 SoapServer 和 SoapClient 示例,而是直接向构造函数传递必要的参数。固然,代码先行模式也有其余的解决方法,一些集成的 PHP 开发工具(如 Zend Studio)就提供了根据 Web 服务实现代码生成 WSDL 文件的功能。

SOAP 客户端类 SoapClient

SOAP 客户端类 SoapClient 用于开发 Web 服务的客户端程序。可用的成员函数主要有建立客户端实例,调用可用操做,查询可用操做和数据类型等。除此以外还包括了可用于程序调试的函数 – 获取上次请求和应答的 SOAP 数据。

SOAP 参数类 SoapHeader, SoapParam, SoapVar

SoapParam 和 SoapVar 主要用来封装用于放入 SOAP 请求中的数据,他们主要在 non-WSDL 模式下使用。事实上,在 WSDL 模式下,SOAP 请求的参数能够经过数组方式包装,SOAP 扩展会根据 WSDL 文件将这个数组转化成为 SOAP 请求中的数据部分,因此并不须要这两个类。而在 non-WSDL 模式下,因为没有提供 WSDL 文件,因此必须经过这两个类进行包装。

SoapHeader 类用来构造 SOAP 头,SOAP 头能够对 SOAP 的能力进行必要的扩展。SOAP 头的一个主要做用就是用于简单的身份认证,后面会有例子说明这一点。

SOAP 异常类 SoapFault

这个类从 PHP 的 Exception 类继承而来,能够用来实现 SOAP 中的异常处理机制,由 SOAP 服务端抛出。SOAP 客户端能够接收该类的实例,用于获取有用的调试信息。

回页首

安装 SOAP 扩展

为了使用 SOAP 扩展,咱们就须要在 Web 服务器上安装它。这里有几个因素须要考虑。

  • 安装的前置条件:在官方的使用手册中能够找到,ext/soap 扩展使用了 GNOME XML 库,所以在安装 SOAP 扩展以前须要安装这个库(须要 2.5.4 以上版本)。
  • PHP 是否已安装:
    • 若是你想在安装 PHP 的同时加入 SOAP 扩展,那再简单不过了。若是是下载 PHP 源代码本身编译安装的状况,则只须要在编译时的 configure 命令中添加选项 --enable-soap 便可。若是是直接使用二进制文件安装(一般只用于 Windows 平台),安装包中则已经包括了这一扩展,不须要额外安装。
    • 而若是须要在已经安装好的 PHP 上添加 SOAP 扩展,须要作的工做就要多一些。在编译 SOAP 扩展的源代码以前须要使用 phpize 命令设置编译环境,而后再使用 configure 命令,以后编译并安装 SOAP 扩展。

编译安装 SOAP 扩展以后,咱们还须要修改 PHP 的配置文件,以便 SOAP 扩展能够正确的被 PHP 加载。对于 Linux 平台来讲,须要在 php.ini 中加入以下代码:

extension = php_soap.so

而对于 Windows 平台,须要加入的代码为:

extension = php_soap.dll

除此以外,可能还须要设置扩展库的位置,这一信息在 php.ini 的 extension_dir 域中保存,例如:

extension_dir = "/usr/local/php/lib/"

上面的工做完成以后,还须要注意的是 SOAP 扩展在配置文件中有独立的代码段:


清单 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

其中的三项设置主要是用来指定 PHP 处理 WSDL 文件时使用缓存的行为。这三项设置分别说明是否启用缓存、缓存文件的路径、缓存的生存时间。启用缓存会加快 PHP 处理 WSDL 文件的速度,但最好在调试代码时关闭缓存,以免一些因缓存行为而出现的问题。

回页首

一个简单的例子:产品资料查询

设想这样一个场景:A 公司是笔记本电脑的生产商,B 公司是 A 公司的经销商。B 公司须要向他们的客户提供一个产品信息查询的网站,用户输入产品编号就能够查询到该产品的详细信息,包括 CPU,内存,屏幕尺寸,硬盘等。因为常常有新产品面世,因此 A 公司的产品信息数据库会频繁地更新,对此比较好的解决方案是 A 公司提供一个产品信息查询的 Web 服务,而 B 公司开发客户端来调用这个 Web 服务提供的操做。整个系统的架构以下图所示:


图 2. 产品资料查询系统架构
图 2. 产品资料查询系统架构 

主要的组成部分有:

  • 产品信息数据库,其中存储了产品代码,CPU 信息,内存容量,屏幕尺寸,硬盘容量等产品信息。
  • Web 服务端,它发布一个 Web 服务,响应客户端的查询请求,并将查询结果放入 SOAP 应答中返回给客户端。
  • 客户机,它接收浏览器发来的查询条件,以今生成 SOAP 请求发送给 Web 服务端,并接收 SOAP 应答,将其发送到浏览器并显示出来。浏览器的输出如图 3 所示。

本质上 Web 服务端和客户机都是一个相对独立的 Web 应用程序,它们之间只是经过 SOAP 消息进行通讯。在不改变通讯“契约”的状况下,Web 服务端和客户端内部实现的改变均不影响这个系统的功能。因此对于“契约”- 即 WSDL 文件的定义就是很是重要的一步。


图 3. 产品信息查询系统页面
图 3. 产品信息查询系统页面 

回页首

WSDL 文件的编写

前面提到过,PHP 自己并无提供能够自动生成 WSDL 文件的方法,所以就须要咱们本身编写 WSDL 文件。WSDL 的结构虽然比较清楚,但彻底依靠文本编辑器建立一个 WSDL 文件依然是个艰难的任务。这是由于 WSDL 中的元素比较多,每一个元素还有若干属性,要彻底掌握这些比较困难。另外一方面,若是没有开发环境的辅助,咱们在编写 WSDL 文件中的错误就很难被发现,存在任何一个微小的错误(例如标签名 message 误写成了 massage),咱们的应用程序也没法正常工做。所以在编写 WSDL 文件时使用适当的开发工具是很必要的。下面咱们介绍两种借助开发工具生成 WSDL 文件的方法,一种适用于契约先行模式,另外一种适用于代码先行模式。

使用 PDT(PHP Development Tool) 编写 WSDL 文件

PDT(PHP Development Tool) 是一个基于 Eclipse 的集成开发环境,它提供了对于 PHP 开发中各类需求的良好支持。咱们能够在菜单中选择 New->Other...,而后在弹出的窗口中选择 Web Service 下的 WSDL File,而后输入文件名,建立 WSDL 文件,PDT 会生成一个默认的 WSDL“框架”,并以图形化的方法显示出来,对应本文的例子,WSDL 文件的图形化表示以下图:


图 4. PDT 中 WSDL 文件的图形化表示
图 4. PDT 中 WSDL 文件的图形化表示 

咱们能够看到,这个图形化的表示方法包含了 WSDL 的所有要素:端口,消息,绑定 (Bindings),数据类型和服务。对于除了数据类型以外的部分,咱们只须要点击相应部分做出修改便可。对于数据类型部分的修改,则须要点击最右侧的灰色箭头,打开数据类型的视图,对应本文需求的数据类型视图以下:


图 5. PDT 中输入的数据类型的图形化表示
图 5. PDT 中输入的数据类型的图形化表示 

图 6. PDT 中输出的数据类型的图形化表示
图 6. PDT 中输出的数据类型的图形化表示 

以后咱们能够修改数据类型的名称,添加元素,编辑复杂数据类型,修改元素的类型和名称等。全部上述的修改都会被 PDT 自动转换成对应的 WSDL 语句。

以上三个视图构成了 WSDL 的完整描述,点击界面下方的 Source 标签,就能够看到 WSDL 文件的源代码:


清单 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>

至此,咱们就完成了对 WSDL 文件的编写。这种方式适用于契约先行模式。根据已经获得的 WSDL 文件,咱们就能够继续开发服务端和客户端的 Web 应用程序。

使用 Zend Studio 生成 WSDL 文件

第二种方法适用于代码先行模式。Zend Studio 是另外一个优秀的 PHP 集成开发环境,它相对于 PDT 的优点之一就是提供了自动生成 WSDL 文件的功能。固然,要使用自动生成功能须要知足一些条件。第一个条件就是必须使用类定义来包含须要发布的 Web 服务中的操做(以 public 函数的方式定义);另外,须要以 PHP Doc 方式在注释中声明所提供操做 (Operation) 的参数和返回值。PHP Doc 与 Java Doc 的语法相似,如下是一个简单的例子:


清单 3. PHP 类定义代码
<?php 
 class SimpleClass 
 { 
 /** 
 * add two parameters, then return result 
 * 
 * @paraminteger $a1 
 * @paraminteger $a2 
 * @returninteger 
 */ 
 function add( $a1, $a2 ) 
 { 
 return $a1 + $a2; 
 } 
 } 
 ?>

生成对应的 WSDL 文件的操做十分简单,在菜单中选取 File->Export...,接着在弹出的窗口中选择 PHP 下的 WSDL File,接着指定要生成 WSDL 文件的类,以及输出的文件名,点击 Finish,Zend Studio 就会根据上面的类定义和对应的 PHP Doc 自动生成的 WSDL 文件。与上例对应的 WSDL 文件的视图以下:


图 7. Zend Studio 生成的 WSDL 文件示例
图 7. Zend Studio 生成的 WSDL 文件示例 

能够看到,惟一没有自动生成的是 Web 服务的位置,只要将这一项填写好,WSDL 的建立工做就完成了。

回页首

开发 Web 服务端

接下来将介绍的是开发 Web 服务端程序的方法。前面已经提到过,PHP 实现 Web 服务的方式有两种:WSDL 模式和 non-WSDL 模式。两种模式下对于 Web 服务端和客户端的实现都有一些不一样,本节主要介绍 Web 服务端的实现方法。

SOAP 扩展中用于实现 Web 服务端的类是 SoapServer,每一个 SoapServer 类的实例对应一个 Web 服务。假设咱们已经使用以前介绍的方法使用 PDT 编写了一个 WSDL 文件,文件名为 QueryService.wsdl。这样,咱们就应该使用 WSDL 模式来建立咱们的 Web 服务。应用 WSDL 模式建立 SoapServer 类实例的语句为:


清单 4. 建立 SoapServer 类的实例(WSDL 模式)
$server = new SoapServer( "./QueryService.wsdl" );

因为 WSDL 中已经包含足够描述 Web 服务的信息,因此咱们只需向 SoapServer 的构造函数提供 WSDL 文件的路径就能够了。而对于 non-WSDL 模式来讲,建立 SoapServer 的对象就须要咱们提供更多的信息,例如服务的位置,编码方案,SOAP 协议的版本等:


清单 5. 建立 SoapServer 类的实例(non-WSDL 模式)
$server = new SoapServer( null, array( "uri" => "http://soapexample.cn/ProductQuery", 
"encoding" => "ISO-8859-1", 
"soap_version" => SOAP_1_2 ) );

下面咱们须要定义一个 Web 服务的操做 (Operation)。一般的方法是定义一个函数 (Function),函数的功能就是对应的 Web 服务中操做的功能。例以下面的函数,它实现了查询产品信息的功能。


清单 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 类包装构造函数的两个参数为元素名称和元素的值,而后放入数组中。

最后的工做是把已经定义好的函数加入 Web 服务中,成为可调用的操做:


清单 7. 把定义好的函数加入 Web 服务中
$server->addFunction( "QuerySpec" ); 
 $server->handle();

最后一个语句调用 SoapServer::handle() 是必要的,做用是通知 SoapServer 开始处理 Web 服务的请求,若是缺乏了这一语句,Web 服务就不会被启动。至此,咱们就完成了对 Web 服务端的开发。

回页首

开发客户端

客户端的实现方法一样分为 WSDL 模式和 non-WSDL 模式两种。首先咱们须要建立 SoapClient 对象,对于本文中的例子,WSDL 模式的代码以下:


清单 8. 建立 SoapClient 类的实例(WSDL 模式)
$client = new SoapClient('./ProductQuery.wsdl');

与 soapServer 相同,只须要向构造函数提供 WSDL 文件的路径便可;non-WSDL 的例子则是这样:


清单 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" ) );

因为没有 WSDL 文件可供使用,咱们至少须要提供服务的存在位置,其余的域是可选的,例如名字空间,编码方案,SOAP 协议版本等。

接下来咱们就能够调用已经发布的操做了。可是在这样作以前,咱们还须要了解两点:

  • 咱们能够调用哪些操做,这些操做须要的参数是什么?
  • 参数的数据类型定义是什么?

要搞清楚这两点,咱们固然能够去直接阅读 WSDL 文件,但因为 WSDL 文件可能会很复杂,因此有的时候要弄清楚这些问题可能会花费很多的时间;另外,有些时候咱们尚未办法获得 WSDL 文件。固然还存在其余方法,SoapClient 类中提供了两个颇有用的成员函数可让咱们轻松得到 Web 服务中提供的操做,以及相关的数据结构定义:


清单 10. 查看 Web 服务开放的方法和数据类型
print_r( $client->__getFunctions() ); 
 print_r( $client->__getTypes() );

经过这两行代码,咱们能够看到浏览器显示的结果:


清单 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; 
  } 
 )

因而咱们能够知道,咱们能够调用 Web 服务中的 QuerySpec 操做,而且得知了这个操做的输入和输出数据的定义。这个时候咱们就能够着手编写调用 QuerySpec 的代码了。下面两个语句均可以完成调用的功能,它们的做用是等效的:


清单 12. 调用 Web 服务开放的操做
$result = $client->__soapCall('QuerySpec', array( array( "ProductCode" => '1175-PXA') ) ); 
 $result = $client->QuerySpec( array( array( "ProductCode" => '1175-PXA') ) );

能够直接使用 Web 服务中的操做名称做为函数进行调用,就像真的在调用本地定义的函数同样,这种方法比较直观;也能够把操做名称做为参数传给 SoapClient::__soapCall(),效果是同样的。

须要注意的依然是参数的结构。和服务端同样,输入的参数依然须要组织成数组的形式,可是有一点点不一样,已定义好的数组又被放入了最外层的数组中。看起来最外面的一层包装彷佛有些多余,可是若是去掉,程序是不会获得正确结果的。

最后咱们须要使用 Web 服务端返回的结果。与前面提到的相似,服务端返回的数据也是以对象嵌套的方式组织的,因此咱们只须要用成员引用操做符 (->) 便可得到相应域的值:


清单 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 />";

稍加修改,咱们就能够获得以前给出的在浏览器中的显示效果了。

到这里咱们的工做彷佛已经结束了。可是实际的开发过程是不可能如此顺利的,若是咱们的代码没有获得正确的结果怎么办?因此,咱们须要了解一些使用 PHP 开发 SOAP 应用程序时的用到的调试知识。

回页首

调试咱们的程序 —— 捕获异常

考虑一个咱们编写代码时极可能出现的错误:在为调用的操做输入参数时,参数中某个元素的名字错误或是没有提供。例如咱们把查询须要的产品代码的名字错误地写成了"ProductCod",这时运行客户端代码,是不可能获得正确的结果的。咱们怎么才能发现这个错误呢?

PHP 5 中新增了不少编程语言中都提供的异常处理机制 try...catch,咱们能够把客户端的实现代码包含在这个结构里 ( 须要注意的是,PHP 5 中不支持 finally 子句 ):


清单 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}

在这个例子中,异常是由 SoapClient 对象直接抛出的,它检查输入的参数,若是发现某个 WSDL 文件中定义的项没有被提供,便抛出这个异常,告诉咱们"ProductCode"属性没有被提供。而咱们经过有针对性的检查代码,就能够比较容易的发现错误所在。

服务端一样也可能抛出异常,这些异常一般是客户端检查时没法发现的,例如某些逻辑错误,若是咱们输入了一个不合法的产品代码,就可能捕获到服务端抛出的“不合法的产品代码”异常。为了实现这一功能,咱们须要在服务端的代码中加入下面的一段语句:


清单 16. Web 服务端抛出产品代码无效的异常
if( !$result ){ 
 throw new SoapFault("Server", "Invalid Product Code!"); 
 }

这段语句在未获得查询结果的状况下(这时认为缘由是提供了无效的产品代码),抛出了一个 SoapFault 异常,用于建立 SoapFault 对象的参数包括错误代码,以及必要的错误信息。须要注意的是,错误代码只能使用 SOAP 标准中已定义的值,使用其余的值不会返回正确的信息。具体可以使用的值能够查看 W3C 的 SOAP 文档。这样,在客户端提供无效的产品代码时,会捕获到的异常信息:


清单 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}

因而咱们就知道提供的产品代码是无效的了。

回页首

调试咱们的程序 —— 跟踪 SOAP 数据

在咱们调试 SOAP 程序时,仅仅依赖异常处理机制是不够的。咱们在调用 Web 服务提供的操做时,若是参数的结构错误,客户端和服务端颇有可能都不抛出异常,例如前面在实现客户端应用程序时提到的问题,咱们把参数最外面的数组去掉:


清单 18. 错误的调用方法
$result = $client->__soapCall('QuerySpec', array( "ProductCode" => '1175-PXA') );

这时咱们看不到任何输出,说明根本没有捕获到异常,但很显然程序没有正常工做。咱们如何来发现错误所在呢?

SoapClient 类提供了两个函数,用来跟踪客户端发出的 SOAP 请求和从服务端收到的 SOAP 应答。咱们能够在 try...catch 结构的后面加入以下代码:


清单 19. 跟踪 SOAP 请求和应答
echo "Request :<br/>".htmlspecialchars($client->__getLastRequest())."<br/>"; 
 echo "Response :<br/>".htmlspecialchars($client->__getLastResponse())."<br/>";

另外为了开启跟踪功能,咱们须要在 SoapClient 的构造函数中输入额外的一个参数:


清单 20. 开启 SOAP 跟踪功能
$client = new SoapClient('./ProductQuery.wsdl' , array( 'trace' => 1 ) );

这样,咱们就能够在浏览器中观察到 SOAP 请求和应答的内容:


清单 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>

能够发现,SOAP 请求的结构跟咱们指望的不一样,咱们就能够知道,是输入的参数不正确形成的,改正了这个错误以后,咱们能够看到正确的 SOAP 请求和应答:


清单 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 开发基于 Web 服务的应用程序总的来讲是比较简单的。从前文的例子中能够看到,咱们不须要不少的代码就能够建立一个简单的 Web 服务端和客户端,惟一的小麻烦多是建立 WSDL 文件,但咱们借助一些 PHP 集成开发环境的帮助同样能够轻松解决。这可让习惯使用 PHP 开发 Web 应用程序的程序员不须要学习其余语言就可以开发本身的基于 Web 服务的应用程序。

本文中的例子相对来讲比较简单,但咱们必须了解,PHP 的 SOAP 扩展目前也存在着一些不足之处。例如:

  • PHP 对于某些 SOAP 协议中的元素不能正确解析,例如目前 SoapServer 类并不能处理客户端发来的 SOAP 请求中的 Header 部分,这使得一些基于 Header 的特性没法在 PHP 中获得实现,例如权限验证等。
  • 因为 PHP 是弱类型语言,而 SOAP 协议中对类型的定义是比较严格的,因此 PHP 没法仅仅根据代码生成可供使用的 WSDL 文件,只能经过 PHP Doc 之类的机制在注释中声明,从而使辅助工具得到参数的类型。
  • PHP 的弱类型性质还形成 SOAP 扩展对类型的检查并不严格,若是服务端的实现中若是返回了类型错误的数据(例如应该返回类型为 integer 的数据,实际上却返回了字符串),则并不会产生异常,而只是将返回的数据解释成 WSDL 中定义的类型,可是这种转换一般是不能获得正确结果的。
  • PHP 的文档中对于 SOAP 调用的参数构造介绍不多,关联数组构造方法与 WSDL 中的数据定义的映射关系也不是十分清晰易懂。对于数据类型较为复杂的状况,单纯使用数组构造一个具备不少层次的参数结构也是困难且容易出错的。

幸运的是,PHP 的开发和维护者们始终把 SOAP 扩展看作 PHP 中重要的组成部分,自从 PHP 5.0.0 中开始提供 SOAP 扩展以来,它就没有中止过更新,每一次新的版本都会有新特性发布,同时也会修正不少原有的缺陷。最新的版本 (5.3.0) 最近刚刚发布,其中对于上述的问题 1 和 4 都有很好的解决。因此咱们有理由相信,PHP 会提供对 SOAP 愈来愈完善的支持。


参考资料

相关文章
相关标签/搜索