使用 Agavi 开辟一个新世界

红薯 发布于 2009/09/08 21:42
阅读 534
收藏 2

如果您是一位严肃的 PHP 开发人员,您可能已经了解(甚至使用过)PHP 应用程序开发框架,比如 Symfony、CakePHP 和 Zend Framework。这些框架提供全面的 API,几乎囊括大部分应用程序的需求,并且是 PHP 应用程序开发的坚实基础。您可以轻松地将它们与第三方库或社区开发的组件集成起来,以实现其他功能。

尽管以上列出的框架无疑是最流行的,但它们却不是仅有的;每个月都会有新的框架出现。这个系列关注一个框架:Agavi,它是一个灵活并且可伸缩的框架,值得专业的 PHP 开发人员考虑采用。

在 这个系列文章中,我将通过 Agavi 引领您了解基于 MVC 的应用程序开发的基础知识,向您介绍基础的框架概念,并演示如何利用 Agavi 的独特方法快速高效地从头构建一个功能齐全的 Web 应用程序。经历这个过程之后,您将了解这个框架的细微之处,理解保证它的安全性和可扩展性的设计决策,并且将这个很有价值的框架添加到您的 PHP 开发工具箱中。现在,我们开始出发!

我首先回答一个非常基础的问题:什么是 Agavi,它有哪些独特的特性?

根据该框架的官方 Web 站点的说法,Agavi 是 “一个强大并且可伸缩的采用 MVC 范式的 PHP5 应用程序框架。”它为构建和部署基于 PHP 的 Web 应用程序提供全面的工具,并为安全性、数据缓存、国际化、输入验证和数据库抽象提供内置支持。它原来是 Mojavi 项目的一部分,目前由德国的一家软件公司 Bitextender GmbH 维护,并根据 GNU Lesser General Public License 2.1 向社区发布。

Agavi 的有趣源于多种原因。下面给出我认为最重要的 3 个原因:

  1. 首 先,它强烈关注代码的可重用性,从而允许开发人员轻松创建通向应用程序的某项功能的不同接口。这对经常需要向内部公开 HTML 和 SOAP 接口的 Web 应用程序尤为重要。例如,假设您想要构建一个通向应用程序的现有搜索引擎功能的 SOAP 接口。通过使用 Agavi,这就像定义一个新的输出类型一样简单,或者像定义一个以新的格式输出现有函数的呈现器一样简单。没有必要修改现有的功能,从头构建的过程是很 简单的,并且很容易实现。
  2. 其次,Agavi 提供一个高级的 URL 路由机制,允许进行大量配置,确定 URL 路由如何映射到应用程序函数。这个路由机制支持可选和必选参数、默认值、嵌套路由以及回调函数。它是 Agavi 的最重要特性之一。这个路由机制就像 Agavi 应用程序的其他配置一样,完全使用 XML 表示,并且 Agavi 的配置子系统允许在运行时访问全局的应用程序设置和变量。
  3. 再 次,Agavi 以开箱即用的方式提供极度严格的请求过滤和输入验证。过滤器可用于提前或推迟处理控制器方法。在每次请求时都验证请求参数,并且 Agavi 会自动删除未知参数,从而大大降低 SQL 注入和类似攻击的风险。Agavi 包含大量用于执行常见任务的内置验证器,比如验证字符串、数字、时间戳、电子邮件地址和文件。对于内置验证器不足的情形,还可以使用正则表达式或定义定制 验证器来执行验证。所有这些特性使得 Agavi 成为开发 Web 应用程序的最安全框架。

除了以上特性之外,Agavi 还提供:

  • 一个条件缓存引擎。
  • 支持最常见的数据库系统(包括 MySQL、PostgreSQL 和 SQL Server)和 ORM(包括 Propel 和 Doctrine)
  • 一个会话管理引擎。
  • 可定制的模板;完全遵从 OOP 原则。

总而言之,这个框架非常酷……现在让我们开始使用它!

安装 Agavi

在 这个系列文章中,我将假设您已经设置好 Apache/PHP/MySQL 开发环境,了解 PHP 和 XML,以及习惯使用 PHP 的简单和复杂数据类型。您应该基本了解 OOP 和 SOAP 概念,以及使用 PHP 的 Document Object Model (DOM) 扩展生成 XML 树。本系列使用 PHP V. 5.2.6 和 Apache V. 2.2.11。

在开始之前,通过以下步骤创建 Agavi 应用程序所需的基本目录结构:

步骤 1:创建应用程序目录结构

转到 Web 服务器的文档根目录(通常为 /usr/local/apache/htdocs on Linux® 或 C:\Program Files\Apache\htdocs on Windows®)并为应用程序创建一个新的子目录。将该目录命名为 wasp/,我随后会解释这样做的原因。

shell> cd /usr/local/apache/htdocs
shell> mkdir wasp

 

在本文中,将这个目录称为 $WASP_ROOT。

在这个目录内,创建另一个名为 lib/ 的子目录。

shell> cd wasp
shell> mkdir lib

 

步骤 2:定义虚拟主机设置

为了更方便地访问应用程序,需要定义一个新的虚拟主机并将其设置为该应用程序的 Web 根目录。虽然这个步骤是可选的,但是我推荐您使用它,尤其是用于开发的机器包含多个正在开发的应用程序时,因为它能够创建一个更密切的目标部署环境副本。

要为应用程序设置一个已命名的虚拟主机,请打开 Apache 配置文件(httpd.conf or httpd-vhosts.conf)并添加以下行:

NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
DocumentRoot "/usr/local/apache/htdocs/wasp/pub"
ServerName wasp.localhost
</VirtualHost>

 

这些行定义一个新的虚拟主机 http://wasp.localhost/,它的文档根目录对应于 $WASP_ROOT/pub/ 目录。重启 Web 服务器激活这些新的设置。注意,您可能需要更新网络的本地 DNS 服务器,让它知道新的主机。

步骤 3:下载和安装 Phing

Agavi 利用 Phing 版本系统自动为操作、视图、模板和验证器生成代码。Phing 要求使用 PHP 5.0 或更高版本。安装 Phing 的最简单方式是使用自动的 PEAR 安装程序,后者应该默认包含在您的 PHP 版本中。

要按照 Phing,仅需在命令提示符处发出以下命令:

shell> pear channel-discover pear.phing.info
shell> pear install phing/phing

 

现在,PEAR 安装程序将连接到新的通道,下载文件包并将其安装到系统的恰当位置上。本文使用 Phing V. 2.3.3。

如果要手动安装文件包,请访问主页,下载源代码压缩文件,并手动地将其解压到任意目录。要获得主页的链接,请查看本文的 参考资料 小节。注意,手动安装要求具备一些关于 PEAR 的包组织结构的知识。

步骤 4:下载安装 Agavi

下一步是下载 Agavi 框架并将其添加到应用程序。在此,还需要决定 Agavi 库是否与应用程序绑定,或者由用户下载安装。每种选择都有其优缺点:

  • 要求用户下载 Agavi 库可以确保他们使用的是最新的版本(包含最新的 bug 修复包)。不过,对于新用户而言,这项工作并不容易,并且如果最新的库与曾经用于开发应用程序的库不兼容,可能会出现难以发现的怪 bug。
  • 将 Agavi 库与应用程序绑定可以确保用户能够立即使用应用程序,并且不会有任何版本兼容问题。不过,它将用户局限于特定版本的 Agavi,从而使他们难以升级到包含新特性和必要 bug 修复包的新版本。

为 了方便本系列文章的讲解,我假设 Agavi 库是与应用程序绑定的。因此,请从官方网站下载最新版本的 Agavi 框架(本系列使用 Agavi V 1.0.1)并将其解压到一个临时位置。然后,将解压缩后的 src/ 目录复制到 $WASP_ROOT/libs/agavi:

shell> cp -R /tmp/1.0.1/src /usr/local/apache/htdocs/wasp/libs/agavi

 

此 外,还需要重命名并将 agavi-dist (Linux) 或 agavi.bat-dist (Windows) 文件从解压缩的 bin/ 目录复制到 $WASP_ROOT/agavi (Linux) 或 $WASP_ROOT/agavi.bat (Windows)。这是 Agavi 的构建脚本,它自动完成 Agavi 框架下的许多常见代码创建任务。

shell> cp /tmp/agavi-1.0.1/bin/agavi-dist /usr/local/apache/htdocs/wasp/agavi
shell> chmod +x /usr/local/apache/htdocs/wasp/agavi

 

步骤 5:配置和测试 Agavi 构建脚本

最后一个步骤是配置 Agavi 构建脚本,并告诉它系统上针对 Agavi 的文件位置。在文本编辑器中打开这个脚本,并使用路径(最好使用绝对路径,相对路径也可行)将 $AGAVI_SOURCE_DIRECTORY 变量更新到 $WASP_ROOT/libs/agavi。现在给出一个例子,它显示了在 Linux 系统上的配置:

SET AGAVI_SOURCE_DIRECTORY="./libs/agavi"

 

对于 Windows 系统,您也应该设置 PHP_EXECUTABLE 变量,该变量定义到 PHP 二进制文件的完整磁盘路径。下面是一个例子:

SET AGAVI_SOURCE_DIRECTORY="./libs/agavi"
SET PHP_EXECUTABLE="C:\Program Files\PHP\php.exe"

 

保存文件,然后切换到 $WASP_ROOT 并运行以下命令测试它:

shell> agavi status



图 1. agavi status 命令的输出
Linux 上的 agavi status 命令的输出

图 1 显示了在 Linux 上的输出例子。该输出列出了 PHP、Phing、Agavi 和现有项目的版本和目录路径。如果您的系统没有显示类似的输出,可能是出问题了,您必须查找并修复问题。本文的 参考资料 部分包含关于 Agavi 手册的链接,可以帮助您完成这个过程。如果您看到类似的输出,您的 Agavi 应用程序已经准备就绪,您可以向它添加代码了!

开始新的 Agavi 项目

在 开始之前,有必要先理解您将构建的示例应用程序。这个应用程序是一个虚拟汽车交易 Web 站点,专门销售二手跑车。这个交易 Web 站点的原始计划非常乏味:一些包含基础联系信息和服务信息的静态页面,以及一个让访问者直接与销售代表联系的查询表单。然而,在一次很迟的午餐之后,交易 团队想出一个有趣的新点子:一个在线二手车分类系统,它允许卖方上传图片和二手车描述,并允许买方根据自己的预算、品牌和关键字搜索二手车列表。站点维护 人员可以直接访问上传的列表,并手动批准可以在搜索结果中显示的合适汽车。汽车列表数据库还可以通过 SOAP 接口访问,以轻松集成到其他应用程序中。

他们还为该站点想出一个很酷的名字:Web Automobiles Sales Platform 或 WASP。

WASP 示例应用程序的构造包含了日常应用程序开发碰到的常见需求:静态页面、输入表单、图片上传、登录管理面板、数据分页和排序、多种输出类型和关键字搜索。要 实现这些特性,您必须理解表单处理、输入验证、会话管理、身份验证和安全、CRUD 数据库操作、Web 服务 API,以及与第三方库集成的细节。因此,理解使用 Agavi 开发应用程序是一个好开端。

要开始构建这个应用程序,必须先使用 Agavi 构建脚本初始化一个新的项目。切换到 $WASP_ROOT 目录并运行以下命令:

shell> agavi project-wizard

 

为该项目设置一个名称:

Project name [New Agavi Project]: WASP
Project prefix (used, for example, in the project base action) [Wasp]: WASP

 

在以下提示中,全部使用默认值,但下面设置除外:

Should an Apache .htaccess file with rewrite rules be generated (y/n) [n]? y

 

这个项目向导将自动创建一个空的应用程序容器,并使用默认设置填充它。图 2 是一个实际操作屏幕截图。


图 2. 实际操作中的 Agavi 项目向导
Agavi 项目向导的屏幕截图

作为整个过程的一部分,该项目向导将向应用程序目录结构添加一些新的目录:$WASP_ROOT/app/ 用于存储应用程序代码;$WASP_ROOT/pub/ 用于存储图像、样式表和静态 HTML 页面等 Web 访问内容;$WASP_ROOT/dev/ 用于存储开发文件。

图 3 显示了最后的布局:


图 3. Agavi 应用程序的文件布局
Agavi 应用程序的文件布局的屏幕截图

注意,$WASP_ROOT/dev/ 包含 Apache .htaccess 文件的一个副本。它专门用于使用分布式版本控制系统(比如 SVN 或 CVS)的项目,因为它允许您维护工作副本,同时不干扰共享工作空间。如果仅考虑当前的目标,完全可以安全删除该目录;查看 参考资料 获得关于 Agavi 手册页面的链接,它详细解释了该目录的使用。

项目创建过程完成之后,您可以打开 Web 浏览器并访问先前设置的虚拟主机。输入 URL http://wasp.localhost/。您应该会看到 Agavi 欢迎页面,如 Figure 4 所示。


图 4. 新的 Agavi 应用程序的欢迎页面
新的 Agavi 应用程序的欢迎页面

恭喜您!您现在拥有一个正常运行的 Agavi 应用程序(虽然很简单)。接下来,仔细查看它的内部结构,并修改它们以支持前面描述的功能。

理解基础概念

用 Agavi 框架开发应用程序遵循传统的模型-视图-控制器模式:数据模型与处理逻辑分离,处理逻辑与表示分离。Agavi 使用模型(Models)操作(Actions)视图(Views)路由(Routes)实现这个模式。

  • 模型表示数据,并提供管理、操作、计算数据所需的函数,它们通常存储(虽然不是总是)在数据库中。Agavi 包含常见数据库的适配器,以及针对 Propel 和 Doctrine ORM 的驱动器。
  • 视图表示用户可以看到的输出。视图与页面模板紧密结合,页面模板包含必要的布局代码和标记,以将视图正确地呈现给用户。视图还可以给模板变量赋值;这些值会在运行时自动地填充到页面模板中。
  • 操作提供模型和视图之间的链接。它们使用模型更改应用程序的数据,然后调用视图向用户显示最终结果。一个操作可以有多个视图,并且可以根据需要显示的结果调用不同的视图。
  • 路由提供用户请求和操作之间的链接。当用户发出获取应用程序 URL 的请求时,路由系统将拦截该请求,并根据 URL 模式决定应该调用哪个操作来响应请求。路由在模式匹配时使用正则表达式,并且使用 XML 表示。

您是否对这 4 个组件是如何协调工作的感到困惑?要了解通过 Agavi 应用程序发出的 Web 请求的典型流程,Agavi 手册提供了最好的解释(见 参考资料 获得链接),手册中写道:“当 Web 请求到达时,路由机制将选择需要执行的初始操作;操作通过调用模型执行必要的应用程序状态更改;它还在完成之后选择将要执行的一个视图。此后,指定的视图显示应用程序的输出。

默认情况下,所有操作位于默认的模块中。不过,您通常希望根据 Web 应用程序的不同功能领域对操作(及其相关的模型和视图)进行分组。模块提供一种实现方式。例如,如果您的应用程序包含搜索子系统、用户配置文件管理和新闻,您可以分别创建名为 SearchProfilesNews 的模块,并将相应的操作放到这些模块中。

了解所有这些背景信息之后,现在回到项目向导生成的代码,并仔细查看如何生成默认的 Agavi 欢迎页面。首先,查看存储在 $WASP_ROOT/app/config/routing.xml 中的应用程序路由表。在 清单 1 中,您可以找到应用程序索引页面的路由:


清单 1. 应用程序索引页面的路由

				   
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations
xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
<ae:configuration>
<routes>
<route pattern="" module="Welcome" action="Index" />
...
</routes>
</ae:configuration>
</ae:configurations>

 

一个路由项通常包含 patternmoduleaction 属性。pattern 属性指定路由匹配的 URL 模式,而 moduleaction 属性指出应该调用哪个模块和操作来响应请求。默认情况下,Agavi 将使用它查找到的第一个匹配项;因此,作为一般规则,应该将最特别的路由安排在前面,越普通的路由位置越靠后。要更改这一行为,需要向路由添加一个 stop=false 属性;这告诉 Agavi 在查找到匹配项之后仍然继续处理。

清单 1 中的路由是一个非常普通的路由。它确保对于任何 URL 请求,Agavi 都将调用 Welcome 模块的 Index Action。这个操作由 Agavi 项目向导自动创建(作为一个 PHP 类),您可以在 $WASP_ROOT/app/modules/Welcome/actions/IndexAction.class.php 上找到它。


清单 2. Index Action

				 
<?php
class Welcome_IndexAction extends WASPWelcomeBaseAction
{
public function getDefaultViewName()
{
return 'Success';
}
}
?>

 

每个 Action 类都有一些标准方法,Agavi 将使用这些方法决定如何处理不同类型的请求。例如,executeRead() 方法指定如何处理 GET 请求,而 executeWrite() 方法指定如何处理 POST 请求。如果这两个方法都没有定义,那么每个 Action 至少必须有一个 getDefaultViewName() 方法,它为操作指定默认的视图。从 清单 2 的代码中,可以看到 IndexAction 的默认视图被命名为 Success。

根 据 Agavi 的文件命名约定,Welcome 模块中的 Index Action 的 Success 视图应该存储在 $WASP_ROOT/app/modules/Welcome/views/IndexSuccessView.class.php 中。导航到该文件,您将看到类似于 清单 3 的内容:


清单 3. Success 视图

				
<?php
class Welcome_IndexSuccessView extends WASPWelcomeBaseView
{
public function executeHtml(AgaviRequestDataHolder $rd)
{
$this->setupHtml($rd, 'simple');
$this->setAttribute('agavi_release', AgaviConfig::get('agavi.release'));
}
}
?>

 

在显示一个视图时,Agavi 自动地调用一个名为 executeXXX() 的方法,其中 XXX 对应于所需的输出类型。要生成 HTML 输出,视图必须包含一个 executeHtml() 方法,它设置必要的模板变量,并且为显示准备模板。在本文的后面部分您将看到,还可以生成其他格式的视图。例如,要生成 XML 输出,需要定义一个 executeXml() 方法,要生成 YAML 输出,需要定义一个 executeYaml() 方法,等等。您可以通过 setAttribute() 方法在视图内部定义模板变量,然后在模板内部以 $t 数组的键的形式访问这些变量。

需要注意的是,由视图生成的 HTML 标记不是存储在 View 类文件本身,而是存储在另一个模板文件中。根据 Agavi 约定,这个模板文件必须以视图的名称命名;因此,IndexSuccessView 的模板位于 $WASP_ROOT/app/modules/Welcome/templates/IndexSuccess.class.php。打开这个文件,您看到的 HTML 标记将生成如 清单 4 所示的输出。


清单 4. HTML 标记

				
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<base href="<?php echo $ro->getBaseHref(); ?>" />
<title>Welcome to Agavi!</title>
</head>
<body>
<div>
<p>You are running <em><?php echo $t['agavi_release']; ?>
</em></p>
</div>
</body>
</html>

 

从上面的 清单 3 代码片段中,看看如何使用 setAttribute() 方法在视图中定义模板变量 $t['agavi_release'],然后动态地插入到模板文件中。

最后,您应该在应用程序的共享区域(在本例中为 $WASP_ROOT/pub/—)中存储视图使用的所有 Web 访问文件,比如图像文件、JavaScript 库或 flash 动画,然后从模板内部正确地引用它们。

现在,简单总结一下:用于请求任何应用程序 URL 的 HTTP GET 请求都会通过 Agavi 的路由子系统自动匹配到 Welcome/IndexAction,而 Welcome/IndexAction 通过 getDefaultViewName() 方法知道应该调用 IndexSuccessView。然后,IndexSuccessView 调用它的 executeHtml() 方法生成 HTML 输出,将一些模板变量插入到 HTML 标记中,并将输出发送回到客户端以供显示。

根据这段解释,您会了解到几件事情:

  1. 首先,只要操作和视图的命名和位置正确,您需要做的工作就很少。这个框架将自动地查找并执行文件,而不需要任何手动干预。如果您觉得文件命名约定不太好理解,请不要担心 —— 因为您在 提供静态内容 中可以看到,有一个向导能够为您处理所有这些细节!
  2. 其 次,路由在匹配 URL 请求并将其指向恰当的操作中起到重要作用。这还意味着,与其他框架不同,操作可以存在于体系结构的任何位置中,并且应用程序的 URL 不需要直接反映内部操作分类和位于不同类别中的模块。类似地,安全性和特权绑定到操作本身,而不是绑定到模块。
  3. 再次,一个操作可以有多个视图,并且每个视图可以处理不同的输出类型。这增加了代码的可重用性,并且便于日后向相同的操作添加 XML 或 RSS 输出视图。

设置应用程序索引页面和主模板

现在,您已经对 Agavi 如何工作有了一定了解,可以开始构建应用程序了。尽管 Agavi 的欢迎页面看起来很简单,但您需要向用户首先展示它。因此,作为第一步,您将为应用程序创建一个新的索引页面。

首先,转到 $WASP_ROOT 目录并删除由项目向导自动生成的 Welcome 模块,如下所示:

shell> rm -rf app/modules/Welcome
shell> rm -rf app/pub/welcome

 

此外,在 $WASP_ROOT/app/config/routing.xml 文件上编辑路由表,并删除其中的各项,最后得到一个空配置(暂时的),如 清单 5 所示:


清单 5. 路由表

				
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations
xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
<ae:configuration>
<routes>

</routes>
</ae:configuration>
</ae:configurations>

 

项目向导已经创建了一个默认的模块,它包含一个 IndexAction 和一个 IndexSuccessView;这对应用程序的索引页面而言是一个好选择。在您的文本编辑器中打开文件 $WASP_ROOT/app/modules/Default/actions/IndexAction.class.php,确保它包含 清单 6 所示的行:


清单 6. Default/Index Action 定义

				        
<?php
class Default_IndexAction extends WASPDefaultBaseAction
{
public function getDefaultViewName()
{
return 'Success';
}
}
?>

 

接下来,检查是否存在视图文件 $WASP_ROOT/app/modules/Default/views/IndexSuccessView.class.php,然后添加 清单 7 中的代码:


清单 7. Default/IndexSuccess View 定义

				        
<?php
class Default_IndexSuccessView extends WASPDefaultBaseView
{
public function executeHtml(AgaviRequestDataHolder $rd)
{
$this->setupHtml($rd);
$this->setAttribute('_title', 'Index');
}
}
?>

 

然后,为应用程序的索引页面创建模板,并修改模板文件 $WASP_ROOT/app/modules/Default/templates/IndexSuccess.php 使其包含 清单 8 中的代码:


清单 8. Default/IndexSuccess 模板

				        
Welcome to WASP, your one-stop shop for used sports cars online.
<p/>
We have a wide selection of used automobiles for your driving pleasure,
and all our models are backed with our unique 120-day money-back guarantee.
<p/>
Use the links above to navigate this web site.

 

最后,向路由表添加一个条目,让 Agavi 知道将针对索引页面的所有请求指向 Default/IndexAction(清单 9):


清单 9. Default/Index 路由定义

				        
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations
xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
<ae:configuration>
<routes>
<!-- default action for site root "/" -->
<route name="index" pattern="^/$" module="Default" action="Index" />
</routes>
</ae:configuration>
</ae:configurations>

 

现在,在您的 Web 浏览器中通过 http://wasp.localhost/ 再次访问应用程序的索引页面,您将看到修改后的索引页面,如 图 5 所示。


图 5. WASP 索引页面
WASP 索引页面的屏幕截图

它的外观还不是很醒目,需要对图片进行修改!

对于更改页面的总体布局,您可以使用两种方法。第一种方法是向选中的页面模板添加布局标记。不过,选择这种方法的话,需要对应用程序中的每个模板重复该操作,这不仅耗费时间,还不便于更新。一种更好的方法是更改应用程序的主模板,它位于 $WASP_ROOT/app/templates/Master.php,从而使新的布局自动反映到所有页面模板中。

清单 10 显示了修改后的主模板的代码:


清单 10. 应用程序的主模板

				        
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<base href="<?php echo $ro->getBaseHref(); ?>" />
<link rel="stylesheet" type="text/css" href="/css/default.css" />
<title><?php if(isset($t['_title'])) echo htmlspecialchars($t['_title'])
. ' - '; echo AgaviConfig::get('core.app_name'); ?></title>
</head>
<body>
<!-- begin header -->
<div id="header">
<div id="logo">
<img src="/images/logo.jpg" />
</div>
<div id="menu">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">For Sale</a></li>
<li><a href="#">Other Services</a></li>
<li><a href="#">About Us</a></li>
<li><a href="#">Contact Us</a></li>
</ul>
</div>
</div>
<!-- end header -->

<!-- begin body -->
<div id="body">
<?php echo $inner; ?>
</div>
<!-- end body -->

<!-- begin footer -->
<div id="footer">
<p>Powered by <a href="http://www.agavi.org/">Agavi</a>.
Licensed under <a href="http://www.creativecommons.org/">Creative Commons</a>.</p>
</div>
<!-- end footer -->
</body>
</html>

 

您将注意到该主模板利用了两个额外工具 —— CSS 样式表和徽标图像。将这些文件放到应用程序的公共区域,让连接客户端能够通过 HTTP 获取它们。相应地,在 $WASP_ROOT/pub/ 目录中创建 css/ 和 image/ 子目录,并将必要的东西复制到这两个位置(可以在本文的 下载归档 中找到它们)。

清单 11 显示了来自 $WASP_ROOT/pub/css/default.css 的样式表规则:


清单 11. 应用程序主样式表

				        
body {
margin: 0;
padding: 0;
font-family: 'Verdana' sans-serif;
}

#header {
height: 80px;
background: #4062A8;
}

#logo {
float: left;
padding-left: 50px;
padding-top: 5px;
}

#menu {
float: right;
background: #4062A8;
margin-right: 20px;
}

#menu ul {
list-style: none;
}

#menu li {
margin-top: 35px;
float: left;
padding-right: 25px;
padding-left: 25px;
}

#menu a, #footer a {
color: white;
font-weight: bold;
}

#body {
padding-top: 20px;
padding-left: 50px;
min-height: 375px;
}

#footer {
font-size: x-small;
color: white;
float: right;
text-align: right;
background: #4062A8;
width: 400px;
clear: both;
margin-top: 20px;
}

 

然后,重新访问应用程序的索引页面,您将看到如 图 6 所示的内容:


图 6. 修改后的 WASP 索引页面
修改后的 WASP 索引页面的屏幕截图

大功告成!索引页面的外观是不是更漂亮了?

提供静态内容

到 目前为止,您仅在应用程序中修改了现有的操作。不过,Agavi 构建版本使得向应用程序添加新的操作非常容易。为了演示,您将添加一个 StaticPageAction 和相应的视图,该视图负责提供静态的内容,比如公司的 “关于我们” 和 “服务” 页面。下面的步骤是向 Agavi 应用程序添加新功能的标准流程。

步骤 1:创建 placeholder 类

回到 shell 并向下面这样调用 Agavi 构建脚本:

shell> agavi action-wizard

 

设置一个名为 StaticContentAction 的新操作,并将其与两个视图链接起来(StaticContentErrorView 和 StaticContentSuccessView),收到提示时提供以下值:

Module name: Default
Action name: StaticContent
Space-separated list of views to create for StaticContent [Success]: Error Success

 

Agavi 将生成必要的类文件,并将它们放到正确的位置。

步骤 2:定义路由

当该过程完成时,重新访问 $WASP_ROOT/app/routing.xml 文件,并为引用新创建的操作的静态内容添加一个基路由,如 清单 12 所示:


清单 12. Default/StaticContent 路由定义

				        
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations
xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
<ae:configuration>
<routes>
...
<!-- action for static pages "/content/$page" -->
<route name="content" pattern="^/content/(page:[\w-]+)$" module="Default"
action="StaticContent" />

</routes>
</ae:configuration>
</ae:configurations>

 

这个路由比前面见到的要复杂一些。首先,它包含一个 “name” 属性;这提供一个手动生成路由(随后就会看到)时可以使用的标签。其次,正则表达式将以以下格式之一匹配 URL:

/content/hello
/content/HelloWorld
/content/hello-world
/content/Hello-World-123

 

很明显,上面例子中的最后部分是变量。必须通过将该部分的路由定义包含在圆括号中,将相关信息告诉 Agavi 的路由子系统。使用圆括号创建了 Agavi 手册中的捕捉组(apture group)。捕捉组可用于自动地将 URL 的特定部分转换为 Agavi 变量,这些变量最终需要经过验证并放到一个 AgaviRequestDataHolder 对象中,然后从操作或视图的内部访问它们。

步骤 3:定义验证规则

然 而,用捕捉组定义路由仅完成了一半工作。路由中的变量要进入操作或视图中,必须经过 Agavi 的极其严格的输入验证过滤器。默认情况下,这个过滤器设置为最严格的级别;这意味着它不认识(或不能通过特定的验证测试)的任何 GET 或 POST 变量都被自动删除,并且再也不能进入应用程序。这种异常严格的输入检查策略是 Agavi 应用程序如此安全的原因之一。

Agavi 自带有大量内置输入验证器,并且可以通过简单的 XML 文件配置它们。输入验证器与它所属的操作同名,并且可以在模块的 validate/ 目录中找到。要查看 StaticContentAction 验证器,需要在 $WASP_ROOT/app/modules/Default/validate/StaticContent.xml 上编辑该文件,并使用 清单 13 中的 XML 填充它:


清单 13. Default/StaticContent Action 定义

				        
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations
xmlns="http://agavi.org/agavi/config/parts/validators/1.0"
xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
parent="%core.module_dir%/Default/config/validators.xml"
>
<ae:configuration>

<validators method="read">
<validator class="regex">
<arguments>
<argument>page</argument>
</arguments>
<ae:parameters>
<ae:parameter name="pattern">#^[\w-]+$#</ae:parameter>
<ae:parameter name="match">true</ae:parameter>
</ae:parameters>
</validator>
</validators>

</ae:configuration>
</ae:configurations>

 

非常简单!这个 XML 块为 page 变量设置一个 AgaviRegexValidator,并且检查它是否匹配在验证器定义中指定的正则表达式。

受到步骤 23 的影响,当传递 URL /content/hello 时,Agavi 的路由和验证子系统将自动检查该 URL,初始化一个名为 page 的 AgaviRequestDataHolder 变量,并且将值 hello 赋给它。

步骤 4:编写操作代码

那 么,为什么要捕捉 URL 的特定部分呢?这是一个好问题,并且答案将慢慢明晰。现在,我们继续设置 StaticContentAction,您可以在 $WASP_ROOT/app/modules/Default/actions/StaticContentAction.class.php 中找到它。编辑这个文件使其包含 清单 14 中的代码:


清单 14. Default/StaticContent View 定义

				        
<?php
class Default_StaticContentAction extends WASPDefaultBaseAction
{
public function getDefaultViewName()
{
return 'Success';
}

public function executeRead(AgaviRequestDataHolder $rd)
{
return 'Success';
}
}
?>

 

如前所述,Agavi 要求显式地指定用于处理不同类型请求的视图。例如,executeRead() 方法指定如何处理 GET 请求,而 executeWrite() 方法指定如何处理 POST 请求。在这个例子中,因为 StaticContentAction 仅处理 GET 请求,所以它必须包含一个 executeRead() 方法,并且这个方法应该引用 StaticContentSuccessView。

需要注意的是,仅当前面提到的输入验证测试成功时,才会调用 Success View。如果输入验证测试失败,Agavi 的默认行为是生成 Error View。

步骤 5:编写视图代码

现 在我们转向这个部分的重点:设置 StaticContentSuccessView 以提供静态页面。在 $WASP_ROOT/app/modules/Default/views/StaticContentSuccessView.class.php 打开视图文件,然后用 清单 15 中的代码填充它。


清单 15. Default/StaticContentSuccess View 定义

				        
<?php
class Default_StaticContentSuccessView extends WASPDefaultBaseView
{
public function executeHtml(AgaviRequestDataHolder $rd)
{
$this->setupHtml($rd);
$words = explode('-', $rd->getParameter('page'));
array_walk($words, create_function('&$i', '$i = ucfirst(strtolower($i));'));
$tmpl = 'StaticContent' . implode($words);
if (file_exists(
dirname($this->getLayer('content')->getResourceStreamIdentifier())
. "/$tmpl.php")) {
$this->getLayer('content')->setTemplate($tmpl);
} else {
return $this->createForwardContainer(
AgaviConfig::get('actions.error404_module'),
AgaviConfig::get('actions.error404_action'));
}
}
}
?>

 

这看起来可能比较复杂,但实际并不复杂。一般而言,这个视图尝试将该 URL 请求与一个静态页面模板匹配,如果可用就显示它。首先,视图的 executeHtml() 方法使用 AgaviRequestDataHolder 对象的 getParameter() 方法获取前几个步骤中由路由子系统设置的 page 变量。然后,它根据一个预定义的文件命名约定重新格式化这个值,并且尝试查找并显示具有相同名称的模板文件。

例如,如果客户端请求的 URL 为 /content/hello-world,那么对 $rd->getParameter('page') 的调用将返回值 hello-world, 并且视图将查找一个名为 $WASP_ROOT/StaticContentHelloWorld.php 的模板。如果该模板文件存在,视图将把它发送给客户端;如果不存在,将自动转到 Agavi 的默认 Error404 视图,向客户端显示 “Page not found” 错误。

这次,在 $WASP_ROOT/app/modules/Default/views/StaticContentErrorView.class.php 上设置 StaticContentErrorView 还是个好主意,以在输入验证失败时识别行为(清单 16)。


清单 16. Default/StaticContentError View 定义

				        
<?php
class Default_StaticContentErrorView extends WASPDefaultBaseView
{
public function executeHtml(AgaviRequestDataHolder $rd)
{
return $this->createForwardContainer(
AgaviConfig::get('actions.error404_module'),
AgaviConfig::get('actions.error404_action'));
}
}
?>

 

这非常简单 —— 您仅需再转到默认的 Error404 操作。

现在,剩下的事情就是定义静态页面。查看 图 6(或 清单 10 中的模板)。您将在主菜单中看到两个项,它们属于静态内容 —— “关于我们” 和 “其他服务”。分别在 $WASP_ROOT/app/modules/Default/templates/StaticContentAboutUs.php$WASP_ROOT/app/modules/Default/templates/StaticContentOtherServices.php 创建两个新的模板,并使用一些静态内容填充它们,如 清单 17 所示。


清单 17. Default/StaticContentAboutUs 模板

				        
We've been dealing with used sports cars since 1996, and have built up an
enviable reputation for honesty, transparency, and fairness in all our dealings.
Rest assured, when you buy a used car from us, you're not just buying an automobile...
you're buying a solid guarantee of service and quality!
<p/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
<p/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.

 

然后,打开您的 Web 浏览器并访问 http://wasp.localhost/content/about-us 或 http://wasp.localhost/content/other-services,您应该会看到自己的静态页面(图 7)。


图 7. 静态页面
样例静态页面屏幕截图,关于我们

步骤 6:将各项链接起来

您还有一件小事情需要做:将主菜单中的各项与新创建的静态内容页面链接起来。打开主模板文件,并使用 Agavi 的路由生成器自动生成到这些页面的路由,如 清单 18 所示。


清单 18. 更新后的应用程序主模板

				        
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
...
<div id="menu">
<ul>
<li><a href="<?php echo $ro->gen('index'); ?>">
Home</a></li>
<li><a href="#">For Sale</a></li>
<li><a href="<?php echo $ro->gen('content',
array('page' => 'other-services')); ?>">Other Services</a>
</li>
<li><a href="<?php echo $ro->gen('content',
array('page' => 'about-us')); ?>">About Us</a></li>
<li><a href="#">Contact Us</a></li>
</ul>
</div>
...
</html>

 

在 Agavi 中手动生成路由也很简单。在您的视图或模板内部,使用由 $ro 表示的 AgaviRouting 对象通过使用路由名调用它的 gen() 方法生成路由(还记得您在定义路由时设置的 name 属性吗?)。路由参数可以在一个数组中指定,并作为第二个参数传递给 gen() 方法。

保存对主模板的更改,并重新访问应用程序的索引页面。现在,应该激活了静态页面的主菜单链接。

需 要指出的是,对于比较保守的人,实际上有一种在 Agavi 应用程序中提供静态页面的简便方法:仅需将这些页面作为 HTML 文件放到应用程序的 pub/ 目录中,并手动链接它们。不过,这些页面不能使用 Agavi 的主模板,并且需要自己的布局标记,这会为日后的更新带来问题;此外,它们不能享受到 Agavi 的缓存和安全机制的好处。

本文到此结束。我向您介绍了 Agavi,解释了它的独特特性,并引导您在自己的开发机器上安装和配置该框架。我还解释了 Agavi 如何处理 Web 请求的基础知识,以及如何使用 Agavi 构建脚本快速设置新的项目和类文件模板。最后,我们查看了样例 WASP 应用程序,它将作为整个系列的试验应用程序。

您的样例应用程序仍然处于初级阶段:它知道如何提供索引页面和一些静态内容,并且有 一个可用于进一步开发的良好主模板。尽管我们实现的是非常基础的功能,但是能够洞察到 Agavi 应用程序的最重要组件 —— 路由、操作、视图、验证器、模板和模块 —— 并为本系列第 2 部分要实现的功能打下了基础。

您可以 下载 包含本文讨论的功能的 WASP 应用程序。我建议您下载并开始试用它,尝试向它添加新东西。我敢保证您能从中获得更多的知识。祝您实验愉快,下次见!

加载中
0
leonshei
leonshei
刚看完。很长,跟着步骤走了一遍,可以应用在我下一个开发中。
返回顶部
顶部