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

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

  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 显示了一个示例响应:

  清单 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欢迎页面

图 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请求结果

图 2. 被转换成一个原生PHP数组的SOAP请求结果

  下面我们将在《如何用Zend Framework实现SOAP服务?(下)》中继续为您介绍相关内容。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

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可谓两分天下。