利用PHP创建由Oracle驱动的SOAP服务(二)

日期: 2007-12-10 来源:TechTarget中国

  生成 WSDL

  尽管前面的示例确实是一个用于创建 SOAP 服务的完整 PHP 脚本,但它根本没有解决 WSDL 文档的问题。查看 WSDL 文档是整个过程的一个重要组成部分,生成 WSDL 文档则需要采取一些额外的操作。

  遗憾的是,由于 PHP 的无类型本质,目前 PHP 还不能像强类型化语言(如 Java)或 .NET 服务那样拥有即席自动生成 WSDL 文档的合理方法。WSDL 文档必须指定每个参数的类型,因此您需要使用其他方法在脚本中表达,因为变量 $a 和 $b 提供的是非类型化信息。有多种选择可用:

  自己手动编写 WSDL 文档。
  通过手动输入每个方法和类型化信息,使用基于 Web 的 WSDL 生成器来生成文件。
  使用 Zend Studio 的自动 WSDL 生成器。
  尽管这三个选择都可行,但我将演示如何使用 Zend Studio 的 WSDL 生成器来生成 WSDL 文档,原因有两个:第一,这是目前为止生成 WSDL 文档的最简单、最可靠的方法;第二,Zend Studio 几乎在每个正规的 PHP 柜台都有售。

  为了使用 Studio WSDL 生成器生成 WSDL 文档,您首先必须为每个公开方法识别其参数的类型化信息,然后使用名为 PHPDoc(常用 JavaDoc 的 PHP 版本)的内嵌文档注释来返回值。PHPDoc 只是一个置于每个函数开头的块注释,其使用的特定可分析语法可用于自动生成文档。Zend Studio 还使用该信息收集生成 WSDL 文档所需的类型化信息。

  继续前面的示例,下面是先前使用的同一 math 类,但这次使用的是 PHPDoc 注释:

/**
* A simple math utility class
* @author John Coggeshall john@zend.com
 */
class math {
    /**
* Add two integers together
     *
* @param integer $a The first integer of the addition
* @param integer $b The second integer of the addition
* @return integer The sum of the provided integers
     */
public function add($a, $b) {
return $a + $b;
    }

    /**
* Subtract two integers from each other
     *
* @param integer $a The first integer of the subtraction
* @param integer $b The second integer of the subtraction
* @return integer The difference of the provided integers
     */
public function sub($a, $b) {
return $a – $b;
    }
}

  正确使用这些 PHPDoc 注释之后,通过执行 Studio 的 Tools 菜单下的 WSDL 生成器,您可以让 Zend Studio 为该类自动生成合适的 WSDL 文档:

  正确使用 PHPDoc 注释之后,就可以减少为 SOAP 服务器生成 WSDL 文档所需的其他繁琐而无意义的任务,而只需遵循一个非常简单的分步向导即可。完成后,Studio 将打开其中的 WSDL 文档,以供您查看并保存到所选的位置。

  生成文档之后,必须将该文档放在服务器能够访问的位置(在实例化类时需要),以及可能使用该服务的潜在 SOAP 客户端能够访问的位置。通常,这很容易实现,只需将 WSDL 文档与托管 SOAP 服务的终端 PHP 脚本放在同一位置即可。

  创建 BookManager 类

  现在,您已经熟悉了用 PHP 实施 SOAP 服务的所有内容,下面我们来讨论数据库。出于本手册的需要,我创建了一个名为 BookManager 的类。该类的作用将与前面示例中的 math 类相同,除了要与数据库进行交互,并提供一个 SOAP 服务,以允许您执行一般维护并查询本教程开头描述的书籍表。具体而言,BookManager 类将实施以下要公开为 SOAP 调用的方法:

addBook($isbn, $author, $title, $price); // Adds a Book to the database
delBook($isbn); // Deletes a book by ISBN number
findBookISBNByAuthor($author); // Returns an array of ISBN numbers of books written by a        
// specific author
findBookISBNByTitle($title); // Returns an array of ISBN numbers of books whose title
// matches the substring provided
getBookByISBN($isbn); // Returns the details of the book identified by ISBN
listAllBooks(); // Returns an array of all ISBN numbers in the database

  尽管该类本身有其他几个方法,但只有上述六个方法是声明的公共方法(当然,除了构造函数以外),因而也是仅有的公开为 SOAP 服务的方法。虽然详细说明每个方法会超出本教程讨论范围(特别是它们在形式上基本相同),但出于完整性需要,我们来看一下 delBook() 方法:
/**
  * Delete a book from the database by ISBN
  *
  * @param string $isbn The ISBN serial number of the book to delete
  *
  * @return mixed SOAP Fault on error, true on success
  */
 public function delBook($isbn) {
  
  $query = "DELETE FROM books
                  WHERE isbn = :isbn";
  
  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: " . 
                                         oci_error($stmt) . ")");
  }
  
  oci_bind_by_name($stmt, "isbn", $isbn, 32);

  if(!oci_execute($stmt)) {
   oci_rollback($this->getDB());
   throw new SoapFault(-1, "Failed to execute query (reason: " .
                                          oci_error($stmt) . ")");
  }
  
  oci_commit($this->getDB());
  
  return true;
 }

  对于那些熟悉用于 PHP 的 Oracle API 的开发人员来说,上述方法应该很简单。对于其余开发人员来说,我们从 oci_parse() 方法开始来探究该函数的某些关键点。该方法以字符串的形式接受 SQL 查询(如果需要,在查询中包含每个变量的占位符),然后返回表示该查询的语句资源。在这里,该语句资源的占位符可以通过 oci_bind_by_name() 方法直接映射到 PHP 变量,该方法将接受语句、占位符名称、对应的 PHP 变量以及可选的当前最大列长度作为参数。一旦 PHP 将每个占位符绑定到一个 PHP 变量,就可以执行语句并获得结果了。当然,由于该操作是一个针对表的 write 操作,您可以通过将更改提交到数据库并返回成功状态,来成功完成函数执行。

  为便于参考,下面是一个完整的 BookManager 类,以及使用该类公开 SOAP 服务的相应服务器脚本。

<?php

/**
 * SOAP Service class to manage the books table
 *
 * @author John Coggeshall <john@zend.com>
 *
 * @throws SoapFault
 */
class BookManager {
 
 private $objDB;

 const DB_USERNAME="demo";
 const DB_PASSWORD="password";
 const DB_DATABASE="myoracle";
 
 /**
  * Object Constructor: Establishes DB connection
  *
  */
 function __construct() {
  
  $this->objDB = oci_connect(self::DB_USERNAME,
                             self::DB_PASSWORD,
                             self::DB_DATABASE);
      
  if($this->objDB === false) {
   throw new SoapFault(-1, "Failed to connect to database backend (reason: " .
                                         oci_error() . ")");
  }
 }

 /**
  * Private method to return the DB connection and make sure it exists
  *
  * @return unknown
  */
 private function getDB() {
  if(!$this->objDB) {
   throw new SoapFault(-1, "No valid database connection");
  }
  
  return $this->objDB;
 }
 
 /**
  * Add a new book to the database
  *
  * @param string $isbn The ISBN serial number for the book (32 char max)
  * @param string $author The name of the primary author (50 char max)
  * @param string $title The title of the book (50 char max)
  * @param float $price The price of the book in USD
  *
  * @return mixed SOAP Fault on error, true on success
  */
 public function addBook($isbn, $author, $title, $price) {

  $query = "INSERT INTO books (isbn, author, title, price)
                 VALUES (:isbn, :author, :title, :price)";
  
  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: "  .
                                         oci_error($stmt) . ")");
  }
  
  // The numbers 32, 50, 50 are the max column lengths
  oci_bind_by_name($stmt, "isbn", $isbn, 32);
  oci_bind_by_name($stmt, "author", $author, 50);
  oci_bind_by_name($stmt, "title", $title, 50);
  oci_bind_by_name($stmt, "price", $price);
  
  if(!oci_execute($stmt)) {
   oci_rollback($this->getDB());
   throw new SoapFault(-1, "Failed to execute query (reason: " .
                                         oci_error($stmt) . ")");
  }
  
  oci_commit($this->getDB());
  
  return true;
 }
 
 /**
  * Delete a book from the database by ISBN
  *
  * @param string $isbn The ISBN serial number of the book to delete
  *
  * @return mixed SOAP Fault on error, true on success
  */
 public function delBook($isbn) {
  
  $query = "DELETE FROM books
                  WHERE isbn = :isbn";
  
  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: " .
                                          oci_error($stmt) . ")");
  }
  
  oci_bind_by_name($stmt, "isbn", $isbn, 32);

  if(!oci_execute($stmt)) {
   oci_rollback($this->getDB());
   throw new SoapFault(-1, "Failed to execute query (reason: " . 
                                          oci_error($stmt) . ")");
  }
  
  oci_commit($this->getDB());
  
  return true;
 }
 
 /**
  * Return a list of books with a specific substring in their title
  *
  * @param string $name The name of the author
  *
  * @return mixed SOAP Fault on error, an array of ISBN numbers on success
  */
 public function findBookISBNByTitle($title) {
  
  $query = "SELECT isbn
              FROM books
             WHERE title LIKE :titlefragment";

  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: " .
                                         oci_error($stmt) . ")");
  }
  
  $bindVar = "%$title%";
  
  oci_bind_by_name($stmt, ":titlefragment", $bindVar, 50);
  
  if(!oci_execute($stmt)) {
   throw new SoapFault(-1, "Failed to execute query (reason: " . 
                                         oci_error($stmt) . ")");
  }  
  
  $rows = array();
  
  while($row = oci_fetch_array($stmt, OCI_ASSOC)) {
   $rows[] = $row[‘ISBN’];
  }
  
  return $rows;
 }
 
 /**
  * Return a list of books written by a specific author
  *
  * @param mixed $author SOAP Fault on error, on array of ISBN numbers on success
  */
 public function findBookISBNByAuthor($author) {

  $query = "SELECT isbn
              FROM books
             WHERE author = :author";

  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: " .
                                         oci_error($stmt) . ")");
  }
  
  oci_bind_by_name($stmt, ":author", $author, 50);
  
  if(!oci_execute($stmt)) {
   throw new SoapFault(-1, "Failed to execute query (reason: " .
                                          oci_error($stmt) . ")");
  }  
  
  $rows = array();
  
  while($row = oci_fetch_array($stmt, OCI_ASSOC)) {
   $rows[] = $row[‘ISBN’];
  }
  
  return $rows;  
 }
 
 /**
  * Return a list of all ISBN numbers in the database
  *
  * @return array An array of ISBN numbers in the database
  */
 public function listAllBooks() {
  
  $query = "SELECT isbn FROM books";

  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: " .
                                         oci_error($stmt) . ")");
  }
  
  if(!oci_execute($stmt)) {
   throw new SoapFault(-1, "Failed to execute query (reason: " .
                                         oci_error($stmt) . ")");
  }  
  
  $rows = array();
  
  while($row = oci_fetch_array($stmt, OCI_ASSOC)) {
   $rows[] = $row[‘ISBN’];
  }
  
  return $rows;
 }
 
 /**
  * Return the details of a specific book by ISBN
  *
  * @param string $isbn The ISBN of the book to retrieve the details on
  *
  * @return mixed SOAP Fault on error, an array of key/value pairs for the ISBN on 
  *               success
  */
 public function getBookByISBN($isbn) {
  
  $query = "SELECT *
              FROM books
             WHERE isbn = :isbn";
  
  $stmt = oci_parse($this->getDB(), $query);
  
  if(!$stmt) {
   throw new SoapFault(-1, "Failed to prepare query (reason: " .
                                          oci_error($stmt) . ")");
  }
  
  oci_bind_by_name($stmt, ":isbn", $isbn, 32);
  
  if(!oci_execute($stmt)) {
   throw new SoapFault(-1, "Failed to execute query (reason: " .
                                         oci_error($stmt) . ")");
  }  
  
  $row = oci_fetch_array($stmt, OCI_ASSOC);  
  return $row;  
 }
}

?>

<?php

require_once ‘BookManager.class.php’;

$server = new SoapServer("bookman.wsdl");
$server->setClass("BookManager");
$server->handle();

?>

  用 PHP 创建 SOAP 客户端

  前面已经说明了如何使用 PHP 创建 SOAP 服务,下面我们来看一下如何创建 SOAP 客户端,以供您的服务器与之通信。

  尽管使用 PHP SOAP 实施通过 SOAP 执行远程过程调用的方法有很多,但我们建议的方法是使用 WSDL 文档。您已经生成了该文档以使 SOAP 服务运行,因此该文档已经存在。

  要使用 PHP 创建 SOAP 客户端,您必须创建一个 SoapClient 类的实例,该类具有以下构造函数:

  $client = new SoapClient($wsdl [, $options]);

  对于 SoapServer 类,$wsdl 参数是要访问服务的 WSDL 文档的位置,可选参数 $options 是配置客户端连接的一组键/值对。以下是一些可用选项(请参见 www.php.net/ 以获得完整列表):
  soap_version:要使用的 SOAP 协议版本,其值为常量 SOAP_1_1 或 SOAP_1_2
  login:如果在 SOAP 服务器上使用 HTTP 身份验证,这是要使用的登录名
  password:如果在 SOAP 服务器上使用 HTTP 身份验证,这是要使用的密码
  proxy_host:如果通过代理服务器连接,这是服务器的地址
  proxy_port:如果通过代理服务器连接,这是代理监听的端口
  proxy_login:如果通过代理服务器连接,这是登录时使用的用户名
  proxy_password:如果通过代理服务器连接,这是登录时使用的密码
  local_cert:如果连接到一个通过安全 HTTP (https) 通信的 SOAP 服务器,这是本地认证文件的位置
  passphrase:与 local_cert 结合使用,以提供认证文件的密码短语(如果有)
  compression:如果设置为 true,PHP 将尝试使用压缩的 HTTP 请求与 SOAP 服务器通信
  classmap:将 WSDL 数据类型映射到 PHP 类以便在客户端使用的一组键/值对
  如果 PHP 中的 SOAP 客户端通过 WSDL 文档实例化,就可以使用返回的客户端对象调用在 SOAP 服务器上公开的方法(就好像它们是自带 PHP 调用),并处理任何可能作为原生 PHP 异常发生的 SOAP 错误。例如,返回到原始 math SOAP 服务示例,以下是一个完整的 PHP SOAP 客户端:
<?php

    $client = new SoapClient(“http://www.example.com/math.wsdl”);
   
    try {
        $result = $client->div(10,rand(0,5); // will cause a Soap Fault if divide by zero
        print “The answer is: $result”;
    } catch(SoapFault $f) {
        print “Sorry an error was caught executing your request: {$e->getMessage()}”;
    }
?>
 
  正如您看到的那样,使用 SoapClient 类访问 SOAP 服务(无论它们是否在 PHP 中实施)很简单。实际上,通过 SOAP 服务为您的书籍数据库创建一个基于 Web 的管理系统是件轻而易举的事!如下所示,与让查询接口直接与 SOAP 服务交互相比,开发这个简单查询接口的逻辑和界面明显需要更多的编码工作。

<HTML>
<HEAD><TITLE>Oracle / SOAP Example by John Coggeshall</TITLE></HEAD>
<BODY>
<?php

$client = new SoapClient("bookman.wsdl");

try {
 
 switch(@$_GET[‘mode’]) {
  
  case ‘title’:
   
   if(!empty($_GET[‘title’])) {
    $isbns = $client->findBookISBNByTitle($_GET[‘title’]); 
   } else {
    print "<B>Error:</B> You must specify at a title fragment!BR/>";
   }
   break;
  case ‘author’:
   
   if(!empty($_GET[‘author’])) {
    $isbns = $client->findBookISBNByAuthor($_GET[‘author’]);
   } else {
    print "<B>Error:</B> You must specify the author to search!<BR/>";
   }
   break;
  default:
   $isbns = $client->listAllBooks();
 }

 print "<TABLE WIDTH=’600′><TR><TD>ISBN</TD><TD>Author</TD>";
     print "<TD>Title</TD><TD>Price</TD></TR>";
 
 if(!isset($isbns) || !is_array($isbns)) {
  
  print "<TR><TD COLSPAN=’4′ ALIGN=’CENTER’><I>No Results Available</I></TD></TR>";
 
 } else {
  
  foreach($isbns as $isbn) {
   $details = $client->getBookByISBN($isbn);
   
   print "<TR>";
   print "<TD>{$details[‘ISBN’]}</TD><TD>{$details[‘AUTHOR’]}</TD>";
   print "<TD>{$details[‘TITLE’]}</TD><TD>{$details[‘PRICE’]}</TD>";
   print "</TR>";
  }
 } 
 print "</TABLE>";
 
} catch(SoapFault $e) {
 
 $msg = (!$e->getMessage()) ? $e->faultstring : $e->getMessage();
 print "Sorry, an error was returned: $msg<HR>";
}

?>
<TABLE>
<FORM ACTION="<?php print $_SERVER[‘PHP_SELF’]; ?>" METHOD="GET">
<INPUT TYPE="hidden" NAME="mode" VALUE="title">
<TR><TD><B>Search By Title:</B></TD>
<TD>
<INPUT TYPE="text" NAME="title" SIZE="50" MAXLENGTH="50">
<INPUT TYPE="submit" VALUE="Search">
</TD></TR>
</FORM>
<FORM ACTION="<?php print $_SERVER[‘PHP_SELF’]; ?>" METHOD="GET">
<INPUT TYPE="hidden" NAME="mode" VALUE="author">
<TR><TD><B>Search By Author:</B></TD>
<TD><INPUT TYPE="text" NAME="author" SIZE="50" MAXLENGTH="50">
<INPUT TYPE="submit" VALUE="Search">
</TD></TR>
</FORM>
<TR>
<TD COLSPAN=’2′ ALIGN=’center’>
<A HREF="<?php print $_SERVER[‘PHP_SELF’]?>">Display All Books</A>
</TD>
</TABLE>
</BODY>
</HTML>

   在执行时,这将通过 PHP 驱动的 Web 服务为 Oracle 数据库提供一个难看但功能完善的界面。

  结论

  现在,您应该具备了所有必备知识,可以使用 Oracle 支持的数据库,并将它们与 PHP 中的 SOAP 功能相结合,以创建强大的 Web 服务。随着 Internet 的演化越来越接近神奇的 Web 2.0,这些服务构成了面向服务体系结构的重要部分,也成为了丰富的 Internet 客户端体验的一个特点。尽管我们没有涵盖 PHP 中的 SOAP 功能的每个细节,但我们只忽略了仅在很少情况下(例如,不使用 WSDL 文档连接到服务)可用的那些功能。

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

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

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

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐

  • SAP收购CallidusCloud 与Salesforce竞争

    一直被称为后台办公巨头的SAP现在似乎也想在前台办公大展拳脚。 最新的迹象是SAP收购CallidusClou […]

  • PHP终于迎来了自己的正式语言规范

    尽管PHP脚本语言早在1995年左右就已经诞生并在Web开发领域占据着重要地位,但其一直没有自己的正式语言规范——只提供广泛的用户说明文档。

  • 通过四种方式让PHP编码变得更轻松

    尽管已经目前PHP语言已经建立起属于自己的软件文化,但要找到它令人抓狂的弊端也绝对不是难事。我们曾经在之前的文章中列出过该语言最让开发人员难以接受的十二大糟糕特性。

  • API设计如龙生九子 各不相同

    IT咨询管理公司CA Technologies对API产业做了个问卷调查,问卷内容涉及API设计风格以及管理部署的新动向。调查结果表明,JSON与XML可谓两分天下。