Web服务最近非常流行,其中基于REST的服务吸引了大部分的关注。REST之所以流行,是由于它简单、直接和能够处理现有HTTP方法。但是也要记住,REST并不是唯一的方法:SOAP,即Simple Object Access Protocol,是一种更正式和更标准的处理 Web 信息交换问题的方法。
虽然基于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 > <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. 一个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 > <env:Body > <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 > <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 > <env:Body > <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请求结果
下面我们将在《如何用Zend Framework实现SOAP服务?(下)》中继续为您介绍相关内容。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
相关推荐
-
SAP收购CallidusCloud 与Salesforce竞争
一直被称为后台办公巨头的SAP现在似乎也想在前台办公大展拳脚。 最新的迹象是SAP收购CallidusClou […]
-
API开发与管理大作战
2014将会是API管理方法新旧PK的一年,据Delyn Simons说,她领导了Mashery开发者的外展团队。应用编程接口(API)的主流化和私有化在新的一年也将掀起波澜,她在波士顿“Future Insights Ultimate Developer Event 2013”大会上预测说。
-
公共API外包管理是否值得考虑?
公共API外包管理是指聘请一个专家小组来解决可扩展性问题,同时也提出几套可替代的方案。
-
API设计如龙生九子 各不相同
IT咨询管理公司CA Technologies对API产业做了个问卷调查,问卷内容涉及API设计风格以及管理部署的新动向。调查结果表明,JSON与XML可谓两分天下。