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

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

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

清单 2. WSDL 源代码

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 文件的编写。这种方式适用于契约先行模式。根据已经得到的 WSDL 文件,我们就可以继续开发服务端和客户端的 Web 应用程序。

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

清单 3. 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 文件示例

p_w_picpath008.jpg

可以看到,唯一没有自动生成的是 Web 服务的位置,只要将这一项填写好,WSDL 的创建工作就完成了。

接下来将介绍的是开发 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 请求中的相应数据。对于仅以顺序方式 ( 数据类型定义中只有以 标签包含的简单类型序列 ) 定义的数据类型,那么这个类的实例中仅仅包含简单类型的成员。对于有多于一个层次的数据结构,那么类中还将包含描述下层数据结构的类的示例,以此类推,形成一个多层次的结构。在 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 . "
";

echo "Product Code:" . $client->CPU . "
";

echo "Product Code:" . $client->RAM . "
";

echo "Product Code:" . $client->Screen . "
";

echo "Product Code:" . $client->HDD . "
";

稍加修改,我们就可以得到之前给出的在浏览器中的显示效果了。

到这里我们的工作似乎已经结束了。但是实际的开发过程是不可能如此顺利的,如果我们的代码没有得到正确的结果怎么办?所以,我们需要了解一些使用 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 . "
";

echo "Product Code:" . $client->CPU . "
";

echo "Product Code:" . $client->RAM . "
";

echo "Product Code:" . $client->Screen . "
";

echo "Product Code:" . $client->HDD . "
";

}

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 程序时,仅仅依赖异常处理机制是不够的。我们在调用 Web 服务提供的操作时,如果参数的结构错误,客户端和服务端很有可能都不抛出异常,例如前面在实现客户端应用程序时提到的问题,我们把参数最外面的数组去掉:

清单 18. 错误的调用方法

$result = $client->__soapCall('QuerySpec', array( "ProductCode" => '1175-PXA') );

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

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

清单 19. 跟踪 SOAP 请求和应答

echo "Request :
".htmlspecialchars($client->__getLastRequest())."
";

echo "Response :
".htmlspecialchars($client->__getLastResponse())."
";

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

清单 20. 开启 SOAP 跟踪功能

$client = new SoapClient('./ProductQuery.wsdl' , array( 'trace' => 1 ) );

这样,我们就可以在浏览器中观察到 SOAP 请求和应答的内容:

清单 21. 错误的 SOAP 请求和应答

Request:

xmlns:ns1="http://soapexample.cn/ProductQuery/">

Response:

xmlns:ns1="http://soapexample.cn/ProductQuery/">

可以发现,SOAP 请求的结构跟我们期望的不同,我们就可以知道,是输入的参数不正确造成的,改正了这个错误之后,我们可以看到正确的 SOAP 请求和应答:

清单 22. 正确的 SOAP 请求和应答

Request:

xmlns:ns1="http://www.ibm.com/ProductQuery/">

1175-PXA

Response:

xmlns:ns1="http://www.ibm.com/ProductQuery/">

1175-PXA

Centrino T9400

3GB DDR3

14.1 inch.

300GB 5400rpm

使用 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 越来越完善的支持。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值