如何用Zend Framework实现SOAP服务?(下)

日期: 2010-06-16 作者:Vikram Vaswani 来源:TechTarget中国 英文

  添加、删除和更新数据

  在了解了如何通过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文件

图 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

官方微博

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的软件接口发展而形成的。