Web 服务最近很是流行,其中基于 REST 的服务吸引了大部分的关注。REST 之因此流行,是因为它简单、直接和可以处理现有 HTTP 方法。可是也要记住,REST 并非惟一的方法:SOAP,即 Simple Object Access Protocol,是一种更正式和更标准的处理 Web 信息交换问题的方法。 mysql
虽然基于 SOAP 的服务实现通常被认为是一个复杂的、耗费时间的过程,可是有许多工具能够显著简化这个过程。其中一个工具是 Zend Framework,它是使用 PHP 构建可扩展 Web 应用的一个完整的 MVC 框架。除了许多强大功能以外 — OOP 形式、i18n 支持、查询和页面缓存和 Dojo 集成等等 — Zend Framework 也提供了大量经过它的 Zend_Soap 组件建立和部署 SOAP 服务的工具包。 sql
在本文中,您将了解使用 Zend Framework 建立一个简单的基于 SOAP Web 服务的过程。除了学习处理客户端请求和返回符合 SOAP 响应以外,您还将了解处理异常和产生 SOAP 错误的过程。最后,您也将使用 Zend_Soap 自动生成一个描述 SOAP 服务的 WSDL 文件,从而使客户端能 “自动发现” SOAP 服务 API。 shell
回页首 数据库
首先,咱们要理解一些关于 SOAP 的词汇。SOAP 是使用与语言无关的 XML 在 Web 上交换信息的方法,从而容许与使用不一样语言编写的应用实现互连。这个 XML 经过 HTTP 传输协议在客户端和服务器之间进行传输,它具备强数据类型,能够保证数据完整性。 编程
REST 以资源 和行为 为中心,而 SOAP 则与之不一样,它基于方法 和 数据类型。一个 REST 服务通常只有 4 个操做,它们对应于 4 个 HTTP 方法 GET、POST、PUT 和 DELETE,而 SOAP 服务则没有这样的限制;开发人员能够根据本身的须要使用更多或较少的方法。并且,这些方法通常是经过 POST HTTP 方法调用的,而这个方法与所请求的操做类型则彻底没有关系。 数组
为了演示 SOAP 的用法,咱们使用一个简单的例子。假设您有一个社交书签应用,而您但愿容许第三方开发人员使用 SOAP 向应用添加书签和从应用查询书签。通常状况下,您会使用getBookmark()和addBookmark()等函数实现一组服务对象,并将这些服务对象经过一个 SOAP 服务器发布出去。这个服务也会负责将 SOAP 数据类型转换成原生数据类型,解析 SOAP 请求数据包,执行相应的服务器函数,并生成包含结果的一个 SOAP 响应数据包。 浏览器
清单 1 显示了getBookmark()过程的一个可能的 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 显示了一个示例响应:
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 客户端访问全部这些函数:
首先,咱们要建立一个标准的 Zend Framework 应用,它包含本文所显示的代码上下文。使用 Zend Framework 工具脚本(Windows® 上则是 zf.bat,UNIX 是 zf.sh)建立一个新项目,以下所示:
shell> zf.bat create project example |
您如今能够在您的 Apache 配置中为这个应用定义一个新的虚拟主机,如 http://example.localhost/,而后将虚拟主机的文档根目录指向应用的 public/ 目录。而后,若是您访问这个主机,您应该能看到默认的 Zend Framework 欢迎页面,如 图 1 所示。
下一步是初始化应用数据库。因此,咱们要建立一个新的 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) |
最后一步是为 Zend Framework 自动加载配置名称空间。这个步骤将在须要时实现自动加载特定应用类到应用中。在这里,我假设应用的名称空间为Example,而特定应用类(如 SOAP 服务类)将存储在 $PROJECT/library/Example/ 中。因此,要修改应用配置文件 $PROJECT/application/configs/application.ini 并添加下面一行到文件中:
autoloaderNamespaces[] = "Example_" |
您如今已经完成了建立一个 SOAP 服务的全部准备工做!
由于这是一个示例应用,我将尽可能保持简单,并只在默认模块的 IndexController 上建立一个处理 SOAP 请求的动做;然而,在实际中,您可能但愿使用一个单独的控制器处理 SOAP 请求。编辑文件 $PROJECT/application/controllers/IndexController.php,而后添加新的动做,如 清单 3 所示:
<?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:
<?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 显示这部分代码:
<?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()函数。
<?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)。
<?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)。
<?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 所示。
在了解了如何经过 SOAP 查询数据后,如今咱们了解一下如何添加和删除数据。
在 Example_Manager 类中实现一个addProduct()函数很是简单。清单 9 演示了实现方法:
<?php class Example_Manager { /** * Adds new product to database * * @param array $data array of data values with keys -> table fields * @return integer id of inserted product */ public function addProduct($data) { $db = Zend_Registry::get('Zend_Db'); $db->insert('products', $data); return $db->lastInsertId(); } } |
清单 9 中的addProduct()函数接收一个新产品记录做为键-值对数组,而后使用 Zend_Db 对象的insert()函数将记录写入到数据库表中。它最后返回最新插入记录的 ID。
删除一个产品也同样简单:只须要增长一个deleteProduct()函数,它接收产品 ID 做为输入,而后使用 Zend_Db 的delete()函数从数据库删除这个记录。清单 10 展现了这个方法的实现:
<?php class Example_Manager { /** * Deletes product from database * * @param integer $id * @return integer number of products deleted */ public function deleteProduct($id) { $db = Zend_Registry::get('Zend_Db'); $count = $db->delete('products', 'id=' . $db->quote($id)); return $count; } } |
在 清单 10中,传递给delete()函数的第二个参数指定了执行 DELETE 操做时使用的约束或过滤器。使用这个参数很重要;由于若是不做限制,Zend_Db 将删除表中的全部记录。
最后,清单 11 展现了一个updateProduct()函数,它可用于更新一个产品记录的值。这个函数接收两个输入参数 — 产品 ID 和一个包含修改记录的数组 — 并使用 Zend_Db 的update()函数对数据库表执行一个 UPDATE 查询。
<?php class Example_Manager { /** * Updates product in database * * @param integer $id * @param array $data * @return integer number of products updated */ public function updateProduct($id, $data) { $db = Zend_Registry::get('Zend_Db'); $count = $db->update('products', $data, 'id=' . $db->quote($id)); return $count; } } |
您能够在如 清单 12 所示的一个 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 { // add a new product // get and display product ID $p = array( 'title' => 'Spinning Top', 'shortdesc' => 'Hours of fun await with this colorful spinning top. Includes flashing colored lights.', 'price' => '3.99', 'quantity' => 57 ); $client = new Zend_Soap_Client(null, $options); $id = $client->addProduct($p); echo 'Added product with ID: ' . $result; // update existing product $p = array( 'title' => 'Box-With-Me Croc', 'shortdesc' => 'Have fun boxing with this inflatable crocodile, made of tough, washable rubber.', 'price' => '12.99', 'quantity' => 25 ); $client->updateProduct($id, $p); echo 'Updated product with ID: ' . $id; // delete existing product $client->deleteProduct($id); echo 'Deleted product with ID: ' . $id; } catch (SoapFault $s) { die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring); } catch (Exception $e) { die('ERROR: ' . $e->getMessage()); } ?> |
上面所列的全部函数的一个共同问题是:它们没有做任何的输入验证。在实际中,忽略这种验证会对您的应用数据库的完整性形成严重影响,而且可能很快会致使数据损坏(最好状况)或彻底破坏(最坏状况)。
幸亏,Zend Framework 包含了一个 Zend_Validate 组件,它为最多见状况提供了内置的验证器。您能够将这个特性与 Zend_Soap_Server 的registerFaultException()函数结合,用于测试客户端所提供的请求数据,而后为不一样的错误状况返回一个 SOAP 错误信息。
要了解它是如何工做的,咱们要先经过扩展 Zend_Exception 建立一个自定义异常类,如 清单 13 所示:
<?php class Example_Exception extends Zend_Exception {} |
将这个类保存到 $PROJECT/library/Example/Exception.php。
接下来,修改各个服务类函数,使它们包含输入验证,并在输入数据无效或缺乏数据时抛出自定义异常。清单 14 展现了修改的 Example_Manager 类:
<?php class Example_Manager { // define filters and validators for input private $_filters = array( 'title' => array('HtmlEntities', 'StripTags', 'StringTrim'), 'shortdesc' => array('HtmlEntities', 'StripTags', 'StringTrim'), 'price' => array('HtmlEntities', 'StripTags', 'StringTrim'), 'quantity' => array('HtmlEntities', 'StripTags', 'StringTrim') ); private $_validators = array( 'title' => array(), 'shortdesc' => array(), 'price' => array('Float'), 'quantity' => array('Int') ); /** * 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|Example_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 Example_Exception('Invalid product ID: ' . $id); } return $result; } /** * Adds new product to database * * @param array $data array of data values with keys -> table fields * @return integer id of inserted product */ public function addProduct($data) { $input = new Zend_Filter_Input($this->_filters, $this->_validators, $data); if (!$input->isValid()) { throw new Example_Exception('Invalid input'); } $values = $input->getEscaped(); $db = Zend_Registry::get('Zend_Db'); $db->insert('products', $values); return $db->lastInsertId(); } /** * Deletes product from database * * @param integer $id * @return integer number of products deleted */ public function deleteProduct($id) { if (!Zend_Validate::is($id, 'Int')) { throw new Example_Exception('Invalid input'); } $db = Zend_Registry::get('Zend_Db'); $count = $db->delete('products', 'id=' . $db->quote($id)); return $count; } /** * Updates product in database * * @param integer $id * @param array $data * @return integer number of products updated */ public function updateProduct($id, $data) { $input = new Zend_Filter_Input($this->_filters, $this->_validators, $data); if (!Zend_Validate::is($id, 'Int') || !$input->isValid()) { throw new Example_Exception('Invalid input'); } $values = $input->getEscaped(); $db = Zend_Registry::get('Zend_Db'); $count = $db->update('products', $values, 'id=' . $db->quote($id)); return $count; } } |
在 清单 14 中,服务 API 加强后包含了对全部输入参数的验证。对于大多数 API 函数,Zend_Validate::is()静态函数提供了一种测试输入参数的便捷方法;在某些状况下,一个额外的 Zend_Filter_Input 过滤器链会用于验证和过滤输入。在输入验证过程当中发生的任何错误都会产生一个 Example_Exception 类的实例。
最后一步是告诉 SOAP 服务器自动将所产生的 Example_Exception 实例转换成 SOAP 错误。通常经过使用registerFaultException()函数将异常类注册到 SOAP 服务器上,如 清单 15 所示的修改后的IndexController::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'); // register exceptions that generate SOAP faults $server->registerFaultException(array('Example_Exception')); // handle request $server->handle(); } } |
要了解它是如何工做的,能够尝试对getProduct()函数发送一个 SOAP 请求,而后给它传递一个无效的 ID。清单 16 显示了一个这样的 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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"> <env:Body> <ns1:getProduct env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"> <param0 xsi:type="xsd:string">nosuchproduct</param0> </ns1:getProduct> </env:Body> </env:Envelope> |
服务器将验证输入,而后发现它是无效的,从而产生一个 Example_Exception,它将会被转化成一个 SOAP 错误,并将它返回给客户端。清单 17 展现所产生的响应数据包:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>Receiver</faultcode> <faultstring>Invalid input</faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
对于 SOAP 客户端来讲,将这个 SOAP 调用封装在一个 try-catch 代码块中是很好的作法,这样相似于上面的 SOAP 错误就会被顺利地捕捉和处理。若是您从新访问 清单 12 中的示例 SOAP 客户端,您将看到展现它的实现方法的一个例子。
PHP 的原生 SOAP 扩展的一个缺点是它不支持为 SOAP 服务自动生成 WSDL 文件。WSDL 文件是颇有用的,由于它们包含了关于可用的 SOAP API 函数的信息,而且能够被链接客户端用于 “自动发现” SOAP API。
然而,Zend Framework 包含了一个 Zend_Soap_AutoDiscover 组件,您可使用它为一个 SOAP 服务自动生成一个 WSDL 文件。它是经过读取 SOAP 服务类中的 PHPDoc 注释实现自动生成 WSDL 的。若是您回顾本文以前的清单,您会看到每个函数都带有 PHPDoc 注释;这是专门用于简化 WSDL 自动生成的。
清单 18 展现了如何使用 Zend_Soap_AutoDiscover 组件实现 WSDL 自动生成:
<?php class IndexController extends Zend_Controller_Action { public function soapAction() { // disable layouts and renderers $this->getHelper('viewRenderer')->setNoRender(true); // initialize server and set WSDL file location $server = new Zend_Soap_Server('http://example.localhost/index/wsdl'); // set SOAP service class $server->setClass('Example_Manager'); // register exceptions that generate SOAP faults $server->registerFaultException(array('Example_Exception')); // handle request $server->handle(); } public function wsdlAction() { // disable layouts and renderers $this->getHelper('viewRenderer')->setNoRender(true); // set up WSDL auto-discovery $wsdl = new Zend_Soap_AutoDiscover(); // attach SOAP service class $wsdl->setClass('Example_Manager'); // set SOAP action URI $wsdl->setUri('http://example.localhost/index/soap'); // handle request $wsdl->handle(); } } |
清单 18 定义了一个新的wsdlAction(), 它初始化了 Zend_Soap_AutoDiscover 组件的一个实例,而后将它指向 Example_Manager 类。经过调用这个实例的handle()函数,它就会读取特定的类,解析其中的 PHPDoc 注释,而后产生一个符合标准的 WSDL 文档,这个文档完整地描述了该服务对象。
要看到这个结果,须要在您的浏览器上访问 http://example.localhost/index/wsdl,而后您应该可以看到如 图 3 所示结果:
如今 SOAP 服务器和客户端均可以使用这个 WSDL 文件了,而不须要手动指定uri和location参数。清单 18 也说明了这一点,经过修改soapAction(),它将 WSDL URL 传递给 Zend_Soap_Server 构造函数,使它以 WSDL 模式启动。链接 SOAP 的客户端就可以使用这个 WSDL URL 自动发现 SOAP 服务 API 了。