2010-10-22 13:09:17 来源:IBM
虽然基于SOAP的服务实现一般被认为是一个复杂的、耗费时间的过程,但是有许多工具可以显著简化这个过程。其中一个工具是Zend Framework,它是使用PHP构建可扩展Web应用的一个完整的MVC框架。除了许多强大功能之外 —OOP形式、i18n支持、查询和页面缓存和Dojo集成等等—Zend Framework也提供了大量通过它的Zend_Soap组件创建和部署SOAP服务的工具包。
在本文中,您将了解使用Zend Framework创建一个简单的基于SOAP Web服务的过程。除了学习处理客户端请求和返回符合SOAP响应之外,您还将了解处理异常和产生SOAP错误的过程。最后,您也将使用Zend_Soap自动生成一个描述SOAP服务的WSDL文件,从而使客户端能 “自动发现”SOAP服务API。
理解SOAP
首先,我们要理解一些关于SOAP的词汇。SOAP是使用与语言无关的XML在Web上交换信息的方法,从而允许与使用不同语言编写的应用实现互连。这个XML通过HTTP传输协议在客户端和服务器之间进行传输,它具有强数据类型,可以保证数据完整性。
REST以资源和行为为中心,而SOAP则与之不同,它基于方法 和 数据类型。一个REST服务一般只有4个操作,它们对应于4个HTTP方法GET、POST、PUT和DELETE,而SOAP服务则没有这样的限制;开发人员可以根据自己的需要使用更多或较少的方法。而且,这些方法一般是通过POSTHTTP方法调用的,而这个方法与所请求的操作类型则完全没有关系。
为了演示SOAP的用法,我们使用一个简单的例子。假设您有一个社交书签应用,而您希望允许第三方开发人员使用SOAP向应用添加书签和从应用查询书签。一般情况下,您会使用getBookmark()和addBookmark()等函数实现一组服务对象,并将这些服务对象通过一个SOAP服务器发布出去。这个服务也会负责将SOAP数据类型转换成原生数据类型,解析 SOAP 请求数据包,执行相应的服务器函数,并生成包含结果的一个SOAP响应数据包。
清单 1 显示了 getBookmark() 过程的一个可能的SOAP请求例子:
清单 1. 一个 SOAP 请求例子
以下是引用片段:
POST /soap HTTP/1.1
Host: localhost
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.3.1
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 471
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://example.localhost/index/soap"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getBookmark env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<param0 xsi:type="xsd:int">4682</param0>
</ns1:getBookmark>
</env:Body>
</env:Envelope>
而清单 2 显示了一个示例响应:
清单 2. 一个SOAP响应例子
以下是引用片段:
HTTP/1.1 200 OK
Date: Wed, 17 Mar 2010 17:13:28 GMT
Server: Apache/2.2.14 (Win32) PHP/5.3.1
X-Powered-By: PHP/5.3.1
Content-Length: 800
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/soap+xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://example.localhost/index/soap"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:enc="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc">
<ns1:getBookmarkResponse
env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<rpc:result>return</rpc:result>
<return enc:itemType="xsd:string" enc:arraySize="3"
xsi:type="enc:Array">
<item xsi:type="xsd:string">http://www.google.com</item>
<item xsi:type="xsd:string">http://www.php-programming-solutions.com
</item>
<item xsi:type="xsd:string">http://www.mysql-tcr.com</item>
</return>
</ns1:getBookmarkResponse>
</env:Body>
</env:Envelope>
在一个典型的SOAP事务中,服务器会接收一个像 清单 1 所示的以 XML 编码的请求,解析这个 XML,执行相应的服务对象方法,然后返回一个如 清单 2 所示的以XML编码的响应到请求客户端。客户端通常能够解析和响应这个SOAP,并将它转换成一个特定语言的对象或数据结构以作进一步处理。您可以选择使用一个WSDL文件告诉客户端关于可用函数的信息,以及输入参数和返回值的个数和数据类型。
Zend Framework具SOAP客户端和服务器的实现,同时支持自动生WSDL文件。服务器和客户端实现在PHP中封装了SOAP扩展;这意味着如果PHP没有包含SOAP扩展支持,那么它们将无法生效。这样,使用带有原生扩展的Zend Framework库大大简化了开发过程,因为开发人员只需要定义一组实现服务API的对象,并将它们附加到服务器以便处理到达的请求。下面的各部分将详细讨论这个方面。
创建示例应用
在开始实现一个SOAP服务之前,您需要知道一些注意点和约定。在本文中,我将假定您拥有正常运行的Apache、PHP+SOAP和MySQL 的开发环境,Zend Framework会安装到您的PHP包含路径,同时您要熟悉SQL、XML和SOAP基础知识。我还将假定您熟悉使用Zend Framework进行应用开发的基本原则,理解行为与控制器之间的交互,并熟悉Zend_Db数据库抽象层。最后,我还假定您的Apache Web服务器配置支持虚拟主机和使用 .htaccess进行URL重写。如果您不熟悉这些概念,您可以通过本文的 参考资料 的链接了解更多信息。
在本文中您将实现的示例SOAP服务允许第三方开发人员将产品添加到应用数据库,以及编辑、删除和查询应用数据库中的产品列表。它使用下面的函数,您可以使用一个标准的SOAP客户端访问所有这些函数:
getProducts():返回数据库中的所有产品
getProduct($id):返回数据库中的一个特定产品
addProduct($data):添加一个新产品到数据库中
deleteProduct($id):从数据库删除一个特定产品
updateProduct($id, $data):将数据库中一个特定产品更新为新值
第 1 步:初始化一个新应用
首先,我们要创建一个标准的Zend Framework应用,它包含本文所显示的代码上下文。使用Zend Framework工具脚本(Windows?上则是zf.bat,UNIX是zf.sh)创建一个新项目,如下所示:
以下是引用片段:
shell> zf.bat create project example
您现在可以在您的Apache配置中为这个应用定义一个新的虚拟主机,如http://example.localhost/,然后将虚拟主机的文档根目录指向应用的 public/ 目录。然后,如果您访问这个主机,您应该能看到默认的Zend Framework欢迎页面,如 图 1 所示。

图 1. 默认的Zend Framework欢迎页面
第 2 步:初始化应用数据库和模型
下一步是初始化应用数据库。所以,我们要创建一个新的MySQL表来保存产品信息,如下所示:
以下是引用片段:
mysql> CREATE TABLE IF NOT EXISTS products (
-> id int(11) NOT NULL AUTO_INCREMENT,
-> title varchar(200) NOT NULL,
-> shortdesc text NOT NULL,
-> price float NOT NULL,
-> quantity int(11) NOT NULL,
-> PRIMARY KEY (id)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在这个表中填入一些示例记录以便开始开发,如下所示:
以下是引用片段:
mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(1,
-> 'Ride Along Fire Engine', 'This red fire engine is ideal for toddlers who
-> want to travel independently. Comes with flashing lights and beeping horn.',
-> 69.99, 11);
Query OK, 1 row affected (0.08 sec)
mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(2,
-> 'Wind-Up Crocodile Bath Toy', 'This wind-up toy is the perfect companion
-> for hours of bathtub fun.', 7.99, 67);
Query OK, 1 row affected (0.08 sec)
第 3 步:配置应用的名称空间
最后一步是为Zend Framework自动加载配置名称空间。这个步骤将在需要时实现自动加载特定应用类到应用中。在这里,我假设应用的名称空间为Example,而特定应用类(如SOAP服务类)将存储在$PROJECT/library/Example/中。所以,要修改应用配置文件$PROJECT/application/configs/application.ini并添加下面一行到文件中:
以下是引用片段:
autoloaderNamespaces[] = "Example_"
您现在已经完成了创建一个SOAP服务的所有准备工作!
查询数据
因为这是一个示例应用,我将尽量保持简单,并只在默认模块的IndexController上创建一个处理SOAP请求的动作;然而,在实际中,您可能希望使用一个单独的控制器处理SOAP请求。编辑文件$PROJECT/application/controllers/IndexController.php,然后添加新的动作,如 清单 3 所示:
清单 3. soapAction() 的定义
以下是引用片段:
<?php
class IndexController extends Zend_Controller_Action
{
public function soapAction()
{
// disable layouts and renderers
$this->getHelper('viewRenderer')->setNoRender(true);
// initialize server and set URI
$server = new Zend_Soap_Server(null,
array('uri' => 'http://example.localhost/index/soap'));
// set SOAP service class
$server->setClass('Example_Manager');
// handle request
$server->handle();
}
}
清单 3 传递一个null值到对象构造函数的第一个参数,以非WSDL模式初始化了一个新的Zend_Soap_Server对象。如果以非WSDL模式创建服务器,我们必须指定服务器URI;在 清单 3 中,这是在作为第二个参数传递给构造函数的选项数组中指定的。
接下来,服务器对象的setClass()函数用于将一个服务类附加到服务器上。这个类实现了SOAP服务的可用函数;这个服务器将在SOAP请求的响应中自动调用这些函数。如果您喜欢,您也可以使用addFunction()和loadFunctions()函数将用户自定义函数附加到服务器上,而不需要使用 setClass()函数附加整个类。
正如之前所提到的,Zend_Soap_Server类并没有提供它自己的SOAP服务器实现;它只是封装了 PHP 的内置SOAP扩展。因此,一旦所有先决条件都准备好后,清单 3 中的handle()函数会负责初始化内置的PHP SoapServer对象,将它传递给请求对象,并调用该对象的handle() 函数处理SOAP请求。
虽然所有这些都做好了,但是这还远远不够,因为服务类还没有定义。接下来我们使用 清单 4 中的代码创建这个类定义,将创建的类定义保存到 $PROJECT/library/Example/Manager.php:
清单 4. 带有get*()函数的服务对象定义
以下是引用片段:
<?php
class Example_Manager {
/**
* Returns list of all products in database
*
* @return array
*/
public function getProducts()
{
$db = Zend_Registry::get('Zend_Db');
$sql = "SELECT * FROM products";
return $db->fetchAll($sql);
}
/**
* Returns specified product in database
*
* @param integer $id
* @return array|Exception
*/
public function getProduct($id)
{
if (!Zend_Validate::is($id, 'Int')) {
throw new Example_Exception('Invalid input');
}
$db = Zend_Registry::get('Zend_Db');
$sql = "SELECT * FROM products WHERE id = '$id'";
$result = $db->fetchAll($sql);
if (count($result) != 1) {
throw new Exception('Invalid product ID: ' . $id);
}
return $result;
}
}
?>
清单 4 创建了一个单独的服务类,它包含两个函数。getProducts()函数使用Zend_Db查询表中所有的产品记录,然后将它们作为一个数组返回,而 getProduct() 函数则接收一个产品标识符,然后只返回特定的一条记录。然后SOAP服务器将这个方法的返回值转换成一个 SOAP 响应数据包,并将它返回给发送请求的客户端。清单8包含一个响应数据包的例子:
如果您还在疑惑Zend_Db是在哪里初始化的,我可以告诉您它是在应用启动加载器中初始化的,即$PROJECT/application/Bootstrap.php。这个 Bootstrap.php包含了一个 _initDatabase()函数,它创建Zend_Db适配器,并将它注册到应用注册表中。清单 5 显示这部分代码:
清单 5. 数据库适配器初始化
以下是引用片段:
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initDatabase()
{
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => 'localhost',
'username' => 'user',
'password' => 'pass',
'dbname' => 'example'
));
Zend_Registry::set('Zend_Db', $db);
}
}
为了看到实际结果,要创建一个SOAP客户端(清单 6),然后使用它连接SOAP服务,并请求getProducts()函数。
清单 6. 一个示例SOAP客户端
以下是引用片段:
<?php
// load Zend libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Soap_Client');
// initialize SOAP client
$options = array(
'location' => 'http://example.localhost/index/soap',
'uri' => 'http://example.localhost/index/soap'
);
try {
$client = new Zend_Soap_Client(null, $options);
$result = $client->getProducts();
print_r($result);
} catch (SoapFault $s) {
die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring);
} catch (Exception $e) {
die('ERROR: ' . $e->getMessage());
}
?>
SOAP客户端将会产生一个请求数据包(清单 7)。
清单 7. getProducts()的一个示例SOAP请求
以下是引用片段:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://example.localhost/index/soap"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getProducts env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"/>
</env:Body>
</env:Envelope>
这个服务器产生一个使用SOAP编码的响应(清单 8)。
清单 8. getProducts() 函数的一个示例SOAP响应
以下是引用片段:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://example.localhost/index/soap"
xmlns:ns2="http://xml.apache.org/xml-soap"
xmlns:enc="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc">
<ns1:getProductsResponse
env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<rpc:result>return</rpc:result>
<return enc:itemType="ns2:Map" enc:arraySize="2" xsi:type="enc:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">title</key>
<value xsi:type="xsd:string">Ride Along Fire Engine</value>
</item>
<item>
<key xsi:type="xsd:string">shortdesc</key>
<value xsi:type="xsd:string">This red fire engine is ideal
for toddlers who want to travel independently.
Comes with flashing lights and beeping horn.</value>
</item>
<item>
<key xsi:type="xsd:string">price</key>
<value xsi:type="xsd:string">69.99</value>
</item>
<item>
<key xsi:type="xsd:string">quantity</key>
<value xsi:type="xsd:string">11</value>
</item>
</item>
...
</return>
</ns1:getProductsResponse>
</env:Body>
</env:Envelope>
然后SOAP客户端会将这个响应转换成一个原生的PHP数组,它可以被进一步处理或检查,如 图 2 所示。

图 2. 被转换成一个原生PHP数组的SOAP请求结果
免责声明:本网站(http://www.ciotimes.com/)内容主要来自原创、合作媒体供稿和第三方投稿,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所提供信息的准确性及可靠性,但不保证有关资料的准确性及可靠性,读者在使用前请进一步核实,并对任何自主决定的行为负责。本网站对有关资料所引致的错误、不确或遗漏,概不负任何法律责任。
本网站刊载的所有内容(包括但不仅限文字、图片、LOGO、音频、视频、软件、程序等)版权归原作者所有。任何单位或个人认为本网站中的内容可能涉嫌侵犯其知识产权或存在不实内容时,请及时通知本站,予以删除。
