转载--开发端到端的 Ajax 应用程序,第 3 部分: 集成、测试和调试应用程序

转载 2007年09月16日 11:00:00
开发端到端的 Ajax 应用程序,第 3 部分: 集成、测试和调试应用程序
隔离应用程序层以产生干净优雅的 Web 应用程序
developerWorks
文档选项
将此页作为电子邮件发送


级别: 中级
Senthil Nathan (sen@us.ibm.com), 高级软件工程师, IBM
2007 年 8 月 13 日
Ajax (Asynchronous JavaScript + XML)正在迅速地成为时髦的技术,它可以为在浏览器中运行的 Web 应用程序提供具有桌面质量的软件特性。这个分三部分的系列讨论如何使用开放源码技术开发端到端 Ajax 应用程序,本文是这个系列的最后一篇文章。

本系列的前两部分 中,设置了一个开发环境,它由 LAMP 风格的运行时和 Eclipse IDE 组成。定义了一个虚构的银行场景以演示重要的 Ajax 概念。然后,完成了场景的一部分,包括创建数据库、中间层 PHP 逻辑和一个简单 XHTML 以提供一个单页面浏览器 GUI,还提供了 CSS 样式代码和几个 XML HTTP Request(XHR)实用程序函数。在本系列的第三篇(也就是最后一篇)中,我们要用 JavaScript 实现 Ajax 客户端逻辑,从而完成这个场景的其余部分。还要用 PHP 构建一个 Representational State Transfer(REST)请求分配器以完成客户的银行任务,比如存款、取款和股票组合价值查询。还要用 PHP 开发一个 SOAP(Simple Object Access Protocol)Web 服务客户机,用它访问一个真实的第三方(免费)Web 服务。您将看到如何把本系列中开发的所有组件(XHTML、CSS、JavaScript、PHP、Web 服务客户机和 MySQL)集成起来。最后,本文解释用 LAMP 风格的运行时部署、测试和调试场景的基本方法。到那时,我们就完成了一个比较复杂的端到端场景示例,它演示了 Ajax 客户机、Apache-PHP-MySQL 运行时和相关的 Eclipse IDE 的强大特性。

developerWorks Ajax 资源中心
请访问 Ajax 资源中心,这里几乎囊括了关于 Ajax 编程模型的所有信息,包括各种文章和教程、论坛、博客、wikis、活动和新闻。
简介

在开始学习本文之前,要确保(本系列 第 2 部分 中开发的)Bank 场景工件 Bank DB、Bank Logic 和 Bank Portal 在 Eclipse IDE 中保持原样。这些工件包括创建和填充数据库的 SQL 脚本;提供数据库访问的 PHP 代码;以及单页面浏览器应用程序所需的 XHTML、CSS 和 XHR。用这些工件编写的代码只提供了 图 1 所示的银行场景的一部分功能。还缺少将这些已经开发的工件组合起来的组件。如果预览 XHTML 文件,就会看到它在一个浏览器窗口中显示应用程序特有的各个 UI 屏幕。仍然需要编写适当的客户端 JavaScript 逻辑以集成这些 UI 屏幕,产生单页面浏览器应用程序的效果。在客户端代码中,还需要添加适当的 XHR 异步通信逻辑。如果没有客户端 JavaScript 代码,就无法执行银行出纳员功能。


图 1. 银行场景
银行场景
现 在已经到了用 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)这两种流行的数据交换格式。

用 JavaScript 编写的客户端逻辑

在这个单页面浏览器应用程序中,需要通过客户端逻辑处理 Ajax 风格的用户界面并与中间层服务进行数据交换。正如本系列 第 2 部分 所述,可以使用当前的许多 Ajax 框架和库之一进行开发。但是,由于我们只关注很少的几种 Ajax 特性,所以将用 JavaScript 编写客户端逻辑。这样做有助于我们将注意力集中在本文的 Ajax 相关目标上:

  • 单页面浏览器应用程序
  • 浏览器 DOM 操作
  • 在应用程序的上下文中使用 XHR
  • JSON 在应用程序数据交换方面的作用
  • Ajax 代码的客户端调试
一旦理解了这个银行场景中的 JavaScript 代码,您就可以尝试使用某个 Ajax 框架实现相同的客户端逻辑。无论采用哪种方式,基于 Eclipse 的 Aptana Web IDE 都是进行客户端代码开发的理想环境。

众 所周知,JavaScript 提供了强大的语言特性。在浏览器环境中,JavaScript 提供了事件处理方法,可以捕捉用户操作和系统活动(比如计时器和网络事件)。在我们的银行场景中,将使用事件处理方法处理鼠标操作(mouseover、 mouseout、mouseclick 等等),并使用 XML HTTP Request(XHR)对象处理服务器响应异步回调。

单 页面浏览器应用程序只需在浏览器中输入应用程序 URL 时,从服务器下载一次所有与用户界面相关的代码工件(XHTML、CSS 和 JavaScript)。这意味着,客户端逻辑应该获得处理银行场景的客户端需求所需的所有东西。屏幕布局和相关联的样式是通过 XHTML 和 CSS 文件提供的。其他客户端逻辑由 JavaScript 代码提供。逻辑的主要部分涉及到分割不同的应用程序屏幕,并根据用户导航显示适当的一个屏幕。本系列的 第 2 部分 讲解了如何使用 <DIV>、<SPAN> 和 <TABLE> HTML 元素安排不同的屏幕,从而为整个 Web 应用程序创建单一 XHTML 页面。通过使用 JavaScript,您将学习如何操作浏览器 DOM 以隐藏和显示 <DIV> 元素中的用户界面控件。另外,还要学习如何动态地分配 CSS 规则,从而控制用户界面控件的样式。每当这个应用程序的用户(常常是银行出纳员)执行帐户相关任务(比如存款、取款或股票组合价值更新)时,客户端 JavaScript 逻辑会以 REST 方式与中间层 PHP 服务进行通信。在这种交互中,在浏览器客户机和中间层 PHP 服务之间交换应用程序特有的数据。这些操作都使用 XHR 对象完成(参见本系列 第 2 部分 中的说明)。JavaScript 客户端逻辑使用 XHR 对象与服务器交换应用程序特有的数据。您将学习以异步模式接收服务器响应的 Ajax 方式。

在 Ajax 的组成部分(异步、JavaScript 和 XML)中,我们已经讨论了异步JavaScript。 现在,要讨论 Ajax 开发的另一个重要组成部分:数据表示。尽管 Ajax 这个缩写词暗示以 XML 作为数据表示格式,但是 XML 不是 Web 应用程序可用的惟一数据交换机制。因为 XML 是软件开发社区所熟悉的格式,所以本文不需要重复介绍 XML 的知识,而是介绍另一种流行的 Web 数据交换格式 JSON。与 XML 相似,JSON 也是基于文本的,是人和机器都可读的。从机器可读性的角度来看,JSON 似乎更容易解析和使用 —— 尤其是在浏览器应用程序中 —— 因为它采用 JavaScript 对象和数组数据结构的语法。在浏览器应用程序中使用 JSON,就可以将整个数据结构作为第一类 JavaScript 对象对待。这对于许多 Web 开发人员是个好消息,因为这完全避免了额外的解析工作。JSON 格式还可以在其他流行的编程语言中使用,比如 Java 语言、PHP、Ruby 和 C++ 等等。下一节详细讲解 JSON 的组成部分。还可以从 参考资料 一节找到完整的 JSON 信息的链接。还有一些工具可以将现有的 XML 数据转换为 JSON 格式。可以通过 参考资料 中引用的另一篇 IBM® developerWorks 文章了解这种技术。我们的银行场景的客户端逻辑使用 JSON 作为浏览器和中间层 PHP 服务之间的数据交换格式。在后面几节中,学习如何在 JavaScript 和 PHP 中处理 JSON。

JSON 的结构
JSON 由两种结构组成:
  • 名称/值对的集合
    • 在各种语言中,它表示为以下形式之一:
      • 对象
      • 记录
      • 结构
      • 散列表
      • 关联数组
  • 有序的值列表
    • 在大多数语言中,这表示为数组
这些是通用的数据结构。
  • 所有现代编程语言都支持它们。
  • 由这些结构组成的数据格式(JSON)可以在不同的编程语言之间方便地交换。

了解了这些基本组件,再加上从本系列的 第 1 部分第 2 部分 获得的知识和经验,您现在应该能够理解 Ajax 开发的概念。尽管组成 Ajax 的许多技术已经存在很长时间了,但是以 Ajax 方式组合使用这些技术来开发应用程序还是一个新现象。这种技术组合方式使得以端到端方式调试 Web 应用程序变得非常重要。在本文后面,您将在银行场景的上下文中学习调试方法。

用 JavaScript 实现客户端逻辑

正如前一节中指出的,JSON 是这个银行场景选用的数据交换格式。可以通过 JavaScript eval 语句在浏览器应用程序中直接使用。但是,直接执行任意的 JSON 代码会导致安全问题。为了缓解这些安全问题,Douglas Crockford(JSON 的创建者)开发了一个简单的 JavaScript 库,它先对 JSON 格式的文本进行安全检查,然后才能将它转换为 JavaScript 对象。我们依靠这些库函数处理 JSON 数据。

首先,用 JavaScript 创建客户端逻辑:

  1. 打开浏览器并访问 URL:http://www.json.org/json.js
    1. 在 File Download 对话框中,单击 Save
    2. 将 json.js 文件保存在 c:/eclipse/workspace/BankTeller 目录中并关闭浏览器。
  2. 切换到 Eclipse 中的 Aptana 透视图:单击 Window->Open Perspective->Other->Aptana 并单击 OK
  3. 在 Aptana 透视图的左下角,选择 Project 选项卡式视图。
  4. 右击 BankTeller 项目并单击 Refresh。现在应该会看到 json.js 文件已经成为 BankTeller 项目的一部分。
  5. 右击 BankTeller 项目并选择 New->JavaScript File
    1. 在 File name 字段中,输入 BankTeller.js 并单击 Finish
  6. 粘贴 清单 1 中的源代码以替换这个文件的内容,然后保存文件。可以通过这个文件中的注释了解代码的作用,或者参考下一节中对代码逻辑的解释。
对客户端逻辑的说明

BankTeller.js 文件中的客户端逻辑覆盖了重要的 Ajax 概念,涉及创建单页面浏览器应用程序、以异步方式与中间层服务进行通信以及使用 REST/JSON 进行服务访问和数据交换。这些工作主要通过处理用户交互的 JavaScript 事件处理方法和 XHR 对象来完成。这个文件中的函数还需要另外两个 JavaScript 文件(xhr.js 和 json.js)中的其他实用程序函数。这个文件中的代码首先定义这个应用程序特有的变量和常量。然后是 JavaScript 函数(其次序无所谓)。以下 10 个函数与银行出纳员应用程序的用户交互相关:

  • initOnPageLoad
  • showFooterOptions
  • changeOptionsLinkStyleForSelection
  • changeOptionsLinkStyleToDefault
  • processTellerOperation
  • hideShowAndInsertAsTopElement
  • populateDropDownListWithAccountOwnersData
  • setElementFocusAndTextAndCursorPosition
  • processFooterOperation
  • updateTellerActionResultTextArea

以下 9 个函数用于与中间层服务交换应用程序特有的数据:

  • getAllAccountsInformation_Async
  • http_response_async_callback_for_get_all_acounts_info
  • depositAmountAction_Async
  • http_response_async_callback_for_deposit_to_account
  • debitAmountAction_Async
  • http_response_async_callback_for_debit_from_account
  • portfolioAction_Async
  • http_response_async_callback_for_get_stock_portfolio_value
  • getHostnameFromThisURL

这 个文件中的注释很充分,足以帮助您理解这个 Ajax 浏览器应用程序的内部工作原理。下面从较高层面解释一些客户端 JavaScript 函数的作用。可以通过 BankTeller.js 文件中的注释了解更多细节。当用户的浏览器指向这个应用程序的 URL 时,只从服务器获取这个应用程序的一个 XHTML 页面,并装载到浏览器中。在装载最初的页面时,运行 initOnPageLoad 函数。正如前面提到的,这个应用程序的用户界面只包含一个 XHTML 页面,这个页面用 HTML <DIV> 元素分割为不同的部分。有一个称为 “mainPage” 的主 <DIV> 元素,它处于浏览器 DOM 树结构的顶层。其他所有 <DIV> 元素都是主 <DIV> 元素的子元素。在这个函数中,将这个应用程序中的所有 <DIV> 元素存储在全局变量中。对于除了主页面和出纳员菜单项之外的所有 <DIV> 元素,都应用一个 CSS 规则以隐藏它们。因为这些 <DIV> 元素已经存储在全局变量中,所以还从父 <DIV> 元素中删除隐藏的 <DIV> 元素,以避免浏览器中出现垂直滚动条。然后发出一个 Ajax XHR 调用,以异步方式获得所有银行帐户信息。

showFooterOption 函数在应用程序的一个 <DIV> 部分中可选地显示或隐藏页脚。页脚显示当前的出纳员姓名和一个返回主菜单的选项。它使用 “visible” 或 “hidden” 等 CSS 内置样式显示或隐藏页脚。

这 个应用程序使用一个菜单选择要执行的银行出纳员选项。应用程序的主屏幕上显示这个菜单。这个菜单结构是用常规的 HTML 代码编写的,嵌入在 XHTML 文件中的 <SPAN> 元素中。我们给这些 <SPAN> 元素设置了 mouseover、mouseout 和 onclick 鼠标事件处理函数。当发生 mouseover 和 mouseout 事件时,调用对应的事件处理函数(changeOptionsLinkStyleForSelection 和 changeOptionsLinkStyleToDefault)。在这些函数中,用一个 CSS 类规则切换这些 <SPAN> 元素的样式,分别设置背景颜色和取消背景颜色。这样就实现了突出显示菜单项的效果。

processTellerOperation 函数是 onclick 事件的处理函数,这个事件在用户单击 <SPAN> 元素中的菜单项文本时触发。传递给这个函数的参数表示用户选择了哪个菜单项。在这个函数中,当前选择的菜单项被存储在一个全局变量中,这个变量记录出纳员 当前执行的银行出纳员选项。然后,隐藏菜单项 <DIV> 元素,并显示与所选的银行出纳员选项对应的另一个 <DIV> 元素。在新显示的屏幕(存款、取款或股票组合价值)上,列出了所有帐户持有人的姓名。出纳员可以从这个列表中选择一个帐户持有人,并执行所需的银行出纳员 操作。

hideShowAndInsertAsTopElement 是一个实用程序函数,它有几个输入参数,比如要隐藏的元素、要显示的元素、具有焦点的元素的 id、在输入字段上设置的可选的光标位置以及是否显示页脚的选项。它根据输入参数显示或隐藏对应的元素。它还可以通过其他输入参数实现其他效果。

populateDropDownListWithAccountOwnersData 函数将所有帐户持有人的姓名插入一个列表框。这帮助银行出纳员选择一个特定的客户以执行所需的出纳员函数。这个函数还对帐户持有人的姓名进行排序。

updateTellerActionResultTextArea 函数更新一个文本区域中的银行出纳员操作的结果。另一个函数 processFooterOperation 处理页脚中可用的选项。

现 在,我们来看看与 XHR 相关的函数。因为我们的应用程序是一个真正的单页面 Ajax 应用程序,所以表示内容只在应用程序启动时下载一次。在此之后,浏览器客户机对服务器的访问仅仅是为了交换应用程序特有的数据。通过以 REST(Representational State Transfer)风格访问中间层服务,使用一个 XHR 对象以异步方式执行数据交换。REST 使用 HTTP 动词,比如 PUT、GET、POST 和 DELETE,这指出如何通过数据交换访问远程服务器资源。(关于 REST 的更多信息参见 参考资料。)

在 许多 Ajax 应用程序中,XHR 与 REST 访问机制配合得很好。正如前几节中提到的,这个应用程序使用 JSON 格式进行数据编码。在这个应用程序中,在浏览器和中间层服务之间有四种不同的数据交换。这四种数据交换涉及的过程是非常相似的。因此,这里只对银行出纳员 存款操作的数据交换进行说明。每次数据交换通过两个不同的 JavaScript 函数完成:一个函数向中间层服务发送请求,另一个函数以异步方式接收服务器响应数据。当银行出纳员执行存款操作时,他输入存款数量并选择帐户持有人姓名, 然后按 Deposit 按钮。这个按钮的 onclick 事件处理函数设置为 depositAmountAction_Async。 在这个函数中,从输入字段读取存款数量并进行检验。然后,从下拉列表中选择帐户持有人姓名,从完全限定的文档 URL 获得主机名。这时,创建一个 JSON 对象,它有两个对象属性:帐户持有人姓名和存款数量。这就是用 REST 和 HTTP 动词 POST 发送到中间层服务的数据。

为 了将 JSON 对象转换为 JSON 格式的字符串,要使用 json.js 文件(由 Douglas Crockford 开发)中的一个实用程序函数。然后,创建一个 HTTP POST 查询字符串,其中包含两个键 - 值对。第一个键 - 值对告诉中间层服务需要执行什么资源或命令 —— 在这个示例中,它是存款命令。第二个键 - 值对告诉中间层服务从浏览器传入的请求数据 —— 在这个示例中,它是由帐户持有人姓名和存款数量组成的 JSON 格式的数据。然后,指定中间层服务的 URL。在此之后,创建一个新的 XHR 对象;最重要的是,设置回调函数 http_response_async_callback_for_deposit_to_account。然后调用 xhr.js 中的实用程序函数 sendHttpRequest,用 XHR 对象、回调函数、中间层服务 URL 和 POST 数据作为输入参数。这会向中间层服务发送存款操作的数据交换请求。

现在看看这个回调函数。在每次 HTTP 状态转换时,都调用这个回调函数。但是,在这个回调函数中检查的 HTTP 状态只有 HTTP_READYSTATE_LOADEDHTTP_STATUS_OK。这两个状态表示已经完整地收到了服务器响应数据,而且 HTTP 内部状态码是 200。如果这两个条件都满足,那么从 XHR 对象的内部属性 responseText 读取服务器响应数据。可以使用 json.js 中的实用程序函数 parseJSON 安全可靠地转换这个 JSON 格式的字符串数据。这个转换操作会产生一个 JavaScript 对象。在这个对象中,检查应用程序特有的状态,了解存款操作是否成功了。如果成功了,就将返回的帐户信息对象存储在全局变量 tellerActionResultInfo 中。最后,隐藏当前的存款操作屏幕,显示银行出纳员操作结果屏幕,并更新结果数据。
前面的段落对客户端 JavaScript 代码的内部逻辑做了高层面的说明。为了简洁,客户端逻辑没有处理真实环境中可能发生的许多运行时错误。请务必仔细查看 清单 1 并阅读其中的注释。
下面几节关注 Web 服务并完成银行场景实现的其余部分。
Web 服务体系结构模型

Web 服务 是一个描述一组操作的接口,可以用 XML 和 JSON 等标准化消息格式通过网络访问这些操作。Web 服务主要用于程序之间的交互。Web 服务使应用程序可以更快速、轻松而且成本低廉地集成起来。通常在协议栈的较高层,基于以业务服务语义为中心的消息进行集成。这样就可以在企业内外以通用的 方式对业务功能进行松散的集成。

实现通用 Web 服务模型的关键是一组标准。比较重要的标准包括 HTTP、SOAP、REST、XML、JSON 和 WSDL(Web Services Description Language)。

图 2 描述了 Web 服务模型中涉及的重要角色和交互。Web 服务体系结构模型中涉及三个角色:

  • 服务提供者
  • 服务注册处
  • 服务请求者

Web 服务体系结构模型中涉及三种交互:

  • 发布
  • 寻找
  • 绑定

图 2. Web 服务角色和交互
WS 角色和交互

服务提供者(service provider) 是服务的所有者或平台。服务请求者(service requester) 或服务消费者(service consumer)是寻找并调用服务或者发起与服务的交互的应用程序。服务消费者角色可以是由人控制的浏览器应用程序,也可以是没有用户界面的程序(比如 PHP、Java、Ruby、servlet、EJB)。服务注册处(service registry) 是一个可搜索的服务描述目录,服务提供者将他们的服务描述发布到这里。服务消费者通过注册处寻找服务并获得服务的相关信息。

发布(Publish)交互让服务提供者能够发布服务描述,从而让服务消费者可以找到服务。寻找(Find)交互让服务消费者能够直接获得服务描述。绑定(Bind)交互让服务消费者能够使用服务描述中指定的绑定细节,在运行时调用服务或者发起与服务的交互。
Web 服务模型中的一个重要部分是服务描述。WSDL 是一种服务描述标准,它让服务提供者能够以机器和人都可读的格式描述他们的服务。图 3 显示 WSDL 文档的结构。WSDL 标准有两个部分:服务接口和服务实现。服务接口(service interface) 是一个抽象的或可重用的服务定义,可以由多个服务实现引用。服务实现(service implementation) 描述一个给定的服务提供者如何实现某个服务。

在 WSDL 服务接口中,类型定义服务消费者和服务提供者之间的交互所用的定制 XML 数据类型。消息指定在服务消费者和服务提供者之间传输的有效负载的各个部分的 XML 数据类型。操作指定在 Web 服务交互的输入和输出中可以出现哪些消息。端口类型定义允许的 Web 服务操作。最后,绑定描述协议和数据格式。

在 WSDL 服务实现中,端口元素将一个端点(网络地址位置或 URL)与服务接口中的一个绑定元素关联起来。服务元素包含端口元素的集合。

图 3. WSDL 的结构
WSDL 的结构
实现 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 服务客户机:

  1. 切换到 Eclipse PHP 透视图:单击 Window->Open Perspective->Other->PHP 并单击 OK
  2. 在 PHP Explorer 视图(左上方)中,右击 BankTeller 项目并选择 New->PHP File
    1. 输入文件名 GetStockPrice.php
    2. 单击 Finish
  3. 输入或粘贴 清单 2 中的源代码作为这个文件的内容并保存文件。可以通过这个文件中的注释了解代码的作用,或者参考下一节中对代码逻辑的解释。

清单 2. GetStockPrice.php 文件的内容
                
<?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");

/*
============================================================
Function: getStockPortfolioValue

Last modified: May/17/2007.

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
?>

PHP Web 服务客户机中的逻辑

前 一节中创建 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 结果进行解析。


清单 3. 远程股票报价 Web 服务的 XML 响应示例
                
<?xml version="1.0" encoding="utf-8" ?>
<string xmlns="http://www.webserviceX.NET/">
<StockQuotes>
<Stock>
<Symbol>IBM</Symbol>
<Last>101.17</Last>
<Date>4/27/2007</Date>
<Time>4:01pm</Time>
<Change>+0.27</Change>
<Open>100.30</Open>
<High>101.17</High>
<Low>100.06</Low>
<Volume>6141359<Volume>
<MktCap>152.3B</MktCap>
<PreviousClose>100.90</PreviousClose>
<PercentageChange>+0.27%</PercentageChange>
<AnnRange>72.73 - 101.17</AnnRange>
<Earns>6.262</Earns>
<P-E>16.11</P-E>
<Name>INTL BUSINESS MAC</Name>
</Stock>
</StockQuotes>
</string>

用 PHP 编写银行操作 REST 请求分配器

我 们差不多已经实现和理解了所有必需的工件。最后一部分是 REST 请求分配器,它将所有三层的代码粘合在一起。在 PHP 中,很容易接收 REST 服务调用(在这个示例中是 HTTP POST),把请求分派给适当的服务函数,然后将结果返回给浏览器客户机。浏览器客户机和这个 PHP 模块之间的数据交换采用 JSON 格式,还需要一个用 PHP 编写的 JSON 解析器。我们将使用 PEAR.net 提供的一个开放源码 JSON 解析器。本节提供 REST 请求分配器 PHP 模块的实现细节并进行说明。

按照以下步骤实现 REST 请求分配器 PHP 模块:

  1. 切换到 Eclipse PHP 透视图:选择 Window->Open Perspective->Other->PHP 并单击 OK
  2. 在 PHP Explorer 视图(左上方)中,右击 BankTeller 项目并选择 New->PHP File
    1. 输入文件名 JSON.php
    2. 单击 Finish
  3. 打开浏览器并访问 URL:http://mike.teczno.com/JSON/JSON.phps:
    1. 将浏览器中显示的所有内容复制到剪贴板,关闭浏览器。
    2. 在 Eclipse 编辑器中粘贴剪贴板中的内容,替换 JSON.php 文件的所有内容。
    3. 单击 File->Save
    4. 关闭文件。
  4. 在 PHP Explorer 视图(左上方)中,右击 BankTeller 项目并选择 New->PHP File
    1. 输入文件名 BankActions_REST_Service.php
    2. 单击 Finish
  5. 输入或粘贴 清单 4 中的源代码以替换这个文件的内容,单击 File->Save 保存文件。

BankActions_REST_Service.php 中的代码逻辑分派浏览器客户机请求,执行所请求的银行出纳员操作。这个 PHP 模块依赖于另外三个 PHP 模块。其中两个模块是这个项目的一部分(BankLogic.php 和 GetStockPrice.php),另一个模块是一个用 PHP 编写的外部开放源码 JSON 解析器。我们已经讨论了两个 PHP 模块中的所有函数。JSON.php 库非常容易使用。它让我们很容易在 PHP 编程环境中使用 JSON。JSON.php 提供的 JSON 解析器包含以下函数:

  • decode() 将 JSON 数据转换为 PHP 对象。
  • encode() 将 PHP 关联数组转换为 JSON 数据结构。

每 当浏览器客户机向银行场景中间层服务发出 REST(HTTP POST)服务调用时,BankActions_REST_Service.php 中的代码首先收集浏览器客户机发送的服务输入。浏览器客户机发送的输入数据可以在 PHP 服务器的超级全局 $_REQUEST 关联数组中找到,采用键 - 值对的形式。浏览器客户机发送的输入数据包含两个键 - 值对。其中之一包含需要执行的银行出纳员命令。它常常是存款、取款或获取/更新股票组合价值。另一个数据项包含执行银行出纳员命令所需的应用程序特有数 据。这些数据用 JSON 格式进行编码。解析银行出纳员命令和服务输入数据之后,这里的逻辑使用 JSON 解析器将 JSON 格式的文本转换为 PHP 对象。在此之后,一个开关语句将控制传递给其他 PHP 模块中适当的银行出纳员函数。在调用这些函数时,将从 JSON 文本解析出来的参数传递给它们。当银行出纳员函数返回时,它们在一个 PHP 关联数组中提供最终结果(成功或失败)。将最终结果数据转换为 JSON 格式的文本,并返回给浏览器客户机。

现在,我们已经实现并解释了端到端银行场景的三层上所有的代码工件。

清单 4. BankActions_REST_Service.php 文件的内容
                
<?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 都不需要应用程序特有的运行时集成,因此大大减少了端到端应用程序的开销和复杂性。

部署银行场景组件
既然已经开发并集成了各个银行场景组件,就该在 Zend Core PHP 服务器上部署它们了。请记住,Zend Core 是在 Apache Web 服务器中运行的。
  1. 确保激活 Eclipse PHP 透视图:单击 Window->Open Perspective->Other->PHP 并单击 OK
  2. 确保选择 Window->Web Browser->Internal Web Browser 选项。
  3. 在 PHP Explorer 视图(左上方)中,右击 BankTeller 项目并选择 Run As->Run
    1. 在 Run 对话框的左面板中,单击 “PHP Web Page” 选项旁边的 + 符号展开它。
    2. 检查在 “PHP Web Page” 选项下面是否有一个称为 New_configuration 的条目。
    3. 如果没有看到 New_Configuration 条目,那么右击 PHP Web Page 并选择 New 以创建条目。
    4. 单击 New_Configuration 条目。
    5. 在同一对话框的右面板中,会看到 New_Configuation 的各个配置参数。确保 Server 字段设置为 Default PHP Web Server
    6. 在 File/Project 字段中,单击 Browse 按钮,选择 BankTeller 并单击 OK
    7. 确保选中 Publish files to Server 复选框。
    8. 在 Publish To: 字段中,追加文本 BankTeller,从而将这个配置参数设置为 C:/Program Files/Zend/Apache2/htdocs/BankTeller。
    9. 确保选中 Auto Generate 复选框。
    10. 单击 Run
这些步骤将文件部署到 Zend Core 上并启动 Eclipse 内部浏览器。现在可以关闭 Eclipse 内部浏览器。
如果在 Eclipse Browser 屏幕上出现了银行出纳员应用程序主菜单,就说明一切顺利。如果没有,就需要检查是否不小心跳过了某些步骤。部署和运行端到端 Ajax 应用程序就这么简单。
测试银行场景组件
现在,该享受本系列中所有工作的成果了!银行场景已经到了可以进行测试的状态。按照以下步骤测试在三层上开发和部署的代码,测试银行出纳员操作的效果:
  1. 在 Eclipse 工具栏上,单击 Eclipse 内部 Web 浏览器图标(像地球的图标)。
  2. 确定 Eclipse 内部浏览器启动了。
  3. 在浏览器的 URL 地址字段中,输入 http://localhost/BankTeller(这是在本地机器上运行的银行场景应用程序的 URL)并按 Enter
  4. 这时会显示银行出纳员应用程序的主屏幕,见 图 4。 正如前面指出的,这是银行出纳员应用程序惟一的 Web 页面。在测试这个应用程序时,您可以随时注意 URL。这是整个应用程序运行期间看到的惟一 URL。这个应用程序的各个部分(或者说屏幕)都是这个页面的一部分。现在,这些屏幕的用户界面控件都是隐藏的,只有主屏幕是可见的。Ajax 技术的要点就是,在这个浏览器会话期间,这个应用程序不再从服务器进行页面刷新。
  5. 可以将鼠标指针移入和移出菜单项。您会看到菜单项突出显示和恢复一般状态。这是一种简单的 Ajax 特性,只需在发生 mouseover 和 mouseout 事件时修改包含菜单文本的 <SPAN> 元素的 CSS 类样式即可。
  6. 单 击第一个菜单项 “Deposit to an account”,这会以 Ajax 方式显示存款屏幕(无需服务器的帮助)。这时应该会看到一个列表框,其中包含当前的所有帐户持有人。这些帐户数据是使用 Ajax 异步 XHR 功能在后台从中间层服务器获得的。还可以注意到页脚中显示一个虚构的银行出纳员姓名,它还提供一个返回主菜单的选项。
    1. 选择一个帐户持有人 Merry 并输入存款数量 125.00,见 图 5
    2. 单击 Deposit。应该会看到 图 6 所示的结果页面,其中显示交易细节。
    3. 单击页脚中的 Back to main menu 链接。
  7. 单击第二个菜单项 Debit from an account。这会立即打开取款屏幕,没有延迟。
  • 选择一个帐户持有人,输入取款数量,并单击 Debit
  • 检查从服务器返回的结果数据。
  • 单击页脚中的 Back to main menu 链接。
单击第三个菜单项 Current portfolio value。这会立即打开股票组合价值屏幕:
  • 在列表框中,选择帐户持有人 Gandalf,见 图 7
  • 单击 Get Stock Portfolio Value。应该会看到 图 8 所示的结果页面,其中显示更新的股票组合价值。这个银行出纳员操作向中间层 PHP 服务发送一个 REST 请求,进而为远程股票报价服务创建一个动态的 Web 服务客户机,并调用这个服务。然后在数据库中更新新的股票价格,计算新的股票组合价值并返回给浏览器客户机。
  • 单击页脚中的 Back to main menu 链接。
关闭 Eclipse 内部 Web 浏览器。
这个测试过程说明,这个单页面 Web 应用程序可以与中间层和数据层上的应用程序工件进行交互,顺畅地执行比较复杂的任务。

图 4. 银行出纳员主屏幕
主屏幕

图 5. 银行出纳员存款屏幕
存款屏幕

图 6. 银行出纳员存款结果屏幕
存款结果屏幕

图 7. 银行出纳员股票组合价值屏幕
股票组合价值屏幕

图 8. 银行出纳员股票组合价值结果屏幕
股票组合价值结果屏幕
调试方法

在 任何软件开发环境中,调试工具对于项目的开发速度和质量都非常重要。在银行场景这样的多层 Web 应用程序中,调试器是必不可少的开发工具。我们需要的一项功能是在一个 IDE 中调试 Web 应用程序的所有层 —— 目前这还不可行。但是,最近的基于 Eclipse 的开放源码成果,比如 PHP Development Tool(PDT)和 Aptana Web IDE,很有希望实现这个功能。因为它们都插入到相同的 Eclipse 平台中,所以我们可以期望它们不久之后就能够提供端到端调试功能,让开发人员能够在 JavaScript 函数中设置断点,同时在 PHP 函数中设置另一个断点。这样就可以在浏览器和 Zend Core 服务器上同时调试一个端到端事务。

PDT 和 Aptana 分别为 PHP 和 JavaScript 提供了出色的调试特性。Aptana 与流行的浏览器(比如 Firefox、Internet Explorer、Safari 和 Opera)集成,可以进行 JavaScript 调试。与它相似,Firebug(一个 Firefox 插件)也提供了出色的 JavaScript 调试特性。但是,到编写本文时,这些调试工具还无法一起配合工作,所以无法同时调试浏览器代码和服务器代码。

由于目前没有端到端调试器,所以本文介绍的调试过程分成下面两个部分。这两个调试方法的效果都不错,可以单独调试端到端银行场景的服务器部分和浏览器部分。
  1. 使用 PDT 调试器调试 Zend Core 服务器上的 PHP 代码。
  2. 使用 Firebug 调试器调试 Firefox 浏览器上的客户端 JavaScript 代码。
调试服务器端 PHP 代码
按照以下步骤使用 PDT 调试器调试中间层 PHP 服务器逻辑。以下说明假设已经部署了银行场景应用程序,并按照前两节中的描述进行了一般测试。
  1. 确保激活 Eclipse PHP 透视图:单击 Window->Open Perspective->Other->PHP 并单击 OK
  2. 确保选择 Window->Web Browser->Internal Web Browser 选项。
  3. 到编写本文时,应用程序的初始页面必须是 PHP 文件,PDT 和 Zend 调试器才能起作用。因此,必须将 index.html 文件重命名为 index.php。这只改变文件扩展名,根本不会影响应用程序的行为:
    1. 在 PHP project explorer 视图中,右击 BankTeller 项目下面的 index.html 并选择 Refactor->Rename
    2. 将文件名改为 index.php
  4. 在 Eclipse PHP project explorer 视图中,右击 BankTeller 项目并选择 Debug As->Debug
    1. 在 Debug 对话框的左面板中,选择 PHP Web Page->New_configuration。这时会显示一个右面板,其底部有一个 Debug 按钮。
    2. 单击 Debug
    3. 如果提示打开 PHP Debug 透视图,那么单击 Yes。这会打开 PHP Debug 透视图,其中同时打开了 index.php 文件和 Eclipse 内部浏览器。调试器将停在 index.php 文件顶部(这个文件只包含 HTML 脚本,没有任何 PHP 代码)。
    4. 快速单击 Debug 视图(屏幕左上方)中的 Resume 图标(绿色箭头和黄色条)。如果过了 60 秒还没有单击,那么内部浏览器就会超时,必须从头开始调试步骤。
    5. 在 最初装载页面时,我们使用 Ajax XHR 从服务器获取所有帐户信息。当 XHR 请求到达 Zend Core 服务器时,调试器停在 REST 请求分配器 PHP 文件的第一行。(调试器被配置为停在执行的 PHP 文件的第一行。这是除了我们设置的断点之外的默认断点。)
    6. 现在可以通过 Debug 视图顶部的图标使用各种调试特性,比如分步执行函数或跳过函数。
    7. 除了这些调试特性之外,还可以检查 PHP 代码中应用程序变量的值。这需要使用 Debug 透视图中的 Variables 视图。
    8. 为了避免浏览器超时,应该在 60 秒内完成检查并单击 Debug 视图中的 Resume 图标(绿色箭头和黄色条)。
  5. 我们来试试在 PHP 代码中设置断点:
    1. 切换到 PHP 透视图。
    2. 在 BankTeller 项目中,打开 BankActions_REST_Service.php 文件。
    3. 在这个文件中,找到 PHP 语句 $json = new Services_JSON()
    4. 双击这个语句的行号(在编辑器的最左边)。这样就会在这个语句上设置一个断点。
    5. 前进到这个文件的末尾,在最后一行上设置另一个断点:echo($responseToBeSent)
    6. 切换到 Eclipse PHP Debug 透视图。
    7. 如果现在 Eclipse 内部浏览器打开着,就关闭它。
    8. 在 Eclipse 菜单栏中,选择 Run->Debug 菜单项。这会打开 Debug 对话框。
    9. 在这个对话框的右面板中,单击 Advanced 选项卡。
    10. 在 Breakpoint 部分中,选择 Override project/workspace Break at First Line 复选框。取消选中 Break at First Line 复选框。
    11. 单击 Debug。这会启动内部浏览器,并直接跳到 JSON 语句上设置的第一个断点。它停在这里是因为在最初装载页面时用 XHR 请求获取所有帐户信息。
    12. 可以单击 Debug 视图中的 Resume 图标。程序继续运行,并停在这个文件末尾的第二个断点。可以再次单击 Resume 图标继续执行。
    13. 现在可以切换到内部浏览器并执行任何银行出纳员操作。
  6. 在 PHP Debug 透视图中,单击 Breakpoints 视图。
    1. 右击任何断点并选择 Remove All
    2. 如果出现确认对话框,就单击 Yes 确认。
注意:到编写本文时,PDT 调试器有一个小问题需要注意。在 PHP 调试会话期间,在完成一个 PHP 脚本的调试之后,可能会在 Debug 视图中看到下面这样的一个或多个消息:<terminated> New_configuration [PHP Web Page]。在完成整个应用程序的调试过程之前,不要删除它们。到编写本文时,如果删除这些消息,那么就无法在同一个调试会话中调试后续的应用程序函数,可能必须启动一个新的调试会话。当 PDT 从非正式版本(RC3)变成正式版本时,这个问题和内部浏览器超时问题可能会被修复。
调试客户端 JavaScript 代码
按照以下步骤使用本系列 第 1 部分 中安装的 Firebug 调试器调试客户端 JavaScript 逻辑。以下说明假设已经部署了银行场景应用程序,并按照前面的描述进行了一般测试。
  1. 在自己的机器上启动 Firefox 浏览器。
  2. 输入 URL:http://localhost/BankTeller
  3. 确定浏览器屏幕上出现了银行出纳员应用程序主菜单。
  4. 在 Firefox 菜单栏上,单击 Tools->Firebug->Open Firebug。这会在 Firefox 浏览器窗口的底部打开 Firebug 窗口。
  5. 单击 Firebug 窗口中的 HTML 选项卡:
    1. 展开 HTML 的 <body> 部分。
    2. 在这里显示的 HTML 文本中,将鼠标移动到 <div id="mainPage" 上。这会突出显示应用程序屏幕中与 mainPage <DIV> 元素对应的部分。
    3. 将鼠标移动到文本 <h1 title=... 上,就会看到现在突出显示应用程序标题。
    4. 对 tellerOptions 和 pageFooter <DIV> 也进行同样的检查。
    5. 尽管在这个屏幕上隐藏了 pageFooter,但是 Firebug 仍然能够显示它。
  6. 单击 Firebug 窗口的 CSS 选项卡。可以查看这个应用程序的整个 CSS 文件。
  7. 单击 Firebug 窗口的 Script 选项卡。可以查看这个应用程序中使用的所有 JavaScript 文件。
    1. 在 Firebug 窗口顶部,单击 JavaScript 文件名旁边显示的向下箭头。可以选择这里列出的任何 JavaScript 文件并查看代码。
  8. 单击 Firebug 窗口的 DOM 选项卡。它显示银行出纳员浏览器应用程序的整个 DOM 结构。
  9. 单击 Firebug 窗口的 Net 选项卡:
    1. 这会显示银行出纳员应用程序中到目前为止的所有网络活动。
    2. 可以分别选择 HTML、CSS、JavaScript 和 Images,并查看 HTTP 头、Post 数据和响应数据。
    3. 还有一个图形视图,显示获取所选内容类型所花费的时间。
    4. 在 Firebug 窗口顶部,单击 XHR。这显示银行出纳员应用程序中发生的 XHR 活动。到目前为止,只用 XHR 执行了一次数据交换。还显示访问的服务器 REST 资源。
    5. 展开 BankActions_REST_Service.php。
    6. 单击 Post 和 Response 选项卡,查看与中间层服务交换的 JSON 请求和响应数据。
  10. 我们利用这些 Firebug 调试特性试着调试一下 JavaScript 代码:
    1. 单击 Firebug 窗口的 Script 选项卡。
    2. 使用 Firebug 窗口顶部的向下箭头,选择 BankTeller.js。
    3. 滚动到 BankTeller.js 文件的末尾,找到这个文件中定义的最后一个函数。
    4. 在最后一个函数中,单击下面这行源代码的行号:var textResult = teller_request.responseText;
    5. 这会在这一行上设置一个断点。(再次单击就会取消断点。在这个示例中,保留这一行上的断点。)
    6. 向上滚动到前一个函数:portfolioAction_Async()
    7. 在这个函数的第一个源代码语句上设置一个断点。
    8. 在银行出纳员应用程序菜单上(在浏览器窗口的上半部),选择第 3 个选项(当前股票组合价值)。马上就会看到股票组合价值屏幕。
    9. 选择一个帐户持有人并单击 Get Stock Portfolio Value。JavaScript 代码的执行过程停在 portfolioAction_Async() 函数中的断点处。
    10. 在 Firebug 窗口的右下角,可以看到 continue、step over、step into 和 step out 调试器选项。
    11. 单击 step over 图标(或按 F10 键)。这会继续执行下一行。
    12. 可以继续执行几行,并在 Firebug 窗口右边查看变量值。
    13. 可以再继续执行几行,观察如何创建 JSON 对象并将它转换为 JSON 格式的字符串。
    14. 单击 Continue 图标(或按 F8 键)。
    15. 这会执行完当前函数 —— 向中间层服务器发送请求,要求获取股票组合价值。
    16. 如果机器已经连接到互联网,中间层 PHP 服务会调用远程股票报价 Web 服务,并向服务器返回结果。这会导致 JavaScript 代码停在 XHR 回调函数上设置的另一个断点处。
    17. 连 续按 F10(step over),执行完这个函数的所有语句。请注意从服务器收到的 JSON 响应,以及如何用 parseJSON 进行解析。在执行过程中,还会看到如何隐藏一个应用程序屏幕(获得股票组合)并显示另一个应用程序屏幕(出纳员操作结果),也会看到这不需要从中间层服务 器进行任何页面刷新。
    18. 单击银行出纳员应用程序的页脚中的 Back to main menu 链接。
    19. 关闭 Firebug 窗口。
  11. 关闭 Firefox 浏览器。
这就是用 Firebug 调试 Ajax 应用程序的方法。它提供了许多容易使用的特性,并与 Firefox 浏览器很好地集成。按照我的观点,Firebug 优雅而且简单,未来的调试器产品应该会借鉴它的风格。
小结
本系列的三篇文章 中,我们研究了开发端到端 Ajax 应用程序的各个阶段。本系列的目标之一是让您体会一下 Ajax Web 应用程序提供的许多改进。下一节列出了一部分改进。如果您能够通过我们的端到端 Ajax 应用程序体会到它们的效果,本系列的目标就实现了。
要点
  • 在这个 Ajax 应用程序中,需要单击链接并等待页面刷新吗?不需要。
  • 由于 Web 应用程序中的所有客户端逻辑都放在本地,所以实现了快速响应。
  • 在单击 Deposit、Debit 和 Get Stock Portfolio Value 按钮时,这个应用程序与 PHP 中间层服务器进行通信,交换少量数据。(这会显著改进网络带宽利用率。)
  • 在使用这个应用程序时,用户看起来是在不同的页面之间导航。但实际上,只有一个 HTML 页面,只需隐藏和显示这个页面中的不同 DIV 元素,就可以实现页面切换效果。
  • 以这种 Ajax 风格设计的 Web 应用程序可以在浏览器中实现丰富的用户交互方式。这会改进客户和用户的满意度。
  • 采用这种体系结构的 Web 应用程序还会减少中间层服务器上的负载,因为服务器不再需要处理不必要的 HTML 页面刷新。相反,应用程序只在需要数据交换时才会联系服务器。
  • 在操作这个应用程序时,您看到浏览器 URL 发生变化了吗?它没有变,整个应用程序只有这一个 URL。这说明它是一个单页面浏览器应用程序。表示内容(HTML、CSS 和 JavaScript)只在启动时从 Web 服务器下载一次。
结束语
developerWorks Ajax resource center
Ajax 资源中心 可以找到关于 Ajax 编程模型的各种信息,包括文章、教程、论坛、blog、wiki、活动和新闻。

您 刚刚开发了一个跨越三层(客户机层、中间层和数据层)的 Ajax 应用程序。我们使用几种开放源码技术以构建和部署这个应用程序。在这个过程中,使用了 Zend Core 的一些核心特性和一些 Eclipse IDE 工具。我们讨论了 MySQL、PHP、Ajax(XHTML、CSS、JavaScript、XHR)、REST、JSON、XML 和 Web 服务客户机的概念。还了解了 Eclipse IDE 中 PHP 和 Aptana 透视图的使用方法,学习了如何用 Zend Core/Apache 服务器进行部署和测试。本文提供了一个可下载的文件(见 下载),其中包含本文中使用的 JavaScript 客户端代码、PHP Web 服务客户机、PHP REST 请求分配器和股票报价服务 WSDL。

这个银行场景覆盖了 Ajax、PHP 和 MySQL 的基本概念。掌握了本系列提供的基本知识之后,可以继续学习这些强大的开放源码技术,用它们进行更高级的 Web 应用程序开发。

总 之,本系列演示了 Zend Core 和 Eclipse(PDT 和 Aptana)开发环境的强大功能。对于中小型企业而言,它们是非常可靠的技术组合,它们可以作为开放源码中间件,还可以帮助企业从 .NET 环境迁移到开放源码环境。还可以通过这些运行时环境和基于 Eclipse 的工具对厂商提供的商业软件和服务进行扩展。最后,由于 Zend 和 Aptana 的开发人员在 Zend Core、Eclipse PDT 和 Aptana Web IDE 项目上的努力,这种新的 Web 开发方式得到了简化。






回页首


下载
名字 大小 下载方法
wa-aj-end2end3.zip 28KB HTTP
关于下载方法的信息


参考资料
学习

获得产品和技术


关于作者
Photo of Senthil Nathan

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 开发。

 
转自:http://www.ibm.com/developerworks/cn/web/wa-aj-end2end3/

生成一个端到端的Windows Store应用程序-第二部分: 集成云服务

[原文地址] http://blogs.msdn.com/b/somasegar/archive/2012/08/28/building-an-end-to-end-windows-store-a...

Qt入门-使用QT+VS2008开发windows应用程序

QT是跨平台的应用程序开发工具,闻名遐迩,下面使用VS2008结合QT开发一个应用程序。 (1)打开VS2008,新建QT工程   (2)点击下一步,这里是选择需要使用的QT...

IOS开发学习(1)-IOS应用程序周期

IOS应用程序周期

Qt入门-使用QT+VS2008开发windows应用程序

QT是跨平台的应用程序开发工具,闻名遐迩,下面使用VS2008结合QT开发一个应用程序。 (1)打开VS2008,新建QT工程   (2)点击下一步,这里是选择需要使用的QT库 (3)...
  • xgbing
  • xgbing
  • 2012年07月18日 14:36
  • 19296

Java Web应用程序开发-深入体验Java Web开发内幕之初步

从今天起我将一边介绍XML介绍之Schema一边开始介绍JavaWeb应用程序开发的介绍。作为第一次介绍,肯定是从WEB站点的构建过程开始讲起的即:   用Tomcat构建WEB站点 相关知识:...

论GIS应用程序开发的CBD开发策略--制作MapXtreme 瘦控件【转载】

CBD(Component Based Development)的开发方法,就是要以控件作为软件组装的基本单位,而不是以函数、过程、类作 为软件组装的基本单位。所以在Winform的GIS开发中,如...

用 Hadoop 进行分布式数据处理,第 3 部分: 应用程序开发

此系列的前两篇文章 专注于单节点和多节点集群的 Hadoop 安装及配置。最后这篇文章探索了 Hadoop 编程 — 特别是在 Ruby 语言中 map 和 reduce 应用程序开发。我之所以选择...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:转载--开发端到端的 Ajax 应用程序,第 3 部分: 集成、测试和调试应用程序
举报原因:
原因补充:

(最多只允许输入30个字)