添加、删除和更新数据
在了解了如何通过SOAP查询数据后,现在我们了解一下如何添加和删除数据。
在Example_Manager类中实现一个addProduct()函数非常简单。清单 9 演示了实现方法:
清单 9. 定义了addProduct()函数的SOAP服务对象
以下是引用片段: <?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 展示了这个方法的实现:
清单 10. 定义了deleteProduct()函数的SOAP服务对象
以下是引用片段: <?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查询。
清单 11. 定义了updateProduct()函数的SOAP服务对象
以下是引用片段: <?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客户端尝试所有这些函数:
清单 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()); } ?> |
生成SOAP错误信息
上面所列的所有函数的一个共同问题是:它们没有作任何的输入验证。在实际中,忽略这种验证会对您的应用数据库的完整性造成严重影响,并且可能很快会导致数据损坏(最好情况)或完全破坏(最坏情况)。
幸好,Zend Framework包含了一个Zend_Validate组件,它为最常见情况提供了内置的验证器。您可以将这个特性与Zend_Soap_Server的 registerFaultException()函数结合,用于测试客户端所提供的请求数据,然后为不同的错误情况返回一个 SOAP 错误信息。
要了解它是如何工作的,我们要先通过扩展Zend_Exception创建一个自定义异常类,如 清单 13 所示:
清单 13. 一个自定义异常类
以下是引用片段: <?php class Example_Exception extends Zend_Exception {} 将这个类保存到 $PROJECT/library/Example/Exception.php。 |
接下来,修改各个服务类函数,使它们包含输入验证,并在输入数据无效或缺少数据时抛出自定义异常。清单 14 展示了修改的Example_Manager类:
清单 14. 修改后带有输入验证和异常的SOAP服务对象
以下是引用片段: <?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:
清单 15. 修改的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请求例子:
清单 16. 一个带有无效输入参数的SOAP请求
以下是引用片段: <?xml version=”1.0″ encoding=”UTF-8″?> <env:Envelope > <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 展示所产生的响应数据包:
清单 17. 所生成的一个SOAP错误
以下是引用片段: <?xml version=”1.0″ encoding=”UTF-8″?> <SOAP-ENV:Envelope BORDER-RIGHT: #cccccc 1px dotted; TABLE-LAYOUT: fixed; BORDER-TOP: #cccccc 1px dotted; BORDER-LEFT: #cccccc 1px dotted; BORDER-BOTTOM: #cccccc 1px dotted” cellSpacing=0 cellPadding=6 width=”95%” align=center border=0> |
以下是引用片段: <?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 所示结果:
图 3. 一个动态生成的WSDL文件
现在SOAP服务器和客户端都可以使用这个WSDL文件了,而不需要手动指定uri和location参数。清单 18 也说明了这一点,通过修改 soapAction(),它将WSDL URL传递给Zend_Soap_Server构造函数,使它以WSDL模式启动。连接 SOAP 的客户端就能够使用这个WSDL URL自动发现SOAP服务API了。
结束语
Zend Framework提供了快速有效地将一个SOAP API添加到一个Web应用的完整工具包。通过这个工具包,您可以在Web应用之间使用众所周知的 SOAP 标准实现一种经济有效的信息交换方法。Zend Framework对于SOAP客户端和服务器的内置支持,以及WSDL自动生成,使它成为快速SOAP 服务实现和部署的非常好的方法。而最后,因为 Zend Framework 是一个基于 MVC 模式的框架,它还能轻松地将SOAP API移植到一个现有的 Zend Framework应用,而不会对现有的代码库产生重大影响(并且不会产生太多的回归问题)。
本文所实现的所有代码的链接,以及您可用于添加、编辑、删除和查询产品的一个简单SOAP客户端,请参见下载部分。本文所使用的工具,请参见 参考资料。我建议您下载这些代码,然后试验这些代码,并且您可以尝试在里面自己添加新代码。我保证不会出现问题,而这肯定会让您有所收获。尽情享受吧!
本文中所讨论的示例应用下载:example-app-soap.zip
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
相关推荐
-
API开发与管理大作战
2014将会是API管理方法新旧PK的一年,据Delyn Simons说,她领导了Mashery开发者的外展团队。应用编程接口(API)的主流化和私有化在新的一年也将掀起波澜,她在波士顿“Future Insights Ultimate Developer Event 2013”大会上预测说。
-
公共API外包管理是否值得考虑?
公共API外包管理是指聘请一个专家小组来解决可扩展性问题,同时也提出几套可替代的方案。
-
最适合大数据应用的是SOA还是REST?
跟所有的企业数据一样,大数据唯有通过应用投射给用户才有用。对于设计或重新设计大数据应用的架构师来说,一个关键问题是究竟是用SOA还是RESTful的API?
-
弹性资源对传统的REST架构构成挑战了吗?
组件化应用程序需要机制来将组件传递到下一个工作地。从一开始,人们对连接流程及其实施就有不同的观点。可以证明,SOA阵营是由RPC和SOAP的软件接口发展而形成的。