现 在已经到了用 Ajax 技术开发三个应用程序层的最后阶段。我们要使用 Zend Core 和 Eclipse IDE 工具构建、部署和测试应用程序。还要研究 MySQL、PHP、Ajax(XHTML、CSS、JavaScript、XHR)、REST、JSON、XML 和一个 Web 服务客户机。
您 可能会注意到,提供特定银行出纳员功能的所有 HTML 表单都将用户数据发送给一个基于 REST 的中间层服务。我们将在另一个 PHP 模块中实现这个服务,这个模块接受银行出纳员的浏览请求,包括存款、取款和当前股票组合价值查询。这个 PHP 模块作为银行操作的请求分配器,它解析银行出纳员的请求并调用适当的 Bank Logic 函数以处理这个请求。您会看到用 PHP 编写这样的 REST 服务是多么容易。
在开发用于 REST 请求分派的 PHP 服务之后,我们的重点将转到一个可以通过互联网访问的基于 .NET 的 Web 服务。这个免费的 Web 服务提供给定股票的当前价格。我们将在 PHP 中间层中开发一个 Web 服务客户机,这样就可以从基于 PHP 的银行操作请求分配器远程调用这个 Web 服务,从而获得股票的报价。然后,银行操作请求分配器将用当前股票价格调用 Bank Logic PHP 模块,计算出给定帐户持有人的当前股票组合价值。您将学习使用 SOAP 访问 Web 服务的技术,还要学习 XML 和 JavaScript Object Notation(JSON)这两种流行的数据交换格式。
Web 服务 是一个描述一组操作的接口,可以用 XML 和 JSON 等标准化消息格式通过网络访问这些操作。Web 服务主要用于程序之间的交互。Web 服务使应用程序可以更快速、轻松而且成本低廉地集成起来。通常在协议栈的较高层,基于以业务服务语义为中心的消息进行集成。这样就可以在企业内外以通用的 方式对业务功能进行松散的集成。
实现通用 Web 服务模型的关键是一组标准。比较重要的标准包括 HTTP、SOAP、REST、XML、JSON 和 WSDL(Web Services Description Language)。
在 WSDL 服务接口中,类型定义服务消费者和服务提供者之间的交互所用的定制 XML 数据类型。消息指定在服务消费者和服务提供者之间传输的有效负载的各个部分的 XML 数据类型。操作指定在 Web 服务交互的输入和输出中可以出现哪些消息。端口类型定义允许的 Web 服务操作。最后,绑定描述协议和数据格式。
银 行出纳员操作之一是获得给定的帐户持有人的当前股票组合价值。当银行出纳员从 Web 页面上选择这个选项时,一个 HTTP 请求被发送到中间层 PHP 服务。在调用 Bank Logic PHP 模块计算股票组合的总价值之前,需要将当前股票价格作为参数发送给它。为了获得帐户持有人股票组合中一只股票的当前股票价格,需要访问互联网上的一个远程 Web 服务。
为了访问远程 Web 服务,必须使用 WSDL 生成一个 Web 服务客户机代理。在 PHP 中,只需几行代码就可以完成这个任务。通过使用 PHP SOAP Client 库,就可以从服务提供者提供的 WSDL 动态地生成 Web 服务客户机。在这个示例中,我们要使用 WebserviceX.net 在互联网上免费提供的一个基于 .NET 的股票报价服务。您应该访问 参考资料 一节中提供的 WebServiceX.net 链接,花点儿时间研究这个远程股票报价服务的 WSDL 文件。
按照以下步骤用 WebserviceX.net 上发布的股票报价服务 WSDL 文件在 PHP 中创建一个 Web 服务客户机:
<?php /* ============================================================ Project: End-to-End-Ajax application development
Purpose: This is an example scenario to be used in an IBM developerWorks article.
Last modified: May/17/2007.
This PHP module provides the logic to access a remote Web service hosted on the Internet. This Web service will return the complete and current stock details about a given stock ticker symbol.
This module shows how easy it is to consume a remote Web Service (running on a .NET platform) using PHP dynamically by pointing to a remote WSDL file for that Web service.
It uses PHP SOAP Client library to accomplish that. ============================================================ */ // Get the required Bank Logic PHP module. require_once("BankLogic.php");
This function accepts an account holder name as input. In this scenario, all the account holders have a single stock in their respective portfolio. The code here obtains the ticker symbol of the stock held by the given account holder. Then it uses PHP SOAP client to remotely invoke the .NET based stock quote Web service and obtains the XML-based response returned by that service. It parses the XML-based stock details response and gets the current stock price of that stock. It then calls another function inside of the Bank Logic PHP module to update the database with the new portfolio value. ============================================================ */ function getStockPortfolioValue($accountHolderName) { // From the Bank Logic module, obtain all the account // information stored in the database. // [If someone wants to optimize it, a new function // could be written directly to return the account // information for the given account holder, instead of // retrieving all the account information.] $finalResult = getAllAccountInformation();
// Ensure that all the account information was read from the DB. if ($finalResult["ResultCode"] != 0) { // Some error occurred. return now. return($finalResult); } // End of if ($finalResult["ResultCode"] != 0)
// Get the account info of the customer whom we are dealing with now. $accountInfoArray = $finalResult["AccountInfo"]; $tickerSymbol = null;
// Loop through all the available account information and filter // the account information for the account holder's name that was // passed as a parameter to the function we are in now. foreach ($accountInfoArray as $accountInfo) { // Is it the account for the person of interest? if (strcasecmp($accountInfo["AccountHolderName"], $accountHolderName) == 0) { // Get the stock ticker symbol of the only stock held in this portfolio. $tickerSymbol = $accountInfo["StockName"]; // We got it. Skip out of the loop now. break; } // End of if (strcasecmp($accountInfo["AccountHolderName"] ... } // End of foreach ($accountInfoArray as $accountInfo)
// If there was an error in getting the ticker symbol, return now. if ($tickerSymbol == null) { $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "Unable to get ticker symbol from the account of $accountHolderName."; return($finalResult); } // End of if ($tickerSymbol == null)
// This is the WSDL published by the service provider. $wsdl = "http://www.webservicex.net/stockquote.asmx?WSDL"; // The magic happens here in the next four lines to execute // a fairly complex function remotely over the Internet. // It is really cool. Why can't the other middleware runtimes follow // the simplicity of PHP to do such things. // Instantiate the PHP SOAP client library by pointing directly to // remote WSDL URL. $proxy = new SoapClient($wsdl, array("trace"=>1,"exceptions"=>0)); // Set the required input parameter for the remote service. // In our case, it is just the ticker symbol. $param['symbol'] = $tickerSymbol; // Execute the remote logic. It is that simple. $result = $proxy->GetQuote($param); // You have the XML-based response string now. $quoteResult = $result->GetQuoteResult;
// Convert the XML formatted string into a DOM object. // PHP also does XML processing so elegantly and simply. $xml = simplexml_load_string($quoteResult); $stockPrice = 0.0;
// The XML-based stock quote response is somewhat elaborative. // It contains information about different aspects of the given stock. // We are interested only in the current market price of that stock. // That information is available as: // <Stock>...<Last>56.34</Last>...</Stock> // All we have to do is just parse the value of the <Last> XML element. // See for yourself how easy it is to do this in PHP. if (property_exists($xml, "Stock") == true) { // We have the <Stock> element in the Web service response. $stockInfo = $xml->Stock;
// Now, check if the <Stock> element contains a child named <Last>. if (property_exists($stockInfo, "Last")) { // Just retrieve the last market price of this stock. $stockPrice = $stockInfo->Last; } else { $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "Unable to get current stock price of " . "$accountHolderName's stock $tickerSymbol."; return($finalResult); } // End of if (property_exists($stockInfo, "Last")) } else { $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "Unable to get current stock price of " . "$accountHolderName's stock $tickerSymbol."; return($finalResult); } // End of if (property_exists($xml, "Stock") == true)
// Now that we have the current stock price, compute the // portfolio value and update it in the DB. // Return the result to the caller of this function. $finalResult = portfolioValue($accountHolderName, $stockPrice); return($finalResult); } // End of function getStockPortfolioValue ?>
前 一节中创建 Web 服务客户机的过程只是 PHP SOAP 客户机库支持的简单方式之一。GetStockPrice.php 中的逻辑让 Web 服务客户机访问 URL http://www.webserviceX.net 上的股票报价服务。PHP SOAP 客户机库直接指向服务提供者发布的 WSDL URL,并使用 WSDL 中的服务描述信息在内存中动态地创建一个内部服务代理。然后,代理代码进行远程调用;因此,对于客户机而言,股票报价功能就像是在本地一样。在这个示例 中,客户机代理使用 SOAP 访问协议与远程股票报价服务进行交互,它发送一个股票代号作为输入,然后接收股票报价服务输出的一个 XML 文档。
GetStockPrice.php 中的逻辑很简单,它只包含一个函数:getStockPortfolioValue。这个 PHP 模块需要通过 BankLogic.php 模块让这里的数据库访问函数可用。getStockPortfolioValue 函数接受一个帐户持有人姓名参数,并为这个帐户计算和更新股票组合价值。它获取数据库中存储的所有帐户信息,并过滤出属于给定帐户持有人的信息。可以不获 取所有帐户的信息,而是只获取特定帐户持有人的帐户信息。(这个修改留给您作为练习。)在过滤后的帐户信息中,获得股票组合中一只股票的代号。然后,实例 化一个 PHP SoapClient 对象,并将股票报价服务 WSDL URL 作为参数传递给类构造器。股票代号作为这个 Web 服务的输入参数。然后调用代理上的 GetQuote 方法,这个方法会对远程 Web 服务进行 SOAP 调用。调用成功时,Web 服务返回一个 XML 格式的字符串。PHP 提供了将 XML 字符串转换为 XML DOM 对象结构的简便方法。在这个 XML 树中,<Last> 元素包含股票当前的市场价格。解析 <Last> 的值。最后,调用 Bank Logic PHP 模块中的 portfolioValue 函数,并以帐户持有人姓名和当前股票价格作为参数。这个函数使用当前股票价格计算新的股票组合价值,更新数据库并返回一个关联数组,其中包含以前的股票组合价值和当前的价值。这个关联数组也返回给 getStockPortfolio 函数的调用者。
远程股票报价服务的结果是一个 XML 文档,其中包含关于指定的股票代号的许多信息。清单 3 给出股票报价服务对于股票代号 IBM 的输出示例。输出的 XML 包含许多信息,比如报告股票价格的日期和时间、开盘价和收盘价、价格变化的百分比、本交易日的最高价和最低价、成交量、一年内的价格波动范围、每股收入和 股票的 P/E 比率。在所有这些信息当中,我们只关心最近的价格,这一信息包含在 <Last> XML 元素中。getStockPrice 方法中其余的代码使用 DOM 解析器对 Web 服务的整个 XML 结果进行解析。
<?php /* ============================================================ Project: End-to-End-Ajax application development
Purpose: This is an example scenario to be used in an IBM developerWorks article.
Last modified: May/17/2007.
This module is the REST service request handler for the server-side of the bank scenario. It receives the REST (HTTP POST) requests from the single-page based bank teller browser clients. It dispatches different request requests to different bank logic functions. It also sends and receives application-specific data in a previously agreed JSON format. ============================================================ */ // Get the other required PHP modules. // JSON.php is an open source JSON parser in PHP. // It can be obtained from: : http://mike.teczno.com/JSON/JSON.phps require_once("JSON.php"); // Bank Logic module has all the core bank teller server-side functions. require_once("BankLogic.php"); // GetStockPrice is a Web service proxy client to a remote Web service. require_once("GetStockPrice.php");
// Define all the constants that will be used here. define ("BANK_TELLER_COMMAND_KEY", "Bank_Teller_Command"); define ("BANK_TELLER_POST_DATA_KEY", "Post_Data"); define ("GET_ALL_ACCOUNTS_INFO", "Get_All_Accounts_Info"); define ("DEPOSIT_TO_ACCOUNT", "Deposit_To_Account"); define ("DEBIT_FROM_ACCOUNT", "Debit_From_Account"); define ("GET_STOCK_PORTFOLIO_VALUE", "Get_Stock_Portfolio_Value");
// XHTML form data from the bank teller Ajax browser client will be // available in one of the PHP superglobals // i.e. $_REQUEST associative array. // Each REST request from the client will have two key-value pairs. // First one is the bank teller command that tells if the client // wants to perform deposit, debit, portfolio value etc. // The second key-value pair is the application specific data sent as // JSON-formatted string. $bankTellerCommand = $_REQUEST[BANK_TELLER_COMMAND_KEY]; $bankTellerPostData = $_REQUEST[BANK_TELLER_POST_DATA_KEY];
// Using JSON in PHP is as easy as it can get. // Instantiate the JSON parser (available through JSON.php) $json = new Services_JSON(); // In one statement, convert the JSON formatted text to // an equivalent PHP class structure. That is it. $bankTellerInput = $json->decode($bankTellerPostData); $responseToBeSent = ""; $bankActionResult = null;
// Switch through the bank teller command issued by the browser client. switch($bankTellerCommand) { case GET_ALL_ACCOUNTS_INFO: // Go and get all the account information stored in the database. $bankActionResult = getAllAccountInformation(); break;
case DEPOSIT_TO_ACCOUNT: // Perform the deposit account transaction. // Pass the account holder name and the deposit amount as // sent by the browser client. Third parameter of 1 indicates DEPOSIT. // This function is available in BankLogic.php $bankActionResult = accountTransaction( $bankTellerInput->accountHolderName, $bankTellerInput->amount, 1); break;
case DEBIT_FROM_ACCOUNT: // Perform the debit account transaction. // Pass the account holder name and the debit amount as // sent by the browser client. Third parameter of 2 indicates DEBIT. // This function is available in BankLogic.php $bankActionResult = accountTransaction( $bankTellerInput->accountHolderName, $bankTellerInput->amount, 2); break;
case GET_STOCK_PORTFOLIO_VALUE: // Perform the "Update Stock Portfolio Value" transaction. // This will require going out on the Internet to a remote Web service. // This function is available in GetStockPrice.php $bankActionResult = getStockPortfolioValue($bankTellerInput->accountHolderName); break;
default: // Invalid Bank teller command. $bankActionResult["ResultCode"] = 1; $bankActionResult["ResultMsg"] = "Invalid Teller Command"; } // End of switch($bankTellerCommand)
// We completed everything that the client asked us to do. // Let us return the final results in JSON formatted text. // It takes one line of code in PHP to convert a // PHP associative array data structure into JSON formatted text. $responseToBeSent = $json->encode($bankActionResult); // Set the HTTP header to indicate that we are sending JSON plain text data. // There are also efforts underway at this time (MAY/2007) to define a // new content type called text/json. header("Content-Type: text/plain"); // That is it. Return the JSON formatted response in // RESTful way back to the browser now. echo($responseToBeSent); ?>
现在,已经开发了银行场景所需的 Ajax 客户机、PHP 中间层服务和 MySQL 数据库组件,如下所示:
BankDB(基于 MySQL 的银行数据库)
BankLogic(一个 PHP 模块中银行出纳员函数的代码逻辑)
BankPortal(使用 Ajax 技术的银行出纳员 Web 客户机)
BankActions(用 PHP 编写的银行出纳员操作 REST 服务)
Stock Quote Web 服务客户机(使用 PHP SOAP Client 库的 SOAP 代理)
这 些组件分别处理银行场景中的一项任务。正如在这个系列中看到的,每个组件依赖于其他一些组件,从而连接成银行场景的端到端体系结构。在典型的三层 Web 应用程序中,为了集成各个应用程序组件,.NET 这样的底层框架需要详细配置依赖项。但是,用于实现这个场景的技术不需要这么复杂的集成过程。在实现这些组件时,已经考虑到了必需的所有依赖项。Ajax (XHTML、CSS、JavaScript、REST、JSON)、PHP 或 MySQL 都不需要应用程序特有的运行时集成,因此大大减少了端到端应用程序的开销和复杂性。
Senthil Nathan 是位于纽约 Hawthorne 的 IBM T.J. Watson Research Center 的一位高级软件工程师。在为不同类型的企业应用程序构建软件方面,他有 22 年经验。他当前感兴趣的领域包括 SOA、Web 服务、Java 2 Platform, Enterprise Edition(J2EE)、PHP、Ruby On Rails、Web 2.0 和 Ajax 开发。