ZendFramework2-示例-全-

ZendFramework2 示例(全)

原文:zh.annas-archive.org/md5/19327a3449d05bb94ec226a3c77b720a

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Zend Framework 2 是知名的 Zend Framework 的最新更新。这个版本大大简化了使用即插即用组件以最小开发努力构建复杂 Web 应用的过程。Zend Framework 2 还提供了一个高度健壮和可扩展的框架,用于开发 Web 应用。

本书将指导你通过使用 ZF2 开发强大 Web 应用的过程。它涵盖了 Zend Framework 应用开发的各个方面,从安装和配置开始;任务设计得易于读者理解和使用,以便他们能够轻松地构建自己的应用。

本书从 Zend Framework 的基本安装和配置开始。随着你完成练习,你将彻底熟悉 ZF2。通过本书,你将了解使用 Zend Framework 2 构建稳固的 MVC Web 应用的基本概念。详细的分步指导将使你能够构建诸如群聊、文件和媒体共享服务、搜索以及简单的商店等功能。你还将使用各种外部模块来实现原生不可用的功能。

到本书结束时,你将熟练掌握使用 Zend Framework 2 构建复杂且功能丰富的 Web 应用。

本书涵盖的内容

第一章,开始使用 Zend Framework 2.0,介绍了开发环境的配置。在本章中,我们将设置 PHP 应用服务器,安装 MySQL,并创建一个开发数据库,该数据库将在后续章节中用于我们的 Zend Framework 学习练习。

第二章,构建您的第一个 Zend Framework 应用,解释了创建 Zend Framework 2 项目的步骤;我们将通过创建模块、控制器和视图来回顾构建 ZF2 MVC 应用的一些关键方面。我们将在 Zend Framework 中创建自己的自定义模块,该模块将在本书的后续章节中得到进一步扩展。

第三章,创建通信应用,介绍了 Zend\Form。在本章中,我们将创建我们的第一个注册表单,并使用 Zend Framework 组件设置注册用户的登录和认证。

第四章,数据管理和文档共享,涵盖了 Zend Framework 的一些数据和管理文件的概念。在本章中,我们将学习 Zend Framework 的各个方面,包括 ServiceManager、TableGateway 模式、处理上传和文件共享。

第五章,聊天和电子邮件,涵盖了在您的应用程序中使用 JavaScript 的内容。本章以一个简单的群聊实现为例,解释了在您的应用程序中使用 JavaScript 的用法;您还将了解到如何使用 Zend\Mail 和 ZF2 事件管理器发送电子邮件。

第六章,媒体共享,解释了使用 Zend Framework 管理和共享图片和视频。在本章中,我们将使用各种外部 Zend Framework 2 模块来处理图片和视频。

第七章,使用 Lucene 进行搜索,介绍了使用 Zend Framework 的 Lucene 搜索实现。本章首先解释了用户关于安装 ZendSearch\Lucene 模块,然后我们涵盖了实现数据库记录和文档文件搜索的细节。

第八章,创建一个简单的商店,介绍了电子商务。在本章中,我们将构建一个简单的在线商店,以展示购物车开发过程中涉及的过程。在本章中,我们将使用 PayPal Express Checkout 作为我们的支付处理器。

第九章,HTML5 支持,介绍了 Zend Framework 2 中的 HTML5 支持。与之前版本相比,ZF2 提供了对各种 HTML5 功能的全面支持;本章涵盖了 ZF2 HTML5 支持的两个主要方面——新的输入类型和多个文件上传。

第十章,构建移动应用程序,介绍了在 Zend Framework 2 和 Zend Studio 10 的帮助下开发原生移动应用程序。在本章中,我们将学习使用 Zend Framework 构建云连接移动应用程序的基础知识;我们还将了解设置 Zend PHP 开发者云环境。

你需要这本书的内容

您需要一个能够运行 Zend Server CE 和 MySQL 的系统。书中涉及的任务性能所需的前提软件在第一章,开始使用 Zend Framework 2.0 中进行了介绍。

这本书面向的对象

如果您是一位新接触 Zend Framework 的 PHP 开发者,但希望快速上手该产品,这本书就是为您准备的。预期您具备 PHP 面向对象编程的基本知识。

惯例

在这本书中,你将发现几个经常出现的标题。

为了清楚地说明如何完成一个程序或任务,我们使用:

行动时间 - 标题

  1. 行动 1

  2. 行动 2

  3. 行动 3

指令通常需要一些额外的解释,以便它们有意义,因此它们后面跟着:

刚才发生了什么?

这个标题解释了您刚刚完成的任务或指令的工作原理。

您还会在书中找到一些其他的学习辅助工具,包括:

快速问答——标题

这些是旨在帮助您测试自己理解的简短多项选择题。

尝试一下英雄——标题

这些实践挑战为您提供了对所学知识的实验想法。

您还会发现多种文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码词如下所示:“TableGateway类扩展了AbstractTableGateway,它实现了TableGatewayInterface。”

代码块设置如下:

    // Add Document to index
    $indexDoc = new Lucene\Document();
    $indexDoc->addField($label);
    $indexDoc->addField($owner);
    $indexDoc->addField($fileUploadId);
    $index->addDocument($indexDoc);
  }
  // Commit Index
  $index->commit();

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

    // Add Document to index
    $indexDoc = new Lucene\Document();
    $indexDoc->addField($label);
    $indexDoc->addField($owner);
    $indexDoc->addField($fileUploadId);
 $index->addDocument($indexDoc);
  }
  // Commit Index
  $index->commit();

任何命令行输入或输出都应如下编写:

$ sudo apt-get install php5-cli 
$ sudo apt-get install git
$ curl -s https://getcomposer.org/installer | php

术语重要 词汇 以粗体显示。您在屏幕上看到的,例如在菜单或对话框中的文字,将以如下方式显示:“在选择 目标 位置屏幕上,点击下一步以接受默认目标。”

注意

警告或重要注意事项以如下框中显示。

小贴士

技巧和窍门看起来如下。

读者反馈

读者反馈始终受到欢迎。告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大收益的标题非常重要。

要向我们发送一般反馈,只需发送电子邮件到 <feedback@packtpub.com>,并在邮件主题中提及书名。

如果您在某个主题领域有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在,您已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。

下载示例代码

您可以从您在www.packtpub.com购买的 Packt 图书的账户下载所有示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

错误清单

尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的勘误部分现有的勘误列表中。

盗版

在互联网上对版权材料的盗版是所有媒体中持续存在的问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即向我们提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过发送链接到疑似盗版材料至<copyright@packtpub.com>与我们联系。

我们感谢您在保护我们的作者以及为我们提供有价值内容的能力方面的帮助。

问题

如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>与我们联系,我们将尽力解决。

第一章. 开始使用 Zend Framework 2.0

在本章中,我们将设置并配置我们的开发环境,以便开始使用 Zend Framework 2.0 进行开发。我们将设置一个 PHP 应用程序服务器,安装 MySQL,并创建一个开发数据库,该数据库将在后续章节中用于我们的 Zend Framework 学习练习。那么,让我们开始吧。

Zend Framework 2.0

Zend Framework 最后一个主要版本,即 2007 年发布的 1.0 版;在过去的五年里,Zend Framework 经历了许多变化,成为了一个成功的基于 PHP 的框架。但仅仅更新框架,Zend Framework 仍然保留了一些在 Zend Framework 1.0 中固有的问题。

Zend Framework 2.0 是通过从核心重构框架来尝试使 Zend Framework 更好的一个尝试。以下列出了 Zend Framework 2.0 相较于其前版本的几个关键特性:

  • PHP 5.3 特性,如命名空间和闭包

  • 模块化应用程序架构

  • 事件管理器

  • 依赖注入(DI)

在接下来的章节中,我们将了解如何实现 Zend Framework 2.0 的新特性。

在本章中,我们将介绍 Zend Framework 2.0 的一些先决条件的安装和配置。ZF2 可以安装在大多数支持 PHP 5.3.3 或更高版本的 PHP 启用 Web 服务器上。

我们已经使用 Zend Server Community Edition 作为我们的默认 Web 服务器;然而,任何支持 PHP 5.3.3 的其他 PHP 栈也可以使用。或者,你也可以分别下载 Apache 和 PHP,然后在 Apache 上安装 PHP。

注意

为了简化安装过程,我在本书中主要使用 Linux 作为主要开发环境。本书中使用的所有工具都适用于 Windows,并且可以用来执行相同的活动。

Zend Server Community Edition (CE) 简介

Zend Server Community Edition 是流行的 Zend Server 栈的免费版本。Zend Server 栈提供了一个预集成的 PHP 应用程序栈,可以在开发、测试和生产中使用。这使得应用程序开发团队能够在整个开发阶段保持一致的环境。

Zend Server CE 还提供了诸如 Zend Optimizer+ 用于 PHP 字节码缓存和 Zend Guard 用于编码文件等功能。

Zend Server CE – 系统要求

Zend Server 为 Windows、Mac OS X 和与大多数 Linux 发行版兼容的通用安装包提供了安装程序。

关于安装要求的更多详细信息可以在 www.zend.com/en/products/server/system-requirements 找到。

行动时间 - 安装 Zend Server CE

我们接下来的步骤将是下载和安装 Zend Server CE;我正在运行 Ubuntu 12.04 精确斑马。其他操作系统的安装程序可能不同;您始终可以参考 Zend Server 网站上的安装说明。以下安装 Zend Server CE 的步骤:

  1. 访问 Zend Server 社区版网站(www.zend.com/en/community/zend-server-ce)并下载适用于您操作系统的最新版本的 Zend Server。在这种情况下,我们将下载 Linux 安装程序。

  2. 一旦下载了安装程序,请将其内容提取到临时位置:

    $ tar -zxvf ZendServer-5.6.0-RepositoryInstaller-linux.tar.gz
    
    
  3. 提取后,需要以管理员权限启动安装程序:

    $ cd ZendServer-RepositoryInstaller-linux/
    $ sudo ./install_zs.sh 5.3 ce
    
    

    注意

    我们向安装程序传递了两个参数。第一个是需要安装的 PHP 版本;在这种情况下是5.3。第二个参数标识需要安装的 Zend Server 版本;在这种情况下是ce,代表社区版。

  4. 在安装过程中,安装程序将要求您下载各种包:安装 Zend Server CE 的行动时间

  5. Zend Server 默认将安装到/usr/local/zend;默认文档根目录将指向/var/www。您可以使用以下文件对 Zend Server 实例进行配置更改:

    • Apache 主配置文件位于/etc/apache2/apache2.conf

    • PHP 配置由/var/local/zend/etc/php.ini控制

    以下截图显示了 Zend Server 的安装位置:

    安装 Zend Server CE 的行动时间

  6. 安装完成后,您应该能够在您的网页浏览器中打开http://localhost。这应该会带您到一个类似于以下截图的测试页面:安装 Zend Server CE 的行动时间

提示

要重启 Zend Server,请使用$ sudo service zend-server restart命令。

发生了什么?

Zend Server CE 已安装并准备好使用。现在我们有一个运行中的 Web 服务器和兼容的 PHP 版本——这满足了运行 Zend Framework 2.0 的核心要求。

尝试一下英雄

我们将使用 Git 从 GitHub 检出 Zend Framework;Zend Framework 2.0 发生的主要变化之一是源代码控制已从 SVN 更改为 Git。

您接下来的任务将是安装 Git。在设置我们的 Zend Framework 项目时,我们将使用 Git。

提示

Git 二进制文件可以从www.git-scm.com/下载,或者从您的操作系统的仓库中安装。

Git 的安装说明可以在以下链接找到:

git-scm.com/book/en/Getting-Started-Installing-Git

配置 Zend Server CE

我们的下一步将是设置 Zend Server CE 并进行一些配置更改,这将使我们能够运行其他 PHP 应用程序。

Zend Server CE – 管理界面

Zend Server CE 的管理界面是一个基于 Web 的用户界面,它提供了以下功能:

  • 管理 PHP 扩展

  • 配置 PHP 指令

  • 管理 Zend Server 组件

  • 监控 PHP 状态、扩展状态和应用/服务器日志

在我们的下一个任务中,我们将通过使用其管理界面来对 Zend Server 进行配置更改。

行动时间 - 配置 Zend Server CE

安装完成后,需要配置 Zend Server。以下是为配置 Zend Server CE 的步骤:

  1. 在您的默认浏览器中打开 Zend Server 的管理控制台(http://localhost:10081/)。

    小贴士

    Zend Server UI 控制台在端口 10081 上运行,而 Web 服务器在端口 80 上运行。这就是为什么我们需要在访问 UI 控制台的 URL 中隐式指定端口号。

  2. 当第一次打开 Zend Server 管理界面时,您将看到一个配置向导。请查看并接受 Zend 的最终用户许可协议页面的条款和条件:行动时间 - 配置 Zend Server CE

  3. 如下截图所示,您将被要求为 Zend Server 安装设置密码:行动时间 - 配置 Zend Server CE

  4. 在完成初始配置向导后,您将被重定向到 Zend Server 管理界面的主页。行动时间 - 配置 Zend Server CE

  5. 我们需要设置会话保存路径。为了做到这一点,请执行以下步骤:

    1. 服务器设置中导航到指令

    2. 搜索session.save_path

    3. 将值设置为/tmp

    4. 点击保存更改然后重启 PHP

    行动时间 - 配置 Zend Server CE

发生了什么?

我们已成功使用 Zend Server 的管理界面修改了服务器配置,并且已重新启动在 Zend Server 上运行的 PHP 实例。

MySQL

MySQL 无需介绍——它是全球最广泛使用的开源数据库应用程序。它是免费的,并且可在互联网上提供给希望使用 MySQL 数据库开发网站和应用程序的个人和企业。

Zend Framework 2.0 支持 MySQL,以及 SQLite、PostgreSQL 和 Microsoft SQL Server。

我们的下一次练习将在我们的开发机上安装 MySQL。MySQL 可以从所有 Linux 仓库下载。Windows 和 Mac 用户必须从 MySQL 网站下载安装程序(dev.mysql.com/downloads/)。

小贴士

Windows 和 Mac 用户如果选择将 MySQL 服务器作为 Zend Server CE 安装的一部分进行安装,则可以跳过本节。Zend Server 安装程序允许 Windows 和 Mac 用户在安装过程中下载和安装 MySQL 服务器。

创建数据库的操作时间 – 安装 MySQL

需要按照以下步骤安装和配置 MySQL 服务器和客户端;在本书中,我们将使用 MySQL 作为我们的主要数据库:

  1. 在标准的 Ubuntu 安装中,您可以在 shell 提示符中执行以下命令来安装 MySQL:

    $ sudo apt-get install mysql-server mysql-client
    
    
  2. 安装完成后,MySQL 服务器将自动启动。要检查 MySQL 服务器是否正在运行,请运行以下命令:

    $ sudo netstat -tap | grep mysql
    
    
  3. 命令应该给出类似于以下输出的结果;这意味着 MySQL 守护进程正在运行:

    tcp     0      0 localhost:mysql     *:*     LISTEN      923/mysqld
    
    
  4. 如果由于某种原因 MySQL 服务器没有运行,您可以通过运行 restart 命令来启动服务器:

    $ sudo service mysql restart
    
    

刚才发生了什么?

我们刚刚安装了 MySQL;我们也准备好了 LAMP 堆栈。我们的下一步将是创建 MySQL 服务器中的数据库。

注意

由于我们使用的是 Zend Server,因此我们不需要安装 php5-mysql 软件包。如果您使用的是默认未启用 MySQL 支持的堆栈,您将必须手动安装必要的软件包。

尝试一下英雄

经过本节学习后,请随意尝试下一节的任务。

phpMyAdmin

phpMyAdmin 是一个用 PHP 编写的免费开源的基于 Web 的数据库管理工具。phpMyAdmin 提供了一个基于 Web 的用户界面来管理 MySQL 数据库服务器;添加/删除/管理数据库、用户、权限等。在本书中,我们将使用 phpMyAdmin 作为数据库管理界面来管理我们的数据库(们)。

现在我们已经安装了 Apache、PHP 和 MySQL,我们的下一步将是创建 MySQL 服务器中的一个空白数据库。

要完成此操作,我们需要在 Zend Server 中安装和配置 phpMyAdmin。

提示

您可以从 www.phpmyadmin.net/ 下载 phpMyAdmin,或者从您的操作系统的软件仓库中安装。

phpMyAdmin 的安装说明可以在以下链接中找到:

docs.phpmyadmin.net/en/latest/setup.html

在我们的下一个任务中,我们将创建一个 MySQL 数据库,在 MySQL 服务器中创建用户,并授予他们连接到数据库和执行数据库操作的访问权限。

创建数据库的操作时间

要创建一个新的数据库,请在您的网络浏览器中打开一个 phpMyAdmin 实例,并按照此处描述的步骤进行操作:

  1. 通过访问 http://localhost/phpmyadmin 在您的网络浏览器中打开 phpMyAdmin:创建数据库的操作时间 – 创建数据库

  2. 选择 数据库,在 创建新数据库 中输入新数据库的名称为 zf_app,然后点击 创建创建数据库的操作时间 – 创建数据库

  3. 在创建数据库后,为该数据库创建一个数据库用户;这可以通过从权限中选择添加新用户来完成。请提供以下详细信息:

    用户字段
    用户名 zf_user
    主机 localhost
    密码 zf_pass

    完成此操作后,您将看到以下屏幕:

    行动时间 – 创建数据库

  4. 用户创建后,转到权限部分,并为 zf_user 选择编辑权限

  5. 特定数据库权限部分,选择 zf_app 数据库。

  6. 您将被重定向到 zf_app 数据库的 zf_user 用户权限部分。选择全选并点击前往行动时间 – 创建数据库

现在,您可以通过退出 phpMyAdmin 并使用 zf_user 的用户凭据重新登录来测试数据库。您现在应该只能看到 zf_app 数据库。

刚才发生了什么?

我们刚刚在 MySQL 中创建了我们的第一个数据库。我们还在数据库中创建了一个用户,并将该用户映射到具有管理权限的数据库;现在我们可以使用这些凭据在我们下一章将要构建的应用程序中。

尝试一下

现在您已经启动并运行了 PHP 网络服务器,并且也拥有了一个 MySQL 数据库,创建一个简单的名为 Students 的表,并使用 phpMyAdmin 向表中添加一些记录。

您的任务是创建一个简单的 PHP 网页,该网页将在页面上显示 Students 表中的所有记录。

速问速答 – Zend Framework 2.0

Q1. 运行 Zend Framework 2.0 所需的最小 PHP 版本是什么?

  1. PHP 4.3 及以上版本

  2. PHP 5.2.0 及以上版本

  3. PHP 5.3.3 及以上版本

  4. PHP 5.4.7 及以上版本

Q2. 在新的 Zend Server 安装中,php.ini 的默认位置是什么?

  1. /home/<user>/etc/php/php.inc

  2. /etc/php/php.ini

  3. /var/www/php.ini

  4. /usr/local/zend/etc/php.ini

摘要

在本章中,我们学习了 Zend Server 的 PHP 应用程序堆栈的设置和配置。我们继续安装 MySQL 服务器并创建了我们的第一个数据库。在您的练习中,您学习了 Git 和 phpMyAdmin 的安装。

在下一章中,我们将学习关于 Zend Framework 项目结构和核心 MVC 组件,如视图和控制器。

第二章 构建您的第一个 Zend Framework 应用程序

在本章中,我们将创建我们的第一个 Zend Framework 2.0 项目;我们将通过创建模块、控制器和视图来回顾构建 ZF2 MVC 应用程序的一些关键方面。我们将在 Zend Framework 中创建自己的自定义模块,该模块将在本书的后续章节中得到进一步扩展。

先决条件

在您开始设置第一个 ZF2 项目之前,请确保您已在您的开发环境中安装并配置了以下软件:

  • PHP 命令行界面

  • Git:Git 用于从各种 github.com 仓库检出源代码

  • Composer:Composer 是用于管理 PHP 依赖的依赖管理工具

提示

以下命令将有助于安装设置 ZF2 项目所需的工具:

  • 要安装 PHP 命令行界面:

    $ sudo apt-get install php5-cli

  • 要安装 Git:

    $ sudo apt-get install git

  • 要安装 Composer:

    $ curl -s https://getcomposer.org/installer | php

ZendSkeletonApplication

ZendSkeletonApplication 为开发者提供了一个示例骨架应用程序,可以作为开始使用 Zend Framework 2.0 的起点。骨架应用程序使用了 ZF2 MVC,包括一个新的模块系统。

ZendSkeletonApplication 可从 GitHub 下载 (github.com/zendframework/ZendSkeletonApplication)。

行动时间 – 创建 Zend Framework 项目

要设置新的 Zend Framework 项目,我们需要下载最新版本的 ZendSkeletonApplication 并设置一个虚拟主机以指向新创建的 Zend Framework 项目。步骤如下:

  1. 导航到您想要设置新 Zend Framework 项目的文件夹位置:

    $ cd /var/www/
    
    
  2. 从 GitHub 克隆 ZendSkeletonApplication 应用程序:

    $ git clone git://github.com/zendframework/ZendSkeletonApplication.git CommunicationApp
    
    

    行动时间 – 创建 Zend Framework 项目

    提示

    在某些 Linux 配置中,当前用户可能没有写入 /var/www 的必要权限。在这种情况下,您可以使用任何可写文件夹并对虚拟主机配置进行必要的更改。

  3. 使用 Composer 安装依赖项:

    $ cd CommunicationApp/
    $ php composer.phar self-update
    $ php composer.phar install
    
    

    以下截图显示了 Composer 下载和安装必要依赖项的过程:

    行动时间 – 创建 Zend Framework 项目

  4. 在添加虚拟主机条目之前,我们需要在我们的 hosts 文件中设置一个主机名条目,以便系统在每次使用新主机名时指向本地计算机。在 Linux 中,这可以通过向 /etc/hosts 文件添加条目来完成:

    $ sudo vim /etc/hosts
    
    

    提示

    在 Windows 中,此文件可以在 %SystemRoot%\system32\drivers\etc\hosts 中访问。

  5. 将以下行添加到 hosts 文件中:

    127.0.0.1    comm-app.local
    
    

    最终的 hosts 文件应如下所示:

    行动时间 – 创建 Zend Framework 项目

  6. 我们下一步将是在我们的 web 服务器上添加一个虚拟主机条目;这可以通过创建一个新的虚拟主机配置文件来完成:

    $ sudo vim /usr/local/zend/etc/sites.d/vhost_comm-app-80.conf
    
    

    小贴士

    这个新的虚拟主机文件名可能因您使用的 web 服务器而异;请查阅您的 web 服务器文档以设置新的虚拟主机。

    例如,如果您在 Linux 上运行 Apache2,您需要在 /etc/apache2/sites-available 中创建新的虚拟主机文件,并使用命令 a2ensite comm-app.local 启用该站点。

  7. 将以下配置添加到虚拟主机文件中:

    <VirtualHost *:80>
      ServerName comm-app.local
      DocumentRoot /var/www/CommunicationApp/public
      SetEnv APPLICATION_ENV "development"
      <Directory /var/www/CommunicationApp/public>
        DirectoryIndex index.php
        AllowOverride All
        Order allow,deny
        Allow from all
      </Directory>
    </VirtualHost>
    

    小贴士

    如果您使用不同的路径来检出 ZendSkeletonApplication 项目,请确保您在 DocumentRootDirectory 指令中都包含该路径。

  8. 在配置虚拟主机文件后,需要重新启动 web 服务器:

    $ sudo service zend-server restart
    
    
  9. 安装完成后,您应该能够在您的网页浏览器中打开 http://comm-app.local。这应该会带您到以下测试页面:动手时间 – 创建 Zend Framework 项目

    小贴士

    测试重写规则

    在某些情况下,mod_rewrite 可能默认没有在您的 web 服务器上启用;为了检查 URL 重定向是否正常工作,尝试导航到一个无效的 URL,例如 http://comm-app.local/12345;如果您得到一个 Apache 404 页面,那么 .htaccess 重写规则没有正常工作;它们需要被修复,否则如果您得到如下所示的页面,您可以确信 URL 正常工作。

    动手时间 – 创建 Zend Framework 项目

发生了什么?

我们已经通过从 GitHub 检出 ZendSkeletonApplication 并使用 Composer 下载必要的依赖项(包括 Zend Framework 2.0)成功创建了一个新的 ZF2 项目。我们还创建了一个指向项目 public 文件夹的虚拟主机配置,并在网页浏览器中测试了该项目。

小贴士

下载示例代码

您可以从您在 www.packtpub.com 的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册以将文件直接通过电子邮件发送给您。

小贴士

其他安装选项

我们已经看到了安装 ZendSkeletonApplication 的一种方法;还有其他的方法可以做到这一点。

您可以使用 Composer 直接下载骨架应用程序,并使用以下命令创建项目:

$ php composer.phar create-project --repository-url="http://packages.zendframework.com" zendframework/skeleton-application path/to/install

您还可以使用递归 Git 克隆来创建相同的项目:

$ git clone git://github.com/zendframework/ZendSkeletonApplication.git --recursive

参考:

framework.zend.com/downloads/skeleton-app

Zend Framework 2.0 – 模块

在 Zend Framework 中,一个模块可以被定义为一种可移植和可重用的软件单元,它可以与其他模块相互连接,构建更大、更复杂的应用程序。

模块在 Zend Framework 中并不新鲜,但使用 ZF2,模块在 Zend Framework 中的使用方式进行了全面革新。在 ZF2 中,模块可以在各种系统间共享,并且可以相对容易地进行重新打包和分发。即将到来的 ZF2 的另一个重大变化是,主应用程序现在也被转换成了一个模块;即应用程序模块。

以下列出了 Zend Framework 2.0 模块的一些关键优势:

  • 自包含、便携、可重用

  • 依赖管理

  • 轻量级且快速

  • Phar 打包和 Pyrus 分发的支持

Zend Framework 2.0 – 项目文件夹结构

ZF2 项目的文件夹布局如下所示:

Zend Framework 2.0 – 项目文件夹结构

文件夹名称 描述
config 用于管理应用程序配置。
data 用作存储应用程序数据的临时存储位置,包括缓存文件、会话文件、日志和索引。
module 用于管理所有应用程序代码。
module/Application 这是与ZendSkeletonApplication一起提供的默认应用程序模块。
public 作为应用程序的入口点;网站的文档根目录指向此处。所有网络资源,包括 CSS 文件、图像和 JavaScript,都存储在此处。
vendor 用于管理应用程序使用的通用库。Zend Framework 也安装在此文件夹中。
vendor/zendframework Zend Framework 2.0 安装在此处。

操作时间 – 创建模块

我们接下来的活动将是关于在 Zend Framework 2.0 中创建一个新的Users模块。Users模块将用于管理用户,包括用户注册、认证等。我们将利用 Zend 提供的ZendSkeletonModule,如下所示:

  1. 导航到应用程序的module文件夹:

    $ cd /var/www/CommunicationApp/
    $ cd module/
    
    
  2. ZendSkeletonModule克隆到所需的模块名称中,在本例中是Users

    $ git clone git://github.com/zendframework/ZendSkeletonModule.git Users
    
    
  3. 检查出完成后,文件夹结构应如下截图所示:操作时间 – 创建模块

  4. 编辑Module.php;此文件位于模块下的Users文件夹中(CommunicationApp/module/Users/module.php),并将命名空间更改为Users。将namespace ZendSkeletonModule;替换为namespace Users;

  5. 以下文件夹可以删除,因为我们不会在我们的项目中使用它们:

    * Users/src/ZendSkeletonModule

    * Users/view/zend-skeleton-module

发生了什么?

我们已经为 Zend Framework 安装了一个骨架模块;这只是一个空模块,我们需要通过创建自定义控制器和视图来扩展它。在我们的下一个活动中,我们将专注于为该模块创建新的控制器和视图。

小贴士

使用 ZFTool 创建模块

ZFTool 是一个用于管理 Zend Framework 应用程序/项目的实用工具,也可以用于创建新模块;为了做到这一点,您需要安装 ZFTool 并使用 create module 命令通过 ZFTool 创建模块:

$ php composer.phar require zendframework/zftool:dev-master
$ cd vendor/zendframework/zftool/
$ php zf.php create module Users2 /var/www/CommunicationApp

在以下链接中了解更多关于 ZFTool 的信息:

framework.zend.com/manual/2.0/en/modules/zendtool.introduction.html

MVC 层

任何 MVC 框架的基本目标都是使 MVC 的三层(模型、视图和控制器)更容易分离。在我们深入了解创建模块的细节之前,让我们快速了解一下这三个层在 MVC 框架中的工作方式:

  • 模型:模型是数据的表示;模型还持有各种应用程序事务的业务逻辑。

  • 视图:视图包含用于在网页浏览器中显示各种用户界面元素的显示逻辑。

  • 控制器:控制器控制任何 MVC 应用程序中的应用程序逻辑;所有操作和事件都在控制器层处理。控制器层通过控制模型状态和表示视图的变化,充当模型和视图之间的通信接口。控制器还提供了访问应用程序的入口点。

  • 在新的 ZF2 MVC 结构中,所有模型、视图和控制器都按模块分组。每个模块都将有自己的模型、视图和控制器集合,并且将与其他模块共享一些组件。

Zend Framework 模块 – 文件夹结构

Zend Framework 2.0 模块的文件夹结构有三个关键组件——配置、模块逻辑和视图。以下表格描述了模块中内容的组织方式:

文件夹名称 描述
config 用于管理模块配置
src 包含所有模块源代码,包括所有控制器和模型
view 用于存储模块中使用的所有视图

行动时间 – 创建控制器和视图

现在我们已经创建了模块,我们的下一步就是定义我们自己的控制器和视图。在本节中,我们将创建两个简单的视图,并编写一个控制器在它们之间切换:

  1. 导航到模块位置:

    $ cd /var/www/CommunicationApp/module/Users
    
    
  2. 创建控制器文件夹:

    $ mkdir -p src/Users/Controller/
    
    
  3. 创建一个新的 IndexController 文件,< ModuleName >/src/<ModuleName>/Controller/

    $ cd src/Users/Controller/
    $ vim IndexController.php
    
    
  4. 将以下代码添加到 IndexController 文件中:

    <?php
    namespace Users\Controller;
    use Zend\Mvc\Controller\AbstractActionController;
    use Zend\View\Model\ViewModel;
    class IndexController extends AbstractActionController
    {
        public function indexAction()
        {
            $view = new ViewModel();
            return $view;
        }
        public function registerAction()
        {
            $view = new ViewModel();
            $view->setTemplate('users/index/new-user');
            return $view;
        }
        public function loginAction()
        {
            $view = new ViewModel();
            $view->setTemplate('users/index/login');
            return $view;
        }
    }
    
  5. 上述代码将执行以下操作;如果用户访问主页,用户将看到默认视图;如果用户通过操作 register 到达,用户将看到 new-user 模板;如果用户通过设置操作为 login 到达,则渲染 login 模板。

  6. 现在我们已经创建了控制器,我们将必须为每个控制器操作创建必要的视图。

  7. 创建视图文件夹:

    $ cd /var/www/CommunicationApp/module/Users
    $ mkdir -p view/users/index/
    
    
  8. 导航到视图文件夹,<Module>/view/<module-name>/index

    $ cd view/users/index/
    
    
  9. 创建以下视图文件:

    • index

    • login

    • new-user

    1. 为了创建view/users/index/index.phtml文件,使用以下代码:

      <h1>Welcome to Users Module</h1>
      <a href="/users/index/login">Login</a> | <a href="/users/index/register">New User Registration</a>
      
    2. 为了创建view/users/index/login.phtml文件,使用以下代码:

      <h2> Login </h2>
      <p> This page will hold the content for the login form </p>
      <a href="/users"><< Back to Home</a>
      
    3. 为了创建view/users/index/new-user.phtml文件,使用以下代码:

      <h2> New User Registration </h2>
      <p> This page will hold the content for the registration form </p>
      <a href="/users"><< Back to Home</a>
      

发生了什么?

我们现在为我们的新 Zend Framework 模块创建了一个新的控制器和视图;该模块目前还没有达到可以测试的状态。为了使模块完全功能化,我们需要对模块的配置进行更改,并在应用程序的配置中启用该模块。

Zend Framework 模块 – 配置

Zend Framework 2.0 模块配置分散在一系列文件中,这些文件可以在骨架模块中找到。以下是一些配置文件的描述:

  • Module.php:Zend Framework 2 模块管理器在模块的根目录中查找Module.php文件。模块管理器使用Module.php文件来配置模块,并调用getAutoloaderConfig()getConfig()方法。

  • autoload_classmap.php:骨架模块中的getAutoloaderConfig()方法加载autoload_classmap.php以包含除使用标准自动加载格式加载的类之外的所有自定义覆盖。可以向autoload_classmap.php文件中添加或删除条目以管理这些自定义覆盖。

  • config/module.config.phpgetConfig()方法加载config/module.config.php;此文件用于配置各种模块配置选项,包括路由、控制器、布局以及各种其他配置。

行动时间 – 修改模块配置

在本节中,我们将对Users模块进行配置更改,使其能够使用以下步骤与新建的控制器和视图一起工作:

  1. 自动加载配置 – 由ZendSkeletonModule提供的默认自动加载配置需要被禁用;这可以通过编辑autoload_classmap.php并替换为以下内容来完成:

    <?php
    return array();
    
  2. 模块配置 – 模块配置文件位于config/module.config.php中;此文件需要更新以反映已创建的新控制器和视图,如下所示:

    • 控制器 – 默认控制器映射指向ZendSkeletonModule;这需要替换为以下片段中显示的映射:

          'controllers' => array(
              'invokables' => array(
       'Users\Controller\Index' => 'Users\Controller\IndexController',
              ),
          ),
      
    • 视图 – 模块的视图需要映射到适当的视图位置。确保视图使用由连字符分隔的小写名称(例如,ZendSkeleton将被称为 zend-skeleton):

          'view_manager' => array(
              'template_path_stack' => array(
       'users' => __DIR__ . '/../view',
              ),
          ),
      
    • 路由 – 最后一个模块配置是定义一个从浏览器访问此模块的路由;在这种情况下,我们定义的路由为/users,它将指向Users模块的Index控制器的index操作:

          'router' => array(
              'routes' => array(
       'users' => array(
                      'type'    => 'Literal',
                      'options' => array(
       'route'    => '/users',
                          'defaults' => array(
       '__NAMESPACE__' => 'Users\Controller',
       'controller'    => 'Index',
       'action'        => 'index',
                          ),
                      ),
      
  3. 在上一节中详细说明的所有配置更改完成后,最终的配置文件 config/module.config.php 应该看起来像以下这样:

    <?php
    return array(
        'controllers' => array(
            'invokables' => array(
                'Users\Controller\Index' => 'Users\Controller\IndexController',
            ),
        ),
        'router' => array(
            'routes' => array(
                'users' => array(
                    'type'    => 'Literal',
                    'options' => array(
                        // Change this to something specific to your module
                        'route'    => '/users',
                        'defaults' => array(
                            // Change this value to reflect the namespace in which
                            // the controllers for your module are found
                            '__NAMESPACE__' => 'Users\Controller',
                            'controller'    => 'Index',
                            'action'        => 'index',
                        ),
                    ),
                    'may_terminate' => true,
                    'child_routes' => array(
                        // This route is a sane default when developing a module;
                        // as you solidify the routes for your module, however,
                        // you may want to remove it and replace it with more
                        // specific routes.
                        'default' => array(
                            'type'    => 'Segment',
                            'options' => array(
                                'route'    => '/[:controller[/:action]]',
                                'constraints' => array(
                                    'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                                    'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
                                ),
                                'defaults' => array(
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        ),
        'view_manager' => array(
            'template_path_stack' => array(
                'users' => __DIR__ . '/../view',
            ),
        ),
    );
    
  4. 应用程序配置 – 在应用程序配置中启用模块,这可以通过修改应用程序的 config/application.config.php 文件,并将 Users 添加到已启用模块列表中来实现:

        'modules' => array(
            'Application',
            'Users',
        ),
    
  5. 要在网页浏览器中测试模块,请在您的网页浏览器中打开 http://comm-app.local/users/;您应该能够在模块内进行导航。

模块主页如下所示:

操作时间 – 修改模块配置

注册页面如下所示:

操作时间 – 修改模块配置

发生了什么?

我们已修改 ZendSkeletonModule 的配置,使其与新创建的 Users 模块的控制器和视图一起工作。现在我们使用新的 ZF 模块系统拥有了一个完全功能性的模块并正在运行。

尝试一下英雄

现在我们有了创建和配置自己模块的知识,您的下一个任务将是设置一个新的 CurrentTime 模块。此模块的要求是按照以下格式渲染当前时间和日期:

时间:14:00:00 GMT 日期:2012 年 10 月 12 日

快速测验 – Zend Framework 2.0

Q1. ZendSkeletonApplication 在 PHP 中管理依赖关系所使用的工具是什么?

  1. Git

  2. 编曲家

  3. PHP 命令行界面

  4. Pyrus

Q2. 模块配置文件的文件名是什么?

  1. <App>/module/<Module>/config.inc

  2. <App>/<Module>/config/config.php

  3. <App>/module/<Module>/module.config.php

  4. <App>/module/<Module>/config/module.config.php

摘要

我们现在已经学习了如何使用 Zend 的骨架应用程序和模块来设置新的 Zend Framework 项目。在接下来的章节中,我们将专注于进一步开发此模块并将其扩展为一个完整的应用程序。

第三章:创建通信应用

在前一章中,我们介绍了在新的 Zend Framework 模块中创建控制器和视图。在本章中,我们将创建我们的第一个注册表单,并使用 Zend Framework 组件为注册用户设置登录和身份验证。

我们将在本章中关注的一些关键组件如下列出:

  • Zend\Form

  • Zend\InputFilter

  • Zend\Validator

  • 模型和 Zend\Db

Zend\Form

表单通常是通过创建表单的 HTML 页面、为各种表单事件编写单独的验证和过滤,以及最后为表单操作编写控制器和动作来构建的。使用 Zend Framework,Zend\Form 组件在一个组件中提供了之前所述的所有功能。

Zend\Form 允许开发者在应用程序中以编程方式创建和处理表单。Zend\Form 支持表单渲染、表单处理、输入过滤和验证以及表单配置。在下一个任务中,我们将设置我们的第一个 ZF2 表单。

行动时间 - 创建注册表单

要创建我们的第一个注册表单,我们将创建一个新的控制器来显示注册表单;我们还将创建新的表单和视图。我们需要对 Users 模块进行以下更改:

  1. 表单 – 我们还需要在 src/Users/Form/RegisterForm.php 下创建一个注册表单:

    1. RegisterForm 类扩展了 Zend\Form\Form;表单的配置添加到构造函数中:

      <?php
      // filename : module/Users/src/Users/Form/RegisterForm.php
      namespace Users\Form;
      use Zend\Form\Form;
      class RegisterForm extends Form
      {
          public function __construct($name = null)
          {
              parent::__construct('Register');
              $this->setAttribute('method', 'post');
              $this->setAttribute('enctype','multipart/form-data');
      
    2. 所有字段都是通过在表单构造函数上使用 $this->add() 方法添加到表单中的:

              $this->add(array(
                  'name' => 'name',
                  'attributes' => array(
                      'type'  => 'text',
                  ),
                  'options' => array(
                      'label' => 'Full Name',
                  ),
              ));
      
    3. 在声明表单字段时,可以添加额外的验证器/过滤器到字段。在这种情况下,我们对 EmailAddress 字段添加了特殊的验证:

              $this->add(array(
                  'name' => 'email',
                  'attributes' => array(
                      'type'  => 'email',
                  ),
                  'options' => array(
                      'label' => 'Email',
                  ),
                  'attributes' => array( 
                      'required' => 'required' 
                  ), 
                  'filters' => array( 
                      array('name' => 'StringTrim'), 
                  ), 
                  'validators' => array( 
                      array( 
                          'name' => 'EmailAddress', 
                          'options' => array( 
                              'messages' => array( 
                                  \Zend\Validator\EmailAddress::INVALID_FORMAT => 'Email address format is invalid' 
                              ) 
                          ) 
                      ) 
                  ) 
              ));
      
    4. 使用相同的方法添加 passwordconfirm_passwordsubmit 字段;passwordconfirm_password 将是 password 类型,而 submit 将是 button 类型。

  2. 视图 – 为了支持注册过程,以下视图需要被创建:

    1. 注册页面:注册页面的视图是在 src/view/users/register/index.phtml 中创建的。

    2. 视图由三个主要部分组成——显示错误信息的部分、用于生成表单标签的视图逻辑,以及用于生成实际表单元素的视图辅助器。以下逻辑用于显示错误信息:

      <section class="register">
      <h2>Register</h2>
      <?php if ($this->error): ?>
      <p class="error">
          There were one or more issues with your submission. Please correct them as 
          indicated below.
      </p>
      <?php endif ?>
      
    3. 以下代码块用于使用分配给控制器中视图的 form 对象生成 <form> HTML 标签:

      <?php 
      $form = $this->form;
      $form->prepare();
      $form->setAttribute('action', $this->url(NULL, array('controller'=>'Register', 'action' => 'process')));
      $form->setAttribute('method', 'post');
      echo $this->form()->openTag($form);
      ?>
      
    4. 以下部分用于为 姓名电子邮件密码确认密码提交 字段生成单个表单元素:

      <dl class="zend_form">
      <dt><?php echo $this->formLabel($form->get('name')); ?></dt>
      <dd><?php 
          echo $this->formElement($form->get('name'));
          echo $this->formElementErrors($form->get('name'));
      ?></dd>
      <dt><?php echo $this->formLabel($form->get('email')); ?></dt>
      <dd><?php 
          echo $this->formElement($form->get('email'));
          echo $this->formElementErrors($form->get('email'));
      ?></dd>
      <dt><?php echo $this->formLabel($form->get('password')); ?></dt>
      <dd><?php 
          echo $this->formElement($form->get('password'));
          echo $this->formElementErrors($form->get('password'));
      ?></dd>
      <dt><?php echo $this->formLabel($form->get('confirm_password')); ?></dt>
      <dd><?php 
          echo $this->formElement($form->get('confirm_password'));
          echo $this->formElementErrors($form->get('confirm_password'));
      ?></dd>
      <dd><?php 
          echo $this->formElement($form->get('submit'));
          echo $this->formElementErrors($form->get('submit'));
      ?></dd>
      </dl>
      
    5. 最后需要关闭 form HTML 标签:

      <?php echo $this->form()->closeTag() ?>
      </section>
      
    6. 确认页面:确认页面的视图相当简单,视图是在 src/view/users/register/confirm.phtml 中创建的。

      <section class="register-confirm">
      <h2>Register Sucessfull</h2>
      <p> Thank you for your registration. </p>
      </section>
      
  3. 控制器 – 现在我们已经有了表单和视图,我们的下一步将是放置一个控制器,这将帮助我们访问此表单。我们将创建一个新的RegisterController类,并在其 index 操作中加载新创建的表单。新控制器将在src/Users/Controller/RegisterController.php文件中创建:

    <?php
    namespace Users\Controller;
    use Zend\Mvc\Controller\AbstractActionController;
    use Zend\View\Model\ViewModel;
    use Users\Form\RegisterForm;
    class RegisterController extends AbstractActionController
    {
    
        public function indexAction()
        {
                    $form = new RegisterForm();
                    $viewModel  = new ViewModel(array('form' => $form)); 
                    return $viewModel; 
        }
        public function confirmAction()
        {
                    $viewModel  = new ViewModel(); 
                    return $viewModel; 
        }
    }
    
  4. 配置 – 现在我们已经创建了显示表单所需的所有组件,我们需要将我们的控制器添加到模块配置中的invokables列表(config/module.config.php):

    'controllers' => array(
      'invokables' => array(
        'Users\Controller\Index' => 'Users\Controller\IndexController',
        'Users\Controller\Register' => 'Users\Controller\RegisterController',
      ),
    
  5. 为了测试注册表单的显示,请打开任何网页浏览器并尝试访问以下 URL:

    http://comm-app.local/users/register

    注册表单应该看起来像以下这样:

    行动时间 – 创建注册表单

发生了什么事?

到目前为止,我们已经创建了一个可以用于显示在注册过程中使用的所有必要字段的表单。让我们尝试了解表单是如何被渲染的。当我们调用http://comm-app.local/users/register页面时,控制器会创建RegisterForm类的新实例,并在网页浏览器中显示它。我们使用构造函数向RegisterForm类添加了以下字段:

  • 姓名

  • 电子邮件

  • 密码

  • 确认密码

  • 提交按钮

这些字段被添加到新创建的Form对象中。ViewModel模式渲染表单,并将form对象传递给视图进行渲染,每个字段都按照视图中的逻辑使用FormElement视图助手进行渲染。

小贴士

FormElement作为一个魔法助手,根据传递给它的Zend\Form\Element标签的类型渲染任何表单字段。有用于渲染特定表单字段的单独助手。可以从 ZF 文档中获取完整的表单视图助手列表,该文档位于framework.zend.com/manual/2.0/en/modules/zend.form.view.helpers.html

尝试一下英雄

在我们进入下一节之前,请以我们创建注册表单相同的方式创建一个登录表单。该表单将包含以下字段:

  • 电子邮件

  • 密码

  • 提交按钮

我们将在本章的末尾使用此登录表单进行身份验证。

表单验证

如果你仔细查看表单代码,你会注意到我们为电子邮件地址字段添加了一些验证,如下面的代码片段所示:

            'attributes' => array( 
                'required' => 'required' 
            ), 
            'filters' => array( 
                array('name' => 'StringTrim'), 
            ), 
            'validators' => array( 
                array( 
                    'name' => 'EmailAddress', 
                    'options' => array( 
                        'messages' => array( 
                            \Zend\Validator\EmailAddress::INVALID_FORMAT => 'Email address format is invalid' 
                        )

因此,我们添加了以下内容:

  • 一个属性使字段成为必填字段

  • 一个用于修剪传入字符串的过滤器

  • 一个验证器用于验证电子邮件地址是否为有效格式

通过介绍 Zend Framework 的 InputFilter,我们可以验证整个表单,而不是将验证附加到每个表单字段。这使得代码更加简洁,并且提高了 Zend Forms 的可扩展性。因此,我们可以有效地在网站的多个部分使用相同的表单,每个部分都有自己的验证规则集,这些规则集不依赖于表单的验证。在我们下一节中,我们将为注册表单设置一个新的验证器。

Zend\InputFilter

可以通过使用Zend\InputFilter来执行表单和各种其他输入的验证。该组件允许过滤和验证通用输入数据集。对于特定的表单元素,您可以在特定元素上应用验证和过滤,但如果我们需要过滤像$_GET请求或$_POST请求这样的输入集,可以使用InputFilter类来实现。

在我们的下一个任务中,我们将向注册表单添加InputFilter类。

执行时间 – 向注册表单添加验证

要向现有表单添加InputFilter类,我们需要创建一个新的InputFilter类,并在表单提交时使用它进行验证,如下面的步骤所示:

  1. src/Users/Form/RegisterFilter.php中创建一个新的InputFilter类。RegisterFilter类将扩展Zend\InputFilter\InputFilter类,并在其构造函数中添加所有必要的验证器:

    <?php
    namespace Users\Form;
    use Zend\InputFilter\InputFilter;
    
    class RegisterFilter extends InputFilter
    {
        public function __construct()
        {
    
  2. 使用$this->add()方法,我们可以向注册表单添加各种过滤器选项:

    1. 对于电子邮件地址字段,我们将添加一个验证器以检查输入的值是否为有效的电子邮件地址:

              $this->add(array(
                  'name'       => 'email',
                  'required'   => true,
                  'validators' => array(
                      array(
                          'name'    => 'EmailAddress',
                          'options' => array(
                              'domain' => true,
                          ),
                      ),
                  ),
              ));
      
    2. 对于名称字段,我们将添加一个验证器以限制其大小在2140个字符之间,并且还会添加一个过滤器以去除 HTML 标签:

              $this->add(array(
                  'name'       => 'name',
                  'required'   => true,
                  'filters'    => array(
                      array(
                          'name'    => 'StripTags',
                      ),
                  ),
                  'validators' => array(
                      array(
                          'name'    => 'StringLength',
                          'options' => array(
                              'encoding' => 'UTF-8',
                              'min'      => 2,
                              'max'      => 140,
                          ),
                      ),
                  ),
              ));
      
    3. 对于密码确认密码字段,我们不会添加任何验证器,但将它们设置为必填项:

      'password'        ));
              $this->add(array(
                  'name'       => 'confirm_password',
                  'required'   => true,
              ));
      
  3. 这个InputFilter类尚未映射到RegisterForm类;我们将在表单提交时执行验证。我们需要修改RegisterController类以启用processAction方法并在提交时验证表单。

  4. 修改RegisterController类以启用processAction方法:

    public function processAction()
    {
      if (!$this->request->isPost()) {
        return $this->redirect()->toRoute(NULL , 
          array( 'controller' => 'register', 
                'action' =>  'index' 
          ));
      }
      $post = $this->request->getPost();
      $form = new RegisterForm();
      $inputFilter = new RegisterFilter();
      $form->setInputFilter($inputFilter);
      $form->setData($post);
      if (!$form->isValid()) {
        $model = new ViewModel(array(
          'error' => true,
          'form'  => $form,
        ));
        $model->setTemplate('users/register/index');
        return $model;
      }
      return $this->redirect()->toRoute(NULL , array( 
        'controller' => 'register', 
        'action' =>  'confirm' 
      ));
    }
    
  5. 现在,在您的网络浏览器中打开注册页面并测试验证:执行时间 – 向注册表单添加验证

发生了什么?

我们现在已经在注册表单上启用了验证。在RegisterController类的processAction()函数中,您将看到创建了一个新的RegisterFrom类实例,并使用$form->setInputFilter()方法应用了RegisterFilter。将数据作为输入添加到表单中,并通过使用isValid()方法执行验证。错误信息使用FormElementErrors视图助手在表单中渲染。

我们需要确保在向InputFilter类添加验证时,InputFilter中的名称正确映射到表单中的名称。

尝试一下英雄

你刚刚学习了如何使用上一个任务添加自定义的InputFilter类到 Zend 表单;在你继续到下一节之前,为你在上一个练习中构建的Login表单设置一个验证InputFilter

模型和数据库访问

模型提供了 MVC 应用程序中数据的表示。Zend Framework 没有提供Zend\Model组件,因此开发人员必须决定模型的实现部分。模型本身不能与数据库通信,以检索或处理数据,因此它们通常连接到映射对象或使用 ORM 连接到数据库。在这个例子中,我们将使用TableGateway模式将数据存储在数据库中。

注意

TableGateway是内置的 Zend Framework 2 DB 模式,它作为数据库表的网关,可以访问所有表行以执行各种 SQL 操作,包括selectinsertupdatedelete

TableGateway

TableGateway模式用于创建一个表示数据库中表的对象;在这个例子中,我们需要一个TableGateway对象来表示User表。

提示

如果模型使用TableGateway进行数据库存储,则需要在模型中声明exchangeArray()方法。

行动时间 - 创建模型和保存表单

在这个任务中,我们将创建一个新的用户模型,在 MySQL 数据库中创建一个表来保存注册数据,使用TableGateway将注册数据存储到表中。最后,我们将我们的注册表单连接到UserTable,以便新的注册信息存储在数据库中。执行以下步骤来完成此操作:

  1. 需要创建一个新的表来在 MySQL 数据库中存储注册信息:

    CREATE TABLE user (
      id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
      name TEXT NOT NULL,
      email VARCHAR(255) NOT NULL,
      password TEXT NOT NULL,
      PRIMARY KEY (id),
      UNIQUE INDEX idx_email(email)
    );
    
  2. 需要修改应用程序的全局配置以添加数据库连接的引用,如下面的代码片段所示。这可以在<Application_Home>/config/autoload/global.php中找到。

    return array(
        'db' => array(
            'driver'         => 'Pdo',
            'dsn'            => 'mysql:dbname=test;host=localhost',
            'username'         => 'db_user',
            'password'         => '',
            'driver_options' => array(
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
            ),
        ),
        'service_manager' => array(
            'factories' => array(
                'Zend\Db\Adapter\Adapter'
                        => 'Zend\Db\Adapter\AdapterServiceFactory',
            ),
        ),
    );
    
  3. User类创建一个新的模型。这需要在src/Users/Model/User.php下创建。

    <?php
    namespace Users\Model;
    class User
    {
      public $id;
      public $name;
      public $email;
      public $password;
    }
    
  4. User模型将定义setPassword()exchangeArray()方法:

    1. 实现一个setPassword()方法,该方法将为UserTable实体分配一个 MD5 版本的密码以进行存储:

        public function setPassword($clear_password)
        {
          $this->password = md5($clear_password);
        }
      
    2. 实现一个exchangeArray()方法;该方法在将User实体映射到UserTable实体时使用:

        function exchangeArray($data)
        {
          $this->name = (isset($data['name'])) ? $data['name'] : null;
          $this->email = (isset($data['email'])) ? $data['email'] : null;
          if (isset($data["password"]))
          {
            $this->setPassword($data["password"]);
          }
        }
      
  5. User创建一个新的表引用。这需要在src/Users/Model/UserTable.php下创建:

    <?php
    namespace Users\Model;
    use Zend\Db\Adapter\Adapter;
    use Zend\Db\ResultSet\ResultSet;
    use Zend\Db\TableGateway\TableGateway;
    class UserTable
    {
      protected $tableGateway;
      public function __construct(TableGateway $tableGateway)
      {
        $this->tableGateway = $tableGateway;
      }
      public function saveUser(User $user)
      {
        $data = array(
          'email' => $user->email,
          'name'  => $user->name,
          'password'  => $user->password,
        );
        $id = (int)$user->id;
        if ($id == 0) {
          $this->tableGateway->insert($data);
        } else {
          if ($this->getUser($id)) {
            $this->tableGateway->update($data, array('id' => $id));
          } else {
            throw new \Exception('User ID does not exist');
          }
        }
      }
      public function getUser($id)
      {
        $id  = (int) $id;
        $rowset = $this->tableGateway->select(array('id' => $id));
        $row = $rowset->current();
        if (!$row) {
          throw new \Exception("Could not find row $id");
        }
        return $row;
      }
    }
    
  6. 现在我们可以使用UserTable将新的注册信息保存到数据库中。为了保存注册信息,我们需要对RegisterController类进行修改。首先,我们将创建一个用于保存用户注册的新函数:

    protected function createUser(array $data)
    {
      $sm = $this->getServiceLocator();
      $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
      $resultSetPrototype = new \Zend\Db\ResultSet\ResultSet();
      $resultSetPrototype->setArrayObjectPrototype(new \Users\Model\User);
      $tableGateway = new \Zend\Db\TableGateway\TableGateway('user', $dbAdapter, null, $resultSetPrototype);
    
      $user = new User();
      $user->exchangeArray($data);
      $userTable = new UserTable($tableGateway);
      $userTable->saveUser($user);
      return true;
    }
    

    注意

    TableGateway构造函数接受以下参数,并生成一个TableGateway对象作为响应:

    • $table:用于提供 TableGateway 对象的表名。

    • Adapter $adapter:用于提供数据库适配器名称。

    • $features(可选):TableGateway 功能 API 允许在不扩展基类的情况下扩展 TableGateway 功能。功能可以在此处指定。

    • ResultSet $resultSetPrototype(可选):用于提供 ResultSet 类型。

    • **Sql \(sql**(可选):用于提供任何额外的 SQL 条件;确保 SQL 对象绑定到与 `\)table` 相同的表。

    • 更多信息请参阅:framework.zend.com/manual/2.0/en/modules/zend.db.table-gateway.html#zend-db-tablegateway

  7. 接下来,我们需要确保在重定向到确认页面之前,processAction() 方法调用此函数:

    // Create user
    $this->createUser($form->getData());
    
  8. 在您最喜欢的浏览器中打开注册页面,并使用 MySQL 数据库检查注册信息是否已正确存储在数据库中。注册确认页面应如下截图所示:执行动作 – 创建模型和保存表单

您可以检查 MySQL 数据库以查看记录是否已正确插入:

执行动作 – 创建模型和保存表单

发生了什么?

我们已经修改了表单以将新的用户注册保存到数据库中;我们的下一步将是根据数据库中存储的信息设置认证。

Zend\Authentication

Zend\Authentication 是由 Zend Framework 提供的一个认证组件,它可以用于多种认证机制,包括数据库表、HTTP 认证和 LDAP 认证。该组件还允许您将会话信息存储到多种存储中。

在这个示例中,我们将使用 Zend\Authentication 组件来验证登录表单中提交的用户凭据。

执行动作 – 用户认证

在这个任务中,我们将使用以下步骤使用 Zend\Authentication 组件对登录表单进行认证:

  1. 在登录控制器 src/Users/Controller/LoginController.php 中添加一个函数以返回认证服务:

    // References
    use Zend\Authentication\AuthenticationService;
    use Zend\Authentication\Adapter\DbTable as DbTableAuthAdapter;
    // Class definition
    public function getAuthService()
    {
      if (! $this->authservice) {
        $dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
        $dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter, 'user','email','password', 'MD5(?)');
        $authService = new AuthenticationService();
        $authService->setAdapter($dbTableAuthAdapter);
        $this->authservice = $authService;
      }
      return $this->authservice;
    }
    
  2. LoginControllerprocessAction() 方法中,检查表单提交是否有效,并使用 AuthService 方法通过 authenticate 方法验证凭据:

    public function processAction()
    //
    $this->getAuthService()->getAdapter()
                                  ->setIdentity($this->request->getPost('email'))
                                  ->setCredential($this->request->getPost('password'));
    $result = $this->getAuthService()->authenticate();
    if ($result->isValid()) {
      $this->getAuthService()->getStorage()->write($this->request->getPost('email'));
      return $this->redirect()->toRoute(NULL , array( 
                                          'controller' => 'login', 
                                          'action' =>  'confirm' 
                                          ));			
    }
    
  3. ConfirmAction 函数将渲染已登录用户的欢迎屏幕:

        public function confirmAction()
        {
      $user_email = $this->getAuthService()->getStorage()->read();
      $viewModel  = new ViewModel(array(
                'user_email' => $user_email 
            )); 
      return $viewModel; 
        }
    
  4. /view/users/login/confirm.phtml 下创建的用户主页视图如下:

    <section class="login-confirm">
    <h2>Login Successful</h2>	
    <p> Welcome!  <?php echo $this->user_email; ?> </p>
    </section>
    
  5. 在您的浏览器中打开登录页面,并尝试使用您在注册时使用的凭据进行登录。登录表单应如下所示:执行动作 – 用户认证

登录成功后,您将被重定向到以下所示的登录成功页面。

行动时间 - 用户认证

刚才发生了什么?

我们为user表创建了一个新的数据库表认证适配器,用于验证emailpassword字段。通过使用认证适配器,我们已经能够为注册用户执行认证。

快速测验 - Zend Framework 2.0

Q1. 应该修改哪个文件来存储应用程序范围内的数据库凭证?

  1. <App>/module/<Module>/config.inc

  2. <App>/config/autoload/global.php

  3. <App>/module/<Module>/module.config.php

  4. <App>/module/<Module>/module.config.php

Q2. 将输入过滤器分配给表单的正确方法是什么?

  1. $form->setInputFilter($inputFilter)

  2. $form->useInputFilter($inputFilter)

  3. $form->assignInputFilter($inputFilter)

  4. $form->mapInputFilter($inputFilter)

摘要

在本章中,我们学习了创建表单、进行基本验证、将表单数据存储到数据库、使用模型以及与数据库进行认证。在下一章中,我们将学习高级数据库操作,这些操作将基于本章所介绍的TableGateway模式。

第四章:数据管理和文档共享

在上一章准备好编写自己的基本模型之后,你现在可以学习如何在本章中充分利用 Zend Framework 的数据和文件管理概念。

在本章中,我们将涵盖以下关键主题:

  • Zend Framework 2 ServiceManager

  • TableGateway 模式

  • 使用 Zend Framework 进行文件上传和文件共享

Zend Framework 2 ServiceManager

ZF2 ServiceManager 实现了服务定位器设计模式。服务定位器是一个用于检索其他对象的服务/对象定位器。

ServiceManager 配置分为六个主要类别;你的应用程序/模块配置将属于以下表中列出的一个或多个类别:

配置类型 描述
abstract_factories 用于定义抽象类的数组。
aliases 用于定义别名名称/目标名称对的关联数组。
factories 用于定义服务名称/工厂类名称对的数组。在此定义的工厂类应实现 Zend/ServiceManager/FactoryInterface 或可调用类。
invokables 用于定义服务名称/类名称对的数组。列出的类可以直接实例化,无需任何构造函数参数。
services 用于定义服务名称/对象对的数组。服务基本上是一个类的实例。服务可以用来注册已经初始化的类。
shared 用于定义服务名称/布尔值对的数组,指示服务是否应该共享。所有服务默认都是共享的;此 ServiceManager 选项可用于在特定服务上禁用共享。

ServiceManager 配置可以存储在应用程序配置或模块配置中;这可以根据需要、应用程序或模块来选择。通常,跨应用程序静态的配置存储在应用程序级配置中;所有其他信息存储在模块级别。

ServiceManager 的配置按以下顺序合并:

  1. 使用 Module 类的 getServiceConfig() 方法提供的模块配置。这将按照模块处理的顺序进行处理:

        public function getServiceConfig()
        {
            return array(
                'abstract_factories' => array(),
                'aliases' => array(),
                'factories' => array(),
                'invokables' => array(),
                'services' => array(),
                'shared' => array(),
            );
        }
    
  2. 模块配置存在于 service_manager 键中;同样,这也是按照模块处理的顺序进行处理。

  3. 应用程序配置存在于 config/autoload/ 目录中的各种配置文件中,按照它们处理的顺序:

    <?php
    return array(
        'service_manager' => array(
            'abstract_factories' => array(),
            'aliases' => array(),
            'factories' => array(),
            'invokables' => array(),
            'services' => array(),
            'shared' => array(),
        ),
    );
    

动手实践时间 - 将现有代码迁移到 ServiceManager

我们的下一步将是将现有代码块迁移到使用 ServiceManager。以下是一些可以移动到 ServiceManager 中的关键工厂:

  • 数据库连接

  • 模型和表网关

  • 表单和过滤器

  • 身份验证服务

如果你审查现有的代码,你将能够弄清楚所有数据库连接都已经使用 Zend Framework 2 ServiceManager 模型来存储凭证。我们将进一步一步,将剩余的工厂使用以下步骤移动到 ServiceManager:

  1. 修改Module.php文件并添加一个新函数以加载 ServiceManager 配置:

    public function getServiceConfig()
    {
      return array(
        'abstract_factories' => array(),
        'aliases' => array(),
        'factories' => array(
    
          // DB
          'UserTable' =>  function($sm) {
            $tableGateway = $sm->get('UserTableGateway');
            $table = new UserTable($tableGateway);
            return $table;
          },
          'UserTableGateway' => function ($sm) {
            $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
            $resultSetPrototype = new ResultSet();
            $resultSetPrototype->setArrayObjectPrototype(new User());
            return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
          },
    
          // FORMS
          'LoginForm' => function ($sm) {
            $form = new \Users\Form\LoginForm();
            $form->setInputFilter($sm->get('LoginFilter'));
            return $form;
          },
          'RegisterForm' => function ($sm) {
            $form = new \Users\Form\RegisterForm();
            $form->setInputFilter($sm->get('RegisterFilter'));
            return $form;
          },
    
          // FILTERS
          'LoginFilter' => function ($sm) {
            return new \Users\Form\LoginFilter();
          },
          'RegisterFilter' => function ($sm) {
            return new \Users\Form\RegisterFilter();
          },
        ),
        'invokables' => array(),
        'services' => array(),
        'shared' => array(),
      );
    }
    
  2. 确保Module.php文件包含所有必要的命名空间:

    use Users\Model\User;
    use Users\Model\UserTable;
    
    use Zend\Db\ResultSet\ResultSet;
    use Zend\Db\TableGateway\TableGateway;
    

    小贴士

    使用命名空间

    可以通过使用 PHP 5.3 的namespaceuse关键字来利用命名空间。所有 ZF2 类都有一个命名空间,它与包含该类的文件夹结构直接匹配;该文件夹中存储的所有类都直接由它们的命名空间确定。

    默认情况下,use关键字为命名空间的最后一个部分创建一个别名,这可以通过在关键字上使用as选项来更改。例如,请参阅以下代码:

    use Zend\Form\Element as Element;
    use Zend\Form\Element; // same as previous line
    
  3. 对控制器进行必要的修改以从 ServiceManager 获取实例:

    // to get Login Form
    $form = $this->getServiceLocator()->get('LoginForm');
    
    // to get User Table
    $userTable = $this->getServiceLocator()->get('UserTable');
    
  4. 为了检查更改是否按预期工作,尝试使用新凭证进行注册和登录。

刚才发生了什么?

我们已经将代码迁移到使用 Zend 的 ServiceManager 框架。ServiceManager 在代码更简洁、高度有效的重构能力和核心应用程序组件的集中注册方面提供了巨大的好处。

来试试吧,英雄

现在你已经理解了 Zend ServiceManager 的功能,这里有一个简单的任务给你。登录控制器(CommunicationApp/module/Users/src/Users/Controller/LoginController.php)使用getAuthService()进行身份验证服务。修改该函数,以便从 ServiceManger 获取身份验证服务。

数据库操作

在上一章中,我们学习了如何实现基本的数据库操作,即table insert。在本节中,你将学习所有构建简单CRUD创建、读取、更新和删除)接口所需的基本数据库操作。

更多关于 TableGateway 的信息

TableGateway类扩展了AbstractTableGateway,该类实现了TableGatewayInterfaceTableGatewayInterface的接口定义在以下代码片段中;所有基本表操作都在接口中定义:

interface Zend\Db\TableGateway\TableGatewayInterface
{
    public function getTable();
    public function select($where = null);
    public function insert($set);
    public function update($set, $where = null);
    public function delete($where);
}

TableGateway类提供了一系列方法来执行基本数据库操作;以下部分解释了一些最常用的方法:

  • getTable(): 返回一个包含与TableGateway对象映射的表名的字符串。例如,请参阅以下代码:

    $myTableName = $myTableGateway->getTable();
    
  • select($where = null): 用于根据$where中指定的条件选择一组行;它可以是基于Zend\Db\Sql\Wherewhere条件,或者是一组条件的数组。例如,请参阅以下代码:

    $rowset = $myTableGateway->select( array('id' => 2));
    
  • insert($set): 用于将$set中定义的数据作为新记录插入到表中。例如,请参阅以下代码:

    $myTableGateway->insert( array('id' => 2, 'name'=>'Ravi'));
    
  • update($set, $where = null): 用于根据$where中指定的标准更新一组行;它可以是基于Zend\Db\Sql\Wherewhere条件或一组标准。$set包含将更新与$where匹配的所有记录的数据。例如,请参阅以下代码:

    $rowset = $myTableGateway->update(array('name' => 'Jerry') , array('id' => 2));
    
  • delete($where): 用于根据$where中指定的标准删除一组行;它可以是基于Zend\Db\Sql\Wherewhere条件或一组标准。例如,请参阅以下代码:

    $myTableGateway->delete( array('id' => 2));
    
  • getLastInsertValue(): 返回表的主键的最后一个insert值。返回类型是整数。例如,请参阅以下代码:

    $myTableGateway->insert( array('name'=>'Ravi'));
    $insertId = $myTableGateway-> getLastInsertValue ();
    

行动时间——实现管理用户的后台 UI

在这个任务中,我们将创建一个用于管理我们应用程序中用户的行政用户界面。以下操作将包括列出所有用户、编辑现有用户、删除用户和添加用户:

  1. 使用以下代码修改CommunicationApp/module/Users/src/Users/Model/UserTable.php。添加以下函数:

    • fetchAll()

    • getUser($id)

    • getUserByEmail($userEmail)

    • deleteUser($id)

      public function fetchAll()
      {
        $resultSet = $this->tableGateway->select();
        return $resultSet;
      }
      
      public function getUser($id)
      {
        $id  = (int) $id;
        $rowset = $this->tableGateway->select(array('id' => $id));
        $row = $rowset->current();
        if (!$row) {
          throw new \Exception("Could not find row $id");
        }
        return $row;
      }
      
      public function getUserByEmail($userEmail)
      {
        $rowset = $this->tableGateway->select(array('email' => $userEmail));
        $row = $rowset->current();
        if (!$row) {
          throw new \Exception("Could not find row $ userEmail");
        }
        return $row;
      }
      
      public function deleteUser($id)
      {
        $this->tableGateway->delete(array('id' => $id));
      }
      
  2. CommunicationApp/module/Users/src/Users/Controller/UserManagerController.php下创建一个新的用户管理控制器。

  3. UserManagerController控制器将具有以下动作:

    • indexAction(): 此动作用于渲染系统中所有可用的用户,我们还将渲染添加/编辑和删除链接,如下面的代码所示:

      $userTable = $this->getServiceLocator()
                                    ->get('UserTable');
      $viewModel  = new ViewModel(array(
                       'users' => $userTable->fetchAll())); 
      return $viewModel;
      
    • editAction(): 此动作用于渲染edit表单以修改与用户相关的信息:

      $userTable = $this->getServiceLocator()
                                    ->get('UserTable');
      $user = $userTable->getUser(
                             $this->params()->fromRoute('id')); 
      $form = $this->getServiceLocator()
                               ->get('UserEditForm');
      $form->bind($user);
      $viewModel  = new ViewModel(array(
                 'form' => $form, 
                 'user_id' => $this->params()->fromRoute('id')
                ));
      return $viewModel;
      

      提示

      绑定方法

      Form函数中使用的bind方法允许将模型映射到表单。该函数双向工作——它使用模型中的数据更新视图中的表单,如果表单验证通过,它还会使用表单提交数据更新模型,即$form->isValid()

      更多信息请见:

      framework.zend.com/manual/2.2/en/modules/zend.form.quick-start.html#binding-an-object

    • processAction(): 当用户edit表单提交时使用processAction动作;processAction保存更新后的记录并返回到indexAction

      // Get User ID from POST
      $post = $this->request->getPost();
      $userTable = $this->getServiceLocator()
                                    ->get('UserTable');       
      // Load User entity
      $user = $userTable->getUser($post->id);
      
      // Bind User entity to Form
      $form = $this->getServiceLocator()
                               ->get('UserEditForm');
      $form->bind($user);	
      $form->setData($post);
      
      // Save user
      $this->getServiceLocator()
                       ->get('UserTable')->saveUser($user);
      
    • deleteAction(): 此动作用于删除用户记录:

      $this->getServiceLocator()->get('UserTable')
              ->deleteUser($this->params()
              ->fromRoute('id'));
      
  4. 创建必要的视图并修改模块的config/module.config.php文件以指定一个唯一的子路由来访问此控制器:

    'user-manager' => array(
      'type'    => 'Segment',
      'options' => array(
        'route'    => '/user-manager[/:action[/:id]]',
        'constraints' => array(
          'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
          'id'     => '[a-zA-Z0-9_-]*',
        ),
        'defaults' => array(
          'controller' => 'Users\Controller\UserManager',
          'action'     => 'index',
        ),
      ),
    ),
    
  5. 最后,将新的控制器添加到invokables数组中:

    'Users\Controller\UserManager' => 'Users\Controller\UserManagerController',
    
  6. 现在,打开您的网络浏览器并访问控制器,登录到您的应用程序,并打开http://comm.-app.local/users/user-manager。您应该能看到一个类似于以下截图的页面:行动时间——实现管理用户的后台 UI

编辑用户链接应将您重定向到一个类似于以下截图中的用户编辑表单:

实施管理用户的管理 UI - 行动时间

删除用户链接可以用来从用户列表中删除用户:

实施管理用户的管理 UI - 行动时间

刚才发生了什么?

我们现在为我们的通信应用创建了一个管理用户界面,用于添加、修改和从应用中删除用户。我们已经利用了TableGateway模型的所有核心功能,并为在表访问对象上执行 CRUD 操作创建了函数。

在接下来的时间里,我们将利用TableGateway的一些更高级的应用。

尝试一下英雄

在我们进入下一节之前,这里有一个小任务供您练习。本节的任务是为创建一个新的添加用户表单。请参考以下截图:

尝试一下英雄

此表单将与我们在上一章中创建的注册表单类似。一旦表单提交,用户将被带回到用户列表页面。必须在用户列表页面中添加到该表单的链接。

文档管理

在本节中,我们将创建一个新的文档管理界面。文档管理界面将允许用户上传文档、管理上传,并将上传的文档与其他用户共享。用户界面还将允许用户管理共享,并添加/删除共享。

在本节中,我们将专注于为用户提供创建文件上传和管理这些上传的选项。我们将使用文件系统来存储上传的文件,并且上传文件的相对路径将存储在数据库中,与上传文件的用户相对应。

在文件上传中使用的某些重要的 Zend 框架组件包括:

  • 文件上传表单元素(Zend\Form\Element\File):File上传元素用于上传表单中显示文件输入框。此元素相当于 HTML 中用于允许用户上传文件的<input type='file'../>样式元素。可以通过在表定义中设置'type' => 'file'来渲染文件输入元素。

  • 文件传输适配器(Zend\File\Transfer\Adapter\Http):文件传输适配器在表单提交时处理文件上传。文件传输适配器中的setDestination()方法允许用户设置目的地并在该目的地接收文件。receive()方法用于启动传输。

创建文件上传表单 - 行动时间

在这个任务中,我们将创建一个新的文档上传表单;文件上传将存储在文件系统中,有关文件上传的信息将存储在名为uploads的数据库表中。文件上传存储在模块配置中定义的文件夹位置。执行以下步骤来完成此操作:

  1. 我们的第一步将是定义一个文件可以上传的位置,在模块的配置文件中(config/module.config.php):

    <?php
    return array(
      // Other configurations
      // ..
      // ..
      // MODULE CONFIGURATIONS
      'module_config' => array(
        'upload_location' => __DIR__ . '/../data/uploads',
      ),
    );
    
  2. 接下来,我们需要创建一个表来存储上传信息:

    CREATE TABLE IF NOT EXISTS uploads (
      id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
      filename VARCHAR( 255 ) NOT NULL ,
      label VARCHAR( 255 ) NOT NULL ,
      user_id INT NOT NULL,
      UNIQUE KEY (filename)
    );
    
  3. 创建UploadUploadTable类,用于与uploads表交互。添加默认方法,如saveUpload()fetchAll()getUpload()deleteUpload()。还添加了一个获取特定用户上传的方法getUploadsByUserId($userId)

    public function getUploadsByUserId($userId)
    {
      $userId  = (int) $userId;
      $rowset = $this->tableGateway->select(
            array('user_id' => $userId));
      return $rowset;
    }
    
  4. 创建一个用于管理文件上传的UploadManagerController控制器。添加indexAction()以显示用户完成的上传列表:

    $uploadTable = $this->getServiceLocator()
                                 ->get('UploadTable');
    $userTable = $this->getServiceLocator()
                                 ->get('UserTable');
    // Get User Info from Session
    $userEmail = $this->getAuthService()
                                 ->getStorage()->read();
    $user = $userTable->getUserByEmail($userEmail);
    
    $viewModel  = new ViewModel( array(
    'myUploads' => $uploadTable->getUploadsByUserId($user->id),
    ));
    return $viewModel;
    
  5. 创建一个上传表单,其中包含一个文件输入,如下代码片段所示:

    $this->add(array(
      'name' => 'fileupload',
      'attributes' => array(
        'type'  => 'file',
      ),
      'options' => array(
        'label' => 'File Upload',
      ),
    ));
    

    行动时间 – 创建文件上传表单

    上传表单

  6. 创建文件上传表单和index动作的视图。现在我们拥有了处理文件上传的所有必要元素。我们需要读取文件上传路径的配置,并使用 Zend HTTP 文件传输适配器在配置位置接收文件。服务定位器上的get('config')方法用于检索配置。以下代码用于从配置中读取文件上传位置:

    public function getFileUploadLocation()
    {
      // Fetch Configuration from Module Config
      $config  = $this->getServiceLocator()->get('config');
      return $config['module_config']['upload_location'];
    }
    
  7. 最后一步是处理文件上传过程。一旦表单成功提交,需要发生两个动作:

    1. 上传的文件必须移动到文件上传位置。

    2. 需要在'uploads'表中添加一个条目来描述上传,使用以下代码:

      $uploadFile = $this->params()->fromFiles('fileupload');
      $form->setData($request->getPost());
      
      if ($form->isValid()) {
        // Fetch Configuration from Module Config
        $uploadPath    = $this->getFileUploadLocation();
      
        // Save Uploaded file    	
        $adapter = new \Zend\File\Transfer\Adapter\Http();
        $adapter->setDestination($uploadPath);
        if ($adapter->receive($uploadFile['name'])) {
          // File upload sucessfull
          $exchange_data = array();
          $exchange_data['label'] = $request->getPost()->get('label');
          $exchange_data['filename'] = $uploadFile['name'];
          $exchange_data['user_id'] = $user->id;
      
          $upload->exchangeArray($exchange_data);
          $uploadTable = $this->getServiceLocator()->get('UploadTable');
          $uploadTable->saveUpload($upload);
      
          return $this->redirect()
                  ->toRoute('users/upload-manager' , 
                        array('action' =>  'index'
                    ));
        }
      }
      
  8. UploadManager控制器添加一个子路由(上传管理器),并将控制器添加到invokables列表中。

  9. 打开网页并测试上传表单。

最终的表单将看起来如下截图所示:

行动时间 – 创建文件上传表单

刚才发生了什么?

现在我们已经创建了一个文件上传流程,允许用户将文件上传到应用程序中并查看已上传的文件。我们使用了 Zend 框架的文件上传处理组件来处理文件上传。在下一节中,我们将设置一个文件共享机制,以便文档可以与不同的用户共享。在我们继续实现文件共享之前,请完成以下任务。

英雄行动

您的下一个任务是将删除选项添加到上传中,允许用户删除上传的文件,如下截图所示。同时,确保在触发删除动作时从文件系统中删除文件。

英雄行动

管理文件共享

现在我们已经拥有了一个完全功能的文档管理部分,我们的下一个任务是将这个文档管理系统扩展以支持与其他用户的文件共享。实现文件共享机制最重要的部分是存储上传共享的信息;我们通过将文档与upload_sharing表中的用户 ID 链接来实现这一点。

行动时间 – 实现文件共享系统

为了实现文件共享,我们需要创建一个新的表upload_sharing,并将所有共享相关信息存储在该表中。以下步骤将解释如何在我们的应用程序中实现这一点:

  1. 创建一个名为upload_sharing的新表;此表将保存关于与用户共享的上传的关系:

    CREATE TABLE IF NOT EXISTS uploads_sharing (
      id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
      upload_id INT NOT NULL ,
      user_id INT NOT NULL,
      UNIQUE KEY (upload_id, user_id)
    );
    
  2. 在模块定义Module.php中,为uploads_sharing表添加一个简单的TableGateway对象:

    'UploadSharingTableGateway' => function ($sm) {
      $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
      return new TableGateway('uploads_sharing', $dbAdapter);
    },
    
  3. 修改UploadTable类的构造函数,使其接受一个额外的上传共享TableGateway对象参数:

    public function __construct(TableGateway $tableGateway, 
                    TableGateway $uploadSharingTableGateway)
    {
        $this->tableGateway = $tableGateway;
        $this->uploadSharingTableGateway = $uploadSharingTableGateway;
    }
    
  4. 修改模块配置(Module.php)以支持UploadTable工厂的UploadSharingTableGateway

    'UploadTable' =>  function($sm) {
      $tableGateway = $sm->get('UploadTableGateway');
      $uploadSharingTableGateway = $sm->get('UploadSharingTableGateway');
      $table = new UploadTable($tableGateway, $uploadSharingTableGateway);
      return $table;
    },
    
  5. 修改UploadTable类以支持以下文件共享功能:

    • addSharing(): 为给定上传添加新的共享权限给用户

    • removeSharing(): 删除特定上传/用户组合的共享权限

    • getSharedUsers(): 获取共享上传的用户列表

    • getSharedUploadsForUserId(): 获取为该用户共享的上传列表

    这可以通过以下代码实现:

    public function addSharing($uploadId, $userId)
    {
      $data = array(
        'upload_id' => (int)$uploadId,
        'user_id'  => (int)$userId,
      );
      $this->uploadSharingTableGateway->insert($data);
    }
    
    public function removeSharing($uploadId, $userId)
    {
      $data = array(
        'upload_id' => (int)$uploadId,
        'user_id'  => (int)$userId,
      );
      $this->uploadSharingTableGateway->delete($data);
    }
    
    public function getSharedUsers($uploadId)
    {
      $uploadId  = (int) $uploadId;
      $rowset = $this->uploadSharingTableGateway->select(
                          array('upload_id' => $uploadId));
      return $rowset;
    }    
    
    public function getSharedUploadsForUserId($userId)
    {
      $userId  = (int) $userId;
    
      $rowset = $this->uploadSharingTableGateway->select(
      function (Select $select) use ($userId){
        $select->columns(array()) 
          ->where(array('uploads_sharing.user_id'=>$userId))
          ->join('uploads', 'uploads_sharing.upload_id = uploads.id');
      });
    
      return $rowset;
    }
    

    管理文档部分列出了特定用户的全部上传,同时也列出了与其他用户共享的上传:

    实施文件共享系统的时间 - 实现文件共享系统

  6. 修改编辑上传表单以显示共享上传的用户列表;这可以通过将上传 ID 传递给UploadTable对象的getSharedUsers()方法来实现。

  7. 在编辑上传表单中添加一个新部分,允许添加新的共享;这是通过在下拉列表中显示系统中的所有用户列表来实现的。当用户点击添加共享时,upload_sharing表中将添加一条新记录:

    $userTable = $this->getServiceLocator()
                  ->get('UserTable');
    $uploadTable = $this->getServiceLocator()
                  ->get('UploadTable');
    $form = $this->getServiceLocator()->get('UploadForm');
    $request = $this->getRequest();
    if ($request->isPost()) {
      $userId = $request->getPost()->get('user_id');
      $uploadId = $request->getPost()->get('upload_id');
      $uploadTable->addSharing($uploadId, $userId);
    }
    

    以下截图显示了带有下拉列表以添加共享的上传共享页面:

    实施文件共享系统的时间 - 实现文件共享系统

  8. 文件共享实现的最后部分是允许用户下载共享文件。这由我们文件共享应用程序中定义的fileDownloadAction()函数提供:

    public function fileDownloadAction()
    {  
      $uploadId = $this->params()->fromRoute('id');
      $uploadTable = $this->getServiceLocator()->get('UploadTable');
      $upload = $uploadTable->getUpload($uploadId);
    
      // Fetch Configuration from Module Config
      $uploadPath    = $this->getFileUploadLocation();
      $file = file_get_contents($uploadPath ."/" . $upload->filename);
    
      // Directly return the Response 
      $response = $this->getEvent()->getResponse();
      $response->getHeaders()->addHeaders(array(
        'Content-Type' => 'application/octet-stream',
        'Content-Disposition' => 'attachment;filename="' .$upload->filename . '"',
    
      ));
      $response->setContent($file);
    
      return $response;      
    }
    

    提示

    文件下载

    为了实现文件下载,我们需要禁用布局。这可以通过直接为特定操作提供 HTTP 响应对象作为输出来实现,如前代码所示。这也可以通过setTerminal()来实现,如下代码所示:

       $result = new ViewModel();
       $result->setTerminal(true);
       return $result;
    

    大文件下载

    file_get_contents()方法能够处理小文件上传,在处理大文件时会消耗大量内存。为了提高性能,你可以创建一个流 HTTP 响应对象Zend\Http\Response\Stream(),然后流式传输文件下载。

  9. 现在我们已经建立了一个完全功能的文件共享系统。测试文件共享系统;首先与不同用户共享文件,然后以不同用户身份登录和登出。

最终表单应如下截图所示:

实施文件共享系统的时间 - 实现文件共享系统

发生了什么?

你创建了一个可以存储用户和上传关系的表;你修改了 UploadTable 类以支持额外的共享功能。你创建了控制器和视图以启用文件共享,并最终提供了用户使用文件下载脚本来下载共享文件的能力。至此,你已经成功实现了文件共享系统,用户现在可以在系统中上传、编辑和共享文档。

快速问答 - 数据管理和文档共享

Q1. 在 TableGateway 中,哪个函数用于确定最后插入的记录 ID?

  1. getLastId()

  2. getLastInsertId()

  3. get('last_insert_id')

  4. getLastInsertValue()

Q2. 哪个方法可以用来在视图模型中禁用布局?

  1. $viewModel->setNoLayouts(true)

  2. $ viewModel->Layouts(false)

  3. $viewModel->setTerminal(true)

  4. $viewModel->setLayouts(false)

摘要

在本章中,我们讨论了数据和管理文件的相关主题。首先,我们详细说明了 TableGateway 数据库模式的用法。然后,我们通过利用 Zend 框架的文件传输组件实现了简单的文件上传服务。最后,我们通过利用 Zend 框架的文件传输组件和 TableGateway 模式实现了简单的文件共享服务。在下一章中,我们将专注于前端开发,特别是 JavaScript 和 AJAX 调用。

第五章:聊天和电子邮件

在任何 Web 应用程序开发中,都将非常依赖客户端脚本,主要包括 JavaScript 和 CSS。Zend Framework 的 MVC 模型提供了对发送到浏览器输出的基本支持。Zend Framework 2 中的视图辅助工具类提供了对在客户端浏览器中渲染的内容的最大控制。

在本章中,我们将专注于构建一个简单的群聊和电子邮件组件,该组件将利用 Zend Framework 2.0 的各种前端功能。本章涵盖的一些重要主题包括:

  • 在 Zend Framework 2 应用程序中使用外部 JavaScript 库

  • 使用 Zend Framework 2 和 JavaScript 实现一个简单的群聊应用程序

  • 使用 Zend\Mail 发送电子邮件

  • Zend Framework 事件管理器简介

布局和视图

Zend Framework MVC 使用布局和视图在浏览器中渲染页面;整体页面内容由布局规范控制,视图级别的信息包含在视图中。其概念是尽量减少为每个视图生成冗余 HTML 代码的数量。

通过使用布局,应用程序可以拥有一致的用户界面,这也便于定制;视图提供了修改目标内容的灵活性,并允许最大程度地定制。这也被称为 两步 视图。

当生成新的视图时,从 view_manager 配置中的布局定义中识别适当的布局,并使用该布局渲染视图。

布局和视图

上述示意图解释了布局和视图是如何组合成 HTML 页面的,因此对于每个视图,视图部分会变化,而布局部分保持静态。

视图辅助工具

Zend Framework 2 提供了一系列视图辅助工具,帮助我们执行视图上的复杂操作;如果包含的辅助工具不足,你可以通过实现接口 Zend\View\HelperInterface 来定义自己的自定义辅助工具。

在本节中,我们将快速回顾一些包含在 Zend Framework 2 中的辅助工具。

URL 辅助工具

此辅助工具的语法是 url($name, $urlParams, $routeOptions = array(), $reuseMatchedParams = array())

URL 辅助工具用于生成特定路由的 URL。可以将路由段匹配参数传递给 URL 辅助工具,根据路由选项形成 URL;例如,参见以下:

<a href="<?php $this->url('users/upload-manager', array('action'=>'edit', 'id' =>  10));">Edit</a>

如果路由定义如下,此代码将生成 <a href="/users/upload-manager/edit/10">编辑</a>

'route' => '/user-manager[/:action[/:id]]'

BasePath 辅助工具

此辅助工具的语法是 basePath()

BasePath 辅助工具返回视图的基本 URL,开发者可以使用它来在其自定义 URL 前面添加,并为各种资源创建链接。

JSON 辅助工具

此辅助工具的语法是 json($jsonData = array())

JSON 辅助函数用于将 PHP 数组渲染为 JSON 编码的数据。大多数 AJAX 库根据其内容头部分类 JSON 内容,此辅助函数也将内容类型头设置为 application/json

具体的占位符实现

Zend 框架利用占位符辅助函数在 HTML head 部分执行一些标准操作,包括添加/删除对新 JavaScript 库的引用、链接新样式、添加和交叉引用脚本,以及添加/删除 HTML head 部分的 meta 内容。

这是通过以下称为具体占位符辅助函数的辅助函数列表实现的。之所以称它们为占位符辅助函数,是因为辅助函数本身不会对内容的渲染方式做出任何改变。例如,如果你在 HTML 代码中添加 <?php echo $this->headLink(); ?>,这不会做任何事情,直到你通过使用 appendStylesheet 或其他函数向 headLink 辅助函数添加内容。

HeadLink 辅助函数用于修改 HTML head 部分的 <link> 标签;此辅助函数用于附加或管理外部 CSS。

在此辅助函数中最常用的函数如下列出:

  • appendStylesheet($href, $media, $conditionalStylesheet, $extras)

  • offsetSetStylesheet``($index, $href, $media, $conditionalStylesheet, $extras)

  • prependStylesheet``($href, $media, $conditionalStylesheet, $extras)

  • setStylesheet($href, $media, $conditionalStylesheet, $extras)

注意

要在 HTML 布局/视图中渲染 Link 标签,请使用以下脚本:

<?php echo $this->headLink(); ?>

HeadMeta 辅助函数

HeadMeta 辅助函数用于修改 HTML head 部分的 <meta> 标签;此辅助函数用于操作 HTML meta 信息。

在此辅助函数中最常用的函数如下列出:

  • appendName($keyValue, $content, $conditionalName)

  • offsetSetName($index, $keyValue, $content, $conditionalName)

  • prependName($keyValue, $content, $conditionalName)

  • setName($keyValue, $content, $modifiers)

  • appendHttpEquiv($keyValue, $content, $conditionalHttpEquiv)

  • offsetSetHttpEquiv($index, $keyValue, $content, $conditionalHttpEquiv)

  • prependHttpEquiv($keyValue, $content, $conditionalHttpEquiv)

  • setHttpEquiv($keyValue, $content, $modifiers)

  • setCharset($charset)

注意

要在 HTML 布局/视图中渲染 meta 标签,请使用以下脚本:

<?php echo $this->headMeta(); ?>

HeadScript 辅助函数

HeadScript 辅助函数用于修改 HTML head 部分的 <script> 标签;此辅助函数用于附加外部 JavaScript 并将 <script> 标签添加到 HTML head 部分中。

在此辅助函数中最常用的函数如下列出:

  • appendFile($src, $type = 'text/javascript', $attrs = array())

  • offsetSetFile($index, $src, $type = 'text/javascript', $attrs = array())

  • prependFile($src, $type = 'text/javascript', $attrs = array())

  • setFile($src, $type = 'text/javascript', $attrs = array())

  • appendScript($script, $type = 'text/javascript', $attrs = array())

  • offsetSetScript($index, $script, $type = 'text/javascript', $attrs = array())

  • prependScript($script, $type = 'text/javascript', $attrs = array())

  • setScript($script, $type = 'text/javascript', $attrs = array())

注意

要在 HTML 布局/视图中渲染 Script 标签,请使用以下脚本:

<?php echo $this->headScript(); ?>

HeadStyle 辅助工具

HeadStyle 辅助工具用于修改 HTML head 部分的 <style> 标签;此辅助工具通过向 HTML head 部分添加 <style> 标签来添加内部样式。

此辅助工具中最常用的函数如下所示:

  • appendStyle($content, $attributes = array())

  • offsetSetStyle($index, $content, $attributes = array())

  • prependStyle($content, $attributes = array())

  • setStyle($content, $attributes = array())

注意

要在 HTML 布局/视图中渲染 Style 标签,请使用以下脚本:

<?php echo $this->headStyle(); ?>

HeadTitle 辅助工具

HeadTitle 辅助工具用于在 HTML head 部分的 <title> 标签中渲染标题;对 headTitle() 辅助工具的多次调用会创建一个标题列表,当在布局/视图中输出标签时渲染。可选参数 $setType 可以设置为覆盖现有的标题数组,默认为 APPEND,可以覆盖为 PREPENDSET(覆盖)。

此辅助工具的语法是 headTitle($title, $setType = null);

注意

要在 HTML 布局/视图中渲染 Title 标签,请使用以下脚本:

<?php echo $this->headTitle(); ?>

使用 jQuery UI 在简单页面中操作的时间

在此任务中,我们将将一些现有的页面转换为使用 jQuery UI 库,并在该页面中使用 jQuery UI 渲染按钮:

  1. 查看如下截图所示的现有应用程序主页;我们的下一个任务是转换登录注册链接,使其以 jQuery UI 按钮的形式渲染:使用 jQuery UI 在简单页面中操作的时间 – 使用 jQuery UI

    现有的应用程序主页

  2. index 视图(module/Users/view/users/index/index.html)中的登录注册链接替换,并将 ui-button 类添加到链接中,如下代码片段所示:

    <a href="/users/login" class='ui-button'>Login</a>
    <a href="/users/register" class='ui-button'>Register</a>
    
  3. 在视图的开始处添加对 jQuery UI 的外部引用:

    // Attached jQuery UI Scripts
    $this->headScript()
    ->appendFile('http://code.jquery.com/jquery-1.8.3.js','text/javascript');
    
    $this->headScript()
    ->appendFile('http://code.jquery.com/ui/1.10.0/jquery-ui.js','text/javascript');
    
    // Attach jQuery UI Styles 
    $this->headLink()->appendStylesheet('http://code.jquery.com/ui/1.10.0/themes/base/jquery-ui.css');
    

    提示

    引用自定义 JavaScript 库

    除了直接引用外部脚本外,你也可以选择将脚本下载到应用程序的 /public 文件夹,并将相对链接作为参数传递给 appendFileappendStylesheet 函数。你也可以使用 basePath() 辅助工具添加基本 URL。

  4. 添加一个 UI 初始化脚本以将按钮的外观和感觉应用到链接上:

    // UI Initializer for buttons
    $this->headScript()->appendScript(
    '$(function() {
        $("a.ui-button").button();
      });', 'text/javascript');
    
  5. 现在在浏览器中预览主页,你会看到登录注册按钮都使用了 jQuery UI 进行样式化,如下截图所示:使用 jQuery UI 在简单页面中操作的时间 – 使用 jQuery UI

索引页上的 查看源代码 链接将揭示 headScript() 的应用,如下面的代码所示:

<!DOCTYPE html>
<html lang="en">
...
...
<script type="text/javascript" src="img/jquery-1.8.3.js"></script>
<script type="text/javascript" src="img/jquery-ui.js"></script>
<script type="text/javascript">
    //<!--
    $(function() {
   $("a.ui-button").button();
  });
    //-->
</script>
...
...
</html>

发生了什么事?

我们使用了 Zend 框架的视图助手来连接到外部 JavaScript 库;然后我们使用 headScript() 视图助手在 HTML head 部分添加了自定义 JavaScript。

现在我们已经将我们的应用程序与外部 JavaScript 集成;在下一个练习中,我们将学习更多关于如何将脚本添加到 HTML head 部分的内容。

尝试一下英雄

在我们继续构建 群聊 界面之前,这里有一个简单的任务要你完成。现在你已经了解了如何链接外部 JavaScript 库,你可以从其网站下载 jQuery UI,将其提取到 public/ 文件夹中,并修改之前列出的页面以使用下载的 jQuery UI 版本。

jQuery UI 可以从 jqueryui.com/ 下载。

构建简单的群聊

我们接下来的任务是构建一个简单的群聊应用程序,允许多个用户登录到我们的系统并相互聊天。这个工具的后端相当简单。我们需要创建一个表来存储所有用户消息并在单独的视图中渲染它们;我们将创建一个简单的表单,允许用户发送消息。

行动时间 - 创建简单的群聊应用程序

  1. 创建一个新的 chat_messages 表来存储所有用户消息:

    CREATE TABLE IF NOT EXISTS chat_messages (
      id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
      user_id INT NOT NULL,
      message VARCHAR( 255 ) NOT NULL ,  
      stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
    
  2. CommunicationApp/module/Users/src/Users/Controller/GroupChatController.php 中创建一个群聊控制器。

  3. CommunicationApp/module/Users/config/module.config.php 进行必要的修改,并将新的控制器添加到可调用和路由中:

    // Invokable
    'Users\Controller\GroupChat' => 'Users\Controller\GroupChatController',
    
    // Route
    'group-chat' => array(
      'type'    => 'Segment',
      'options' => array(
        'route'    => '/group-chat[/:action[/:id]]',
        'constraints' => array(
          'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
          'id'     => '[a-zA-Z0-9_-]*',
        ),
        'defaults' => array(
          'controller' => 'Users\Controller\GroupChat',
          'action'     => 'index',
        ),
      ),
    ),
    
  4. CommunicationApp/module/Users/view/users/group-chat/index.phtml 中创建一个新的视图:

    <?php 
    $this->headScript()->appendScript(
    '$(function() {
        $( "#btnRefresh" )
          .click(function( event ) {
      document.getElementById("messageListFrame").contentWindow.location.reload(true);
       })
      });', 'text/javascript');
    
    $this->headStyle()->appendStyle('
      #userName { width:100px; margin-top:10px; display: inline}
      #messageText { width:700px; margin-top:10px;}
    ');
    ?>
    <h3>Group Chat</h3>
    <iframe src="<?php echo $this->url('users/group-chat', array( 
                            'action' =>  'messageList' 
                        )) ?>" width="80%" height="400px" id="messageListFrame"></iframe>
    
    <?php 
    // Render the opening tag
    echo $this->form()->openTag($form);
    
    // ...loop through and render the form elements...
    echo '<label id="userName">'. $userName .': </label>';
    foreach ($form as $element) {
        echo $this->formElement($element);       // <-- Magic!
        echo $this->formElementErrors($element);
    }
    
    // Render the closing tag
    echo $this->form()->closeTag();
    ?>
    
  5. messageList 动作添加到 GroupChatController - CommunicationApp/module/Users/src/Users/Controller/GroupChatController.php;这个动作将查询 chat_messages 表并获取该表中的所有记录,并将其传递给视图:

    public function messageListAction()
    {
      $userTable = $this->getServiceLocator()->get('UserTable');
      $chatMessageTG = $this->getServiceLocator()->get('ChatMessagesTableGateway');
      $chatMessages = $chatMessageTG->select();
    
      $messageList = array();
      foreach($chatMessages as $chatMessage) {
        $fromUser = $userTable->getUser($chatMessage->user_id);
        $messageData = array();
        $messageData['user'] = $fromUser->name;
        $messageData['time'] = $chatMessage->stamp;
        $messageData['data'] = $chatMessage->message;
        $messageList[] = $messageData;
      }
    
      $viewModel  = new ViewModel(array('messageList' => $messageList));
      $viewModel->setTemplate('users/group-chat/message-list');
      $viewModel->setTerminal(true);    
      return $viewModel;
    }
    
  6. 创建一个简单的消息列表视图,CommunicationApp/module/Users/view/users/group-chat/message-list.phtml,它将列出 $messageList 数组中的消息:

    <!DOCTYPE html>
    <html lang="en">
    <body>
    <section id="messages" >
      <?php foreach ($messageList as $mesg) : ?>
      <div class="message" style="clear:both;">
        <span class='msg-time'>
      [<?php echo $this->escapeHtml($mesg['time']);?>]
        </span> 
        <span class='msg-user'> 
      <?php echo $this->escapeHtml($mesg['user']);?>: 
        </span>
        <span class='msg-data'>
      <?php echo $this->escapeHtml($mesg['data']);?>
        </span>
      </div>
      <?php endforeach; ?> 
    </section>
    </body>
    </html>
    
  7. 创建一个名为 sendMessage() 的方法,当用户发送消息时调用,将消息存储在数据库中,如下面的代码所示。这需要放在群聊控制器 CommunicationApp/module/Users/src/Users/Controller/GroupChatController.php 中。

    protected function sendMessage($messageTest $fromUserId)
    {
      $chatMessageTG = $this->getServiceLocator()
                        ->get('ChatMessagesTableGateway');
      $data = array(
        'user_id' => $fromUserId,
        'message'  => $messageTest,
        'stamp' => NULL
      );
      $chatMessageTG->insert($data);
      return true;
    }
    
  8. 修改 indexAction 函数以显示一个 发送消息 表单,并在表单提交时调用 sendMessage()。这需要放在群聊控制器 CommunicationApp/module/Users/src/Users/Controller/GroupChatController.php 中。

    public function indexAction(
    {
      $user = $this->getLoggedInUser();	
      $request = $this->getRequest();
      if ($request->isPost()) {
        $messageTest = $request->getPost()->get('message');
        $fromUserId = $user->id;
        $this->sendMessage($messageTest, $fromUserId);
        // to prevent duplicate entries on refresh
        return $this->redirect()->toRoute('users/group-chat');
      }
    
      //Prepare Send Message Form
      $form    = new \Zend\Form\Form();
    
      $form->add(array(
        'name' => 'message',
        'attributes' => array(
          'type'  => 'text',
          'id' => 'messageText',
          'required' => 'required'
        ),
        'options' => array(
          'label' => 'Message',
        ),
      ));
    
      $form->add(array(
              'name' => 'submit',
              'attributes' => array(
                  'type'  => 'submit',
                  'value' => 'Send'
              ),
      ));
    
      $form->add(array(
        'name' => 'refresh',
        'attributes' => array(
          'type'  => 'button',
          'id' => 'btnRefresh',
          'value' => 'Refresh'
        ),
      ));
    
      $viewModel  = new ViewModel(array('form' => $form, 
                                  'userName' => $user->name));
      return $viewModel;
    }
    
  9. 为了测试更改,使用不同的凭据从两个不同的计算机或两个不同的浏览器登录到浏览器,并测试 群聊 界面。行动时间 - 创建简单的群聊应用程序

刚才发生了什么?

我们现在已成功实现了使用 Zend Framework 的 群组聊天 界面;该界面适用于多人进行群组聊天。我们的下一个任务将需要构建一个机制向系统中的其他用户发送电子邮件;为此,我们将充分利用 Zend Framework 的邮件功能。

尝试一下英雄

在你进入下一节之前,这里有一个简单的练习供你尝试。在 群组聊天 界面中,我们有一个 刷新 按钮,用于重新加载 iframe 标签。编写一些 JavaScript 并将其附加到视图上,这将每五秒重新加载 IFrame。

发送邮件

Zend Framework 提供了 Zend\Mail 库来发送和接收电子邮件。在本节中,我们将介绍 Zend Framework 的邮件功能的基础,并实现一个简单的邮件脚本。

Zend\Mail 支持纯文本和 MIME 兼容的多部分电子邮件消息。框架默认支持 Sendmail、SMTP 和文件传输;可以使用 Zend\Mail\Transport\TransportInterface 实现新的传输。

Zend\Mail\Transport

Mail 传输用于将电子邮件消息发送给收件人;Zend\Mail 支持以下传输:

  • 使用 Zend\Mail\Transport\Sendmail 的 Sendmail

  • 使用 Zend\Mail\Transport\Smtp 的 SMTP

  • 使用 Zend\Mail\Transport\File 的文件传输

Mail 传输实现了 send() 方法;此方法接受一个类型为 Zend\Mail\Message 的对象作为参数;此对象(Zend\Mail\Message)包含电子邮件消息所需的所有必要信息;消息通过传输发送。

Zend\Mail\Message

Zend\Mail\Message 用于在 Zend Framework 中编写邮件消息;此对象接受包括发件人地址、收件人地址、主题和正文在内的各种参数。如果消息是一个 MIME 兼容的多部分消息,则可以使用 setBody() 方法将消息的正文设置为 Zend\Mime\Message 邮件消息对象,然后发送消息。以下列出了 Zend\Mail\Message 中一些最常用的方法:

  • setFrom()

  • setHeaders

  • setTo()

  • addCc()addBcc()

  • setSubject()

  • setBody()

Zend\Mime\Message 和 Zend\Mime\Part

对于发送 HTML 或多部分内容,每个消息部分都定义为 Zend\Mime\Part 对象,包括其类型,并使用 setParts() 方法与 Zend\Mime\Message 对象关联。使用 setBody() 方法将 Zend\Mime\Message 对象分配给 Zend\Mail\Message 对象。

动手实践 - 创建简单的电子邮件表单

在这个活动中,我们将创建一个电子邮件表单,利用 Zend 的邮件功能:

  1. 创建一个简单的电子邮件表单,包括主题、消息内容和收件人输入字段。

  2. 设置一个新的控制器以显示表单并编写必要的视图。

  3. 修改控制器,使其引用 Zend\Mail 命名空间。

    use Zend\Mail;
    
  4. 创建一个新的控制器方法来执行实际的电子邮件发送;这可以通过以下代码放置在我们的群聊控制器(CommunicationApp/module/Users/src/Users/Controller/GroupChatController.php)中:

    protected function sendOfflineMessage($msgSubj, 
                             $msgText, $fromUserId, $toUserId)
    {
      $userTable = $this->getServiceLocator()
                                   ->get('UserTable');
    
      $fromUser = $userTable->getUser($fromUserId);
      $toUser = $userTable->getUser($toUserId);
    
      $mail = new Mail\Message();
      $mail->setFrom($fromUser->email, $fromUser->name);
      $mail->addTo($toUser->email, $toUser->name);
      $mail->setSubject($msgSubj);
      $mail->setBody($msgText);
    
      $transport = new Mail\Transport\Sendmail();
      $transport->send($mail);
    
      return true;
    }
    

    提示

    Sendmail传输(Zend\Mail\Transport\Sendmail)在 Linux 中默认可用,可用于发送电子邮件消息。Windows 用户可以使用 SMTP 传输(Zend\Mail\Transport\Smtp)连接 SMTP 服务器以发送电子邮件消息。以下参考链接提供了一个使用 SMTP 传输的快速示例:

    packages.zendframework.com/docs/latest/manual/en/modules/zend.mail.transport.html#zend-mail-transport-quick-start-smtp-usage

  5. 在网页浏览器中预览表单,并测试是否收到电子邮件;收件人将收到类似以下的消息:行动时间 – 创建简单的电子邮件表单行动时间 – 创建简单的电子邮件表单

发生了什么?

我们已经使用Zend\Mail对象在系统中通过Sendmail邮件传输发送电子邮件;我们还学习了如何发送 HTML 或多部分邮件消息。

尝试一下英雄

在进入下一节之前,尝试实现用于发送 HTML 电子邮件的电子邮件表单。

Zend\EventManager

Zend Framework 2 是一个事件驱动的框架;事件管理器允许您将事件附加到几乎任何功能。在 Zend Framework 的事件管理中,有三个主要术语,如下所述:

  • 事件管理器EventManager对象是包含一组监听器和它们相关事件的集合

  • 监听器:监听器是对触发事件做出反应的回调

  • 事件:事件是事件管理器触发的动作

事件管理器提供了attach()trigger()来分别创建和触发事件。我们主要将依赖于 MVC 事件进行各种操作,MVC 应用程序事件执行的顺序在以下图中描述:

Zend\EventManager

提示

以下链接中的文章解释了 ZF2 应用程序中事件序列:

akrabat.com/zend-framework-2/a-list-of-zf2-events/

成功执行的事件流如下:

  1. Zend\Mvc\Application: 引导

  2. Zend\Mvc\Application: 路由

  3. Zend\Mvc\Application: 分发

  4. Zend\Mvc\Controller\ActionController: 分发(如果控制器扩展此类)

  5. Zend\Mvc\Application: 渲染

  6. Zend\View\View: 渲染器

  7. Zend\View\View: 响应

  8. Zend\Mvc\Application: 完成

在分发(或)路由过程中出现错误的情况下,事件流将如下所示:

  1. Zend\Mvc\Application: 分发错误

  2. Zend\Mvc\Application: 渲染

  3. Zend\View\View: 渲染器

  4. Zend\View\View: 响应

  5. Zend\Mvc\Application: 完成

在我们的下一个活动中,我们将尝试使用 Zend Framework 中的共享事件管理器为多个控制器设置新布局。

操作时间 – 使用 ZF 事件设置模块布局

执行以下步骤以使用 ZF 事件设置模块布局:

  1. 我的账户页面创建一个新的布局并将其保存到CommunicationApp/module/Users/view/layout/myaccount-layout.phtml

  2. 将布局添加到CommunicationApp/module/Users/config/module.config.php文件中的view_manager -> template_map下:

    'layout/myaccount' => __DIR__ . '/../view/layout/myaccount-layout.phtml',
    
  3. 打开CommunicationApp/module/Users/module.php文件并添加对MvcEvent的引用:

    use Zend\Mvc\MvcEvent;
    
  4. 用以下代码覆盖onBootStrap()方法:

    public function onBootstrap($e)
    {
      $eventManager  = $e->getApplication()->getEventManager();
      $moduleRouteListener = new ModuleRouteListener();
      $moduleRouteListener->attach($eventManager);
    
      $sharedEventManager = $eventManager->getSharedManager(); // The shared event manager
      $sharedEventManager->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, function($e) {
        $controller = $e->getTarget(); // The controller which is dispatched
        $controllerName = $controller->getEvent()
               ->getRouteMatch()->getParam('controller');
        if (!in_array($controllerName, 
                  array('Users\Controller\Index', 'Users\Controller\Register', 'Users\Controller\Login'))) {
          $controller->layout('layout/myaccount');
        }
      });
    }
    
  5. 在任何网络浏览器中打开通信应用程序页面;注意布局:操作时间 – 使用 ZF 事件设置模块布局

  6. 登录应用程序并查看新布局的应用:操作时间 – 使用 ZF 事件设置模块布局

发生了什么?

我们已经使用 Zend Framework 事件管理器将监听器附加到模块的Dispatch事件。因此,每次控制器被分发时,都会触发此事件。回调检查控制器是否有效,如果控制器不在具有default布局的控制器列表中,则将这些控制器的myaccount布局应用于它们。

快速问答 – 聊天和电子邮件

Q1. 以下哪些助手可以用来在 HTML head部分定义/附加 CSS 样式?

  1. HeadLink

  2. HeadScript

  3. HeadCss

  4. HeadStyle

Q2. 以下哪些是 Zend Framework 2 支持的邮件传输方式?

  1. Zend\Mail\Transport\Pop

  2. Zend\Mail\Transport\Smtp

  3. Zend\Mail\Transport\Imap

  4. Zend\Mail\Transport\File

摘要

在本章中,我们涵盖了广泛的主题;首先我们学习了如何使用外部 JavaScript。接下来,我们创建了一个简单的群聊应用程序,然后我们学习了Zend\Mail并实现了一个简单的邮件表单。最后,我们学习了事件以及如何在 Zend Framework 中使用这些事件。在下一章中,我们将通过处理各种媒体共享 API 来使用 Zend Framework 进行媒体共享。

第六章:媒体共享

随着社交媒体的出现,上传和管理图片/视频已成为非常普遍的事情。现在越来越多的应用程序允许您通过外部媒体托管/服务(如 Google、Flickr 和 YouTube)分享和检索媒体。在 Zend Framework 1.0 中,Zend_Service 包提供大量第三方集成。这一情况在 ZF2 和新的模块框架中发生了变化。

在本章中,我们将使用各种外部 Zend Framework 2.0 模块来管理图片和视频。让我们快速看一下本章我们将学习的内容:

  • 在 Zend Framework 应用程序中安装外部模块

  • 设置简单的照片画廊

  • 使用 WebinoImageThumb 调整和操作图片

  • Zend GData API 简介

  • 使用 GData API 从 Google Photos 和 YouTube 获取相册

外部模块

Zend Framework 2.0 最重要的特性之一是能够在 PHP 应用程序中集成外部模块,并且这种集成完全通过依赖管理工具(在我们的例子中是 Composer)来管理。

此功能允许在不担心维护应用程序内部外部库的情况下开发 PHP 应用程序。库和应用程序可以解耦并分别维护。

在本章中,我们将使用外部模块来调整图片;我们还将利用外部库来连接 Google 服务。

小贴士

Composer

Composer 是在 Zend Framework 中使用的依赖管理解决方案之一。Composer 允许开发者声明他们应用程序所需的依赖,并将处理这些库的安装。依赖配置存储在名为 composer.json 的文件中。

调整图片大小

Zend Framework 1.0 有一个调整大小的过滤器,允许在上传时调整图片大小;在 Zend Framework 2.0 中,此选项不再存在。我们的下一个任务将是找到一个简单的图片调整大小模块,并将其安装到我们的应用程序中。所以,让我们开始吧。

使用模块调整图片大小的时间

执行以下步骤:

  1. 前往 Zend Framework 2 模块网站:

    modules.zendframework.com/

  2. 搜索 WebinoImageThumb

  3. 要安装此模块,您需要更新应用程序根目录下的 composer.json 并将其作为所需模块包含在内。

  4. 要做到这一点,请编辑 CommunicationApp/composer.json 并修改所需部分:

    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "2.0.*",
        "webino/webino-image-thumb": "1.*",
    }
    
  5. 现在运行 composer.phar update 来安装新添加的依赖。

    $ php composer.phar update
    Loading composer repositories with package information
    Updating dependencies
     - Installing webino/webino-image-thumb (1.0.0)
     Downloading: 100%
    
    Writing lock file
    Generating autoload files
    
    
  6. 您将能够在 vendor 文件夹中看到新安装的模块,如下所示:使用模块调整图片大小的时间

  7. 现在模块已下载,我们需要在 CommunicationApp/config/application.config.php 中激活该模块,通过将 'WebinoImageThumb' 添加到 modules 数组中。

    return array(
        'modules' => array(
            'Application',
          'WebinoImageThumb',
        'Users',
        ),
    

刚才发生了什么?

我们已使用依赖管理工具 Composer 将外部模块安装到我们的应用程序中。我们还在应用程序中激活了该模块,以便模块可以在整个应用程序中访问。

尝试一下

现在您已经知道如何在 Zend Framework 2 应用程序中安装新模块,这里有一个简单的任务给您。在这个应用程序上安装 Zend GData 包。安装此包的说明可在 packages.zendframework.com/ 找到。我们将在本章后续部分使用此模块。

照片相册应用程序

让我们开始使用 Zend Framework 2 实现我们的自定义相册。由于我们已实现了文件管理界面,我们将使用类似的界面来实现相册。

照片相册的架构将与 Upload 实体类似;此外,我们还将有一个字段来存储在上传过程中生成的 thumbnail 文件名。图像和生成的缩略图都将存储在 <Module>\data\images 文件夹中。我们将使用自定义操作在浏览器中显示图像。

在我们开始之前,让我们快速回顾一下由 WebinoImageThumb 支持的一些重要方法:

  • resize ($maxWidth = 0, $maxHeight = 0): 此函数将图像调整到指定的宽度和高度;如果任一值设置为 0,则该维度将不被视为限制

  • adaptiveResize ($width, $height): 此函数尝试将图像调整到尽可能接近提供的尺寸,然后裁剪剩余的溢出(从中心)以使图像达到指定的尺寸

  • crop ($startX, $startY, $cropWidth, $cropHeight): 此函数从给定的坐标裁剪图像到指定的宽度和高度

  • rotateImage ($direction = 'CW'): 通过顺时针或逆时针旋转图像 90 度

  • rotateImageNDegrees ($degrees): 通过指定的度数旋转图像

  • save ($fileName, $format = null): 通过指定的文件名保存图像

行动时间 - 实现一个简单的相册

执行以下步骤:

  1. 创建一个名为 ImageUpload 的新实体,具有以下表结构:

          CREATE TABLE IF NOT EXISTS image_uploads (
            id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
            filename VARCHAR( 255 ) NOT NULL ,
            thumbnail VARCHAR( 255 ) NOT NULL ,
            label VARCHAR( 255 ) NOT NULL ,
            user_id INT NOT NULL,
            UNIQUE KEY (filename)
          );
    
  2. src/Users/Model/ImageUpload.php 文件中创建相关的 ImageUpload 实体,在 src/Users/Model/ImageUploadTable.php 文件中创建 TableGateway 对象,以及在模块 (CommunicationApp/module/Users) 中的 src/Users/Controller/MediaManagerController.php 文件内的 Controller (MediaManagerController)。

  3. Upload 表单的 Submit 过程中,使用名为 generateThumbnail() 的新方法生成缩略图;此方法将现有图像的文件名作为参数。resize 方法将图像调整为 75x75 像素,并带有 tn_ 前缀保存到图像上传目录。

    此方法需要放置在 MediaManagerController 文件中,src/Users/Controller/MediaManagerController.php

        public function generateThumbnail($imageFileName) 
        {
          $path = $this->getFileUploadLocation();
          $sourceImageFileName = $path . '/' . $imageFileName;
          $thumbnailFileName = 'tn_' . $imageFileName;
    
          $imageThumb = $this->getServiceLocator()
                ->get('WebinoImageThumb');
          $thumb = $imageThumb->create($sourceImageFileName, $options = array());
     $thumb->resize(75, 75);
     $thumb->save($path . '/' . $thumbnailFileName);
    
          return $thumbnailFileName;
      }
    
  4. 我们下一步是编写一个操作,以在 FullThumbnail 模式下渲染图像;为此,我们需要创建一个自定义路由,该路由将接受 actionidsubaction 参数。这是通过模块配置文件中的以下路由定义实现的,CommunicationApp/module/Users/config/module.config.php

        'media' => array(
          'type'    => 'Segment',
          'options' => array(
     'route'    => '/media[/:action[/:id[/:subaction]]]',
            'constraints' => array(
     'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
     'id'     => '[a-zA-Z0-9_-]*',
     'subaction'     => '[a-zA-Z][a-zA-Z0-9_-]*',
    
            ),
            'defaults' => array(
              'controller' => 'Users\Controller\MediaManager',
              'action'     => 'index',
            ),
          ),
        ),   
    
  5. 我们下一步是编写一个响应各种图像请求的操作。这个操作需要放置在 MediaManagerController 文件中,src/Users/Controller/MediaManagerController.php

        public function showImageAction()
        {  
          $uploadId = $this->params()->fromRoute('id');
          $uploadTable = $this->getServiceLocator()
              ->get('ImageUploadTable');
          $upload = $uploadTable->getUpload($uploadId);
    
          // Fetch Configuration from Module Config
          $uploadPath    = $this->getFileUploadLocation();
     if ($this->params()->fromRoute('subaction') == 'thumb') 
     {
     $filename = $uploadPath ."/" . $upload->thumbnail;
     } else {
     $filename = $uploadPath ."/" . $upload->filename;
     }
          $file = file_get_contents($filename);
    
          // Directly return the Response 
          $response = $this->getEvent()->getResponse();
          $response->getHeaders()->addHeaders(array(
            'Content-Type' => 'application/octet-stream',
            'Content-Disposition' => 'attachment;filename="' .$upload->filename . '"',
    
          ));
          $response->setContent($file);
          return $response;	    
        }
    
  6. 确保整个过程完全工作,从上传图片到画廊,再到在照片页面上显示。请参阅以下代码,了解在媒体管理器中的 upload 视图中 showImageAction() 的使用,CommunicationApp/module/Users/view/users/media-manager/view.phtml

        <section class="upload">
        <h2><?php echo $this->escapeHtml($upload->label);?></h2>
        <h4><?php echo $this->escapeHtml($upload->filename);?></h4>
     <img src="<?php echo $this->url('users/media',
     array('action'=>'showImage', 
     'id' => $upload->id, 
     'subaction' => 'full'));?>" />
        </section>
        <a href="<?php echo $this->url('users/media');?>">
              &raquo; Show Gallery</a>
    
  7. 现在在您选择的浏览器上测试应用程序。图像上传页面应如下截图所示:实施简单照片画廊的时间 - 实现简单照片画廊

一旦图像上传表单成功提交,图像将被调整大小并在画廊中显示,如下截图所示:

实施简单照片画廊的时间 - 实现简单照片画廊

在调整大小后的图像顶部的 查看图像 链接将您带到包含完整尺寸图像的页面:

实施简单照片画廊的时间 - 实现简单照片画廊

发生了什么事?

我们通过使用外部图像处理库实现了简单的照片画廊。我们使用了 resize 函数来创建缩略图,并为在网页浏览器中处理图像渲染创建了一个自定义操作。

尝试一下英雄

现在您已经了解了如何使用 WebinoImageThumb 模块,您的下一个任务是将照片画廊扩展以支持 rotate 功能。在 查看图像 页面上添加一个 rotate 函数,并允许用户顺时针和逆时针旋转图像。

Google 数据 API

Google 数据 API 为应用程序提供了一个简单的接口,用于读取和写入各种 Google 服务中的数据。数据 API 使用与 Atom 发布协议类似的协议进行数据传输。所有服务都在名为 ZendGdata 的包中实现。

ZendGdata API 支持的一些最常用的 Google 服务如下所示:

  • Picasa 网络相册

  • YouTube

  • Google 日历

  • Google 电子表格

  • Google 文档

  • Google 配置

  • Google 分析

  • Google 博客

  • Google 代码搜索

  • Google 笔记本

由于 ZendGdata 不包含在默认的 Zend 框架安装中,因此需要手动安装。这可以通过 Composer 和获取 "zendframework/zendgdata": "2.*" 来完成。

Google 照片 API

Google Photos API 允许您从 Picasa 或 Google+ 账户中获取、编辑和管理您的照片和相册。数据 API 提供了各种服务;以下列出了其中一些关键功能:

  • getUserFeed(): 获取该用户所有关联的相册

  • insertAlbumEntry(): 创建一个新的相册

  • getAlbumFeed(): 获取指定的相册

  • insertPhotoEntry(): 创建一个新的照片

  • getPhotoFeed(): 获取指定的照片

  • insertCommentEntry(): 创建一个新的评论

  • getCommentEntry(): 获取指定的评论

  • insertTagEntry(): 创建一个新的标签

  • getTagEntry(): 获取指定的标签

  • deleteAlbumEntry(): 删除相册

  • deletePhotoEntry(): 删除照片

  • deleteCommentEntry(): 删除评论

  • deleteTagEntry(): 删除标签

在本例中,我们将获取用户的现有相册以及存储在这些相册中的照片。

小贴士

在继续之前,请确保您已使用 Composer 在应用程序中安装了 ZendGdata 库。请参考以下安装说明:

  • 将以下行添加到 CommunicationApp/composer.jsonrequires 部分:

    "zendframework/zendgdata": "2.*"
    
    
  • 使用 Composer 更新应用程序依赖项:

    $ php composer.phar update
    
    
"zendframework/zendgdata": "2.*"
$ php composer.phar update

在开始之前,请确保您已在您的 Google Photos 账户上上传了一些照片。

动手时间 - 从 Google Photos 获取照片

按照以下步骤从您的 Google Photos 账户获取照片:

  1. 在您的控制器中创建一个名为 getGooglePhotos() 的方法,该方法将连接到 Google Photos 并获取所有相册。此方法需要放置在 MediaManagerController 文件中,src/Users/Controller/MediaManagerController.php

  2. 设置 API 客户端以使用带有禁用 sslverifypeer 选项的 Curl 请求。

        $adapter = new \Zend\Http\Client\Adapter\Curl();
        $adapter->setOptions(array(
          'curloptions' => array(
            CURLOPT_SSL_VERIFYPEER => false,
          )
        ));
    
        $httpClient = new \ZendGData\HttpClient();
        $httpClient->setAdapter($adapter);
    
        $client = \ZendGData\ClientLogin::getHttpClient(
                self::GOOGLE_USER_ID, 
                self::GOOGLE_PASSWORD, 
                \ZendGData\Photos::AUTH_SERVICE_NAME, 
                $httpClient);
    
  3. 现在创建一个新的 Google Photos 客户端,使用 API 客户端。

        $gp = new \ZendGData\Photos($client);
    
  4. 现在通过 getUserFeed() 获取相册列表,并使用 getAlbumFeed() 获取相册内的图片列表。

        $userFeed = $gp->getUserFeed( self::GOOGLE_USER_ID );
        foreach ($userFeed as $userEntry) {
    
          $albumId = $userEntry->getGphotoId()->getText();
          $gAlbums[$albumId]['label'] = $userEntry->getTitle()->getText();
    
          $query = $gp->newAlbumQuery();
          $query->setUser( self::GOOGLE_USER_ID );
          $query->setAlbumId( $albumId  );
          $albumFeed = $gp->getAlbumFeed($query);
          foreach ($albumFeed as $photoEntry) {
            $photoId = $photoEntry->getGphotoId()->getText();
            if ($photoEntry->getMediaGroup()->getContent() != null) {
              $mediaContentArray = $photoEntry->getMediaGroup()->getContent();
              $photoUrl = $mediaContentArray[0]->getUrl();
            }
    
            if ($photoEntry->getMediaGroup()->getThumbnail() != null) {
              $mediaThumbnailArray = $photoEntry->getMediaGroup()->getThumbnail();
              $thumbUrl = $mediaThumbnailArray[0]->getUrl();
            }
    
            $albumPhoto = array();
            $albumPhoto['id'] = $photoId;
            $albumPhoto['photoUrl'] = $photoUrl;
            $albumPhoto['thumbUrl'] = $thumbUrl;
    
            $gAlbums[$albumId]['photos'][] =$albumPhoto;   
          }
        }
    // Return the consolidated array back to the view for rendering
    return $gAlbums;
    
  5. album 视图中的以下代码块用于渲染相册;这可以放置在媒体管理器的 index 视图中,CommunicationApp/module/Users/view/users/media-manager/index.phtml

        <?php foreach ($googleAlbums as $googleAlbum) : ?>
        <h4> <?php echo $this->escapeHtml($googleAlbum['label']);?> </h4>
        <?php foreach ($googleAlbum['photos'] as $googleAlbumPhoto) : ?>
          <div class = "googleAlbumPhoto" style="padding:10px; display:inline">
            <a href="<?php echo $this->escapeHtml($googleAlbumPhoto['photoUrl']);?>">
            <img src="img/<?php echo $this->escapeHtml($googleAlbumPhoto['thumbUrl']);?>" />
            </a> 
          </div>
          <?php endforeach; ?>
        <?php endforeach; ?>
        <hr />
    
  6. 将图片上传到您的 Google Photos 相册:动手时间 - 从 Google Photos 获取照片

  7. 在浏览器窗口中打开页面;您应该能够看到相册内所有可用的相册和照片:动手时间 - 从 Google Photos 获取照片

发生了什么?

我们已成功使用 Google Data APIs 从 Google 获取 Picasa 上传信息,并使用这些信息在我们的应用程序中渲染相册。

动手实践英雄

您接下来的任务是在查看照片库中的照片时使用 Google Data APIs 实现照片上传选项;您将有一个按钮,允许您将照片上传到 Google Photos。

YouTube 数据 API

YouTube 数据 API 允许访问 YouTube 内容;您可以使用此 API 获取视频、播放列表、频道、发表评论以及上传和管理视频。用户允许执行未经身份验证的请求以检索热门视频的源、发表评论等。

以下列出了 YouTube API 中最常用的方法:

  • getVideoFeed(): 从视频查询中检索视频

  • getTopRatedVideoFeed(): 检索特定视频查询中的顶级视频

  • getUserUploads(): 检索用户的上传视频

  • getUserFavorites(): 检索用户的收藏视频

  • getVideoResponseFeed(): 获取特定视频的回复

  • getVideoCommentFeed(): 获取特定视频的评论

  • getPlaylistListFeed(): 获取用户的播放列表

  • getSubscriptionFeed(): 获取用户的订阅

  • insertEntry(): 上传视频到 YouTube

在此示例中,我们将检索特定关键词的视频,并在网页中渲染它们。

行动时间 – 列出关键词的 YouTube 视频

执行以下步骤以列出关键词的 YouTube 视频:

  1. 创建一个函数,用于获取Zend Framework关键词的 YouTube 视频。

  2. 以类似的方式建立连接,就像之前为 Google Photos 建立的连接一样。这需要放置在MediaManagerController文件中的新方法getYoutubeVideos()中,src/Users/Controller/MediaManagerController.php

      $adapter = new \Zend\Http\Client\Adapter\Curl();
      $adapter->setOptions(array(
        'curloptions' => array(
          CURLOPT_SSL_VERIFYPEER => false,
        )
      ));
    
      $httpClient = new \ZendGData\HttpClient();
      $httpClient->setAdapter($adapter);
    
      $client = \ZendGData\ClientLogin::getHttpClient(
        self::GOOGLE_USER_ID,
        self::GOOGLE_PASSWORD,
        \ZendGData\YouTube::AUTH_SERVICE_NAME,
        $httpClient);      
    
  3. 初始化 YouTube 客户端并执行关键词Zend Framework的视频查询:

      $yt = new \ZendGData\YouTube($client);
      $yt->setMajorProtocolVersion(2);
      $query = $yt->newVideoQuery();
      $query->setOrderBy('relevance');
      $query->setSafeSearch('none');
      $query->setVideoQuery('Zend Framework');    
    
  4. 解析查询结果并将其存储在数组中:

      $videoFeed = $yt->getVideoFeed($query->getQueryUrl(2));
    
      $yVideos = array();
      foreach ($videoFeed as $videoEntry) {
         $yVideo = array();
         $yVideo['videoTitle'] = $videoEntry->getVideoTitle();
         $yVideo['videoDescription'] = $videoEntry->getVideoDescription();
         $yVideo['watchPage'] = $videoEntry->getVideoWatchPageUrl();
         $yVideo['duration'] = $videoEntry->getVideoDuration();
         $videoThumbnails = $videoEntry->getVideoThumbnails();
    
         $yVideo['thumbnailUrl'] = $videoThumbnails[0]['url'];
         $yVideos[] = $yVideo;
      }
      return $yVideos;
    
  5. 结果内容在视图中渲染,并显示如下截图中的视频列表:行动时间 – 列出关键词的 YouTube 视频

发生了什么?

我们已经使用了ZendGData API 的 YouTube API 来检索特定关键词的 YouTube 视频列表。

突击测验 – 媒体共享

Q1. 在 Composer 中,哪个命令用于安装新配置的依赖项?

  1. php composer.phar setup

  2. php composer.phar self-update

  3. php composer.phar show

  4. php composer.phar update

Q2. 以下哪个是上传新照片到 Google Photos 的有效方法?

  1. uploadPhoto()

  2. insertPhoto()

  3. uploadNewPhoto()

  4. insertPhotoEntry()

概述

在本章中,我们学习了各种管理媒体的技术;最初我们从实现自己的相册开始,后来转向使用 Google GData API 在网络上检索和存储媒体。

在我们下一章中,我们将实现一个简单的搜索界面。

第七章:使用 Lucene 进行搜索

通常情况下,我们会遇到需要内置搜索功能的 Web 应用程序。有时搜索可能涉及在 MySQL 表中搜索一个简单的字段,或者有时你可能想要搜索一个文档或纯文本文件;使用各种搜索库有多种方法来解决搜索需求。Lucene 就是这样一种库,它为实施全文搜索提供了出色的搜索功能。

在本章中,我们将使用 Zend Framework 的 Lucene 搜索实现。Zend Framework 1.0 内置了Zend_Search_Lucene库,该库支持使用 Lucene 进行索引和搜索;在 ZF 2.0 中,这个库作为ZendSearch\Lucene提供,可以下载并安装到你的 Web 应用程序中。在本章中,我们将学习使用 Lucene 搜索库实现全文搜索的基本原理,以下是一些主题:

  • 在你的应用程序中安装ZendSearch

  • 为简单的 MySQL 数据创建数据索引

  • 查询 Lucene 索引

  • 向索引中添加新的文档文件

Lucene 简介

Lucene 是由 Apache 基金会开发的一个高性能、可扩展的信息检索(搜索)库,可用于在 Web 应用程序中实现全文搜索。Lucene 提供了一个简单易用的 API,它将为你的 Web 应用程序提供强大的索引和搜索功能。要了解更多关于 Lucene 的信息,请访问lucene.apache.org/

Lucene 搜索库最重要的组件解释如下:

  • 索引:Lucene 索引是存储所有已索引文档的数据存储;查询是针对索引执行的,以获取文档。

  • 文档:文档是 Lucene 索引的默认构建块;文档可以与表中的记录相比较。每个文档包含多个字段,查询可以针对这些字段执行。

  • 字段:每个 Lucene 文档包含一个或多个字段;并非所有字段都必须被索引,字段也可以在不索引的情况下存储。

Lucene 搜索基于索引进行,因此有必要更新索引以包含最新内容,以获得最佳的搜索结果。以下图表解释了 Lucene 搜索的概述:

Lucene 简介

现在是时候安装 ZendSearch\Lucene 了

执行以下步骤以安装ZendSearch\Lucene

  1. 在撰写本书时,ZendSearch\Lucene不是一个 composer 包。因此,我们将从 GitHub 仓库中检出源代码。仓库位于github.com/zendframework/ZendSearch

  2. 接下来,我们需要导航到vendor文件夹:

    $ cd /var/www/CommunicationApp/
    vendor/
    
    
  3. 将 Zend 搜索仓库克隆到vendor文件夹中:

    $ git clone https://github.com/zendframework/ZendSearch.git ZendSearch
    
    
  4. 接下来,我们应该使用 composer 配置 ZendSearch 库:

    $ cd ZendSearch/
    $ curl -s https://getcomposer.org/installer | php
    $ php composer.phar install
    
    
  5. 一旦库配置完成,我们需要定义一个模块级别的配置来存储索引位置。为此,我们需要修改 CommunicationApp/module/Users/config/module.config.php,并为 search_index 添加一个新的配置:

       // MODULE CONFIGURATIONS
       'module_config' => array(
          'upload_location'        => __DIR__ . '/../data/uploads',
          'image_upload_location'  => __DIR__ . '/../data/images',
     'search_index'         => __DIR__ . '/../data/search_index'
       ),
    

发生了什么?

我们现在已经下载并配置了 Zend Framework 2.0 的 ZendSearch 库;之前的教程也为我们提供了下载和安装无法直接从 Composer 下载的包的指南。

现在我们已经安装了 ZendSearch\Lucene 搜索库,我们的下一个任务将是为存储在我们通信应用中的某些数据创建一个 Lucene 索引。

索引

使用 ZendSearch\Lucene 进行索引是一个相当直接的过程。我们只需要创建带有字段和值的文档,并持续将文档添加到索引中。您还可以删除文档、更新文档和清除索引。以下类用于索引生成:

  • FieldZendSearch\Lucene\Document\Field 类允许用户定义一个新的文档字段;此字段可以被分类为以下类型之一:

    • Field::keyword($name, $value, $encoding = 'UTF-8'): keyword 字段类型用于标识不需要分词但需要索引和存储的字符串字段。例如,日期和 URL。

    • Field::unIndexed($name, $value, $encoding = 'UTF-8'): unIndexed 字段类型用于在索引中存储字段,而无需对其进行索引/分词。例如,ID 字段。

    • Field::binary($name, $value): binary 字段类型用于在索引中存储二进制值。

    • Field::text($name, $value, $encoding = 'UTF-8'): text 字段类型是最常用的字段类型,用于描述短字符串,这些字符串将被分词并存储在索引中。

    • Field::unStored($name, $value, $encoding = 'UTF-8'): unStored 字段类型用于标识那些将被分词和索引但不会存储在索引中的字段。

  • DocumentZendSearch\Lucene\Document 类允许定义一个新的索引文档。以下是一些在这个类中最常用的方法的描述:

    • addField(Document\Field $field): 向文档中添加一个新的字段

    • getFieldNames(): 用于从文档中检索所有字段名称

    • getField($fieldName): 用于从文档中检索特定字段

    • getFieldValue($fieldName): 用于从文档中检索特定字段值

  • Index – 可以使用 ZendSearch\Lucene 类中的 create()open() 方法检索 Index。这两个方法都接受索引路径作为参数,并返回一个类型为 ZendSearch\Lucene\SearchIndexInterface 的索引。SearchIndexInterface 为操作索引内的文档提供了以下方法:

    • addDocument(Document $document): 向索引中添加一个新的文档

    • delete($id): 根据内部文档 ID 删除索引中的文档

    • optimize(): 通过合并所有段为一个单一段来优化索引,从而提高性能

    • commit(): 用于将事务提交到搜索索引

现在我们已经了解了用于索引生成的各种方法,让我们开始并为我们的通信应用程序中可用的 uploads 表生成索引。

执行动作时间 – 生成 Lucene 索引

执行以下步骤以生成 Lucene 索引:

  1. 创建一个新的搜索控制器,CommunicationApp/module/Users/src/Users/Controller/SearchController.php,它将被用于搜索和生成索引。

  2. 添加对 ZendSearch\Lucene 的引用:

    use ZendSearch\Lucene;
    use ZendSearch\Lucene\Document;
    use ZendSearch\Lucene\Index;
    
  3. 添加一个方法从模块配置中获取索引位置:

    public function getIndexLocation()
    {
      // Fetch Configuration from Module Config
      $config  = $this->getServiceLocator()->get('config');
      if ($config instanceof Traversable) {
        $config = ArrayUtils::iteratorToArray($config);
      }
      if (!empty($config['module_config']['search_index'])) {
        return $config['module_config']['search_index'];
      } else {
        return FALSE;
      }
    }
    
  4. 索引文档需要以下格式生成:

    索引字段 描述
    upload_id 这是一个非索引字段,将用于检索在搜索结果中返回的已上传文件
    label 此字段用于索引 uploads 表的 label 字段
    owner 此字段用于索引上传文档的用户 name 字段
  5. 创建一个新的操作以生成索引:

    public function generateIndexAction()
    {
      $searchIndexLocation = $this->getIndexLocation();
      $index = Lucene\Lucene::create($searchIndexLocation);
    
      $userTable = $this->getServiceLocator()->get('UserTable');
      $uploadTable = $this->getServiceLocator()->get('UploadTable');
      $allUploads = $uploadTable->fetchAll();  
      foreach($allUploads as $fileUpload) {
        //
        $uploadOwner = $userTable->getUser($fileUpload->user_id);
    
        // create lucene fields
        $fileUploadId = Document\Field::unIndexed(
                              'upload_id', $fileUpload->id);
        $label = Document\Field::Text(
                              'label', $fileUpload->label);
        $owner = Document\Field::Text(
                              'owner', $uploadOwner->name);
    
        // create a new document and add all fields
        $indexDoc = new Lucene\Document();
        $indexDoc->addField($label);
        $indexDoc->addField($owner);
        $indexDoc->addField($fileUploadId);
        $index->addDocument($indexDoc);
      }
      $index->commit();
    }
    
  6. 现在在您的网络浏览器中打开操作 URL (http://comm-app.local/users/search/generateIndex),如果一切按预期工作,您将看到在 search_index 文件夹中创建的索引文件。

    以下截图显示了成功更新索引后的浏览器响应:

    执行动作时间 – 生成 Lucene 索引

您可以在以下截图中看到索引文件已生成并存储在 search_index 文件夹中:

执行动作时间 – 生成 Lucene 索引

发生了什么?

现在我们已经创建了一个方法,将存储在 MySQL 表中的数据索引到 Lucene 数据存储中;我们的下一步将是执行针对 Lucene 索引的查询,并获取并显示结果。

搜索

使用 ZendSearch\Lucene 搜索索引相对简单。需要打开索引以进行查询,并将查询字符串传递给 ZendSearch\Lucene\Index 中的 find() 方法。find 方法返回一个数组,匹配特定查询的命中项,然后可以用来渲染搜索结果。

查询索引有两种选项——你可以将纯文本查询字符串传递给查找函数,或者你可以使用 ZendSearch\Lucene\Search\Query 构建自己的 Query 对象。

小贴士

要了解有关 ZendSearch\Lucene 中各种查询选项的更多信息,请查看以下开发者文档:

zf2.readthedocs.org/en/release-2.2.0/modules/zendsearch.lucene.queries.html

在以下示例中,我们将使用纯文本查询,并且您可以通过使用操作符如 :,+,- 和字段搜索来操作搜索结果。例如,请参阅以下列表:

  • 通过以下查询可以检索到由名为 Anne 的用户上传的所有文档:

    owner:Anne
    
  • 通过以下查询可以检索到包含单词 report 并由用户名为 Anne 上传的所有文档:

    report AND owner:Anne
    
  • 通过以下查询可以检索到包含单词 report 的所有文档,并排除由 Anne 上传的文档:

    report -owner:Anne
    

执行操作时间 – 显示搜索结果

执行以下步骤以显示搜索结果:

  1. 为了显示搜索结果,我们需要创建一个新的表单,该表单将显示搜索文本框,并在搜索表单下方渲染搜索结果。该表单将放置在 CommunicationApp/module/Users/src/Users/Controller/SearchController.php 下的 SearchController 中。

  2. 创建一个新的视图,该视图将用于显示查询窗口并渲染搜索结果。这将放置在 CommunicationApp/module/Users/view/users/search/index.phtml 下。

    <h3>Document Search</h3>
    <?php 
    // Search Form
    echo $this->form()->openTag($form);
    foreach ($form as $element) {
        echo $this->formElement($element);       
        echo $this->formElementErrors($element);
    }
    echo $this->form()->closeTag();
    
    // Search Results
    if (count($searchResults)) {
    ?>
    <h5>Results</h5>
    <table  style="width: 600px; border:1px solid #f5f5f5;">
      <tr>
        <th width="30%" align="left"> Label</th>
        <th width="30%" align="left"> Owner</th>
        <th align="left"> File</th>
      </tr>
      <?php   foreach ($searchResults as $searchResult) {
      ?>
      <tr>
        <td><?php echo $searchResult->label; ?></td>
        <td><?php echo $searchResult->owner; ?></td>
        <td><a href="<?php echo $this->escapeHtml($this->url('users/upload-manager',
                array('action'=>'fileDownload', 'id' => $searchResult->upload_id)));?>">Download</a></td>
      </tr>
      <?php 
      }
      ?>
    </table>
    <?php }?>
    
  3. 现在创建一个新的操作,该操作将显示 搜索 表单,并使用 搜索 表单中提供的输入查询 Lucene 索引。这将放置在 CommunicationApp/module/Users/src/Users/Controller/SearchController.php 下的 SearchController 中。

    public function indexAction()
    {
      $request = $this->getRequest();
      if ($request->isPost()) {
     $queryText = $request->getPost()->get('query');
     $searchIndexLocation = $this->getIndexLocation();
     $index = Lucene\Lucene::open($searchIndexLocation);
     $searchResults = $index->find($queryText);
      }
    
      // prepare search form
      $form  = new \Zend\Form\Form();
      $form->add(array(
        'name' => 'query',
        'attributes' => array(
          'type'  => 'text',
          'id' => 'queryText',
          'required' => 'required'
        ),
        'options' => array(
          'label' => 'Search String',
        ),
      ));
      $form->add(array(
              'name' => 'submit',
              'attributes' => array(
                  'type'  => 'submit',
                  'value' => 'Search'
              ),
      ));
    
      $viewModel  = new ViewModel(array(
          'form' => $form, 
     'searchResults' => $searchResults
        )    
      );
      return $viewModel;
    }
    
  4. 在您的浏览器中测试页面;您应该能够看到 标签所有者 字段中可用的关键字搜索结果:执行操作时间 – 显示搜索结果

在使用 所有者名称 搜索时,您将得到以下搜索结果:

执行操作时间 – 显示搜索结果

发生了什么?

我们现在已经实现了搜索结果页面,该页面允许我们通过标签和所有者查询上传的文档。检索到的搜索结果在自定义视图中显示,允许我们从搜索结果中下载文档。

我们的下一步将是扩展搜索以搜索上传文档的内容;为此,我们需要更改生成索引的方式。

索引 Microsoft Office 文档

如前一个示例所示,仅索引文档的元信息通常是不够的。大多数情况下,查询字符串仅存在于文档的内容中。为了实现这一点,我们需要解析文档并索引内容;ZendSearch\Lucene 提供了索引以下文档类型内容的支持:

  • 对于 HTML 文档,以下是指示文档创建方法:

    ZendSearch\Lucene\Document\Html::loadHTMLFile($filename)
    ZendSearch\Lucene\Document\Html::loadHTML($htmlString)
    
  • 对于 Word 2007 文档,以下是指示文档创建方法:

    ZendSearch\Lucene\Document\Docx::loadDocxFile($filename)
    
  • 对于 Powerpoint 2007 文档,以下是指示文档创建方法:

    ZendSearch\Lucene\Document\Pptx::loadPptxFile($filename)
    
  • 对于 Excel 2007 文档,以下是指示文档创建方法:

    ZendSearch\Lucene\Document\Xlsx::loadXlsxFile($filename)
    

所有这些方法都返回一个类型为ZendSearch\Lucene\Document的文档,可以通过添加更多索引字段来进一步改进。

让我们从索引uploads部分可用的文档开始吧。

行动时间 – 索引文档文件

执行以下步骤以索引文档文件:

  1. 要索引办公文档,为样本 Word 和 Excel 文档添加一个新的uploads部分。在这种情况下,我们将上传一个 Word 文档和一个 Excel 电子表格,如下所示:行动时间 – 索引文档文件

    样本 Word 2007 文档

    行动时间 – 索引文档文件

    样本 Excel 2007 电子表格

  2. 将以下行添加到位于CommunicationApp/module/Users/src/Users/Controller/SearchController.php中的SearchController索引函数中,以便该方法能够分别索引 Word 文档和 Excel 电子表格:

    if (substr_compare($fileUpload->filename, 
            ".xlsx", 
            strlen($fileUpload->filename) - strlen(".xlsx"), 
            strlen(".xlsx")) === 0) {
      // index excel sheet
      $uploadPath = $this->getFileUploadLocation();
     $indexDoc = Lucene\Document\Xlsx::loadXlsxFile(
     $uploadPath ."/" . $fileUpload->filename);
    } else if (substr_compare($fileUpload->filename, 
            ".docx", 
            strlen($fileUpload->filename) - strlen(".docx"), 
            strlen(".docx")) === 0) {
      // index word doc
      $uploadPath= $this->getFileUploadLocation();
     $indexDoc = Lucene\Document\Docx::loadDocxFile(
     $uploadPath ."/" . $fileUpload->filename);
    } else {
      $indexDoc = new Lucene\Document();
    }
    
    $indexDoc->addField($label);
    $indexDoc->addField($owner);
    $indexDoc->addField($fileUploadId);
    $index->addDocument($indexDoc);
    
  3. 现在更新索引(导航到http://comm-app.local/users/search/generateIndex),回到文档搜索页面,尝试搜索文档中存在的关键词。你应该能够看到如下截图所示的搜索结果:行动时间 – 索引文档文件

Office 文档内容的搜索结果将如下截图所示:

行动时间 – 索引文档文件

发生了什么?

在上一个任务中,我们看到了如何实现索引和搜索 Microsoft Office 文档的内容。正如你所见,使用ZendSearch\Lucene实现这些功能相对简单。

有所作为的英雄

在你进入下一章之前,这里有一个简单的任务给你。现在我们已经实现了索引和搜索,你的任务将是修改实体,以便每次对上传进行更改时,索引都会更新。如果上传了新文件,需要将文档添加到索引中,如果上传被删除,则应从索引中移除,依此类推。

快速问答 – 搜索

Q1. 以下哪种字段类型未进行分词,但已索引并存储?

  1. keyword ()

  2. unStored ()

  3. text()

  4. unIndexed()

Q2. 以下哪种文件格式不支持作为ZendSearch\Lucene内容索引的有效文档格式?

  1. .docx

  2. .pdf

  3. .xslx

  4. .html

摘要

在本章中,我们学习了如何使用ZendSearch\Lucene实现一个简单的搜索界面。这对于实现你正在工作的任何 Web 应用程序中的搜索功能非常有用。在下一章中,我们将学习如何使用 Zend Framework 2.0 实现一个简单的电子商务商店。

第八章:创建简单商店

在过去的几年里,电子商务已经从仅仅在线广告发展到提供完整的在线购物体验。每天通过使用各种在线支付系统,越来越多的产品和服务在网上提供。电子商务应用和支付网关的作用在这个环境中变得至关重要。

在本章中,我们将构建一个简单的在线商店来展示设置简单购物车所涉及的过程。在本示例中,我们将使用 PayPal Express Checkout 作为我们的支付处理器。本章将涵盖的一些关键主题包括:

  • 设置购物车

  • 创建在线商店管理界面

  • 为 PayPal 配置 Zend Framework 2.0

  • PayPal Express Checkout 简介

  • PayPal Express Checkout 的实现

购物车

在设置在线商店时,必须设计的第一件事之一就是购物车。购物车理想情况下应允许最终用户选择并添加多个产品到购物车,并能够从网站上结账。

结账过程概述如下:

  1. 客户访问产品列表页面。

  2. 客户选择一个产品;他/她将被带到产品详情页面。

  3. 然后,客户选择购买该产品;客户预计将所需数量添加到购物车。

  4. 客户将被重定向到购物车页面;在这里,如果需要,客户可以对订单进行任何更改。

  5. 客户选择支付方式并输入支付信息。

  6. 如果成功,客户将看到一个选项来更新运输详情。

  7. 然后,客户确认订单。

  8. 订单被零售商接收;然后零售商继续处理订单。

因此,让我们开始创建我们的商店前台;我们的下一步将是设计一个支持此商店的表结构。为此,我们创建了以下两个表:

  • store_products:这个表将存储所有与产品相关的信息

  • store_orders:这个表将存储所有与订单相关的信息

行动时间 - 创建商店前台

为了简化,我们将通过跳过一些步骤来缩短Checkout过程。我们已修改过程,以便每个订单只能有一个产品;我们还将跳过更新运输详情和客户订单确认步骤:

  1. 创建存储产品和订单数据的表:

    CREATE TABLE IF NOT EXISTS store_products (
      id int(11) NOT NULL AUTO_INCREMENT,
      name varchar(255) NOT NULL,
      desc varchar(255) NOT NULL,
      cost float(9,2) NOT NULL,
      PRIMARY KEY (id)
    );
    
    CREATE TABLE IF NOT EXISTS store_orders (
      id int(11) NOT NULL AUTO_INCREMENT,
      store_product_id int(11) NOT NULL,
      qty int(11) NOT NULL,
      total float(9,2) NOT NULL,
      status enum('new', 'completed', 
            'shipped', 'cancelled')  DEFAULT NULL,
      stamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      first_name varchar(255) DEFAULT NULL,
      last_name varchar(255) DEFAULT NULL,
      email varchar(255) DEFAULT NULL,
      ship_to_street varchar(255) DEFAULT NULL,
      ship_to_city varchar(255) DEFAULT NULL,
      ship_to_state varchar(2) DEFAULT NULL,
      ship_to_zip int(11) DEFAULT NULL,
      PRIMARY KEY (id)
    );
    
  2. StoreOrderStoreProduct创建实体,并创建必要的数据访问表网关对象。

  3. 创建一个StoreController控制器,它将被用作我们的购物车。

  4. StoreController将支持以下操作:

    • indexAction():这个操作将列出网站上的所有产品

    • productDetailAction():这将显示特定产品的详细信息;这还将允许客户将产品添加到购物车

    • shoppingCartAction(): 此操作用于在离开支付处理页面之前渲染购物车

    • paypalExpressCheckoutAction(): 此操作将用户重定向到 PayPal Express Checkout 页面

    • paymentConfirmAction(): 此操作将在支付成功时处理从 PayPal Express Checkout 重定向回购物车的操作

    • paymentCancelAction(): 此操作将在支付失败时处理从 PayPal Express Checkout 重定向回购物车的操作

  5. 创建必要的视图以显示购物车的内容。

  6. StoreOrder 添加必要的函数以在向订单添加项目时计算订单总额。

  7. 最终用户界面应如下截图所示。产品列表页面列出了网站/类别的所有产品;在这种情况下,两个测试产品如下截图所示:

操作时间 – 创建商店前台

产品详情页面允许用户查看产品的详细信息,并将指定数量的产品添加到购物车中:

操作时间 – 创建商店前台

购物车页面列出了添加到购物车中的所有产品,包括它们的单价、数量和总计:

操作时间 – 创建商店前台

发生了什么?

我们为我们的新商店创建了一个购物车界面;我们将进一步修改此界面以添加对支付处理器的支持。但在达到这一阶段之前,让我们创建一个简单的商店管理界面,以便我们能够管理商店和订单。

商店管理

商店管理用户界面用于在创建订单后检查订单状态,以及管理商店中可供销售的产品列表。商店管理用户界面的两个关键方面是:

  • 管理员应该能够添加、删除和管理产品

  • 管理员应该能够使用此界面管理订单并更改状态

操作时间 – 创建 Store Admin 界面

执行以下步骤以创建 Store Admin 界面:

  1. 为商店管理创建一个新的控制器,并将其命名为 StoreAdminController

  2. 此控制器将具有以下基本操作:

    • indexAction(): 用于列出所有产品

    • addProductAction(): 用于添加新产品

    • deleteProductAction(): 用于删除现有产品

    • listOrdersAction(): 用于列出所有订单

    • viewOrderAction(): 用于查看特定订单

    • updateOrderStatusAction(): 用于更新订单状态

  3. 创建必要的视图,并相应地映射操作。

  4. 打开 phpMyadmin 并在 store_productsstore_orders 表中创建测试记录以测试管理 UI 的功能。

  5. 打开您喜欢的浏览器,登录到应用程序,并打开 eStore 管理界面。界面应如下所示。

    管理产品 页面允许您从管理界面添加、删除和编辑产品:

    行动时间 – 创建 Store 管理界面

订单列表页面列出了店铺中放置的所有订单,并允许您查看订单和修改其状态:

行动时间 – 创建 Store 管理界面

如下所示是显示订单信息和提供更改其状态选项的 订单信息 页面的截图:

行动时间 – 创建 Store 管理界面

发生了什么?

店铺管理用户界面现在已准备就绪,我们的下一步是设置 PayPal Express 结账并集成到我们的店铺中,这将使用户能够使用 PayPal 进行支付。在我们继续下一节之前,下面的部分将提供一个简单的任务供您尝试。

尝试一下英雄

现在您已经知道如何将搜索集成到 Zend Framework 2.0 应用程序中,尝试为我们店铺应用程序的 管理产品 部分添加免费文本搜索功能。

使用 PayPal 进行支付

PayPal 是全球最常用的支付处理器;PayPal 成功的关键贡献者之一是其易于使用的 API 和详尽的文档,这些文档支持此支付网关。对于任何新商家,PayPal 提供了一系列设置支付处理器的选项,其中最重要的是提供的集成类型。PayPal 在支付处理下提供各种产品;其中一些包括:

  • 快速结账

  • PayPal Payments Standards(网站支付标准)

  • PayPal Payments Pro(网站支付 Pro)

我们将在本章中处理快速结账,因为它是最基本的 PayPal 实现方法。

PayPal 和 Zend Framework 2.0

在撰写本书时,没有由 Zend Framework 提供的原生包支持 PayPal 集成。始终有支持此集成的第三方选项。在这个例子中,我们使用了这样一个第三方包,名为 SpeckPaypal

行动时间 – 设置 PayPal

执行以下步骤以设置 PayPal:

  1. 打开 packagist.org/,搜索 speckpaypal

  2. 获取仓库详细信息。

  3. 修改应用程序的 Composer 配置文件以包含 speckpaypal 仓库:

    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "2.0.*",
        "webino/webino-image-thumb": "1.0.0",
        "zendframework/zendgdata": "2.*",
     "speckcommerce/speck-paypal": "dev-master"
    }
    
  4. 使用 Composer 更新来更新项目依赖项:

    .
    Loading composer repositories with package information
    Updating dependencies
     - Removing zendframework/zendframework (2.0.7)
     - Installing zendframework/zendframework (2.0.8)
     Downloading: 100%
    
     - Installing speckcommerce/speck-paypal (dev-master d951518)
     Cloning d951518fd2c98148da5609e23a41697e6cfca06e
    
    Writing lock file
    Generating autoload files
    
    
  5. 现在我们需要 API 凭证来访问 PayPal Express Checkout。这可以通过使用您的 PayPal 凭证登录到 developer.paypal.com 来访问。

  6. 应用程序 中打开 沙盒账户

  7. 选择合适的商家账户,并在 配置文件 中选择 API 凭证。![行动时间 – 设置 PayPal]

  8. 记录 API 凭证。

  9. 现在在模块的配置文件 config 文件(CommunicationApp/module/Users/config/module.config.php)中创建一个新的配置,并将数组索引命名为 speck-paypal-api

      'speck-paypal-api' => array(
        'username'  => '',
        'password'  => '',
        'signature' => '',
        'endpoint'  => 'https://api-3t.sandbox.paypal.com/nvp'
      )
    
  10. 不同的 PayPal 服务有不同的端点。对于沙盒中的 Express Checkout,这是 https://api-3t.sandbox.paypal.com/nvp;如果你正在切换到实时/生产环境,则需要将其更改为 https://api-3t.paypal.com/nvp

发生了什么?

现在我们已经在我们的应用程序中配置了 PayPal 和 SpeckPaypal,我们的下一步是测试使用 PayPal Express Checkout 接收支付。

PayPal Express Checkout

PayPal Express Checkout 允许卖家通过将他们重定向到 PayPal Express Checkout 以进行安全的网络支付,并在交易完成后将他们返回到商家的网站,从而在其网站上接收信用卡/ PayPal 支付。

工作流程解释如下:

  1. 购物车 页面上,客户选择通过 PayPal Express Checkout 进行支付;商家调用 SetExpressCheckout API 调用并获取支付令牌。

  2. 使用支付令牌,客户将被重定向到 PayPal Express Checkout 登录页面;在这里,客户可以输入他的/她的 PayPal 登录信息或获取一个新的 PayPal 账户。

  3. 在下一页,客户将看到一个 审查 选项,以便在继续与商家结账之前审查支付信息。

  4. 现在,客户将被重定向回商家页面;商家随后调用 GetExpressCheckoutDetails API 调用并获取客户信息。客户查看订单并确认订单。然后商家使用 DoExpressCheckoutPayment API 调用完成支付请求。

  5. 客户将看到交易结果以及订单摘要。![PayPal Express Checkout]

    PayPal Express Checkout—概述

小贴士

更多关于 PayPal Express Checkout

你可以在 PayPal 网站上了解更多关于 PayPal Express Checkout 的信息 www.paypal.com/webapps/mpp/express-checkout.

PayPal Express Checkout 的开发者文档可在以下位置找到:

developer.paypal.com/webapps/developer/docs/classic/express-checkout/integration-guide/ECGettingStarted/.

行动时间 – 使用 PayPal 接受支付

使用 PayPal 接受支付的操作步骤如下:

  1. 现在在 购物车 页面上添加一个按钮(可选地带有 PayPal 结账图片)。此按钮应链接到 paypalExpressCheckoutAction() 函数。

  2. 在商店控制器中添加一个方法,该方法将用于生成 PayPal 请求:

    protected function getPaypalRequest()
    {
      $config  = $this->getServiceLocator()->get('config');
      $paypalConfig = new \SpeckPaypal\Element\Config(
                  $config['speck-paypal-api']);
    
      $adapter = new \Zend\Http\Client\Adapter\Curl();
      $adapter->setOptions(array(
        'curloptions' => array(
          CURLOPT_SSL_VERIFYPEER => false,
        )
      ));
    
      $client = new \Zend\Http\Client;
      $client->setMethod('POST');
      $client->setAdapter($adapter);
    
      $paypalRequest = new \SpeckPaypal\Service\Request;
      $paypalRequest->setClient($client);
      $paypalRequest->setConfig($paypalConfig);
    
      return $paypalRequest;
    }
    
  3. 修改 paypalExpressCheckoutAction() 函数以将订单信息发送到 PayPal 并将用户重定向到 PayPal Express Checkout:

    public function paypalExpressCheckoutAction()
    {
      $request = $this->getRequest();
      $orderId = $request->getPost()->get('orderId');
    
      $orderTable = $this->getServiceLocator()->get('StoreOrdersTable');
      $order = $orderTable->getOrder($orderId);
    
      $paypalRequest = $this->getPaypalRequest();
    
      $paymentDetails = new \SpeckPaypal\Element\PaymentDetails
      (array('amt' => $order->total
      ));
      $express = new \SpeckPaypal\Request\SetExpressCheckout(
          array('paymentDetails' => $paymentDetails)
      );
    
      $express->setReturnUrl(
        'http://comm-app.local/users/store/paymentConfirm');
      $express->setCancelUrl(
        'http://comm-app.local/users/store/paymentCancel');
    
      // Send Order information to PayPal   
      $response = $paypalRequest->send($express);
      $token = $response->getToken();
    
      $paypalSession = new \Zend\Session\Container('paypal');
      $paypalSession->tokenId = $token; 
      $paypalSession->orderId = $orderId;
    
      // Redirect user to PayPal Express Checkout
      $this->redirect()->toUrl('https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token=' . $token);
    }
    
  4. 添加一个处理 Express Checkout 中成功支付的方法——paymentConfirmAction();此方法将从 PayPal 捕获支付信息,确认支付,然后使用以下代码在我们的系统中更新订单状态:

    • 从 PayPal 捕获支付信息:

      // To capture Payer Information from PayPal
      $paypalSession = new \Zend\Session\Container('paypal');
      $paypalRequest = $this->getPaypalRequest();
      
      $expressCheckoutInfo = 
       new \SpeckPaypal\Request\GetExpressCheckoutDetails();
      $expressCheckoutInfo->setToken($paypalSession->tokenId);
      $response = $paypalRequest->send($expressCheckoutInfo);
      
      
    • 使用 PayPal 确认订单:

      //To capture express payment
      $orderTable = $this->getServiceLocator()->get('StoreOrdersTable');
      $order = $orderTable->getOrder($paypalSession->orderId);
      $paymentDetails = new \SpeckPaypal\Element\PaymentDetails(array(
       'amt' => $order->total
      ));
      
      $token = $response->getToken();
      $payerId = $response->getPayerId();
      
      $captureExpress = new \SpeckPaypal\Request\DoExpressCheckoutPayment(
       array(
       'token'             => $token,
       'payerId'           => $payerId,
       'paymentDetails'    => $paymentDetails
       ));
      $confirmPaymentResponse = $paypalRequest->send($captureExpress);
      
      
    • 保存带有更新后的发货/账单信息的订单:

      //To Save Order Information
      $order->first_name = $response->getFirstName();
      $order->last_name = $response->getLastName();
      $order->ship_to_street = $response->getShipToStreet();
      $order->ship_to_city = $response->getShipToCity();
      $order->ship_to_state = $response->getShipToState();
      $order->ship_to_zip = $response->getShipToZip();
      $order->email = $response->getEmail();
      $order->store_order_id = $paypalSession->orderId;
      $order->status = 'completed';
      $orderTable->saveOrder($order);
      
      
  5. 最后添加一个处理 Express Checkout 中失败支付的方法——paymentCancelAction()

    public function paymentCancelAction()
    {
      $paypalSession = new \Zend\Session\Container('paypal');
    
      $storeOrdersTG = $this->getServiceLocator()
                          ->get('StoreOrdersTableGateway');
      $storeOrdersTG->update(
                array('status' => 'cancelled'), 
                array('id' => $paypalSession->orderId));
      $paypalSession->orderId = NULL;
      $paypalSession->tokenId = NULL;  
      $view = new ViewModel();
      return $view;
    }
    
  6. 现在再次登录到 developer.paypal.com

  7. 生成一个新的个人类型沙箱账户:PERSONAL

  8. 现在访问商店并尝试使用新创建的沙箱账户进行购买。最终的商店应如下截图所示:行动时间 – 使用 PayPal 接受支付

    在从购物车页面选择结账后,您将被重定向到以下截图所示的使用我的 PayPal 账户登录页面:

    行动时间 – 使用 PayPal 接受支付

    PayPal Express Checkout 的订单查看页面截图如下;此页面用于查看从客户的 PayPal 账户向商户进行的支付:

    行动时间 – 使用 PayPal 接受支付

    一旦订单成功提交,用户将被重定向到以下截图所示的订单确认页面:

    行动时间 – 使用 PayPal 接受支付

  9. 现在登录到沙箱网站以查看商户账户的支付是否已入账:行动时间 – 使用 PayPal 接受支付

发生了什么?

我们刚刚使用 PayPal Express Checkout 在我们的网络应用程序中接收支付并完成简单的商店应用程序。如您所见,PayPal API 使得设置支付网关相对容易。

尝试一下英雄

在你的下一个任务中,使用 DoDirectPayment API 调用来直接在网站上支付,而无需将用户重定向到 PayPal 网站,然后再返回:

快速问答 – 创建一个简单的商店

Q1. 以下哪种方法用于发送 PayPal 重定向的初始支付信息?

  1. RedirectExpressCheckout

  2. SetExpressCheckout

  3. GetExpressCheckoutDetails

  4. DoExpressCheckoutPayment

Q2. 以下哪个字段是请求 PayPal 支付信息所必需的?

  1. token

  2. payerId

  3. paymentDetails

  4. orderID

摘要

在本章中,我们学习了在线设置简单商店和尝试使用 PayPal 接收付款的基础知识。正如您从前面的示例中可以看到,Zend 框架通过允许开发者根据他们的集成需求下载和安装外部第三方模块,简化了应用程序的开发。在下一章中,我们将使用 Zend Framework 2.0 进行 HTML5 开发。

第九章:HTML5 支持

HTML5 是 HTML 规范的最新版本;最终草案可能不会很快完成,但大多数浏览器都支持最新工作草案中指定的大多数功能。

以下列出了 HTML5 的一些最重要的特性:

  • 音频和视频标签

  • CSS3 支持

  • 支持使用 SVG 和 CSS3 2D 和 3D 绘制图形

  • 本地存储、Web/JS 工作线程和地理位置

  • HTML5 表单元素

对于本书的范围,我们将更关注新的表单元素。HTML5 引入了许多新的表单元素。在 HTML 的早期版本中,Web 开发者只能使用早期 HTML 规范中提供的标准输入类型。现在,随着 HTML5 规范的推出,我们有了针对不同用户输入的不同元素。

新增的输入元素列表如下:

  • datetime

  • datetime-local

  • time

  • date

  • week

  • month

  • email

  • url

  • number

  • range

  • color

  • tel

  • search

小贴士

HTML5 规范

有关进一步阅读,请参阅 W3C 网站上的 HTML5.0 规范:www.w3.org/TR/html5/.

以下链接指向 <input> 元素的规范:

www.w3.org/TR/html5/forms.html#the-input-element

在本章中,我们将了解这些输入元素的使用方法。

HTML5 输入元素

Zend Framework 2.0 现在支持所有新指定的 HTML5 输入类型;这些输入类型在 Zend\Form\Element 下可用,就像其他输入类型一样。以下表格描述了每个元素及其类名:

输入类型 描述
datetime
  • 元素:Zend\Form\Element\DateTime

  • 用于渲染 日期/时间元素 输入字段,时区设置为 UTC

  • HTML 标签:<input type="datetime" name="element-date-time">

  • 以下截图显示了在 Opera 12.0 中渲染的 datetime 元素:

HTML5 输入元素 |

datetime-local
  • 元素:Zend\Form\Element\DateTimeLocal

  • 用于渲染客户端浏览器时区的 日期/时间本地元素 输入字段

  • HTML 标签:<input type="datetime-local" name="element-date-time-local">

  • 以下截图显示了在 Opera 12.0 中渲染的 datetime-local 元素:

HTML5 输入元素 |

time
  • 元素:Zend\Form\Element\Time

  • 用于渲染 时间元素 字段

  • HTML 标签:<input type="time" name="element-time">

  • 以下截图显示了在 Opera 12.0 中渲染的时间元素:

HTML5 输入元素 |

date
  • 元素:Zend\Form\Element\Date

  • 用于渲染 日期元素 字段

  • HTML 标签:<input type="date" name="element-date">

  • 以下截图显示了在 Opera 12.0 中渲染的 date 元素:

HTML5 输入元素 |

week
  • 元素: Zend\Form\Element\Week

  • 用于渲染 周元素 字段

  • HTML 标签: <input type="week" name="element-week">

  • 在 Opera 12.0 中渲染的 week 元素如下截图所示:

HTML5 输入元素 |

month
  • 元素: Zend\Form\Element\Month

  • 用于渲染 月份元素 字段

  • HTML 标签: <input type="month" name="element-month">

  • 在 Opera 12.0 中渲染的 month 元素如下截图所示:

HTML5 输入元素 |

email
  • 元素: Zend\Form\Element\Email

  • 用于渲染 电子邮件 输入字段

  • HTML 标签: <input type="email" name="element-email">

|

url
  • 元素: Zend\Form\Element\Url

  • 用于渲染 URL 输入字段

  • HTML 标签: <input type="url" name="element-url">

|

number
  • 元素: Zend\Form\Element\Number

  • 用于渲染 数字元素 输入字段

  • HTML 标签: <input type="number" name="element-number">

  • 在 Opera 12.0 中渲染的 number 元素如下截图所示:

HTML5 输入元素 |

range
  • 元素: Zend\Form\Element\Range

  • 用于使用滑动控件渲染 范围元素 输入字段

  • HTML 标签: <input type="range" name="element-range">

  • 在 Opera 12.0 中渲染的 range 元素如下截图所示:

HTML5 输入元素 |

color
  • 元素: Zend\Form\Element\Color

  • 用于使用颜色选择器渲染 颜色元素 输入字段

  • HTML 标签: <input type="color" name="element-color">

  • 在 Opera 12.0 中渲染的 color 元素如下截图所示:

HTML5 输入元素 |

行动时间 - HTML5 输入元素

在本例中,我们将创建一个测试 HTML5 表单,用于渲染各种类型的 HTML5 输入元素:

  1. 创建一个用于渲染表单元素 formAction() 的测试操作;它可以在新的控制器 Html5TestController - 模块/Users/src/Users/Controller/Html5TestController.php 下创建。

  2. 添加对 Zend\Form\FormZend\Form\Element 的引用:

    use Zend\Form\Element;
    use Zend\Form\Form;
    
  3. 将各种 HTML5 表单元素添加到表单中:

    $form  = new Form();
    
    // Date/Time Element 
    $dateTime = new Element\DateTime('element-date-time');
    $dateTime
    ->setLabel('Date/Time Element')
    ->setAttributes(array(
      'min'  => '2000-01-01T00:00:00Z',
      'max'  => '2020-01-01T00:00:00Z',
      'step' => '1', 
    ));
    $form->add($dateTime);
    
    // Date/Time Local Element 
    $dateTime = new Element\DateTimeLocal('element-date-time-local');
    $dateTime
    ->setLabel('Date/Time Local Element')
    ->setAttributes(array(
      'min'  => '2000-01-01T00:00:00Z',
      'max'  => '2020-01-01T00:00:00Z',
      'step' => '1',
    ));
    $form->add($dateTime);
    
    // Time Element 
    $time = new Element\Time('element-time');
    $time->setLabel('Time Element');
    $form->add($time);
    
    // Date Element 
    $date = new Element\Date('element-date');
    $date
    ->setLabel('Date Element')
    ->setAttributes(array(
      'min'  => '2000-01-01',
      'max'  => '2020-01-01',
      'step' => '1',
    ));
    $form->add($date);
    
    // Week Element 
    $week = new Element\Week('element-week');
    $week->setLabel('Week Element');
    $form->add($week);
    
    // Month Element 
    $month = new Element\Month('element-month');
    $month->setLabel('Month Element');
    $form->add($month);
    
    // Email Element 
    $email = new Element\Email('element-email');
    $email->setLabel('Email Element');
    $form->add($email);
    
    // URL Element 
    $url = new Element\Url('element-url');
    $url->setLabel('URL Element');
    $form->add($url);
    
    // Number Element 
    $number = new Element\Number('element-number');
    $number->setLabel('Number Element');
    $form->add($number);
    
    // Range Element 
    $range = new Element\Range('element-range');
    $range->setLabel('Range Element');
    $form->add($range);
    
    // Color Element 
    $color = new Element\Color('element-color');
    $color->setLabel('Color Element');
    $form->add($color);
    

发生了什么?

我们已经创建了一个仅使用由 Zend Framework 2.0 支持的 HTML5 元素组成的简单表单。当前形状的表单可以通过创建必要的视图来渲染。我们的下一个任务将是使用 HTML5 辅助器构建此表单的视图,并渲染添加到表单中的所有表单元素。

HTML5 视图辅助器

Zend 框架提供了用于渲染上一节中描述的所有表单元素的视图辅助器。formElement() 视图辅助器可以用来根据输入类型动态渲染任何类型的输入,但这并不是建议的做法。

以下表格列出了可用于 HTML5 输入元素的标准化 HTML5 辅助器列表:

输入类型 辅助 辅助函数
datetime Zend\Form\View\Helper\FormDateTime formDateTime()
datetime-local Zend\Form\View\Helper\FormDateTimeLocal formDateTimeLocal()
time Zend\Form\View\Helper\FormTime formTime()
date Zend\Form\View\Helper\FormDate formDate()
week Zend\Form\View\Helper\FormWeek formWeek()
month Zend\Form\View\Helper\FormMonth formMonth()
email Zend\Form\View\Helper\FormEmail formEmail()
url Zend\Form\View\Helper\FormUrl formUrl()
number Zend\Form\View\Helper\FormNumber formNumber()
range Zend\Form\View\Helper\FormRange formRange()
color Zend\Form\View\Helper\FormColor formColor()

除了标准的视图辅助工具列表之外,Zend 框架还提供了 telsearch 输入类型的辅助工具;这些输入类型是文本输入的扩展,但某些浏览器(尤其是移动浏览器)在这两个元素中支持样式化的输入选项。

以下表格列出了可用于 HTML5 输入元素的附加 HTML5 辅助工具列表:

输入类型 辅助工具 辅助函数
tel Zend\Form\View\Helper\FormTel formTel()
search Zend\Form\View\Helper\FormSearch formSearch()

时间行动 – HTML5 视图辅助工具

在本任务中,我们将渲染在前一个任务中创建的所有表单元素。我们将使用 ZF 的 HTML5 视图辅助工具来渲染这些元素。执行以下步骤:

  1. 创建一个简单的视图,用于渲染表单。

  2. 使用以下代码利用视图辅助工具渲染各种表单元素:

    $this->formDateTime($form->get('element-date-time'));
    $this->formDateTimeLocal($form->get('element-date-time-local'));
    $this->formTime($form->get('element-time'));
    $this->formDate($form->get('element-date'));
    $this->formWeek($form->get('element-week'));
    $this->formMonth($form->get('element-month'));
    $this->formEmail($form->get('element-email'));
    $this->formUrl($form->get('element-url'));
    $this->formNumber($form->get('element-number'));
    $this->formRange($form->get('element-range'));
    $this->formColor($form->get('element-color'));
    
  3. 在 Opera 12 等 HTML5 兼容的浏览器中测试表单。您应该能够看到以下截图所示的表单:时间行动 – HTML5 视图辅助工具

  4. 现在,在 IE 9 等 HTML5 不兼容的浏览器中测试相同的表单。您应该能够看到以下截图所示的表单。您可以看到,不支持的输入元素被文本框替换了:时间行动 – HTML5 视图辅助工具

发生了什么?

我们已经使用 ZF2 表单元素创建了我们的第一个 HTML5 表单。到目前为止,Opera 12 提供了最好的 HTML5 支持;其他浏览器如 Chrome 和 Safari 在支持方面也相当不错。因此,如果您正在测试您的 HTML5 表单,请确保您在兼容的浏览器中测试,例如 Opera 12。

注意

HTML5 浏览器兼容性

各浏览器对 HTML5 规范的支持不一致;在合规性方面,Opera 和 Chrome 似乎提供了最好的支持,但它们都不完全符合规范。随着每个新浏览器版本的发布,都会增加对这些特性的支持。互联网上有许多资源可以帮助您检查您的浏览器与 HTML5 的兼容性。

html5test.com/ 是一个基于浏览器对 HTML5 支持情况进行排名和比较的门户网站。

caniuse.com/ 也是一个很棒的网站,用户可以检查他们是否可以在特定的浏览器上使用特定的 HTML5 功能。

尝试一下英雄

在你继续使用高级 HTML5 属性之前,这里有一个简单的任务给你。现在你已经使用所有标准 HTML5 元素创建了一个表单,尝试通过使用视图助手来渲染telsearch类型的输入来扩展这个表单。

HTML5 属性

你可能已经注意到,在章节的开头我们使用了新的属性,如minmaxstep。这些是在 HTML5 规范中定义的新属性,允许开发者对输入元素进行额外的配置。以下列表中讨论了一些重要的属性:

  • max: 适用于数字范围日期字段;允许指定输入的最大值。

  • min: 适用于数字范围日期字段;允许指定输入的最小值。

  • step: 适用于数字范围日期字段;允许指定输入的增量值。

  • list: 适用于各种文本框样式输入。允许开发者将字段映射到数据列表,从而允许最终用户从列表中选择。

  • placeholder: 适用于各种文本框样式输入。允许开发者显示占位文本,直到元素获得焦点。

  • pattern: 适用于各种文本框样式输入。允许开发者验证用户输入是否与正则表达式匹配,并抛出验证错误。

  • required: 防止用户在必填字段中提交空值。

  • multiple: 适用于文件输入;允许从单个文件控件上传多个文件。

多文件上传

为了实现多文件上传,你需要将文件输入元素的multiple属性设置为TRUE。如果浏览器支持多文件上传,则用户将被允许选择多个文件,否则控件将限制为仅选择一个文件。

行动时间 – HTML5 多文件上传

执行以下步骤进行 HTML5 多文件上传:

  1. 创建一个新的ImageUpload表单;确保将File元素的multiple属性设置为TRUE

    <?php
    // filename : module/Users/src/Users/Form/MultiImageUploadForm.php
    namespace Users\Form;
    
    use Zend\Form\Form;
    use Zend\Form\Element;
    use Zend\InputFilter;
    
    class MultiImageUploadForm extends Form
    {
        public function __construct($name = null, $options = array())
        {
            parent::__construct($name, $options);
            $this->addElements();
            $this->addInputFilter();
        }
    
        public function addElements() 
        {
          $imageupload = new Element\File('imageupload');
          $imageupload->setLabel('Image Upload')
            ->setAttribute('id', 'imageupload')
     ->setAttribute('multiple', true); 
     //Enables multiple file uploads
          $this->add($imageupload);
    
          $submit = new Element\Submit('submit');
          $submit->setValue('Upload Now');
          $this->add($submit);
        }
    
        public function addInputFilter()
        {
          $inputFilter = new InputFilter\InputFilter();
          // File Input
          $fileInput = new InputFilter\FileInput('imageupload');
          $fileInput->setRequired(true);
     $fileInput->getFilterChain()->attachByName(
     'filerenameupload',
     array(
     'target' => './data/images/temp.jpg',
     'randomize' => true
     )
     );
          $inputFilter->add($fileInput);
          $this->setInputFilter($inputFilter);
        }
    }
    

    小贴士

    Zend\Filter\File\RenameUpload

    RenameUpload过滤器用于重命名并移动上传的文件到target中指定的新路径。要了解更多信息,请参阅框架文档framework.zend.com/manual/2.2/en/modules/zend.filter.file.rename-upload.html.

  2. 设置一个动作来处理文件上传,并将用户重定向到上传确认页面:

    public function multiUploadAction()
    {
      // prepare form
      $form = $this->getServiceLocator()->get('MultiImageUploadForm');
      $request = $this->getRequest();
      if ($request->isPost()) {
        $post = array_merge_recursive(
          $request->getPost()->toArray(),
          $request->getFiles()->toArray()
        );
            $form->setData($post);
        if ($form->isValid()) {
          $data = $form->getData();
          // Form is valid, save the form!
          return $this->redirect()->toRoute('users/html5-test', array('action' => 'processMultiUpload'));
        }
      }
      $viewModel  = new ViewModel(array('form' => $form));
      return $viewModel;
    }
    
  3. 现在测试您的浏览器中支持 HTML5 多文件上传的表单,例如 Opera 12。您将看到文件选择器界面允许选择多个文件,如下面的截图所示:行动时间 – HTML5 多文件上传

  4. 在您选择 立即上传 并完成上传过程后,您将看到如下所示的确认页面:行动时间 – HTML5 多文件上传

  5. 您可以通过导航到 data/images 目录并查找上传的文件来验证文件是否已成功上传且过滤器已应用。您可以看到所有文件都以 temp 开头,并在文件名中有一个 _<随机数字> 后缀:行动时间 – HTML5 多文件上传

小贴士

具有多个文件上传的过滤器

在应用具有多个文件上传的过滤器时,过滤器将应用于所有成功上传的文件,并使用相同的过滤器选项设置。

刚才发生了什么?

我们现在已经使用 HTML5 属性和 Zend 表单元素创建了一个 HTML5 多文件上传表单。我们还应用了一个过滤器来重命名上传的文件,并看到了过滤器在多文件上传中的工作方式。

快速问答 – HTML5 支持

Q1. 以下哪种方法是新支持的 HTML5 输入类型?

  1. 文本

  2. 单选按钮

  3. 复选框

  4. 数字

以下哪些输入类型在 ZF 2.1 中没有定义 Form 元素?

  1. 电话号码

  2. 日期

  3. 颜色

  4. 搜索

摘要

HTML5 是一种非常强大且健壮的 HTML 规范,目前大多数浏览器仍在部分支持。随着市场上浏览器新版本的推出,您将看到对这一规范的更多增强支持。在我们下一章中,我们将使用 ZF2 来构建移动网络应用。

第十章.构建移动应用程序

移动应用程序开发中的一个主要挑战是在构建移动应用程序时需要针对多样化的平台。例如,PhoneGap 和 Titanium 等平台使开发者能够构建跨平台移动应用程序,但这一模式的缺点是需要管理不同平台上的多个项目,包括移动和 Web 服务。随着 Zend Studio 10 的发布,Zend 试图通过提供一个基于 PhoneGap 的开发平台来填补这一空白,该平台支持在基于云的环境中构建端到端移动应用程序。

随着 Zend Studio 10 的发布,Zend 现在提供了一个使用 Zend Framework 2 的极其简化的移动应用程序开发平台,称为云连接移动工具。在本章中,我们将学习如何使用 Zend Studio 构建云连接移动应用程序的基础知识。以下是一些关键的学习领域:

  • 构建您的第一个云连接移动CCM)应用程序

  • 作为原生应用程序进行测试

  • 实现简单的搜索界面

云连接移动应用程序

Zend Studio 现在提供了一个 CCM 工具,使开发者能够使用云平台构建原生移动应用程序。CCM 支持使用 Zend Framework 2 和 Zend Server Gateway 在云上开发基于 RPC 或 REST 的 Web 服务。

CCM 还通过集成各种移动 SDK(Android SDK/ADT for Android、Xcode for iOS 和 Windows Phone SDK for Windows Phone)提供了开发原生移动应用程序的支持。这使得开发者能够在原生环境/设备上构建和测试应用程序。

CCM 工具还提供了一个简单易用的移动 GUI 编辑器,它帮助开发者轻松地为他们的移动应用程序构建出色的用户界面。

Zend Studio 10

在构建您的移动应用程序的第一步,请确保您已在您的开发机器上安装了 Zend Studio 10。Zend Studio 10 提供了构建云连接移动应用程序的集成支持,并允许开发者将他们的移动应用程序部署到云上。

您可以从 Zend 在线商店购买 Zend Studio 10;同时还有一个 30 天的免费试用期。有关更多信息,请访问www.zend.com/en/products/studio/

phpCloud

Zend 开发者云是一个基于云的 PHP 开发环境,它使开发者能够在云上构建和部署应用程序,无需经历设置 PHP 开发环境、配置和维护环境的麻烦。

此环境已安装 Zend Framework 2 和大量 PHP 扩展;开发者可以使用各种开发工具,如 Zend Studio、Eclipse PDT 和 CLI,在开发者云上构建和部署他们的应用程序。Zend 开发者云还提供了将应用程序推送到其他外部云服务(如 Amazon 和 IBM SmartCloud)的功能。

Zend 开发者云目前处于免费开发者测试版。有关 Zend 开发者云的更多信息,请参阅他们的网站:www.phpcloud.com/

操作时间 – 配置您的 phpCloud 账户

在此任务中,我们将按照以下步骤设置我们的 phpCloud 账户并在 Zend Studio 10 中配置云环境:

  1. 访问 my.phpcloud.com/user/login,注册新账户并登录到您的 phpCloud 账户。

  2. 登录后,您将被要求创建一个容器。您可以指定一个容器名称,该名称将成为容器 URL 的一部分;您还可以选择生成 SSH 密钥对或使用您自己的 SSH 密钥;在这种情况下,我们将生成一个新的 SSH 密钥对。以下截图描述了容器创建界面:操作时间 – 配置您的 phpCloud 账户

  3. 现在下载 SSH 密钥;我们将使用这些密钥在 Zend Studio 中设置部署目标:操作时间 – 配置您的 phpCloud 账户

  4. 在 Zend Studio 中,导航到 窗口 | 显示视图 | 目标操作时间 – 配置您的 phpCloud 账户

  5. 点击 添加目标 图标,并选择如图所示的 phpcloud操作时间 – 配置您的 phpCloud 账户

  6. phpcloud 目标详情 页面上,您将被要求提供以下详细信息:

    • 用户名:用于指定您的 Zend 开发者云用户名

    • 密码:用于指定您的 Zend 开发者云密码

    • SSH 私钥:用于指向在 phpcloud 容器创建界面中刚刚生成的 SSH 密钥

    操作时间 – 配置您的 phpCloud 账户

  7. 点击 完成 后,您将看到新目标已添加到目标列表中:操作时间 – 配置您的 phpCloud 账户

刚才发生了什么?

我们已成功使用 Zend 的云连接移动应用项目创建了我们的第一个移动应用程序。在接下来的章节中,我们将了解如何使用 Zend Framework 2 扩展这些网络服务,将更多功能集成到移动应用程序中。

PhoneGap 和 Zend Studio

PhoneGap 是一个移动应用程序开发框架,允许开发者使用 HTML、CSS 和 JavaScript 构建移动应用程序。PhoneGap 框架用于将这些应用程序转换为原生移动应用程序,无需在原生语言(如 Objective-C)中重写应用程序。

Zend Studio 10 现在将 PhoneGap 集成到 Zend Studio IDE 中;这使得开发者能够轻松构建和测试移动应用程序,而无需依赖外部库。

关于使用 Zend Studio 10 构建云连接移动应用程序的更多信息,请参阅以下文档页面:

files.zend.com/help/Zend-Studio-10/zend-studio.htm#cloud_connect_mobile.htm

动手实践 – 构建您的第一个云连接移动应用程序

执行以下步骤以构建您的第一个云连接移动应用程序:

  1. 新建菜单中选择云连接移动项目选项:动手实践 – 构建您的第一个云连接移动应用程序

  2. 项目向导中,您将被要求提供以下详细信息:

    • 移动项目名称:客户端移动应用程序项目的名称

    • 网络服务项目名称:移动应用程序的网络服务项目名称

    • 网络服务项目部署目标:移动应用程序的部署目标(您可以选择之前创建的 phpcloud 目标)

      提示

      Zend Studio 10 支持各种部署选项;它可以自动检测本地 Zend Server 安装或将应用程序部署到以下目标之一——本地 Zend Server、远程 Zend Server、Zend Developer Cloud(phpCloud)或 OpenShift Cloud。

    动手实践 – 构建您的第一个云连接移动应用程序

  3. 在模板选择页面,选择简单服务,因为它将创建一个包含客户端/服务器端示例的简单项目,如下面的截图所示:动手实践 – 构建您的第一个云连接移动应用程序

  4. 点击完成将创建移动和 Web 服务项目。移动项目中的用户界面设计师使我们能够轻松地更改移动界面,如下面的截图所示:动手实践 – 构建您的第一个云连接移动应用程序

  5. 现在从 Zend Studio IDE 运行项目;它应该启动一个 Zend 模拟器界面,如下面的截图所示:

    提示

    获取列表按钮应通过 RPC 调用从网络服务项目返回客户列表。如果请求没有返回响应并抛出错误,例如Ajax 错误。错误:访问被拒绝。尝试静态数据,则检查MobileApplication/www/js/my.js中的gatewayURL变量。

    确保它指向正确的部署 URL,如下所示:

    var gatewayURL = 'http://zf2cloudapp.my.phpcloud.com/MobileService';

    行动时间 – 构建您的第一个云连接的移动应用程序

发生了什么?

我们已成功使用 Zend 的云连接移动应用程序项目创建了我们的第一个移动应用程序。在随后的章节中,我们将了解如何使用 Zend Framework 2 扩展这些网络服务,以在移动应用程序中构建更多功能。

原生应用程序与移动网络应用程序的比较

原生移动应用程序相较于移动网络应用程序提供了巨大的优势。原生网络应用程序从设备内存中运行,因此几乎不需要网络交互;这些应用程序通常加载和运行得更快。原生移动应用程序的另一个关键优势是它们可以访问设备的原生功能,如相机、设备信息和加速度计;这使原生应用程序相较于移动网络应用程序具有额外的优势。

行动时间 – 作为原生应用程序进行测试

在此任务中,我们将使用 Zend Studio 的原生应用程序部分创建原生 iOS 应用程序。在您开始之前,请确保您的 Mac 上已安装 Xcode IDE。执行以下步骤:

小贴士

对于 Android 应用程序,您需要安装Android 开发工具ADT);这可以直接从 Zend Studio 安装。

对于 Windows Phone 应用程序,需要安装 Windows Phone SDK。

  1. 现在,从我们的移动应用程序项目中选择创建 iOS 应用程序行动时间 – 作为原生应用程序进行测试

  2. 您将被要求提供项目详细信息;请指定公司名称包标识符的值。包标识符的值是指用于识别应用程序的唯一名称;这通常以com.my-company-name.my-application-name的格式提供。当您在苹果商店注册应用程序时,请确保包标识符与苹果提供的匹配。行动时间 – 作为原生应用程序进行测试

  3. 现在您可以在工作区中看到新创建的 iOS 项目,如下截图所示:行动时间 – 作为原生应用程序进行测试

    小贴士

    Zend Studio 允许创建多个相互依赖的移动应用程序项目。如果您需要对客户端代码进行任何更改,可以在父移动项目中做出更改,这将自动更新所有依赖的客户端项目。

    关于创建原生应用程序的更多信息,请参阅以下链接中的 Zend Studio 文档:

    files.zend.com/help/Zend-Studio-10/zend-studio.htm#creating_native_applications.htm

  4. 如果运行项目,应用程序将启动 iOS 模拟器并启动如图所示的移动应用程序:行动时间 – 作为原生应用程序进行测试

发生了什么?

我们已使用 Zend Studio 对原生应用程序的支持创建了一个新的原生 iOS 应用程序;在我们的下一节中,我们将使用 Zend Framework 2 为此应用程序提供网络服务。

尝试一下英雄

现在您已经创建了一个 iOS 原生应用程序,尝试使用 Zend Studio 创建相同应用程序的 Android 版本。为此,您需要在您的 Zend Studio 安装上安装 Android 开发工具。

Zend Server Gateway

Zend Server Gateway 是基于 Zend Framework 2 的轻量级网络服务网关,它允许将网络服务路由映射到各种网络服务的控制器/操作。Zend Server Gateway 负责 CCM 项目中使用的 RPC 和 RESTful API 的认证、验证、过滤和路由。

路由配置映射到config/gateway.xml;路由和配置可以使用在 Zend Studio 中提供的网关编辑器界面进行管理。

行动时间 – 创建移动搜索界面

在本任务中,我们将通过以下步骤创建一个简单的搜索界面,用于通过名称搜索现有客户记录:

  1. 我们需要在CustomerRepository模型中创建一个搜索函数(MyMobileService\src\MyCompany\Model\CustomerRepository.php):

        public function getSearch($query)
        {
          $where = new \Zend\Db\Sql\Where();
          $where->like('name', "%$query%");
          return $this->customerTable->select($where)->toArray();
        }
    
  2. RpcControllerMyMobileService\src\MyCompany\Controller\RpcController.php)中添加一个新操作;这将处理网络服务请求:

        public function getSearchCustomersAction ($query)
        {
          $cr = new CustomerRepository();
          return $cr->getSearch($query);
        }
    
  3. 在网关编辑器中,创建一个新的 RPC 服务;设置以下选项:

    • URL: /search

    • 方法: GET

    • 请求参数(添加): 名称query; 来源路由

    • 处理方法: MyCompany\Controller\RpcController::getSearchCustomersAction

  4. 您可以通过右键单击服务并选择测试服务来测试 RPC 服务。在右侧,您将看到一个提供测试输入并验证服务响应的界面:行动时间 – 创建移动搜索界面

  5. 在移动 GUI 编辑器中,创建一个新的页面searchCustomers,并添加以下元素:

    • 文本框: custsearchinput

    • 按钮: searchbutton

    • 列表视图: custlistview

  6. 在“搜索”按钮的“绑定”部分,将按钮绑定到GET /search:query()网络服务。将custsearchinput文本框映射到“数据”部分的query路由参数。此操作将搜索文本绑定到query路由参数。请注意,query路由参数已经映射到getSearchCsutomerAction

  7. 修改MyMobileApp/www/js/my.js中的onGetSearchquery JavaScript 方法以处理 RPC 响应:

    function onGetSearchquery(response) {
      // TODO Custom logic to handle server response
      customers = response;
    
      var newCustomers = '';
      $.each(customers, function(index, item) {
        newCustomers += '<li data-theme="">' 
            + '<a href="#page2?empId=' + index
           + '" data-transition="none">' + item.name + '</a>' + '</li>';
      });
    
      $('#custlistview li[role!=heading]').remove();
      $('#custlistview').append(newCustomers).listview('refresh');
    }
    
  8. 确保使用按钮从index页面链接到Search页面。

  9. 现在以原生模式运行项目;您将能够看到搜索页面,就像以下屏幕截图所示:动手实践 – 创建移动搜索界面

发生了什么?

我们现在为现有的云连接移动应用程序创建了新的网络服务,并在原生模拟器中测试了移动应用程序。使用 Zend Studio 10,您可以看到构建由云上运行的网络服务支持的移动应用程序的简单性。

快速问答 – 构建移动应用程序

Q1. 以下哪些平台在 Zend Studio 10 中支持原生移动应用程序开发?

  1. Android

  2. Firefox OS

  3. MeeGo

  4. Brew

Q2. 以下哪些网络服务不支持 Zend Server Gateway 构建云连接的移动应用程序?

  1. RPC

  2. SOAP

  3. REST

摘要

云连接的移动应用程序是 Zend 朝着使 PHP 开发者能够使用云平台构建和支持移动应用程序迈出的重要一步。通过 CCM,Zend 提供了一个极其强大且易于使用的平台来构建这些应用程序。

完成本章后,您已经到达了本书的结尾。您通过本书学习了 Zend Framework 在各个不同应用中的大量内容,并完成了一系列任务。本书向您展示了使用 Zend Framework 2 开发应用程序的基本构建块;在 Zend Framework 中还有更多东西要学习,其中大部分都在 Zend Framework 文档中以极其详细的方式进行了解释(framework.zend.com/manual/2.2/en/index.html)。

感谢您阅读本书。请随时就您阅读本书的感受提供反馈。

附录 A. 快速测验答案

第一章,开始使用 Zend Framework 2.0

快速测验 – Zend Framework 2.0

Q1 3
Q2 4

第二章,构建您的第一个 Zend Framework 应用程序

快速测验 – Zend Framework 2.0

Q1 2
Q2 4

第三章,创建通信应用程序

快速测验 – Zend Framework 2.0

Q1 2
Q2 1

第四章,数据管理和文档共享

快速测验 – 数据管理和文档共享

Q1 4
Q2 3

第五章,聊天和电子邮件

快速测验 – 聊天和电子邮件

Q1 1 和 2
Q2 2 和 4

第六章,媒体共享

快速测验 – 媒体共享

Q1 4
Q2 4

第七章,使用 Lucene 进行搜索

快速测验 – 搜索

Q1 1
Q2 4

第八章,创建一个简单的商店

快速测验 – 创建一个简单的商店

Q1 2
Q2 1

第九章,HTML5 支持

快速测验 – HTML5 支持

Q1 4
Q2 1 和 4

第十章,构建移动应用程序

快速测验 – 构建移动应用程序

Q1 1
Q2 2
posted @ 2025-09-09 11:30  绝不原创的飞龙  阅读(19)  评论(0)    收藏  举报