PHP-和-Netbeans-应用开发(全)

PHP 和 Netbeans 应用开发(全)

原文:zh.annas-archive.org/md5/3257ea46483c2860430cdda1bc8d9606

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

生产力是软件开发人员的重要因素。一个良好的开发环境或周围工具,携带着特定编程风格的精髓,可以提高我们的编码生产力,并产生质量和优化的软件产品。为了保持快节奏的开发,开发人员寻求一种让他们感到宾至如归的环境。这样的集成开发环境(IDE)确实可以加速代码实现,并成为项目开发的魔法棒。

一个好的 IDE 更像是一个精心设计的功能齐全的瑞士军刀。它包括:

  • 源代码编辑器

  • 编译器/解释器

  • 调试器

  • 数据库管理支持

  • 版本控制系统

  • 面向对象编程的工具,如类浏览器和对象检查器

IDE,如 NetBeans,具有更大的灵活性,开发人员可以在其中感到宾至如归。此外,NetBeans 是完全免费的,并由开源社区提供。简而言之,PHP 的 IDE 将在各个方面促进您从开发到生产的生产力。

在本书《使用 NetBeans 初学者指南进行 PHP 应用程序开发》中,您将学习如何通过 NetBeans IDE 完成一些真实的、时尚的 PHP 项目,从而成为一个自信的 PHP 开发人员,覆盖不同类别的基于 Web 的应用程序。

本书涵盖的内容

第一章,“设置您的开发环境”,指导您逐步完成 NetBeans 安装并设置 PHP 开发环境的过程。在本章结束时,您的开发环境将在您的操作系统上准备就绪。

第二章,“通过 PHP 编辑器提高编码效率”,展示了如何使用 NetBeans PHP 编辑器编写更快的代码。您将了解 IDE 的一些杀手功能,如代码完成、代码模板、重命名重构和代码生成。在本章结束时,您将对编辑器的智能功能和增加的编码生产力有全面的、实际的了解。

第三章,“使用 NetBeans 构建类似 Facebook 的状态发布器”,直接跳转到一个真实的 PHP 应用程序开发,用于显示类似 Facebook/Twitter 的发布状态流。在本章结束时,您将能够使用 NetBeans IDE 开发简单的 PHP 应用程序。

第四章,“使用 NetBeans 进行调试和测试”,将解释如何使用 IDE 调试和测试 PHP 应用程序。本章涵盖的主题包括配置 XDebug、调试 PHP 源代码、使用 PHPUnit 和 Selenium 进行测试,以及代码覆盖率。

第五章,“使用代码文档”,指导开发人员创建源代码和项目文档的过程。您将熟悉 PHPDoc 标准标签及其用法,以便在编辑器的帮助下对源代码进行文档化。此外,您将使用外部文档生成器生成项目 API。

第六章,“理解 Git,NetBeans 方式”,将向您展示如何使用 Git,这是一个免费的开源分布式版本控制系统。使用 IDE,您将进行 Git 操作,如初始化或克隆存储库,暂存文件,提交更改,恢复修改,以及远程存储库操作,如获取、拉取和推送,同时使用分支。在本章结束时,您将能够成为使用 NetBeans 协作开发功能的开发团队的一部分。

第七章构建用户注册、登录和注销,涉及专业的 PHP 应用程序。您将设计和开发一个 PHP 应用程序,用户可以在其中注册自己,注册后他们可以登录应用程序,查看和更新他们自己的个人资料等。

附录 A在 NetBeans 7.2 中介绍 Symfony2 支持,将发现 NetBeans 对 Symfony2 PHP 框架的支持。这介绍了 Symfony2 的项目创建,运行 Symfony2 命令,并介绍了从 NetBeans 创建 bundle。

附录 BNetBeans 键盘快捷键,是常见 NetBeans 键盘快捷键的便利参考。

您需要为本书做些什么

在第一章设置您的开发环境推荐系统要求部分,解释了系统要求,以及以设置您的开发环境开头的部分解释了特定操作系统的 PHP 开发环境。总之,您应该有以下内容:

  • NetBeans IDE

  • 最新的 Apache、MySQL 和 PHP 包

本书适合谁

本书面向希望在利用 NetBeans 功能简化软件开发工作并利用 IDE 的强大功能的同时开发 PHP 应用程序的初学者级别 PHP 开发人员。不假设熟悉 NetBeans。但是,预期对 PHP 开发有一些了解。

惯例

在本书中,您会经常看到几个标题。

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

行动时间 — 标题

  1. 操作 1

  2. 操作 2

  3. 操作 3

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

刚刚发生了什么?

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

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

弹出测验 — 标题

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

尝试英雄 — 标题

这些设置实际挑战,并给您一些尝试所学内容的想法。

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

文本中的代码词如下所示:“使用文件浏览器设置安装文件夹。”

代码块设置如下:

<?php
echo "Hello World";
?>

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体设置:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Status updater</title> **<link href="<?=BASE_URL?>styles/styles.css" media="screen" rel="stylesheet" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/ libs/jquery/1.7/jquery.min.js">**
</script>
<script src="<?=BASE_URL?>js/status.js"></script>
</head>

任何命令行输入或输出都以以下方式书写:

**sudo apt-get install lamp-server^**

新术语重要单词以粗体显示。您在屏幕上、菜单或对话框中看到的单词,例如,会在文本中以这样的方式出现:“点击下一步按钮,您将被要求接受许可协议。”

注意

警告或重要说明会以这样的方式出现在一个框中。

提示

提示和技巧会出现在这样的形式。

第一章:设置您的开发环境

NetBeans 是一个免费开源的集成开发环境IDE),符合多种编程语言。长期以来,它一直是主要开发者社区的首选编辑器。随着市场需求的增长,NetBeans 自 NetBeans 6.5(2008 年 11 月)以来已经集成了 PHP 开发功能,如今,它已成为 PHP 社区中最受欢迎的 IDE 之一。

在本章中,我们将讨论:

  • 为什么选择 NetBeans 进行 PHP 应用程序开发?

  • 下载 NetBeans IDE

  • 逐步进行 NetBeans 安装

  • 设置您的 PHP 开发环境

  • 创建 NetBeans 项目

那么让我们开始吧…

为什么选择 NetBeans 进行 PHP 应用程序开发?

NetBeans IDE 通过以下方式促进我们日常的 PHP 应用程序开发活动:

  • 创建和管理项目:PHP 的 IDE 使我们能够创建 PHP 项目,并帮助项目增长。它可以执行与项目相关的设置和操作;即创建项目文档,测试项目等。

  • 源代码的编辑功能:代码编辑器在 PHP 项目范围内具有令人兴奋的源代码编辑功能集合。它通过以下功能加快了代码编写速度:

  • 语法高亮使项目文件中的 PHP 语法突出显示。

  • 代码折叠使当前文件中选择的类和方法代码可以折叠和展开。

  • 导航帮助探索当前 PHP 文件中的类和方法。

  • 代码模板帮助使用预定义的代码片段。

  • 代码完成显示代码的自动完成列表。

  • 参数提示提供有关方法的形式参数在方法被调用的地方的信息。

  • 智能缩进在按代码时提供自动格式化。

  • 格式化在当前文件中提供自动代码格式化。

  • 括号补全在编写代码时添加/删除成对的引号、括号和大括号。

  • 标记出现标记在打开的项目文件中代码字符串的所有出现。

  • 错误检测在输入完成后立即显示 PHP 解析错误。

  • 配对匹配突出显示匹配的引号、大括号、括号等。

  • 语义高亮识别关键字、方法名、调用、未使用的变量等。

  • 转到声明将光标发送到所选类型声明的位置。

  • 即时重命名会重命名变量在其范围内的所有出现。

  • 拼写检查显示拼写错误和更正。

  • 代码文档帮助自动生成文档结构。

  • 部署项目:在 PHP 项目内容内提供与远程服务器内容的同步。

  • 数据库和服务:提供对数据库管理和 Web 服务的支持。

  • SCM 工具:提供源代码管理工具,如 Git、Subversion、CVS 和 Mercurial,内置用于源代码版本控制、跟踪更改等。

  • 运行 PHP 脚本:使 PHP 脚本解析,并在不转到浏览器的情况下在 IDE 中产生输出。

  • 调试源代码:您可以检查本地变量,设置监视,设置断点,并实时评估代码。您还可以执行命令行调试,并在不转到浏览器的情况下在 IDE 中检查 PHP 输出,这为远程调试提供了能力。

  • 支持 PHP 框架:它还支持流行的 PHP 框架,如 Zend Framework 和 Symfony。

注意

可以在en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#PHP找到用于 PHP 的集成开发环境的比较。

推荐的系统要求

在我们继续下载最新版本之前,让我们看一下各个平台安装和运行 NetBeans IDE 的推荐系统要求:

  • Microsoft Windows XP Professional SP3/Vista SP1/Windows 7 Professional:

  • 处理器:2.6 GHz 英特尔奔腾 IV 或同等处理器

  • 内存:2 GB

  • 磁盘空间:1 GB 的可用磁盘空间

  • Ubuntu 12.04:

  • 处理器:2.6 GHz 英特尔奔腾 IV 或同等处理器

  • 内存:2 GB

  • 磁盘空间:850 MB 的可用磁盘空间

  • Macintosh OS X 10.7 Intel:

  • 处理器:双核英特尔(32 位或 64 位)

  • 内存:2 GB

  • 磁盘空间:850 MB 的可用磁盘空间

下载 NetBeans IDE

NetBeans 可以成为您日常开发的 IDE,有助于提高编码效率。它是一个免费的开源 IDE,可用于不同的技术,包括 Java、C/C++、PHP 等,以及 Windows、Linux、Mac OS X 或甚至独立于操作系统的捆绑包。此外,您可以仅为 PHP 技术下载 IDE,或者下载包含所有技术的安装程序包。

再次强调,如果您已经在使用 IDE 进行 Java、C/C++等开发,则可以跳过此下载和安装部分,直接转到名为“将 PHP 作为插件添加到已有的 NetBeans 安装”部分。

操作时间-下载 NetBeans IDE

按照以下步骤下载 NetBeans IDE:

  1. 访问netbeans.org/downloads/以下载最新的 NetBeans 版本。下载页面将自动检测您的计算机操作系统,并允许您下载特定于操作系统的安装程序。

请注意,您可以稍后使用 IDE 的插件管理器添加或删除包或插件。此外,如果您想避免安装,您可以选择“OS-independent ZIP”。再次强调,NetBeans 是那些使用多种编程语言平台的程序员必备的 IDE。目前,NetBeans IDE 支持各种开发平台——J2SE、J2EE、J2ME、PHP、C/C++等等。

操作时间-下载 NetBeans IDE

接下来,我们将下载 PHP 捆绑,如上面的截图所示。

  1. 点击“下载”按钮后,页面将被重定向到自动下载,同时显示直接下载链接,如下截图所示:操作时间-下载 NetBeans IDE

如您所见,您的下载将自动开始;Firefox 用户应该会看到一个保存文件的窗口,如下所示:

操作时间-下载 NetBeans IDE

  1. 将文件保存到您的磁盘空间中。

刚刚发生了什么?

我们刚刚下载了 NetBeans PHP 捆绑安装文件。PHP 捆绑提供了用于 PHP 5.x 开发的工具,以及 Zend 和 Symfony 框架支持。如果您点击“全部”下载选项,您将获得所有提到的技术的安装文件,并且在安装过程中您将能够选择安装哪些工具和运行时。所以,现在我们准备启动安装向导。

安装 NetBeans

使用安装向导安装 NetBeans 非常简单,该向导将指导用户完成所需的步骤或配置。已经在使用 NetBeans 进行其他技术(如 Java 或 C/C++)开发的用户可以跳过此部分,直接转到名为“将 PHP 作为插件添加到已有的 NetBeans 安装”部分。

注意

PHP 和 C/C++的 NetBeans 捆绑只需要安装 Java Runtime Environment(JRE)6。但是,如果您计划使用任何 Java 功能,则需要安装 JDK 6 或 JDK 7。

操作时间-逐步安装 NetBeans

在本节中,我们将逐步安装 Windows 7 上的 NetBeans IDE。为了安装软件,您需要运行安装程序并按照以下步骤进行操作:

  1. 运行或执行安装程序。第一步将类似于以下截图:操作时间-逐步安装 NetBeans

  2. 点击下一步按钮,您将被要求接受许可协议:操作时间-逐步安装 NetBeans

  3. 下一步将要求您为 NetBeans 和 JRE 选择安装位置,并提供一些默认的程序文件路径:操作时间-逐步安装 NetBeans

请注意,JRE 是您计算机的 Java 软件,或者 Java 运行环境,也被称为Java 虚拟机(JVM)。JRE 也将被安装。

  1. 使用文件浏览器设置安装文件夹,并点击下一步按钮。下一个截图显示了总安装大小:操作时间-逐步安装 NetBeans

  2. 如果一切都设置好了,点击安装按钮开始安装过程。操作时间-逐步安装 NetBeans

  3. 安装正确后,您将看到完成向导,如下截图所示:操作时间-逐步安装 NetBeans

  4. 您可以根据自己的意愿选择或取消通过提供匿名使用数据为 NetBeans 项目做出贡献复选框。请注意,它将向netbeans.org发送特定于项目的使用数据,因此在勾选之前请仔细阅读屏幕上的说明。点击完成按钮完成安装。现在,转到您的操作系统的程序菜单或安装 IDE 的目录以运行。IDE 将显示一个启动画面,如下所示:操作时间-逐步安装 NetBeans

  5. 最后,运行的 IDE 看起来类似于以下截图:操作时间-逐步安装 NetBeans

刚刚发生了什么?

既然我们已经安装并运行了 IDE,我们可以继续探索在各种操作系统上设置开发环境。

在下一节中,我们将在各种操作系统上配置我们的 PHP 开发环境。我们将使用最新的 Apache-MySQL-PHP 软件包安装程序,即 LAMP、XAMPP 和 MAMP,对应于不同的操作系统。

将 PHP 作为插件添加到已有的 NetBeans 安装中

如果您想要为 NetBeans IDE 配置添加功能,请使用 NetBeans 插件管理器。例如,假设您已经在 NetBeans IDE 中运行了 Java 或 C/C++包。然后您决定尝试 PHP 功能。要做到这一点,从 IDE 中转到 NetBeans 插件管理器(选择工具|插件),并将 PHP 包添加到您已有的安装中。

多个安装支持

多个版本的 NetBeans IDE 5.x、6.x 和 7.x 可以与最新版本共存在同一系统上。您无需卸载早期版本即可安装或运行最新版本。

如果您之前安装过 NetBeans IDE,当您第一次运行最新的 IDE 时,您可以选择是否从现有用户目录导入用户设置。

尝试添加或删除 NetBeans 功能

因此,您的计算机上已经安装并运行了 NetBeans。现在,添加更多功能或删除不必要的功能,从已安装的 NetBeans 中检查新添加的功能。您可以尝试使用插件管理器来实现这一点。

在 Windows 中设置开发环境

我们将使用 XAMPP 软件包而不是单独安装和配置 Apache、MySQL 和 PHP,以便自动安装和配置所有这些。我们将下载并安装最新的 XAMPP 软件包(v. 1.7.7),其中包括以下内容:

  • Apache 2.2.21

  • MySQL 5.5.16

  • PHP 5.3.8

  • phpMyAdmin 3.4.5

  • FileZilla FTP 服务器 0.9.39

行动时间-在 Windows 安装 XAMPP

以下步骤将下载并安装 XAMPP 软件包:

  1. 我们将从以下网址下载最新的 XAMPP 软件包安装程序:www.apachefriends.org/en/xampp-windows.html

  2. 下载完成后,运行.exe文件以继续安装。更多安装细节可以在www.apachefriends.org/en/xampp-windows.html找到。

  3. 您将有选择安装 Apache 服务器和 MySQL 数据库服务器作为服务,因此您无需从 XAMPP 控制面板手动启动它们。同样,您将有选项稍后从 XAMPP 控制面板配置这些服务,如启动/停止、作为服务运行和卸载。

  4. 成功完成安装过程后,您将能够继续进行后续步骤。从操作系统的Start | Programs | XAMPP中打开 XAMPP 控制面板。行动时间-在 Windows 安装 XAMPP

Svc复选框表示该模块已安装为 Windows 服务,因此将随 Windows 启动而启动,或者您可以将其选中以作为服务运行。如果您需要重新启动 Apache web 服务器,请使用 Apache Status旁边的Stop/Start按钮。

  1. 现在,检查您的 XAMPP 安装。从您的 Web 浏览器访问 URL http://localhost;XAMPP 欢迎页面看起来类似于以下截图:行动时间-在 Windows 安装 XAMPP

  2. 从左侧点击phpinfo()以检查您配置的 PHP 版本和已安装的组件:行动时间-在 Windows 安装 XAMPP

  3. 单击Welcome菜单下的Status菜单,以检查已安装工具的状态;相应列旁边的激活绿色状态表示您已成功运行 Apache、MySQL 和 PHP:行动时间-在 Windows 安装 XAMPP

刚刚发生了什么?

我们已成功在系统上安装并运行了 XAMPP 软件包。我们有 XAMPP 控制面板来控制已安装的服务,我们还有一个 Web 界面来管理 MySQL 数据库。

尝试一下-保护您的 XAMPP 安装

XAMPP 不适用于生产环境,而仅适用于开发环境中的开发人员。XAMPP 被配置为尽可能开放,并允许 Web 开发人员获取他们想要的任何内容。对于开发环境来说,这很棒。但是,在生产环境中,这可能是致命的。因此,您需要在www.apachefriends.org/en/xampp-windows.htmlA matter of security部分的帮助下保护您的 XAMPP 安装。

注意

为了在开发环境中显示错误,更新加载的php.ini文件以设置display_errors = On,并为生产环境做相反的display_errors = Off

在 Ubuntu 桌面上设置您的开发环境

Linux,Apache,MySQLPHPLAMP)是一些最常见的网络托管平台。因此,这是一个完美的环境,让您构建和测试您的网站代码。在本节中,我们将在我们的 Ubuntu 12.04 桌面上轻松设置、配置和运行我们自己的 LAMP。

行动时间-在 Ubuntu 桌面上安装 LAMP

按照这里列出的步骤安装 Ubuntu 中的 LAMP 软件包:

  1. 与单独安装每个项目不同,我们将在 Ubuntu 中使用一个包安装 LAMP 服务器,这相当简单,只需一个终端命令:
**sudo apt-get install lamp-server^** 

apt-get命令是一个强大的命令行工具,用于处理 Ubuntu 的高级软件包工具(APT),执行诸如安装新软件包、升级现有软件包、更新软件包列表索引,甚至升级整个 Ubuntu 系统等功能。

注意

sudo用于调用当前用户以获得超级用户的权限,插入符号(^)放在包名后面,表示正在一起执行任务。

  1. 这个命令将立即开始安装 LAMP 软件包,同时安装最新的 PHP5、Apache 2、MySQL 和 PHP5-MySQL 软件。默认情况下,Apache 2 和 MySQL 安装为服务,您的文档根目录将位于/var/www/index.html文件将位于/var/www/

  2. Apache 和 MySQL 都应该在运行。但是,如果需要,您可以使用service start命令启动 Apache,如下所示:

**sudo service apache2 start** 

您可以使用以下命令停止 Apache:

**sudo service apache2 stop** 

  1. 现在,让我们检查 LAMP 安装。将浏览器指向http://localhost/,您将看到默认的 Apache 2 登录页面,如下图所示:在 Ubuntu 桌面上安装 LAMP 的时间

这意味着您的 Apache 2 Web 服务器正在运行。您仍然可以按以下方式检查这些服务状态:

**sudo service apache2 status** 

上一个命令将给您以下输出:

**Apache is running. Process #** 

  1. 同样,要检查 MySQL 状态,只需运行以下命令:
**sudo service mysql status** 

将显示以下输出:

**mysql start/running. Process #** 

  1. 要检查 PHP 安装,只需在/var/www/中创建一个名为test.php的文件,其中包含以下行:
<?php phpinfo(); ?>

注意

您可以使用touch test.php命令从终端创建一个新文件,也可以使用 gedit 应用程序,然后编辑文件并保存。

  1. 现在,将浏览器指向http://localhost/test.php,您将看到已安装的 PHP 和组件的配置详细信息:在 Ubuntu 桌面上安装 LAMP 的时间

步骤 812是可选的,因为我们在这些步骤中安装了phpMyAdmin

  1. 虽然我们可以使用 NetBeans 来维护我们的数据库,但我们仍然需要使用基于 Web 的界面来维护 MySQL 数据库功能。为此,我们可以使用phpMyAdmin
**sudo apt-get install phpmyadmin** 

使用此命令将安装phpMyAdmin,在安装过程中,您将收到一个蓝色窗口询问您要使用哪个服务器——apache2还是lighttpd。选择apache2,然后单击OK继续安装。请注意,在安装过程中,您可能会被要求配置phpMyAdmin以进行数据库配置、密码等。

  1. 安装完成后,使用http://localhost/phpmyadmin/在浏览器中打开,您将能够查看一个phpMyAdmin登录页面,如下图所示:在 Ubuntu 桌面上安装 LAMP 的时间

  2. 如果在http://localhost/phpmyadmin/收到404错误,则需要通过使用gedit(GNOME 桌面的官方文本编辑器)修改/etc/apache2/apache2.conf来手动设置phpMyAdmin在 Apache 下的配置:

**sudo gedit /etc/apache2/apache2.conf** 

  1. gedit将以图形模式打开文件,并将以下行添加到apache2.conf的底部:
**Include /etc/phpmyadmin/apache.conf** 

  1. 现在,重新启动 Apache 服务器以使更改生效:
**sudo service apache2 restart** 

刷新您的浏览器,您现在将看到与上一个屏幕截图中相同的phpMyAdmin登录界面。

刚刚发生了什么?

Ubuntu 桌面上的 PHP 开发环境已成功设置。使用单个终端命令安装 LAMP 服务器真的很简单。我们学会了如何停止或重新启动 Apache 和 MySQL 等服务,并检查它们的状态。

此外,我们还可选择安装phpMyAdmin以通过 Web 界面管理数据库。请注意,phpMyAdmin并不适用于生产环境,而只适用于开发环境中的开发人员。

尝试一下 — 显示错误

由于我们已经为开发环境进行了配置,PHP 错误消息对我们解决问题将非常有帮助。在您的 LAMP 安装中,默认情况下 PHP 错误消息是关闭的。您可以通过修改包含display_errors的行来启用从加载的php.ini文件(参见phpinfo)显示错误消息。请注意,对php.ini的任何更改都需要重新启动 Apache 2 服务器。

在 Mac OS X 中设置您的开发环境

由于我们对在一起设置 AMP 感兴趣,MAMP 包可能是 Mac OS X 的一个不错选择。

在 Mac OS X 中安装 MAMP 的时间

按照以下步骤在您的 Mac OS X 上下载和安装 MAMP:

  1. www.mamp.info/en/下载最新的 MAMP 版本;点击MAMP 包下载器下的立即下载按钮来下载 MAMP:在 Mac OS X 中安装 MAMP 的时间

  2. 如前面的截图所示,在屏幕左侧选择 MAMP 下载。

  3. 解压下载的文件,并运行.dmg文件。接受“使用条款”后,您将看到一个类似以下的屏幕:在 Mac OS X 中安装 MAMP 的时间

  4. MAMP文件夹拖入Applications文件夹;MAMP 在您的 Mac OS X 上现在安装完成。

  5. 现在,让我们检查我们的 MAMP 安装。将浏览器指向http://localhost/MAMP/,您将看到默认的 MAMP 登陆页面,如下图所示:在 Mac OS X 中安装 MAMP 的时间

您可以在 MAMP 的开始页面的顶部栏上检查phpinfo, phpMyAdmin等。

  1. /Applications/MAMP/,双击MAMP.app来运行 Apache、MySQL、PHP 和 MAMP 控制面板。MAMP控制面板显示服务器状态,并允许您启动/停止服务器,如下图所示:在 Mac OS X 中安装 MAMP 的时间

  2. 从 MAMP 控制面板中的首选项... | PHP 选项卡切换到 PHP 版本 5.3(需要重新启动服务器)。

刚刚发生了什么?

我们已经成功地为我们的 Mac OS X 开发环境下载并安装了 MAMP。此外,我们已经测试了安装,并发现它完美地运行起来。MAMP 登陆页面带有选项卡式界面,包括phpinfo, phpMyAdmin, SQLiteManager等。您的 MAMP 包中已安装了以下程序和库:

  • Apache 2.0.63

  • MySQL 5.1.44

  • PHP 5.2.13 和 5.3.2

  • APC 3.1.3

  • eAccelerator 0.9.6

  • XCache 1.2.2 和 1.3.0

  • phpMyAdmin 3.2.5

  • Zend Optimizer 3.3.9

  • SQLiteManager 1.2.4

  • Freetype 2.3.9

  • t1lib 5.1.2

  • curl 7.20.0

  • jpeg 8

  • libpng-1.2.42

  • gd 2.0.34

  • libxml 2.7.6

  • libxslt 1.1.26

  • gettext 0.17

  • libidn 1.15

  • iconv 1.13

  • mcrypt 2.6.8

  • YAZ 4.0.1 和 PHP/YAZ 1.0.14

到目前为止,我们已经安装了最新的 NetBeans IDE,并使用最新的 Apache、MySQL 和 PHP 设置了我们的平台特定的开发环境。现在,我们最近完成的开发环境已经足够精心地开始构建项目。我们将在 IDE 的帮助下进行 PHP 项目的创建和维护。开发人员和 IDE 之间的这种协同作用可以真正提高生产力。

尝试一下 — 保护您的 MAMP 安装

正如我们所了解的,MAMP 并不适用于生产环境,而只适用于开发环境中的开发人员。通过 MAMP 论坛forum.mamp.info/viewtopic.php?t=365来保护您的 MAMP 安装。您可能需要设置 MySQL 密码、phpMyAdmin密码、保护 MAMP 登陆页面等。

创建一个 NetBeans PHP 项目

NetBeans 将用于开发应用程序的所有必要文件分组到一个项目中。项目文件包括您的原始代码以及您的项目可能依赖的任何导入的代码。NetBeans 项目管理使得在大型项目上工作变得更加容易,因为它可以立即显示程序的一个部分的变化将如何影响程序的其余部分。因此,IDE 提供了一些功能来促进项目的发展。

执行操作的时间-创建 NetBeans PHP 项目

最后,我们将创建一个 NetBeans PHP 项目,以便组织 PHP 内容,并对创建的项目有更多的控制。

要创建一个 NetBeans PHP 项目,请按照以下步骤进行:

  1. 为了开始项目创建,从 IDE 菜单栏中转到文件|新建项目

  2. 从这个窗口中,选择PHP作为项目类别,并选择默认选择的PHP 应用程序,这意味着我们将从头开始创建一个 PHP 项目。如果您已经有源 PHP 代码,那么选择具有现有源的 PHP 应用程序,您将需要浏览到您的现有源以设置您的源目录。

  3. 点击下一步按钮,它会带您到下面的屏幕,如下所示:执行操作的时间-创建 NetBeans PHP 项目

  4. 定义项目名称和 IDE,它会自动为给定名称建议源文件夹。但是,我们可以通过浏览文件夹路径来明确定义源文件夹路径。还要选择适当的PHP 版本,根据这个版本,项目将在 IDE 中表现,并选择默认编码。我们将默认选择最新的 PHP 版本行为和UTF-8作为默认编码

  5. 请记住,项目元数据只在本地阶段使用;可选地,您可以将 NetBeans 创建的项目元数据放入一个单独的目录中。要做到这一点,勾选将 NetBeans 元数据放入单独的目录中

  6. 因此,我们将项目命名为chapter1,并点击下一步,如下图所示:执行操作的时间-创建 NetBeans PHP 项目

  7. 定义项目将在哪里运行(远程服务器等),以及项目 URL。默认的项目 URL 是http://localhost/与尾部项目名称连接而成的形式。点击下一步,进入下一个截图:执行操作的时间-创建 NetBeans PHP 项目

如果需要,可以使用可选的复选框将 PHP 框架支持添加到您的项目中。IDE 支持两种流行的 PHP 框架——Symfony 和 Zend 框架。

  1. 最后,点击完成以完成项目向导,并且 NetBeans 将打开 PHP 项目。它应该看起来类似于以下截图:执行操作的时间-创建 NetBeans PHP 项目

一个索引页面index.php会自动创建在项目源中。

  1. 为了测试项目,我们将在 PHP 标签之间放置一些代码。因此,让我们放置一个简单的echo,如下所示:
<?php
echo "Hello World";
?>

  1. 保存文件,并将浏览器指向项目 URL,http://localhost/chapter1/。页面应该会输出类似于以下截图的内容:执行操作的时间-创建 NetBeans PHP 项目

因此,我们可以看到我们的 PHP 项目表现良好。

  1. 将更多的文件和类添加到我们的项目中,您可以右键单击项目名称,这将显示项目菜单:执行操作的时间-创建 NetBeans PHP 项目

通过这个项目上下文菜单,我们可以根据需要从新建中添加更多的 PHP 文件、类等。正如我们在这个截图中所看到的,一个项目也可以被修改。例如,您可以重命名、复制或修改项目配置,如果您愿意的话。

刚刚发生了什么?

在 NetBeans 中创建一个新的 PHP 项目非常顺利。在创建过程中,我们发现可以通过逐步项目创建向导轻松设置项目的多个配置。此外,我们还看到在创建新项目后,IDE 支持从项目上下文菜单中添加新的 PHP 文件、类、接口等。请注意,您可以从项目上下文菜单中管理项目操作,如运行和版本控制任务。要更改现有项目的设置,请将光标放在项目节点上,并从弹出菜单中选择属性

因此,从现在开始,这将是我们在 NetBeans 中创建新的 PHP 项目的步骤。

尝试英雄——从现有源代码创建项目

如果您已经有一些 PHP 项目的基础源代码,您可以将这些源文件引入 NetBeans,以便更好地控制项目。通过选择文件|新建项目从现有源代码创建新项目。

总结

我们已经练习了为特定操作系统设置开发环境。我们已经成功安装和运行了 IDE、Web 服务器、数据库服务器、脚本语言和数据库管理 Web 界面。

具体来说,我们涵盖了:

  • NetBeans IDE 安装

  • 各种平台上的 PHP 开发环境设置

  • 在 NetBeans IDE 中创建 PHP 项目

我们还讨论了使用这样的 IDE 的重要性,以及我们如何从中受益。现在我们已经准备好开始 PHP 开发所需的所有工具包,下一章我们将学习有关编辑器功能,以便进行快速和高效的 PHP 开发。

第二章:通过 PHP 编辑器提高编码生产力

在本章中,我们将讨论如何通过编辑器提高我们的编码生产力,以及如何充分利用 NetBeans 编辑器。

我们将重点关注以下内容:

  • 基本 IDE 功能

  • PHP 编辑器

  • 重命名重构和即时重命名

  • 代码完成

  • 代码生成器

那么让我们开始吧...

熟悉基本 IDE 功能

作为 IDE,NetBeans 支持各种功能,以提高您的日常 PHP 开发。它包括编辑器、调试器、分析器、版本控制和其他协作功能。基本 IDE 提供以下有趣的功能:

  • 快速搜索:NetBeans 为您提供了 IDE 中的搜索功能,例如在文件、类型、符号、菜单操作、选项、帮助和打开项目中进行搜索,按Ctrl+I聚焦在搜索框上。在搜索结果列表中,您将找到输入的搜索词在结果项中的高亮显示:熟悉基本 IDE 功能

  • 插件管理器:工具 | 插件,您将有插件管理器,可以添加、删除或更新功能。此外,许多有趣的第三方插件都可以从插件门户网站获得。请注意,您可以从已安装的插件列表中停用或卸载插件(如 CVS、Mercurial 等),这些插件目前并不是您关心的问题,但您可以这样做以释放一些资源,并在需要时重新添加这些插件:熟悉基本 IDE 功能

  • 项目管理器:窗口 | 项目或按Ctrl+1,您可以固定 IDE 的项目管理器窗格,以对每个可用项目执行操作。项目操作,如运行、调试、测试、生成文档、检查本地历史记录、设置配置和设置项目属性都可以在项目管理器窗口中完成:熟悉基本 IDE 功能

  • 文件管理器:窗口 | 文件或按Ctrl+2,您可以固定 IDE 的文件管理器窗格,以浏览项目文件或对 IDE 可用的文件进行一般文件操作:熟悉基本 IDE 功能

  • 服务管理器:窗口 | 服务或按Ctrl+5,您可以固定 IDE 的服务管理器窗格,以使用预注册的软件即服务SaaS)Web 服务组件。从服务选项卡中拖动项目,将项目放入资源类中,即可生成访问服务所需的代码。此外,服务窗格还可以让您访问所有连接的数据库:熟悉基本 IDE 功能

  • 任务管理器:窗口 | 任务或按Ctrl+6,您可以固定 IDE 的任务管理器或操作项窗格。NetBeans IDE 会自动扫描您的代码,并列出包含诸如TODOFIXME等单词的注释行,以及包含编译错误、快速修复和样式警告的行。连接到 bug 数据库—Bugzilla,并在 IDE 中列出项目的问题报告。请注意,双击任务将直接带您到声明该任务的位置:熟悉基本 IDE 功能

  • 导航:导航菜单,IDE 提供了对文件、类型、符号、行、书签等的导航。这些功能用于快速跳转到项目中或项目外所需的位置:熟悉基本 IDE 功能

如前一截屏所示,一旦我们输入文件名,IDE 会显示匹配文件框中匹配的文件名的动态列表,这样您就可以快速打开该文件:

熟悉基本 IDE 功能

提示

Alt+Shift+O打开转到文件,按Ctrl+O打开转到类型,按Ctrl+B打开转到声明,按Ctrl+G打开转到行,等等。

  • 模板和示例应用程序:您可以在 IDE 中使用给定的示例应用程序开始类似的新项目。要做到这一点,按Ctrl+Shift+N开始一个新项目,并从项目类别中选择Samples | PHP。此外,您可以使用模板,如 PHP 文件的模板和Tools | Templates中的网页模板:熟悉基本 IDE 功能

  • 可定制的工作区和窗口:整个 IDE 工作区是完全可定制的,因此您可以将工具栏和窗格拖动、滑动、调整大小并放置到所需的位置。此外,您可以在工作区内停靠或取消停靠窗格,使其完全适合访问和使用:熟悉基本 IDE 功能

提示

您可以轻松管理工作区中的窗口;双击编辑器选项卡以展开它。Ctrl+Tab显示已打开文件的列表,再次按下将导致在编辑器选项卡之间切换。Ctrl+Pageup/Down在已打开的文件之间切换。按Ctrl+W关闭当前文件窗口。

  • 多个监视器:您可以取消停靠任何编辑器选项卡并将其拖出 IDE,使其可以像独立窗口一样运行,并且可以轻松地将其移动到第二个屏幕上。此外,您可以反向操作将其重新停靠在以前的屏幕上。请注意,第二个屏幕中的所有快捷键仍将保持不变;例如,拖出Files选项卡,然后单击 IDE 中的其他任何位置,然后按CTRL+2重新聚焦文件窗口。熟悉基本 IDE 功能

  • 本地历史记录:本地历史记录类似于经典的版本控制系统,它存储文件的多个版本。但是,存储仅限于您的 NetBeans 安装。本地历史记录使您能够检查文件和文件夹中的内容,让您diff它们,最重要的是,让您将源代码回滚到以前的状态,或者恢复已删除的文件或文件夹。

  • 拼写检查器:检查编辑器中的文本拼写。

注意

请参阅 NetBeans IDE 键盘快捷键附录。

小测验——熟悉基本 IDE 功能

  1. 哪个不是 IDE 功能?

  2. 源代码编辑器

  3. 调试器

  4. 插件管理器

  5. 源代码优化器

  6. 在哪个菜单下可以启用或聚焦所有 IDE 窗口?

  7. 文件菜单

  8. 工具菜单

  9. 导航菜单

  10. 窗口菜单

  11. 哪个是打开转到文件窗口的正确命令?

  12. CTRL+F

  13. CTRL+SHIFT+O

  14. ALT+SHIFT+O

  15. CTRL+G

  16. 为什么使用键盘快捷键CTRL+SHIFT+N

  17. 打开一个新的模板文件

  18. 打开一个新的 PHP 文件

  19. 打开一个新的 PHP 项目

  20. 打开项目窗口

  21. 修复文件管理器窗格的键盘快捷键是什么?

  22. CTRL+1

  23. CTRL+2

  24. CTRL+3

  25. CTRL+5

探索 PHP 编辑器

在本节中,我们将学习如何充分利用 NetBeans 中的 PHP 编辑器。编辑器提供非常方便的代码编写功能,我们将通过在编辑器中测试这些重要功能来学习它们。一旦我们熟悉了以下功能,我们就能掌握编辑器。您只需要练习以下功能的命令。我们开始吧:

  • 语法高亮:此编辑器使语法元素(如 PHP 关键字、变量、常量、HTML 标记和输入表单属性)高亮显示。在编辑器中,当前行用浅蓝色背景标记,发生错误的行用红色下划线显示,如下图所示:探索 PHP 编辑器

提示

双击选择语法元素。按Ctrl+F进行语法搜索以突出显示语法元素的所有出现。

  • 转到声明: 转到声明功能可立即跳转到变量或方法的声明行,从其出现位置:探索 PHP 编辑器

提示

为了使用此功能,请将光标放在所需的变量或方法上,然后按Ctrl+B,或单击出现在屏幕右侧的上下文菜单,选择导航|转到声明,将光标放在声明的起始行。按Ctrl+单击也会将您引导到声明处,并突出显示所有出现的情况。

  • 代码导航器: 代码导航器窗格动态列出文件中的 PHP 结构,按层次顺序列出 HTML 标记;简单地列出文件中的命名空间、函数、方法、类、变量、类属性、HTML 标记等。双击列表中的任何项目以转到该声明:探索 PHP 编辑器

提示

窗口|导航|导航器或按Ctrl+7,您可以专注于代码导航器窗格。列出的项目根据相关项目属性进行图标化。

  • 代码折叠:编辑器为您提供了用于类、方法、注释块、HTML 标记、CSS 样式类等的代码块折叠/展开功能。您可以使用这些功能在编辑器左边缘旁边折叠/展开大型代码块,如下面的屏幕截图所示:探索 PHP 编辑器

提示

单击屏幕左侧的“”或“+”按钮,折叠和展开代码块。

  • 智能缩进:编辑器在键入并按下新行时会在代码之前提供自动缩进:探索 PHP 编辑器

输入iffor语句行,然后按Enter键以查看下一行缩进。

  • 格式化:为了使代码更易于理解,编辑器为您提供了格式化功能,该功能维护适当的语句层次结构,并在代码文件中应用换行、空格、缩进等:探索 PHP 编辑器

提示

选择要格式化的代码块。右键单击上下文菜单,选择格式,或按Alt+Shift+F。要格式化整个代码文件,请选择源|格式,或按Ctrl+AAlt+Shift+F

  • 括号补全:成对字符项的连续第二个字符(例如单引号(''),双引号(""),括号(()),方括号([]))会自动添加第一个字符类型,并且成对的连续字符会随着第一个字符的删除而删除。当键入第一个字符并按Enter时,大括号{}的一对会被补全。当光标指向匹配对中的任何字符时,大括号、花括号和方括号会以黄色突出显示,如下所示:探索 PHP 编辑器

  • 参数提示:编辑器在您开始键入函数名称时会提示您选择 PHP 默认或自定义函数的形式参数。带有函数名称和参数的自动建议列表将显示在光标底部,所选函数的描述将显示在光标顶部:探索 PHP 编辑器

在上一个自动建议列表中,您可以使用“上/下”箭头键进行遍历。您可以按Enter键插入带有占位符的所需函数名称,以在括号内插入参数:

探索 PHP 编辑器

  • 在注释中定义变量类型:您可以在注释中定义变量及其类型,格式为/* @var $variable type */。如果注释编写正确,则var标签将以粗体字显示:探索 PHP 编辑器

在前面的截图中,您可以看到变量名称和类型的注释如何支配自动建议。在前面的示例中,您可以看到方法名称是从相应的类名称中提取的,该类名称在注释中作为变量类型提到。

键入vdoc,然后按Tab键使用变量文档的代码模板。将生成一个定义变量的注释。一旦选择了变量名称,更改它,然后再次按Tab键,以更改类型:

探索 PHP 编辑器

代码模板会自动生成变量名称和类型,以适应注释位置;也就是说,如果您在使用模板之前使用变量,则它将建议该变量名称和类型。

  • 错误消息:编辑器在输入时解析您的 PHP 代码,用红色下划线标记语法错误,在左边缘放置红色错误标志,在右边缘放置红色错误滚动位置。探索 PHP 编辑器

提示

您可以通过将鼠标悬停在错误行上或单击屏幕左侧的红色错误标志来查看工具提示中的错误详细信息。按Alt+Enter显示错误提示。

请参阅 NetBeans IDE 键盘快捷键附录。

弹出测验-探索 PHP 编辑器

  1. 哪个功能不是编辑器功能?

  2. 源代码格式化

  3. 代码自动完成

  4. 语法高亮

  5. 调试

  6. 如何格式化代码块?

  7. 通过右键单击上下文菜单选择代码块,然后选择格式

  8. 选择代码块,然后按ALT+SHIFT+F

  9. 选择代码块,然后选择源代码|格式

  10. 以上所有

  11. 什么是语法搜索键盘命令?

  12. CTRL+W

  13. CTRL+F

  14. CTRL+ALT+F

  15. CTRL+SHIFT+S

  16. 如何转到方法的声明?

  17. 将光标放在方法上,然后按CTRL+B

  18. 右键单击方法名称,然后从上下文菜单中选择导航|转到声明

  19. 在方法名称上按CTRL+单击

  20. 以上所有

更多探索编辑器

我们已经了解了编辑器并练习了提示中给出的快捷方式。在接下来的两节中,我们将学习如何使用重命名重构、代码完成和编辑器的代码生成功能,这些功能对于提高编码非常有帮助。

在下一节中,我们将讨论和练习以下重要的编辑器功能:

  • 重命名重构和即时重命名

  • 代码完成

  • 代码生成器

使用重命名重构和即时重命名

您可以在项目中的所有文件中重命名一个元素,例如类名。此功能使您可以预览所需重命名的每个位置的可能更改,并且您可以排除个别出现不被重命名。

即时重命名允许您在文件中重命名元素。对于即时重命名,将光标放在要重命名的名称上,然后按Ctrl+R;如果该变量适用于即时重命名,则该变量的所有实例将被突出显示如下:

使用重命名重构和即时重命名

即使只有一个实例的名称发生变化,也会同时重命名文件中的所有其他实例:

使用重命名重构和即时重命名

要使用重命名重构,选择要重命名的元素,然后右键单击,选择重构|重命名。将打开一个对话框,供您重命名元素,如下一张截图所示:

使用重命名重构和即时重命名

在此截图中,为元素提供一个新名称,然后单击预览。重构窗口将打开,并列出项目中元素的所有实例:

使用重命名重构和即时重命名

从这个截图中,您可以排除实例并对所选实例应用进行重构

注意

请参阅 NetBeans IDE 键盘快捷键附录。

小测验——使用重命名重构和即时重命名

  1. 如何在整个项目中重构变量名?

  2. 选择变量,然后右键单击并选择重构 | 重命名

  3. 将光标放在变量名上,然后按下CTRL+SHIFT+R

  4. 选择变量,然后选择源 | 重命名

  5. 以上都不是

  6. 哪个是变量的即时重命名快捷键?

  7. SHIFT+ALT+R

  8. CTRL+R

  9. CTRL+ALT+R

  10. CTRL+SPACE+R

使用代码完成

代码完成功能使我们能够以最少的按键或仅使用键盘命令完成所需的语法、方法或代码。

提示

您可以从工具 | 选项 | 编辑器 | 代码完成启用/禁用自动代码完成。默认情况下,您将为所有语言都有复选框。从语言下拉列表中选择 PHP,以获得更多针对 PHP 的代码完成选项。

使用代码完成

以下是编辑器提供的代码完成功能:

  • 片段:这会自动生成各种元素的代码片段。使用代码完成

选择工具 | 调色板 | PHP代码片段,然后调色板管理器将打开。从调色板内容中拖动相关项目图标,并将其放置到代码中的相关位置。将出现一个对话框,用于指定相应代码项的参数。填写参数,然后在该位置生成代码。

  • 上下文敏感的建议:编辑器为任意数量的起始符号提供上下文敏感的建议:

  • 一个 PHP 关键字,包括if、else、elseif、while、switch、function等。

  • 一个 PHP 内置函数。

  • 预定义或用户定义的变量。

输入关键字或函数名的起始字符,然后按下Ctrl+Space。下拉列表将显示该上下文的所有适用建议。每个建议都附有描述和参数提示:

使用代码完成

要生成适用于当前上下文的 PHP 关键字列表,请按下Ctrl+Space,而不输入任何内容:

使用代码完成

要获取有关变量的提示,请键入美元符号($)。将显示当前可用的本地和全局变量列表:

使用代码完成

  • 代码模板和缩写:通过使用定义的缩写来获取扩展的代码模板,例如cls表示类模板,这是最有趣的代码完成功能。要使用此功能,请键入缩写并按下Tab:使用代码完成

您可以看到缩写被替换为相应的 PHP 关键字,并且编辑器提供了该关键字的代码模板,如下截图所示:

使用代码完成

查看代码模板列表及其相关缩写,选择工具 | 选项 | 编辑器 | 代码模板。您可以根据以下截图中显示的方式添加/删除或编辑您的 PHP 代码模板:

使用代码完成

请注意,编码方法会随着时间而改变。因此,建议每隔几个月查看您的模板,并更新以符合任何新变化。

  • 构造函数中的代码完成:new关键字之后,将显示代码完成以及当前项目中所有类的构造函数和参数列表:使用代码完成

  • SQL 代码完成:当字符串以 SQL 关键字(如selectinsert)开头时,按下该关键字后的Ctrl+Space将在编辑器内启用 SQL 代码完成功能。您可以在第一步中选择数据库连接,如下面的屏幕截图所示:使用代码完成

  • 被选中的所有与 IDE 注册的数据库连接将显示如下:使用代码完成

  • 选择数据库连接后,SQL 代码完成功能将提供与该连接关联的所有表:使用代码完成

此外,还将从该表中显示列(如果有)。SQL 代码完成还适用于表别名。

  • PHP 5.3 命名空间:代码完成支持 PHP 5.3 命名空间。

  • 重写和实现的方法:类成员之间的代码完成提供了重写或实现方法的选项。

提示

在希望使用代码完成的地方按下Ctrl+Space

有关 NetBeans IDE 键盘快捷键,请参见附录。

小测验——使用代码完成

  1. 为什么要使用代码完成功能?

  2. 重构变量

  3. 编写新的 PHP 类

  4. 完成所需的语法、方法或代码的快捷键

  5. 完成 PHP 项目

  6. 代码完成功能不支持哪种 PHP 语言特性?

  7. 命名空间

  8. 类声明

  9. 重写方法

  10. 以上都不是

  11. 启用上下文敏感建议的快捷键是什么?

  12. Ctrl+Shift+Space

  13. Ctrl+Space

  14. Ctrl+S

  15. Ctrl+Alt+Space

使用代码生成器

编辑器提供了上下文敏感的代码生成器,以便生成数据库连接、构造函数、getter 或 setter 等。特定的代码生成器将出现在光标位置的上下文中。例如,在类内部,它将显示用于生成构造函数、getter、setter 等的选项。

例如,按Alt+Insert在类内部打开所有可能的代码生成器,如下面的屏幕截图所示:

使用代码生成器

我们将讨论以下代码生成器:

  • 构造函数:在 PHP 类内(但不在任何方法体内),您可以按Alt+Insert打开构造函数生成器。选择生成构造函数,将出现类似于以下屏幕截图的对话框:使用代码生成器

该窗口列出了可以在构造函数中初始化的可用字段。字段名称用作构造函数的参数。您可以决定不选择任何字段;在这种情况下将生成一个空的构造函数。

  • Getter 和 setter:通过在 PHP 类内部按代码生成器命令,您可以选择Getters...,Setters...Getters and Setters来查看可能的函数。如果您已经有 setter,那么您只会看到 getter 方法:使用代码生成器

选择getter/setter后,出现了上一个屏幕截图;您可以指定要为哪个属性生成gettersetter方法,并灵活选择方法的命名约定。

  • 重写和实现的方法:当类内有多个方法时,您可以打开重写和实现方法的代码生成器。对话框将打开,显示您可以插入的方法,并指示它们是重写还是实现:使用代码生成器

注意

有关 NetBeans IDE 键盘快捷键,请参见附录

小测验——使用代码生成器

  1. 在 PHP 类内部打开代码生成器的快捷键是什么?

  2. Alt+Insert

  3. Shift+Alt+Insert

  4. Ctrl+ Alt+Insert

  5. Ctrl+Insert

  6. 什么不能使用代码生成器生成?

  7. 构造函数

  8. Getter 和 setter

  9. 重写方法

  10. 字符串

摘要

在本章中,我们发现了 PHP 编辑器的有用功能,并练习了在编写代码时应用的技巧。熟悉我们所看到的编辑器快捷键将帮助您更快、更准确地编写代码。

我们特别关注了:

  • PHP 编辑器的功能和快捷键

  • 重命名重构和即时重命名

  • 代码自动完成的使用

  • 代码生成器的使用

因此,到目前为止,我们的 PHP 开发环境已经准备就绪。我们已经安装了 IDE,并学会了在需要时如何使用这些酷炫的编辑器功能。在下一章中,我们将直接深入到真实的 PHP 编码中,并将开发一个 PHP 项目,以掌握使用 NetBeans 进行 Web 应用程序开发。

第三章:使用 NetBeans 构建类似 Facebook 的状态发布者

在本章中,我们将使用 NetBeans IDE 构建一个很酷的 PHP 项目。我们的计划很简单明了。

我们将通过以下步骤创建一个类似 Facebook 的状态发布者:

  • 规划项目

  • 创建状态流显示列表

  • 使用 PHP-AJAX 创建状态发布者

大多数社交网络平台,如 Facebook、Twitter 和 Google Plus,都为用户的朋友提供了状态发布功能,并允许用户查看他们朋友的状态发布。因此,我们将研究这是如何工作的,以及我们如何构建类似的功能。让我们选择实现一个类似最流行的社交网络平台 Facebook 的有趣功能。

此外,我们还将讨论 MySQL 数据库连接和 PHP 类创建以及我们的工作流程。所以,让我们开始吧...

规划项目

项目的适当规划对于智能开发和使用样机、图表和流程图至关重要,以便项目可以轻松地可视化需求。此外,它描述了你将要做什么,以及如何做。

我们将创建一个简单的类似 Facebook 的(www.facebook.com)状态发布者,并在其下方添加一个列表,以显示朋友的状态发布,以及您自己的状态。在这个单一的前端 PHP 应用程序中,我们将使用 JavaScript 库jQueryjquery.com/)通过AJAXapi.jquery.com/jQuery.ajax/)发布状态。发布的状态将在不重新加载页面的情况下显示在状态堆栈顶部。

在规划我们的项目时,我们将提前查看 Web 应用程序的最终外观,并尝试了解如何将特定功能放置到工作中。为了讨论工作流程的各个要点,我们还将拥有工作流程图。

让我们看看最终阶段我们将要构建的内容。

规划项目

这个状态发布者将以以下方式运行:

规划项目

根据这个图,用户在状态框中输入并点击“分享”按钮,触发Status.js中绑定的 JavaScript 方法,通过 AJAX 将状态发布到服务器。服务器端脚本StatusPoster.php接收状态以保存到数据库,并在完成任务后响应成功消息。前端代码接收成功通知,并在状态发布的显示堆栈顶部添加状态。

现在,我们将根据以下两部分拆分项目,并相应地开发它们:

  • 状态流显示列表

  • 使用 PHP-AJAX 创建状态发布者

我们已经收集了关于项目工作流程的概念。因此,根据我们的规划,我们可以立即开始实施项目。从这一点开始,我们将直接使用 NetBeans 开始 PHP 应用程序开发,创建一个新的 PHP 项目,并熟练使用 IDE。我们知道该做什么,几分钟内我们将学会如何做。

理解 JSON-JavaScript 对象表示法

JavaScript 对象表示法(JSON)是一种轻量级的数据交换格式,对人类来说易于阅读和编写。它是机器解析和生成的简单格式,基于 JavaScript 编程语言的一个子集。JSON 是一种完全与语言无关的文本格式。

JSON 建立在两种结构上:

  • 一组名称/值对:在各种语言中,这被实现为对象、记录、结构、字典、哈希表、键控列表或关联数组。

  • 一个有序的值列表:在大多数语言中,这被实现为数组、向量、列表或序列。

例如:

{ "firstName":"John" , "lastName":"Doe" }

引入 jQuery-权威的 JavaScript 库

jQuery 是一个快速而简洁的 JavaScript 库,简化了 DOM(文档对象模型)遍历、事件处理、动画和快速网页开发的 AJAX 交互。jQuery 旨在改变您编写 JavaScript 的方式-jquery.com/

我们应该使用 jQuery 的一些原因如下:

  • 免费和开源软件

  • 轻量级足迹

  • 符合 CSS3 标准

  • 跨浏览器

  • 最小代码

  • 现成的插件

简而言之,jQuery 使您能够生成强大和动态的用户界面。

注意

借助各种 jQuery 插件,包括图像滑块、内容滑块、弹出框、选项卡内容等,开发人员的工作可能会减少,因为他们所要做的就是调整或定制 jQuery 插件的小部分,使其与他们的需求相匹配。

理解 AJAX-异步 JavaScript 和 XML

异步 JavaScript 和 XMLAJAX)是一种在客户端使用的编程技术或方法,用于在后台异步地从服务器检索数据,而不干扰现有页面的显示和行为。通常使用XMLHttpRequest对象检索数据。尽管有这个名字,实际上并不需要使用 XML,请求也不需要是异步的。

jQuery 库具有完整的 AJAX 功能。其中的函数和方法允许我们从服务器加载数据,而无需刷新浏览器页面。

介绍 jQuery.ajax()

让我们看一下示例jQuery.ajax() API。

$.ajax({
url: "my_ajax_responder.php",
type: "POST",
data: {'name': 'Tonu'}, //key value paired or can be like "call=login&name=Tonu"
success: function(xh){
//success handler or callback
},
error: function(){
//error handler
}
});

$.ajax()函数中,可以看到 AJAX 配置对象(使用 JavaScript 对象文字创建)被传递给它,这些配置可以描述如下:

  • url表示与之通信的服务器脚本的 URL

  • type表示 HTTP 请求类型;即GET/POST

  • data包含要发送到服务器的数据,可以是键值对或 URL 参数的形式

  • success保存 AJAX 成功回调或在获取数据时执行的方法

  • error保存 AJAX 错误回调

现在,让我们再举一个例子,jQuery.ajax()只是从服务器加载一个 JavaScript 文件:

$.ajax({
type: "GET",
url: "test.js",
dataType: "script"
});

在这里,dataType定义了要从服务器检索的数据类型;这种类型可以是 XML、JSON、script、纯文本等。

介绍 PHP 数据对象(PDO)

PHP 数据对象PDO)扩展定义了一个轻量级和一致的接口,用于在 PHP 中访问数据库。PDO 提供了一个数据访问抽象层,这意味着无论您使用哪个数据库,您都可以使用相同的函数来发出查询和获取数据。PDO 不提供数据库抽象;它不重写 SQL 或模拟缺失的功能。如果您需要该功能,应该使用完整的抽象层。

值得一提的是 PDO 支持预处理语句,即:

  • 更安全:PDO 或底层数据库库将为您处理绑定变量的转义。如果始终使用预处理语句,您将永远不会受到 SQL 注入攻击的威胁。

  • (有时)更快:许多数据库将为预处理语句缓存查询计划,并使用符号引用预处理语句,而不是重新传输整个查询文本。如果您只准备一次语句,然后使用不同的变量重用预处理语句对象,这一点最为明显。

注意

PHP 5.3 内置了 PDO 和 PDO_MYSQL 驱动程序。更多信息请访问www.php.net/manual/en/book.pdo.php

创建 NetBeans PHP 项目

完成任务规划后,我们将处理其实际实施。

按下Ctrl+Shift+N开始新的 NetBeans PHP 项目,并按照第一章中已经讨论过的步骤创建新项目,设置开发环境。让我们将项目命名为chapter3,用于我们的教程。

当我们创建了 PHP 项目时,项目中将自动创建index.php文件。因此,可以通过将浏览器指向http://localhost/chapter3/来定位项目。

创建状态流显示列表

根据项目的第一部分,我们现在将创建状态流显示列表。为了做到这一点,我们需要一个 PHP 类和一个 MySQL 数据库,其中填充了一些代表状态帖子的虚拟数据。PHP 类StatusPoster.php将在其构造函数中包含使用 PDO 的 MySQL 数据库连接,并包含一个从数据库中提取状态条目的方法。

设置数据库服务器

为了从数据库中存储和检索状态帖子,我们连接到 MySQL 数据库服务器,创建数据库和表以插入状态条目,并获取这些条目以在状态流中显示。

执行操作-连接到 MySQL 数据库服务器

在本节中,我们将通过向 IDE 提供访问凭据来创建 MySQL 服务器连接,IDE 将在连接下显示可用数据库的列表:

  1. 首先,我们将在 IDE 内创建 MySQL 数据库服务器连接;按下Ctrl+5服务窗口置于焦点,展开数据库节点,在MySQL 数据库服务器上右键单击,并选择属性以打开MySQL 服务器属性窗口,如下面的屏幕截图所示:执行操作-连接到 MySQL 数据库服务器

在上一个屏幕截图中,IDE 已经填写了 MySQL 服务器详细信息的默认值,如主机名、端口号、用户名和您刚刚添加的密码。您可以随时更新这些详细信息。

  1. 单击管理属性选项卡,允许您输入控制 MySQL 服务器的信息。单击确定按钮以保存设置。

  2. 现在,您应该在MySQL 服务器节点下列出所有可用的数据库,如下面的屏幕截图所示:执行操作-连接到 MySQL 数据库服务器

刚刚发生了什么?

我们已成功连接到 MySQL 服务器,并列出了为提供的数据库用户提供的所有可用数据库。实际上,我们使得可以从 IDE 直接快速操作任何类型的数据库查询。现在,我们将在其中创建一个新的数据库和表。

注意

有关 NetBeans IDE 键盘快捷键,请参见附录

创建数据库和表

为每个项目使用单独的数据库是一种常见做法。因此,我们将为我们的项目使用一个新的数据库,并创建一个表来存储条目。IDE 提供了出色的 GUI 工具,用于数据库管理,如 SQL 编辑器、查询输出查看器和带有列列表的表查看器。

执行操作-创建 MySQL 数据库和表

MySQL 服务器节点,我们将创建一个新的数据库,并运行一个查询来创建表以及必要的列字段。

  1. 服务窗口,在MySQL 服务器节点上右键单击,并选择创建数据库...。将出现一个新的对话框,如下面的屏幕截图所示:执行操作-创建 MySQL 数据库和表

  2. 新数据库名称字段中输入名称status_poster。不要选中授予的复选框。您可以使用此复选框和下拉列表向特定用户授予权限。默认情况下,admin用户拥有所有权限。

  3. 单击 OK,使新数据库列在服务器节点下列出,并且在 Databases 节点下创建新的数据库连接节点,如下图所示:Time for action — creating MySQL database and table

根据这个屏幕截图,在 status_poster 连接节点下有三个子文件夹——Tables, Views,Procedures

  1. 现在,要在我们的数据库中创建一个新表,右键单击 Tables 文件夹,选择 Execute Command... 打开主窗口中的 SQL Editor 画布,如下所示:Time for action — creating MySQL database and table

  2. 在 SQL 编辑器中,输入以下查询来创建新的 Status 表:

CREATE TABLE `status` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`image` varchar(100) NOT NULL,
`status` varchar(500) NOT NULL,
`timestamp` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

正如你所看到的,在 status 表中,我们有 id(每个条目都会自动增加)作为主键。我们有 name 字段,可以存储长达 50 个字符的用户名称。image 字段将存储长达 100 个字符的用户缩略图像。状态字段将存储最多 500 个字符的用户状态帖子,而 timestamp 字段将跟踪状态发布的时间。数据库引擎选择了 MyISAM 以提供更快的表条目。

因此,你只需要在 NetBeans 查询编辑器中输入 MySQL 查询,并运行查询,就可以准备好你的数据库。

  1. 要执行查询,可以单击顶部任务栏上的 Run SQL 按钮(Ctrl+Shift+E),或者在 SQL 编辑器中右键单击并选择 Run Statement。IDE 然后在数据库中生成状态表,并在 Output 窗口中收到类似以下消息:Time for action — creating MySQL database and table

  2. 你还会在 status_poster 数据库连接下的 Table 子文件夹中看到你的表状态,如下图所示:Time for action — creating MySQL database and table

在这个屏幕截图中,扩展的状态表显示了创建的列,主键用红色标记。

刚才发生了什么?

IDE 显示了数据库管理功能;只需点击几下和按几下键就可以完成所有这些数据库和表的创建。可以在 IDE 中迅速运行查询,并且 SQL 命令的执行输出会显示在一个单独的窗口中。接下来,我们将向创建的表中插入一些示例条目,以在状态流列表中显示它们。我们还需要为本教程添加一些演示用户图像文件。

提示

你可以使用 Database Explorer 中的 Create Table 向导来创建表——右键单击 Tables 节点,选择 Create TableCreate Table 对话框会打开,你可以在其中为表添加具体属性的列。

注意

查看 附录 以获取 NetBeans IDE 键盘快捷键。

将示例行插入表中

右键单击 Tables 子文件夹下的 status 表,选择 Execute Command...,在 SQL 编辑器中输入以下查询,向 status 表中插入一些示例行:

INSERT INTO `status` VALUES('', 'Rintu Raxan', 'rintu.jpg', 'On a day in the year of fox', 1318064723);
INSERT INTO `status` VALUES('', 'Aminur Rahman', 'ami.jpg', 'Watching inception first time', 1318064721);
INSERT INTO `status` VALUES('', 'Salim Uddin', 'salim.jpg', 'is very busy with my new pet project smugBox', 1318064722);
INSERT INTO `status` VALUES('', 'M A Hossain Tonu', 'tonu.jpg', 'Hello this is my AJAX posted status inserted by the StatusPoster PHP class', 1318067362);

你可以看到我们有一些 MySQL INSERT 查询来存储一些测试用户的数据,比如姓名、图片、状态帖子和 Unix 时间戳,用于状态流显示列表。每个这样的 INSERT 查询都会向 status 表中插入一行。

因此,我们的表中有一些示例行。为了验证记录是否已添加到 status 表中,右键单击 status 表,选择 View Data...。在主窗口中会打开一个新的 SQL 编辑器选项卡,其中包含 select * from status 查询。执行此语句将在主窗口的下部区域生成一个表格数据查看器,如下图所示:

Inserting sample rows into the table

这个 SQL 查询相当不言自明,其中使用SELECT关键字从表中选择数据,并使用 SQL 简写-*表示应从表中选择所有列。

添加示例用户图像文件

在本教程中,我们已经向status表中插入了一些示例行;在image列中,我们有一些用户图像文件名,实际上我们将它们存储在项目的images目录下的user文件夹中。这些示例用户图像可以在本章的项目源代码中找到。从 Packt Publishing 网站下载完整的项目源代码,并复制示例用户图像。

要在project文件夹内创建一个子文件夹,在chapter3项目节点上右键单击,然后选择新建|文件夹...;在新建文件夹对话框中输入文件夹名称images,然后单击完成以创建文件夹。现在以相同的方式在images目录下创建另一个名为"user"的文件夹,并将复制的示例用户图像文件放在那里。

创建 StatusPoster PHP 类

StatusPoster PHP 类的目的是查询数据库以获取和插入状态条目。该类的一个方法将用于将状态条目插入到数据库表中,另一个方法将用于执行从表中获取条目的操作。简而言之,该类将作为数据库代理,并可用于必要的数据库操作。

操作时间-创建类,添加构造函数和创建方法

我们将使用 NetBeans 代码模板创建StatusPoster.php文件和类骨架,并在类内创建方法时,我们也将使用function模板。我们将在类构造函数中使用 PDO 创建 MySQL 数据库连接,以便在实例化对象和getStatusPosts()方法时创建数据库连接以从表中获取状态帖子。

  1. 项目窗格中,右键单击项目名称chapter3,选择新建|PHP 文件...,并将文件命名为StatusPoster,如下截图所示:操作时间-创建类,添加构造函数和创建方法

  2. 单击完成,将文件添加到我们的项目中,并自动在编辑器中打开。您将看到文件中放置了 PHP 起始和结束标记。

  3. 为了创建 PHP 类的骨架,我们将使用 PHP 代码模板。我们输入cls并按下Tab键,以获得包含构造函数的类骨架,如下所示:操作时间-创建类,添加构造函数和创建方法

  4. 在上述截图中,classname已经被选中。您只需输入StatusPoster作为classname的值,并按下Tab 键选择构造函数名称,如下截图所示:操作时间-创建类,添加构造函数和创建方法

构造函数名称保持不变,因为它是默认的 PHP 5 构造函数命名约定。

  1. 现在,添加一些类常量和属性来保存数据库凭据,如下面的代码片段所示:
class StatusPoster {
private $db = NULL;
const DB_SERVER = "localhost";
const DB_USER = "root";
const DB_PASSWORD = "root";
const DB_NAME = "status_poster";
public function __construct() {
}
}

您可以看到添加的类常量,其中包含数据库信息,如数据库服务器名称、用户名、密码和数据库名称。已添加了一个private类变量$db,用于在 PDO 对象中保存数据库连接。您可以根据自己的需求修改这些常量。

注意

Private: 此属性或方法只能被类或对象使用;它不能在其他地方访问。

  1. 为了从status表中获取状态帖子,我们将在类中添加一个名为getStatusPosts的空方法。为此,输入fnc并按Tab以生成具有所选函数名称的空函数代码。这次输入所选的函数名称为getStatusPosts,并且不要放入参数$param变量。我们的类框架将类似于以下内容:
class StatusPoster {
private $db = NULL;
const DB_SERVER = "localhost";
const DB_USER = "root";
const DB_PASSWORD = "root";
const DB_NAME = "status_poster";
public function __construct() {
}
public function getStatusPosts() {
}
}

我们已经准备好了类的框架,并且将在这些类方法中添加代码。现在,我们将在构造函数中创建数据库连接代码。

  1. 要使用 PDO 连接 MySQL,将以下行输入类构造函数中,使其看起来类似于以下代码片段:
public function __construct() {
$dsn = 'mysql:dbname='.self::DB_NAME.';host='.self::DB_SERVER;
try {
$this->db = new PDO($dsn, self::DB_USER, self::DB_PASSWORD);
} catch (PDOException $e) {
throw new Exception('Connection failed: ' . $e->getMessage());
}
return $this->db;
}

public function __construct()使用 PDO 连接到 MySQL 数据库-以 PDO 实例的形式存储在类的私有变量中。

$dsn变量包含数据源名称(DSN),其中包含连接到数据库所需的信息。使用 PDO 的最大优势之一是,如果我们想要迁移到其他 SQL 解决方案,那么我们只需要调整 DSN 参数字符串。

以下行创建了一个 PDO 实例,表示与请求的数据库的连接,并在成功时返回一个 PDO 对象:

$this->db = new PDO($dsn, self::DB_USER, self::DB_PASSWORD);

请注意,如果尝试连接到请求的数据库失败,它会抛出一个PDOException异常。

  1. 为了从表中选择状态帖子,我们将在getStatusPosts方法中使用自动完成代码编写一个select查询。正如我们在上一章中讨论的那样,SQL 代码自动完成从 SQL 关键字SELECT开始,通过按下Ctrl+空格。因此,我们将按照这些步骤进行,并在这个方法中编写以下查询代码:
public function getStatusPosts() {
$statement = $this->db->prepare("SELECT name, image, status, timestamp FROM status ORDER BY timestamp DESC,id");
$statement->execute();
if ($statement->rowCount() > 0) {
return $statement->fetchAll();
}
return false;
}

通过这段代码,我们从表 status 中选择了列(name, image, statustimestamp),按时间戳降序排列。我们还按默认情况选择了 id 按升序排列。prepare()方法准备要由PDOStatement::execute()方法执行的 SQL 语句。在execute()方法之后,如果找到行,则它会获取并返回所有表条目。

  1. 现在,我们将在文件底部实例化这个类的对象,使用以下行:
$status = new StatusPoster();

刚才发生了什么?

PDO 实例是在类构造函数中创建的,并存储在$db变量中,因此其他成员方法可以访问这个类变量作为$this->db,以使用 PDO 方法,如prepare(), execute()

调用PDO::prepare()PDOStatement::execute()来执行多次的语句可以通过缓存查询计划和元信息等优化性能。

到目前为止,我们在StatusPoster.php中准备好了我们的数据库操作代码。我们将创建一个 HTML 用户界面,以显示从数据库表 status 中获取的状态列表。

注意

查看附录以获取 NetBeans IDE 的键盘快捷键。

小测验-理解 PDO

  1. 哪一个不是 PDO 的特性?

  2. 准备语句

  3. 绑定值

  4. 绑定对象

  5. 数据访问抽象

启动用户界面以显示状态列表

HTML 用户界面将显示由StatusPoster类的getStatusPosts方法检索的状态列表,并且用户将能够查看来自他的测试朋友以及他自己的帖子的状态列表。界面将使用 jQuery 和由 CSS 类样式化的状态列表。

行动时间-向文档添加 CSS 支持

我们将使用index.php作为应用程序的单页面界面,并将向文档添加 CSS 样式表支持。为了保持实践,我们将尝试将样式属性放入类中,以便它们变得可重用,并且可以在需要特定样式类的元素的类名中使用。因此,让我们首先创建 CSS 类:

  1. 在我们的项目源目录中创建一个名为styles的文件夹,用于我们的 CSS 文件。

  2. 为了创建一个级联样式表,其中包含 CSS 类,右键单击项目中的styles文件夹,从新级联样式表对话框中选择新建|级联样式表,将 CSS 文件命名为styles.css,然后点击完成。删除已打开的 CSS 文件中的所有注释和代码块。在 CSS 文件中键入以下样式类:

body {
font-family:Arial,Helvetica,sans-serif;
font-size:12px;
}
h1,input {
color:#fff;
background-color:#1A3C6C;
}
h1,input,textarea,.inputbox,.postStatus {
padding:5px;
}
input,textarea,ul li img,.inputbox {
border:1px solid #ccc;
}
ul li {
width:100%;
display:block;
border-bottom:1px solid #ccc;
padding:10px 0;
}
ul li img {
padding:2px;
}
.container {
width:60%;
float:none;
margin:auto;
}
.content {
padding-left:15px;
}
.content a {
font-weight:700;
color:#3B5998;
text-decoration:none;
}
.clearer {
clear:both;
}
.hidden {
display:none;
}
.left {
float:left;
}
.right {
float:right;
}
.localtime {
color:#999;
}
.inputbox {
height:70px;
margin:15px 0;
}
.inputbox textarea {
width:450px;
height:50px;
overflow:hidden;
}
.inputbox input {
margin-right:30px;
width:50px;
}

我们将使用container类来在文档主体内的应用程序界面容器<div>上应用样式;ul li 将表示列出的项目,这些项目是具有父ul元素的状态li项目,以及其他 HTML 元素,如 h1、imgtextarea,也使用 CSS 类进行样式设置。

  1. index.php文件的顶部添加以下 PHP 代码片段:
<?php
define('BASE_URL', 'http://localhost/chapter3/');
?>

我们已经为 Web 应用程序定义了一个 PHP 常量来定义基本 URL。基本 URL 可用于为项目资产文件(CSS 或 JS 文件)提供绝对路径。您可以在[第三章](ch03.html“第三章。使用 NetBeans 构建类似 Facebook 的状态发布者”)的位置放置您的项目目录名称。

  1. 现在,在<title>标签下的index.php文档标题中添加以下行,以包含 CSS 文件。
<link href="<?=BASE_URL?>styles/styles.css" media="screen" rel="stylesheet" type="text/css" />

有了这行,我们已将 CSS 文件嵌入到我们的 HTML 文档中。在这里,BASE_URL告诉我们styles/styles.css文件在项目目录下可用。因此,我们的界面元素将继承styles.css文件的样式。

刚刚发生了什么?

为了在各种浏览器上保持一致的界面,使用 CSS 类对各种 HTML 元素进行了样式设置,并且一些类是从分配元素将继承样式的位置编写的。

为了将 CSS 代码保持在最小行数,逗号分隔的类或元素名称已用于共享公共属性,如下所示:

h1, input, textarea, .inputbox, .postStatus{
padding:5px;
}

在这里,padding:5px样式将应用于所述元素或具有给定类的元素。因此,类之间的共同属性可以通过这种方式减少。

为了理解类的可重用性问题,让我们看一下以下内容:

.left {
float:left;
}

我们可以使用left作为多个元素的类名,这些元素需要float:left样式,例如<div class="left">,<img class="left" />等。

行动时间-添加 jQuery 支持和自定义 JS 库

我们将为文档添加 jQuery(一个 JavaScript 库;更多信息请访问jquery.com/))支持,并创建基于 jQuery 的自定义 JS 库。

对于 JS 库,我们将创建一个单独的 JavaScript 文件status.js,其中将包含界面 JS 代码以执行界面任务,例如通过 AJAX 发布状态以及一些用于显示本地日期时间的实用方法。因此,让我们创建我们的自定义 JS 库:

  1. 从谷歌内容交付网络(CDN)添加 jQuery 支持到我们的文档中,在index.php文档标题下的<link>标签之后添加以下行:
<script src= "http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js">
</script>

有了这行,我们就可以从 CDN 获取最新的 jQuery 版本。请注意,版本 1.7 表示最新可用版本,即 1.7.X,除非您已指定确切的数字,即 1.7.2 或更高版本。现在,我们的文档已启用 jQuery,并准备使用 jQuery 功能。

  1. 要创建基于 jQuery 的自定义 JS 库,请在js文件夹中添加一个新的 JavaScript 文件,并将其命名为status.js。将文件包含在文档头部,使得<head>标签看起来类似于以下代码片段:
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Status updater</title>
<link href="<?=BASE_URL?>styles/styles.css" media="screen" rel="stylesheet" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<script src="<?=BASE_URL?>js/status.js"></script>
</head>

  1. 现在,在status.js文件中创建Status JS 库骨架,如下所示:
$(document).ready(function ($)
{
var Status = {
};
});

您可以看到变量Status包含一个使用 JavaScript 对象文字(在大括号内封闭的键值对)的对象。

var obj = { a : function(){ }, b : function(){ } }

请注意,库代码被包装在 jQuery $(document).ready()函数中。

  1. 让我们在status对象内编写一些实用的 JavaScript 方法,并键入以下currentTime()方法:
currentTime: function (timestamp) {
if (typeof timestamp !== 'undefined' && timestamp !== '')
var currentTime = new Date(timestamp * 1000);
else
var currentTime = new Date();
var hours = currentTime.getHours();
var minutes = currentTime.getMinutes();
var timeStr = '';
if (minutes < 10) {
minutes = "0" + minutes
}
timeStr = ((hours > 12) ? (hours - 12) : hours) + ":" + minutes + ' ';
if (hours > 11) {
timeStr += "PM";
} else {
timeStr += "AM";
}
return timeStr;
},

currentTime()方法返回从 Unix 时间戳转换的本地时间。请记住,如果时间戳不存在,则返回当前本地时间。示例输出可能是上午 3:22 或下午 2:30。

您可以看到在var currentTime = new Date(timestamp * 1000);这一行中,Unix 时间戳已经转换为毫秒级的 JS 时间戳,并创建了一个新的 Date 对象。小时和分钟分别从currentTime.getHours()currentTime.getMinutes()方法中获取。请注意,currentTime()方法用逗号(,)分隔。

  1. currentDate()方法添加到Status对象中,如下所示:
currentDate: function (timestamp) {
var m_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
if (typeof timestamp !== 'undefined' && timestamp !== '')
var d = new Date(timestamp * 1000);
else
var d = new Date();
var curr_date = d.getDate();
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
var sup = "";
if (curr_date === 1 || curr_date === 21 || curr_date === 31)
{
sup = "st";
}
else if (curr_date === 2 || curr_date === 22)
{
sup = "nd";
}
else if (curr_date === 3 || curr_date === 23)
{
sup = "rd";
}
else
{
sup = "th";
}
return m_names[curr_month] + ' ' + curr_date + sup + ', ' + curr_year;
},

currentDate()方法返回转换后的本地日期。与步骤 4中的先前方法类似,它从 Date 对象中获取日期、月份和年份。

  1. 现在,添加getLocalTimeStr()方法如下:
getLocalTimeStr: function (gmtTimestampInSec) {
return 'at ' + this.currentTime(gmtTimestampInSec)
+ ' on ' + this.currentDate(gmtTimestampInSec);
}

上述方法返回连接的格式化时间和日期字符串。

刚刚发生了什么?

jQuery 为我们提供了一个称为ready的文档对象上的特殊实用程序,允许我们在 DOM 完全加载完成后执行代码。使用$(document).ready(),我们可以排队一系列事件,并在 DOM 初始化后执行它们。$(document).ready()方法接受一个函数(匿名)作为其参数,该函数在 DOM 加载完成后被调用,并执行函数内的代码。

如果您正在开发用于分发的代码,始终重要的是要补偿任何可能的名称冲突。因此,我们将$作为匿名函数的参数传递。这个$在内部指的是jQuery,因此在脚本之后导入的其他$函数不会发生冲突。

最后,为了从 UNIX 时间戳获取本地日期和时间,我们在自定义 JavaScript 库中添加了实用方法。至于用法示例,currentDate()实用方法可以从对象的内部和外部范围分别调用为this.currentDate()Status.currentDate()

操作时间-显示状态列表

我们将把接口元素放在index.php中,并以适当的方式嵌入 PHP 代码。因此,让我们按照以下步骤进行:

  1. 修改index.php文件,在<body>标记内,删除 PHP 标记,并将状态条目放在<div>容器标记和元素中,如下所示:
<body>
<div id="container" class="container">
<h1>Status Poster</h1>
<ul>
</ul>
</div>
</body>

从这段代码中,您可以看到我们的应用程序界面将位于 id 为 container 的<div>容器内,<ul>标记将保存内部<li>项的堆栈,其中包含用户的状态帖子,这些帖子将由一些 PHP 代码填充。

  1. index.php文件的<!DOCTYPE html>标记上方的顶部 PHP 代码片段中,键入以下行,以集成StatusPoster类,使代码片段看起来类似于以下内容:
<?php
require_once 'StatusPoster.php';
$result = $status->getStatusPosts();
define('BASE_URL', 'http://localhost/chapter3/');
?>

从代码中,一次需要 PHP 类文件来集成类,并在我们的应用程序中使用其实例。在这一行,我们调用了$status对象的getStatusPosts()方法,以从数据库中获取所有状态条目,并将返回的结果数组存储到$result中。

  1. 为了显示状态流,我们将编写以下 PHP 代码,以在<ul>标记内循环遍历$result数组:
<?php
if (is_array($result))
foreach ($result as $row) {
echo '
<li>
<a href="#">
<img class="left" src="images/user/' . $row['image'] . '" alt="picture">
</a>
<div class="content left">
<a href="#">' . $row['name'] . '</a>
<div class="status">' . $row['status'] . '</div>
<span class="localtime" data-timestamp="' . $row['timestamp'] . '"></span>
</div>
<div class="clearer"></div>
</li>
';
}
?>

首先,对$result数组进行了正确类型的验证。我们循环遍历数组,将每个条目放入$row变量中。前面的服务器脚本为每个状态条目生成一个<li>项,每个<li>项包含一个用户图像、一个超链接名称、一个用户状态文本和一个 UNIX 时间戳元素。请注意,时间戳已经转储到具有类名localtimespan元素的data-timestamp属性中。为了更好地理解,状态列表的项目骨架如下图所示:

操作时间-显示状态列表

  1. 现在,我们需要在 DOM 准备就绪时使用 jQuery 代码转换data-timestamp属性中的 PHP 转储时间戳。在status.js库的Status对象中添加以下方法:
showLocalTime: function () {
var spans = $('span.localtime[data-timestamp]');
spans.each( function () {
var localTimeStr = Status.getLocalTimeStr( $(this).attr('data-timestamp') );
$(this).html(localTimeStr);
});
},

使用 jQuery 选择器的方法选择所有具有data-timestamp属性的 span 元素为$('span.localtime[data-timestamp]');。对于每个元素,它使用$(this).attr('data-timestamp')解析时间戳,并传递给Status.getLocalTimeStr()以获取本地时间字符串。最后,它将每个span元素的内部 HTML 设置为该本地时间字符串。

  1. 为了使Status.showLocalTime()立即与 DOM 一起工作,调用该方法,如下所示,在ready()方法的终止行之前:
$(document).ready(function ($)
{
var Status = {
//whole library methods...
};
Status.showLocalTime();
});

因此,用户将在每个帖子下显示其本地日期和时间。

  1. 最后,指向项目 URL 的浏览器,或者从工具栏中按下运行项目(第三章)按钮,或者从 IDE 中按下F6,以显示状态流显示列表,看起来类似于以下屏幕截图:行动时间-显示状态列表

刚刚发生了什么?

PHP 脚本将<li>项转储到<ul>标记中,界面 JS 代码Status.showLocalTime();解析转储的时间戳,并在 DOM 准备就绪时以用户的本地时间显示它。如果我们显示 UNIX 时间戳的日期和时间而不进行时区转换,那么我们可能需要提供服务器的日期和时间,这可能不符合用户的时间。再次,用户的本地时区对服务器来说是未知的,对客户端界面来说是已知的。因此,我们以一种快速的方式使用客户端代码来解决本地时间显示问题。

因此,我们已经完成了项目的第一部分。我们已经创建了一个界面,状态流看起来像 Facebook。

到目前为止,我们已经能够使用 IDE 处理数据库操作,并使用 NetBeans 代码模板创建 PHP 类和方法,我们还能够为我们的 Web 应用程序创建必要的用户界面文件。

尝试一下-调整 CSS

对于较大的状态帖子,界面可能会在每个<li>内部找到破损,因此最好修复用户界面问题。您可以在相应的 CSS 文件中的.content类中添加固定宽度。

小测验-理解 CSS

  1. CSS 代表什么?

  2. 级联样式表

  3. 级联样式表

  4. 多彩样式表

  5. 计算机样式表

  6. 引用外部样式表的正确 HTML 格式是什么?

  7. <link rel="stylesheet" type="text/css" href="mystyle.css">

  8. <style src="mystyle.css">

  9. <stylesheet>mystyle.css</stylesheet>

  10. 需要添加到 CSS 类中的属性是什么,以在该元素周围留出一些空间?

  11. 填充

  12. 边距

  13. padding-bottom 和 padding-top

  14. 显示

使用 PHP-AJAX 孵化状态发布者

用户的状态文本应该在不重新加载页面的情况下提交到服务器。为此,我们可以使用 AJAX 方法,其中用户的数据可以使用 HTTP 方法发送到服务器,并等待服务器的响应。一旦服务器响应,我们可以以编程方式解析响应数据,并可能做出我们的决定。在我们的情况下,如果服务器以成功结果响应,我们将根据此更新我们的界面 DOM。

简单地说,我们将使用 AJAX 将用户的状态文本提交到位于index.php的服务器端 PHP 代码,使用HTTP POST方法,并配置从服务器期望的数据类型为 JSON。因此,我们可以轻松解析 JSON 并确定状态是否成功保存。从成功的服务器响应中,我们可以更新状态流显示列表,并将新发布的状态放在该列表的顶部。但是,在任何失败或错误的情况下,我们也可以解析错误消息并将其显示在界面中。

行动时间-向界面添加状态输入框

在本节中,我们将简单地添加一个 HTML 表单,其中包含一个文本区域用于状态发布,以及一个用于表单提交的提交按钮。我们将在index.php<ul>标签之前添加包含div元素的表单。

  1. 为了添加状态发布框,我们将在div#container内添加以下 HTML 代码,位于<ul>标签之前:
<div class="inputbox">
<form id="statusFrom" action="index.php" method="post" >
<textarea name="status" id="status_box">Write your status here</textarea>
<input class="right" type="submit" name="submit" id="submit" value="Share" />
<div id="postStatus" class="postStatus clearer hidden">loading</div>
</form>
</div>

因此,div.inputbox将包含带有sharesubmit按钮的状态输入框。div#postStatus将显示发布提交进度信息状态,以传达状态是否成功发布。在 AJAX 发布进行中,我们将使用一些花哨的加载.gif图像。ajaxload.gif图像也保存在项目的images目录中。

  1. 现在,使用项目 URL 刷新您的浏览器,状态输入框应该看起来与以下截图类似:行动时间-将状态输入框添加到界面

刚刚发生了什么?

查看form标签打开的行,<form id="statusFrom" action="index.php" method="post" >。可以使用 jQuery 选择包含脚本名称为index.phpaction属性的id属性选择表单,这意味着它将被发布到我们正在工作的同一文件。您可以看到method属性包含表单将被提交的 HTTP 方法类型。我们不需要 jQuery 代码的actionmethod属性。相反,在这种情况下,我们将保留它们。如果浏览器的 JavaScript 被禁用,那么我们仍然可以以POST方法提交表单到index.php

请注意,div#postStatus默认使用 CSS 类hidden隐藏,并且只有在 AJAX 工作进行中时才会可见。

注意

有关 NetBeans IDE 键盘快捷键,请参阅附录

将新的状态发布模板添加到 index.php

在编写代码时,我们需要保持行为的分离,即 HTML 标记应与 JavaScript 代码分开。此外,我们需要更新状态流显示列表,并在不刷新页面的情况下将新的状态发布放在列表顶部。

我们知道每个状态条目可以组织在<li>项内,在该项内,用户名、图片和带有本地日期时间的状态发布等条目值应该使用适当的标记元素进行构建。因此,我们需要为新的状态发布创建一个条目模板。使用模板,JavaScript 代码可以生成一个新的界面条目,放置在状态流的顶部。

在文档<body>标签内,div#container结束标签下方添加以下模板:

<div id="statusTemplate" class="hidden">
<li>
<a href="#">
<img class="left" src="#SRC" alt="picture">
</a>
<div class="content left">
<a href="#">#NAME</a>
<div class="status">#STATUS</div>
<span class="localtime">#TIME</span>
</div>
<div class="clearer"></div>
</li>
</div>

我们可以看到有一些占位符,例如#SRC用于个人资料图片的图像 URL,#NAME用于条目的用户名,#STATUS用于状态文本,#TIME用于本地日期时间。通过复制此模板,这些占位符可以替换为适当的值,并在<ul>元素前添加。请注意,整个模板都放在一个隐藏的div元素中,以排除它不被用户看到。

创建 AJAX 状态发布器

AJAX 用于在浏览器和 Web 服务器之间频繁通信。这种著名的技术被广泛用于Rich Internet ApplicationsRIA),而 jQuery 提供了一个非常简单的 AJAX 框架。AJAX 发布器将在不刷新页面的情况下发布状态文本,并将最新的状态条目更新到顶部的状态堆栈中。

行动时间-使用 JQuery AJAX 创建状态发布器

我们将在status.js库中创建一个post()方法,并将该方法与提交按钮的单击事件绑定。我们将通过按照以下步骤逐行添加代码来创建该方法:

  1. 在我们的status.js库中,输入以下post()方法,以逗号结尾,将其添加到Status库中:
post: function () {
var myname = 'M A Hossain Tonu', myimage = 'images/user/tonu.jpg';
var loadingHtml = '<img src="images/ajaxload.gif" alt="loadin.." border="0" >';
var successMsg = 'Status Posted Successfully ...';
var statusTxt = $('#status_box').val(), postStatus = $('#postStatus');
},

在变量声明部分,mynamemyimage变量包含了一个演示已登录用户的名称和个人资料图片 URL。loadingHtml包含用于显示加载 GIF 动画的 img 标签。此外,您可以看到statusTxt包含使用$('#status_box').val()获取的状态框值,postStatus缓存了div#postStatus元素。

  1. 现在,在post()方法中的变量声明部分之后添加以下行:
if ((statusTxt.trim() !== '' && statusTxt !== 'Write your status here'
&& statusTxt.length < 500) === false) return;

此代码验证了statusTxt是否为空,是否包含默认输入消息,以及是否在 500 个字符的最大输入限制内。如果任何此类验证失败,则在执行后返回该方法。

  1. 为了在 AJAX 操作进行时显示动画加载,我们可以在上一行(步骤 2)之后添加以下行:
postStatus.html(loadingHtml).fadeIn('slow');

它会在带有加载图像的 div 元素#postStatus中淡入。

  1. 现在,是时候在方法中添加 AJAX 功能了。在上一行(步骤 3)之后添加以下 jQuery 代码:
$.ajax({
data: $('form').serialize(),
url: 'index.php',
type: 'POST',
dataType: 'json',
success: function (response) {
//ajax success callback codes
},
error: function () {}
});

在这段代码中,您可以看到已添加了 AJAX 骨架,并且使用 jQuery $.ajax()方法传递了配置对象。配置对象是使用 JavaScript 对象字面量技术创建的。您可以看到这些键值对;例如,data包含使用$('form').serialize()序列化的表单值,url保存了数据要提交到的服务器 URL,dataType设置为 JSON,这样我们将在success()回调方法中传递一个 JSON 对象。查看默认的successerror回调方法;您可以看到一个变量response传递到success回调中,实际上是使用 AJAX 从服务器获取的 JSON 对象。

  1. 在成功的 AJAX 提交中,让我们在success回调方法中输入以下代码:
if (response.success === true) {
postStatus.html('<strong>'+successMsg+'</strong>');
$('#status_box').val('');
var statusHtml = $('#statusTemplate').html();
statusHtml = statusHtml
.replace('#SRC', myimage)
.replace('#NAME', myname)
.replace('#STATUS', statusTxt)
.replace('#TIME', Status.getLocalTimeStr());
$('#container ul').prepend(statusHtml);
} else {
postStatus.html('<strong>' + response.error + '</strong>').fadeIn("slow");
}

由于response传入的是一个 JSON 对象,我们检查response对象的response.success属性,其中包含布尔值 true 或 false。如果response.success属性未设置为true,则在元素div#postStatus中显示来自 response.error 的错误消息。

因此,对于来自服务器的成功响应,我们在successMsg中显示消息,并清除输入text_area#status_box的值以进行下一次输入。现在,在var statusHtml = $('#statusTemplate').html();行中,我们将条目模板缓存到statusHtml变量中。在连续的行中,我们用正确的条目值替换了占位符,并最终在<ul>元素中前置了新的条目项,使用了$('#container ul').prepend(statusHtml)行。

  1. 为了使用事件触发Status.post(),我们将该方法与Submit分享)按钮上的click事件绑定。在status.js库中的$(document).ready()方法终止之前(Status.showLocalTime()行之后)添加以下代码:
$('#submit').click(function () {
Status.post();
return false;
});

刚刚发生了什么?

我们已经将表单值序列化以通过 AJAX 发送到服务器,并且服务器响应被 jQuery AJAX 功能解析为 JSON 对象,传递到success回调方法中。我们检查了response对象是否携带了success标志。如果找到了成功标志,我们使用它来解析状态条目模板,准备条目 HTML,并将条目置于状态列表顶部。

因此,我们将 AJAX 状态发布方法post()绑定到状态提交按钮,当单击按钮时触发。请注意,我们在post()方法执行时在用户界面上反映successerror消息,甚至显示加载动画。因此,我们使我们的应用程序具有响应性。

现在,让我们添加服务器代码来响应 AJAX 请求。

再次使用 StatusPoster.php 进行操作。

为了将条目插入数据库表的status字段,我们向我们的 PHP 类添加了一个StatusPoster方法,命名为insertStatus,如下所示:

public function insertStatus(array $values){
$sql = "INSERT INTO status ";
$fields = array_keys($values);
$vals = array_values($values);
$sql .= '('.implode(',', $fields).') ';
$arr = array();
foreach ($fields as $f) {
$arr[] = '?';
}
$sql .= 'VALUES ('.implode(',', $arr).') ';
$statement = $this->db->prepare($sql);
foreach ($vals as $i=>$v) {
$statement->bindValue($i+1, $v);
}
return $statement->execute();
}

该方法接受传入的关联数组$values中的字段值,为status表准备 MySQL 插入查询,并执行查询。请注意,我们已将字段名称保留在$fields数组中,并且已从传递的数组的键和值中提取出$vals数组中的字段值。我们已经在准备的语句中使用?代替所有给定的值,每个值都将用PDOStatement::bindValue()方法绑定。bindValue()方法将一个值绑定到一个参数。

请注意,包含直接用户输入的变量应在发送到 MySQL 的查询之前进行转义,以使这些数据安全。PDO 准备的语句会为您处理转义的绑定值。

最后,无论execute()方法是否成功,该方法都会返回。

将 AJAX 响应器代码添加到 index.php

在位于index.php文件顶部的 PHP 代码中添加以下 AJAX 响应器代码,位于require_once 'StatusPoster.php';的下面:

if (isset($_POST['status'])) {
$statusStr = trim($_POST['status']);
$length = mb_strlen($statusStr);
$success = false;
if ($length > 0 && $length < 500) {
$success = $status->insertStatus(array(
'name' => 'M A Hossain Tonu',
'image' => 'tonu.jpg',
'status' => $statusStr,
'timestamp' => time()
));
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
echo ($success) ? '{"success":true}' : '{"error":"Error posting status"}';
exit;
}
}

此代码检查是否存在由$_POST['status']包含的任何POST值;如果是,则修剪发布的状态值,并确定包含在$statusStr中的发布的状态字符串的长度。使用多字节字符串长度函数mb_strlen()来测量长度。如果字符串长度在提到的范围内,则使用关联数据库列名将状态条目值压缩到数组中,并将StatusPoster类的insertStatus方法传递以保存状态。

由于insertStatus方法对于成功的数据库插入返回true,我们将返回的值保留在$success变量中。此外,可以通过验证$_SERVER['HTTP_X_REQUESTED_WITH']的值是否为XMLHttpRequest来在服务器上识别 AJAX 请求。

因此,对于 AJAX 请求,我们将传递 JSON 字符串;如果$success包含布尔值true,则为{"success":true},如果$success包含布尔值false,则为{"error":"Error posting status"}

因此,检查值XMLHttpRequest确保仅对 AJAX 请求提供 JSON 字符串传递。最后,前面的 PHP 代码插入了带有或不带有 AJAX 请求的状态帖子。因此,在客户端浏览器中禁用 JavaScript 的情况下,状态发布者表单仍然可以被提交,并且提交的数据也可以被插入。

注意

本章的完整项目源代码可以从 Packt 网站 URL 下载。

测试状态发布者的可用性

我们已经准备好状态发布者项目。接口 JavaScript 代码将数据发送到服务器,服务器端代码执行指示的操作和响应,接口代码将 DOM 与响应一起更新。

您可以通过在框中输入状态文本并单击分享按钮来测试状态发布者。单击分享按钮后,您应该在输入框下方看到一个加载图像。几秒钟后,您将看到状态发布成功的消息,因为状态已在状态显示列表中预置。最后,在发布状态"hello world"后,屏幕看起来类似于以下内容:

测试状态发布者的可用性

完成的项目目录结构看起来类似于以下内容:

测试状态发布者的可用性

突击测验 - 复习 jQuery 知识

  1. jQuery 使用哪个符号作为 jQuery 的快捷方式?

  2. ? 符号

  3. 符号

  4. $ 符号

  5. jQuery 符号

  6. 以下哪个是正确的,使用#element_id ID 获取输入框的值?

  7. $('#element_id').value()

  8. $('#element_id').text()

  9. $('#element_id').html()

  10. $('#element_id').val()

  11. 以下哪个返回 JavaScript 中存储在stringVar变量中的字符串的长度?

  12. stringVar.size

  13. length(stringVar)

  14. stringVar.length

  15. 添加DIV元素的正确语句是什么?

  16. $('#container').append('<div></div>');

  17. $('#container').html('<div></div>');

  18. $('#container').prepend('<div></div>');

  19. 以下哪个将导致元素逐渐消失?

  20. $('#element').hide();

  21. $('#element').fadeOut('slow');

  22. $('#element').blur('slow');

  23. 以下哪个将是获取element1的内部 HTML 作为element2的内部 HTML 的正确代码?

  24. $('#element2').html( ) = $('#element1').html( );

  25. $('#element2').html( $('#element1').innerHTML );

  26. $('#element1').html( $('#element2').html() );

  27. $('#element2').html( $('#element1').html( ) );

尝试一下——清理状态输入

由于用户提供的状态输入未经过足够的清理,存在原始标记或 HTML 标记放置在输入中会破坏界面的可能性。因此,正确地清理状态输入,并且在 AJAX 成功时显示这个新的状态条目的 JavaScript 代码也要注意不刷新页面。如果您不希望允许标记,您可以在将其插入到INSERT查询之前使用strip_tags()方法剥离标记。再次,如果您希望保留标记,您可以使用 PHP 的htmlspecialchars()函数。您还需要重构您的 JS 代码;也就是说,您可以使用$('#status_box').text()而不是$('#status_box').val()

总结

在本章中,我们完成了一个真实的 PHP 项目,现在能够使用 NetBeans IDE 创建和维护 PHP 项目。此外,我们现在熟悉了使用 IDE 进行更快速开发的方法。练习这些键盘快捷键、代码补全快捷码、代码生成器和其他 IDE 功能将加快您的步伐,使您的开发更加顺利。所有这些功能都旨在简化您的任务,使您的生活更轻松。

我们特别关注了:

  • 设置数据库

  • 创建 JavaScript 库

  • 真实的 PHP AJAX 网络应用开发

  • 使用 NetBeans 代码模板

到目前为止,我们已经使用 NetBeans 开发了一个 PHP 项目。在下一章中,我们将对一些演示 PHP 项目进行调试和测试,以便在处理项目中的关键时刻时具备更多的技能。

第四章:使用 NetBeans 进行调试和测试

如果调试被定义为从程序中消除错误的艺术,那么编程必须是将错误放入其中。

在本章中,我们将学习使用 NetBeans IDE 调试和测试 PHP Web 应用程序。我们将处理示例项目,以学习捕虫和测试的过程。本章将讨论以下主题:

  • 配置 XDebug

  • 使用 XDebug 调试 PHP 源代码

  • 使用 PHPUnit 和 Selenium 进行单元测试

  • 代码覆盖率

让我们去找猎人,做一些真正的技巧...

调试古老的编程艺术

编写程序后,下一步是测试程序,以查找程序是否按预期工作。有时,当我们第一次运行刚写好的代码时,可能会产生错误,如语法错误、运行时错误和逻辑错误。调试是逐步查找错误的过程,以便修复错误,使程序按预期工作。

现代编辑器几乎可以检测到所有语法错误,因此我们可以在输入代码时修复它们。还有一些可以与 IDE 集成的工具来查找错误,它们被称为调试器。有许多优秀的调试器,如 XDebug 和 FirePHP(适用于 FireBug 粉丝),适用于 PHP。这些调试器还带有应用程序分析器。在本章中,我们将尝试使用 NetBeans 调试 PHP 项目的 XDebug。

使用 XDebug 调试 PHP 源代码

XDebug是高度可配置的,适应各种情况。您可以检查本地变量,设置监视,设置断点,并实时评估代码。您还可以使用转到快捷方式和超文本链接导航到声明、类型和文件。为所有项目使用全局 PHPinclude路径,或者根据项目自定义它。

PHP 的 NetBeans IDE 还提供了命令行调试。PHP 程序的输出会显示在 IDE 本身的命令行显示中,您可以在不切换到浏览器的情况下检查生成的 HTML。

您可以在本地或远程调试脚本和网页。NetBeans PHP 调试器集成允许您将服务器路径映射到本地路径,以启用远程调试。

XDebug 提供以下功能:

  • 错误发生时自动堆栈跟踪

  • 函数调用日志

  • 增强var_dump()输出和代码覆盖信息

堆栈跟踪显示错误发生的位置,允许您跟踪函数调用和原始行号。var_dump()输出以更详细的方式显示在 XDebug 中。

提示

XDebug 覆盖了 PHP 的默认var_dump()函数,用于显示变量转储。XDebug 的版本包括不同颜色的不同变量类型,并对数组元素/对象属性的数量、最大深度和字符串长度进行限制。

配置 XDebug

在每个单独的操作系统上配置 XDebug 都非常容易。在本节中,让我们在我们的开发环境(XAMPP、LAMP 和 MAMP)中配置 XDebug。您只需在php.ini中启用一些行或按一些命令。由于我们已经安装了开发包,我们将在这些堆栈上激活 XDebug。首先,我们将使工具在我们的本地主机系统上运行,然后将其添加到 NetBeans 中。

行动时间-在 Windows 上安装 XDebug

XDebug 扩展默认包含在 XAMPP 捆绑包中。您只需从加载的.ini文件中启用它。请注意,可能存在多个php.ini文件,并且文件位置在不同操作系统之间可能不同。所以,让我们试试看...

  1. 通过将浏览器指向localhost/xampp/phpinfo.php来查找加载的php.ini文件。行动时间-在 Windows 上安装 XDebug

您可以看到位于D:\xampp\php\php.ini的加载的php.ini文件。

  1. 打开位于D:\xampp\php\php.iniphp.ini文件,并找到以下行:
[XDebug]
;zend_extension = "D:\xampp\php\ext\php_xdebug.dll"

  1. 找到并取消注释以下行,删除前导分号:
zend_extension = "D:\xampp\php\ext\php_xdebug.dll"
xdebug.remote_enable = 1
xdebug.remote_handler = "dbgp"
xdebug.remote_host = "localhost"
xdebug.remote_port = 9000

  1. 保存php.ini文件,并从 XAMPP 控制面板重新启动 Apache Web 服务器,以启用 XDebug 扩展。

  2. 要验证 XDebug 对象是否已启用,请刷新您的phpinfo()页面,并查找已启用的 XDebug,如下面的屏幕截图所示:操作时间-在 Windows 上安装 XDebug

  3. 如果启用了 XDebug,它将覆盖 PHP 中的var_dump()。您可以在代码中转储变量,如var_dump($var),浏览器将显示增强的var_dump,如下所示(字符串以红色打印):操作时间-在 Windows 上安装 XDebug

太棒了!您刚刚在开发环境中加载了 XDebug。

刚刚发生了什么?

我们刚刚在 Windows 的 XAMPP 捆绑包中启用了 XDebug,并验证了加载的扩展和配置。请注意,可以遵循此类通用步骤来启用php.ini中的其他内置扩展以启用 XDebug。您只需取消注释php.ini中的扩展并重新启动 Web 服务器以使更改生效。在 LAMP 或 MAMP 堆栈中启用 XDebug 也是非常相似的。

提示

始终检查phpinfo()页面加载的php.ini路径。

在 Ubuntu 上启用 XDebug

在 Ubuntu 中启用 XDebug 非常容易。我们可以通过apt-get软件包安装程序安装它,并更新xdebug.ini以加载配置。

操作时间-在 Ubuntu 上安装 XDebug

从控制台运行以下命令:

  1. 使用以下命令安装 XDebug:
**sudo apt-get install php5-xdebug** 

  1. 使用内置编辑器gedit更新xdebug.ini
**sudo gedit /etc/php5/apache2/conf.d/xdebug.ini** 

  1. 更改xdebug.ini,使其如下所示:
**zend_extension=/usr/lib/php5/20090626+lfs/xdebug.so
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000** 

请注意,这些配置的第一行可能在xdebug.ini中可用,并且您可能需要添加其余行。

  1. 重新启动 Apache。
**sudo service apache2 restart** 

  1. 刷新phpinfo()页面,找到安装的最新 XDebug 版本号。

刚刚发生了什么?

我们刚刚在 Ubuntu 的 LAMP 中启用了 XDebug,并验证了加载的扩展和配置。请注意,可以遵循此类通用步骤来启用php.ini中的其他内置扩展以启用 XDebug。您只需取消注释php.ini中的扩展并重新启动 Web 服务器以使更改生效。

在 Mac OS X 上启用 XDebug

修改加载的php.ini文件的适当版本以在 Mac 上启用 XDebug,取消注释以下行,并从 MAMP 控制面板重新启动 Apache 服务器。

**[xdebug]
zend_extension="/Applications/MAMP/bin/php5.3/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so"
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host=localhost
xdebug.remote_port=9000** 

XDebug 现在在您的 Mac OSX 上运行。MAMP Pro 用户可以轻松从 MAMP Pro 控制面板编辑php.ini,方法是从菜单中选择文件|编辑模板|PHP 5.3.2 php.ini

最后,我们在本地开发环境中启用了 XDebug。

使用 NetBeans 调试 PHP 源代码

要继续,我们想检查 NetBeans 所需的调试设置。选择工具|选项|PHP|调试选项卡:

使用 NetBeans 调试 PHP 源代码

在此窗口中,取消选中在第一行停止复选框,因为我们希望在所需行停止,并选中监视和气球评估复选框。此选项使您能够在调试时观察自定义表达式或变量。

现在,让我们看一下在 NetBeans 窗口中运行的调试会话:

使用 NetBeans 调试 PHP 源代码

在此屏幕截图中,调试工具栏和按钮由功能名称表示。

注意

netbeans.org/kb/docs/php/debugging.html#work了解有关调试工具栏的更多信息。

调试器窗口

当您开始调试会话时,一组调试器窗口会在主编辑器窗口下方打开。要添加新窗口,请选择窗口|调试。以下窗口可用:

  • 本地变量显示了已初始化变量、它们的类型和值的列表

  • 监视显示了用户定义表达式及其值的列表

  • 调用堆栈显示了以相反顺序调用的函数列表;最后调用的函数位于列表顶部

  • 断点显示了设置断点的文件和行号的列表

  • 会话显示了当前活动调试会话的列表

  • 线程窗口指示了当前活动的 PHP 脚本以及它是否在断点处暂停或运行。如果脚本正在运行,您需要转到浏览器窗口并与脚本交互。

  • 窗口显示了调试会话加载的所有文件和脚本。窗口目前不适用于 PHP 项目。

基本调试工作流程

以下是基本的调试工作流程:

  1. 用户在应该暂停 PHP 源代码执行的行处设置断点。

  2. 当达到该行时,用户通过按下F7F8按钮逐行执行脚本,并检查变量的值。

注意

有关 NetBeans IDE 调试和测试的键盘快捷键,请参见附录

进行操作的时间 — 运行调试会话

本节介绍了标准的调试会话,并且我们将创建一个示例项目来练习调试:

  1. 创建一个 NetBeans PHP 项目。对于我们的示例,我们将其命名为chapter4

  2. index.php文件中输入以下代码:

<?php
$fruits = array("Apple", "Banana", "Berry", "Watermelon");
$myfruit = "";
fruit_picker(); //first time call
echo "My fruit is : " . $myfruit . "<br />\n";
fruit_picker(); //second time call
echo "My fruit is now: " . $myfruit . "<br />\n";
fruit_picker(); //third time call
echo "My fruit is finally: " . $myfruit . "<br />\n";
function fruit_picker () {
Global $myfruit, $fruits;
$myfruit = $fruits[rand(0, 3)];
}
?>

前面的代码包含:

  • 一个包含水果名称的$fruits数组。

  • 一个包含单个水果名称的字符串的变量$myfruit,最初为空字符串。

  • 一个fruit_picker()方法,它从$fruits数组中随机选择一个水果名称并更改$myfruit的值。此外,$fruits$myfruit在函数内被定义为全局,以便函数可以在其全局范围内使用和修改它们。

  1. 为了测试调试步骤,我们可以通过按下Ctrl+F8在 PHP 块的开头设置断点,如下截图所示,或者简单地点击该行的行号添加断点:进行操作的时间 — 运行调试会话

  2. 要开始调试会话,请按下Ctrl+F5,或者从调试工具栏中点击调试项目(第四章)按钮,或者右键单击项目名称,在项目窗口中选择调试。调试器将在断点处停止。浏览器以项目调试 URL 的页面加载模式打开,该 URL 是http://localhost/chapter4/index.php?XDEBUG_SESSION_START=netbeans-xdebug

  3. 按下F7三次,从断点处进入第三个执行点。调试器将在第一次调用fruit_picker()函数的行停止。变量窗口显示了变量$fruits$myfruit及其值,类似于以下截图:进行操作的时间 — 运行调试会话

在我们的代码中,您可以看到fruit_picker()函数将连续被调用三次。

  1. 要进入fruit_picker()函数,请按下F7,调试器将开始执行fruit_picker()内部的代码,如下截图所示:进行操作的时间 — 运行调试会话

  2. 按下F7两次,fruit_picker()的执行将结束。现在,在变量窗口中检查$myfruit的新值:进行操作的时间 — 运行调试会话

  3. 再次按下F7三次,从第二次调用fruit_picker()函数的行进入该函数。由于您已经验证了该函数完美运行,您可能想要取消函数的执行。要跳出并返回到下一行,请按下Ctrl+F7进行操作的时间 — 运行调试会话

请注意,$myfruit的值保持变化,您可以将鼠标悬停在该变量上查看它。

  1. 由于您刚刚检查并发现您的代码正在正确运行,因此您可以通过按下F8来跳过当前行。

  2. 最后,您可以通过按下F7来浏览下一行,或者通过按下F8来跳过并到达末尾。再次,如果您希望结束会话,可以按下Shift+F5或单击完成调试会话按钮。在会话结束时,浏览器将显示结果(代码输出)。执行步骤-运行调试会话

刚刚发生了什么?

已经进行了调试会话的练习,希望我们已经掌握了使用 NetBeans 进行调试。您还可以添加多个断点以跟踪程序的执行。因此,您可以跟踪程序中的所有变量、表达式、方法调用顺序、程序控制跳转等。这是找出代码内部出现问题的过程。主要是,您现在已经准备好在编码时征服一些不需要的情况或错误。

添加监视

通过在代码执行中观察表达式,添加监视表达式可以帮助您捕捉错误。现在,让我们来玩一玩...

执行步骤-添加要监视的表达式

例如,我们想要测试fruit_picker()函数是否再次选择了相同的水果名称。我们可以在每次选择新的随机水果名称之前保存$myfruit的值,并使用表达式比较这两个水果名称。因此,让我们通过以下步骤添加表达式监视器:

  1. 修改fruit_picker()函数如下:
function fruit_picker() {
Global $myfruit, $fruits;
$old_fruit = $myfruit;
$myfruit = $fruits[rand(0, 3)];
}

我们刚刚添加了一行$old_fruit = $myfruit;来保存$myfruit的先前值,以便我们可以在函数结束时比较$old_fruit中的先前选择和$myfruit中的新选择。我们实际上想要检查是否选择了相同的水果。

  1. 选择调试|新建监视或按Ctrl+Shift+F7。打开新建监视窗口。执行步骤-添加要监视的表达式

  2. 输入以下表达式,然后单击确定

($old_fruit == $myfruit)

我们将在fruit_picker()函数的闭括号(})处观察此表达式结果。如果表达式在函数闭括号处产生(bool)1,那么我们将知道新选择的水果是否与旧的相同,或者再次选择了相同的水果。添加的监视表达式可以在监视变量窗口中找到。

  1. 运行调试会话,如前一节所示。当调试器停在fruit_picker()函数的闭括号处时,检查表达式值是否为(bool)0,如果新选择与旧选择不同,则该值为(bool)1,如果是连续选择相同的话,则该值为(bool)1执行步骤-添加要监视的表达式

通过这种方式,您可以继续观察表达式以查找错误。

刚刚发生了什么?

在调试会话中添加监视表达式很有趣。您可以添加多个监视以分析一些编程缺陷。简而言之,调试使您能够查看变量、函数、表达式、执行流程等,因此可以轻松地发现错误并清除它。

注意

有关 NetBeans IDE 调试和测试的键盘快捷键,请参阅附录

突发测验-使用 XDebug 进行调试

  1. 以下哪些是 XDebug 的功能?

  2. 出现错误时自动堆栈跟踪

  3. 自动修复错误

  4. 函数调用日志记录

  5. 增强的var_dump()

  6. 当 NetBeans 中出现断点时会发生什么?

  7. IDE 将跳过断点并显示结果

  8. IDE 将在那一点停止代码执行,让您看看窗口调试中发生了什么

  9. IDE 将终止调试会话并重置正在调试的窗口的结果

  10. 以上都不是

  11. 监视的目的是什么?

  12. 在 NetBeans 中显示时间

  13. 在代码执行中观察表达式

  14. 观察表达式时间

  15. 检查调试会话

尝试一下——探索 NetBeans 调试功能

调试窗口中,启用名为显示请求的 URL的功能。启用后,在调试期间将出现一个新的输出窗口,并显示当前处理的 URL。还要启用另一个名为PHP 调试器控制台输出窗口,以查看其中调试脚本的输出。请记住在您的php.ini文件中设置output_buffering = Off,以立即看到它。

使用 PHPUnit 进行测试

源代码测试在测试驱动开发方法中是必不可少的。测试描述了检查代码是否按预期行为的方式,使用一组可运行的代码片段。单元测试测试软件部分(单元)的正确性,其可运行的代码片段称为单元测试。NetBeans IDE 支持使用 PHPUnit 和 Selenium 测试框架进行自动化单元测试。

配置 PHPUnit

在 Windows 框中运行 XAMPP 时,它提供了一个内置的 PHPUnit 包。请注意,如果您的项目在 PHP 5.3 中运行,则应使用 PHPUnit 3.4.0 或更新版本。在我们的情况下,最新的 XAMPP 1.7.7(带有 PHP 5.3.8)堆栈中安装了 PHPUnit 2.3.6,这与 PHP 5.3 不兼容。您还需要升级现有的 PHP 扩展和应用程序存储库(PEAR)安装,以安装最新的 PHPUnit 和所需的 PEAR 包。

要检查已安装的 PEAR、PHP 和 Zend 引擎的版本,请从命令提示符或终端中浏览 PHP 安装目录D:\xampp\php,并输入pear version命令,将得到以下输出:

**PEAR Version: 1.7.2
PHP Version: 5.3.8
Zend Engine Version: 2.3.0
Running on: Windows NT....** 

所以现在是安装最新的 PHPUnit 的时候了。为了做到这一点,首先应该升级 PEAR。

行动时间——通过 PEAR 安装 PHPUnit

在接下来的步骤中,我们将升级 PEAR 并通过 PEAR 在相应的环境中安装 PHPUnit:

  1. 以管理员身份运行命令提示符,转到pear.bat文件所属的 PHP 安装目录(D:\xampp\php),并执行以下命令:
**pear upgrade pear** 

这将升级现有的 PEAR 安装。在 Ubuntu 或 Mac OS X 系统中,运行以下命令:

**sudo pear upgrade pear** 

在 MAMP 的情况下,如果遇到错误 sudo: pear: command not found,则请参阅配置 MAMP部分的问题。

  1. 要安装最新的 PHPUnit,请输入以下两个命令:
**pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit** 

它会自动发现下载频道并安装最新的 PHPUnit 以及可用的包。

  1. 要检查 PHPUnit 安装,请运行以下命令:
**phpunit version** 

您将看到类似以下的命令:

**PHPUnit 3.6.10 by Sebastian Bergmann**.

  1. 要列出 PHPUnit 的远程包,请运行以下命令:
**pear remote-list -c phpunit** 

刚才发生了什么?

我们使用pear upgrade pear命令升级了 PEAR 安装。我们启用了 PEAR 频道、自动发现配置,并使用这些自动安装频道安装了最新的 PHPUnit。其他 PHP 扩展可以通过这种方式轻松地从扩展存储库中安装。

再次,如果您已经升级了 PEAR 安装并且之前已启用了自动发现功能,那么只有pear install pear.phpunit.de/PHPUnit命令就可以完成 PHPUnit 的安装。

提示

在 Windows 中以管理员身份运行命令提示符,以便更轻松地处理目录权限。您可以右键单击程序并选择以管理员身份运行

配置 MAMP 问题

在使用 MAMP 时,如果在终端中使用 PEAR 命令时遇到错误pear: command not found,那么运行which php将指向 OS X 的默认版本。

**$ pear -bash: pear: command not found $ which php /usr/bin/php** 

您可能需要修复它。为了纠正这一点,我们需要将 PHP 的bin目录添加到我们的路径中。PATH是一个环境变量,表示要查找命令的目录。可以通过编辑home目录下的.profile文件来修改PATH。我们在本教程中使用了PHP5.3 bin版本路径,但您可以从可用的版本中进行选择。

从终端运行以下命令,将所需的 PHP 的bin目录添加到php, pear和其他相关可执行文件的使用中:

**$ echo "export PATH=/Applications/MAMP/bin/php/php5.3/bin:$PATH" >> ~/.profile** 

如您所见,在用户的home目录中的.profile文件中添加了一行,其中包括php5.3 bin目录路径到环境变量PATH

现在,停止 MAMP 并使用以下命令更改文件的权限,使这些文件可执行:

**chmod 774 /Applications/MAMP/bin/php5.3/bin/pear chmod 774 /Applications/MAMP/bin/php5.3/bin/php** 

chmod命令更改文件模式或访问控制列表。774表示文件的“所有者”和文件用户的“组”将被允许读取,写入和执行文件。其他人只能读取它,但不能写入或执行文件。

在编写时,最新的 MAMP 1.9 版本带有损坏的 PHP 版本的pear.conf文件。因此,使用以下命令将该文件重命名以防止其加载到系统中:

**mv /Applications/MAMP/conf/php5.3/pear.conf /Applications/MAMP/conf/php5.3/backup_pear.conf** 

实际上,在给定的pear.conf文件中,PHP 路径字符串包含php5而不是php5.3php5.2

现在,重新启动 MAMP 并重新启动您的终端会话。因此,MAMP 的问题已经解决,您可以通过从终端运行which phpwhich pear命令来测试它。为了使用 MAMP 安装 PHPUnit,您现在可以继续本章的安装 PHPUnit via PEAR 的行动时间-步骤 1

将 PHPUnit 添加到 NetBeans

要将 PHPUnit 设置为 NetBeans IDE 的默认单元测试器,请选择工具 | 选项 | PHP 选项卡 | 单元测试选项卡,使用搜索自动在PHPUnit 脚本字段中输入 PHPUnit 的.bat脚本路径,并单击确定

将 PHPUnit 添加到 NetBeans

同样,对于 Mac OS X,PHPUnit 的路径将类似于/Applications/MAMP/bin/php5.3/bin/phpunit

PEAR 的小测验

  1. PEAR 代表什么?

  2. PHP 扩展应用程序存储库

  3. PHP 扩展和应用程序存储库

  4. PHP 扩展社区库

  5. PHP 额外适用的存储库

创建和运行 PHPUnit 测试

在本节中,我们将学习创建和运行 PHPUnit 测试。NetBeans IDE 可以为文件中的所有 PHP 类创建测试脚本并运行 PHPUnit 测试。IDE 自动化测试脚本生成和整个测试过程。为了确保测试脚本生成器能够正常工作,请将 PHP 文件命名为文件中的第一个类相同的名称。

行动时间-使用 PHPUnit 进行测试

在本教程中,我们将创建一个新的 NetBeans 项目,使用 PHPUnit 从 IDE 中测试我们的 PHP 类。为了做到这一点,请按照以下步骤进行操作:

  1. 创建一个名为Calculator的新项目,在项目中添加一个名为Calculator的 PHP 类(右键单击项目节点,然后选择新建 | PHP 类,然后插入类名),并为Calculator类输入以下代码:
<?php
class Calculator {
public function add($a, $b) {
return $a + $b;
}
}
?>

您可以看到add()方法只是执行两个数字的加法并返回总和。我们将对这个方法进行单元测试,以查看它是否返回了正确的总和。

  1. 在下面的代码中添加一个带有@assert注释和一些示例输入和输出的注释块。请注意,以下示例中包含一个不正确的断言:
<?php
class Calculator {
/**
* @assert (0, 0) == 0
* @assert (0, 1) == 1
* @assert (1, 0) == 1
* @assert (1, 1) == 2
* @assert (1, 2) == 4
*/
public function add($a, $b) {
return $a + $b;
}
}
?>

  1. 项目窗口中,右键单击Calculator.php节点,然后选择工具 | 创建 PHPUnit 测试。请注意,您可以使用源文件节点中的上下文菜单为项目中的所有文件创建测试。行动时间-使用 PHPUnit 进行测试

  2. 第一次创建测试时,会打开一个对话框,询问您要存储测试脚本的目录。在本例中,可以使用浏览功能(按钮)创建一个tests目录。行动时间-使用 PHPUnit 进行测试

我们可以将测试文件与源文件夹分开。此外,如果您希望将这些测试脚本排除在未来的源代码版本控制之外,可以将它们分开。

  1. IDE 会在名为CalculatorTest.php的文件中生成一个测试类,该文件将显示在项目窗口中并在编辑器中打开。行动时间-使用 PHPUnit 进行测试

请注意,为类内的每个@assert注释创建了测试方法。

  1. 要测试Calculator.php文件,请右键单击文件节点并选择测试,或按Ctrl+F6。IDE 会运行测试并在测试结果窗口中显示结果。行动时间-使用 PHPUnit 进行测试

正如您所看到的,由于不正确的输入,其中一个测试失败了。这在测试结果窗口中用黄色感叹号标记。此外,您可以看到通过和失败的测试数量。因此,可以获得总体通过测试百分比(用绿色条表示)。

  1. 请注意,您也可以对整个项目运行测试。右键单击项目节点并选择测试,或按Alt+F6。还要考虑检查输出窗口,以获取更详细的文本输出。

刚刚发生了什么?

PHP 类或项目可以部分测试,使用 PHPUnit。这里最好的部分是你不需要担心生成测试脚本并以图形方式显示测试结果,因为 IDE 会处理它。您可以在www.phpunit.de/manual/current/en/了解更多关于 PHPUnit 测试的信息。

请注意

www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions了解更多关于断言的例子。

使用 PHPUnit 处理代码覆盖

NetBeans IDE 通过 PHPUnit 提供了代码覆盖功能。代码覆盖检查 PHPUnit 测试是否覆盖了所有方法。在本节中,我们将看到代码覆盖是如何与我们现有的Calculator类一起工作的。

使用代码覆盖的行动时间

按照以下步骤查看 NetBeans 中代码覆盖功能的工作方式:

  1. 打开Calculator.php,添加一个重复的add函数,并将其命名为add2Calculator类现在看起来类似于以下内容:
<?php
class Calculator {
/**
* @assert (0, 0) == 0
* @assert (0, 1) == 1
* @assert (1, 0) == 1
* @assert (1, 1) == 2
* @assert (1, 2) == 4
*/
public function add($a, $b) {
return $a + $b;
}
public function add2($a, $b) {
return $a + $b;
}
}
?>

  1. 右键单击项目节点。从上下文菜单中选择代码覆盖|收集和显示代码覆盖。默认情况下,也会选择显示编辑器栏行动时间-使用代码覆盖

  2. 编辑器现在在底部有一个代码覆盖率编辑器栏。由于代码覆盖率尚未经过测试,编辑器栏报告了0.0%的覆盖率(在单击清除以清除测试结果后,它还会显示这样的百分比)。行动时间-使用代码覆盖

  3. 单击测试以测试已打开的文件,或单击所有测试以运行项目的所有测试。测试结果将显示。此外,代码覆盖栏会告诉您有多少百分比的方法已被测试覆盖。在编辑器窗口中,覆盖的代码会以绿色突出显示,未覆盖的代码会以红色突出显示。查看以下代码覆盖会话:行动时间-使用代码覆盖

  4. 代码覆盖率栏中,点击报告...代码覆盖率报告打开,显示了在项目上运行的所有测试的结果。栏中的按钮让你清除结果,重新运行所有测试,并停用代码覆盖率(点击完成)。

如你所见,add2()方法没有被单元测试覆盖,所以报告显示50%的代码覆盖率,否则将显示100%的覆盖率。

刚刚发生了什么?

我们已经完成了使用 NetBeans 代码覆盖功能与 PHPUnit,因此我们可以确定哪些单元没有被 PHPUnit 测试覆盖。因此,当你为代码单元创建 PHPUnit 测试并希望确保所有单元都已被测试覆盖时,可以应用代码覆盖。但是,预期有一个最大的代码覆盖百分比。

提示

重构测试脚本时,也要重构代码。

使用 Selenium 框架进行测试

Selenium 是一个用于 Web 应用程序和自动化浏览器的便携式软件测试框架。主要用于跨多个平台自动化测试目的的 Web 应用程序。NetBeans IDE 具有一个包含 Selenium 服务器的插件。通过此插件,你可以在 PHP、Web 应用程序或 Maven 项目上运行 Selenium 测试。要在 PHP 上运行 Selenium 测试,你需要将Testing_Selenium包安装到你的 PHP 环境中。

安装 Selenium

由于我们已经升级了 PEAR 并安装了最新的 PHPUnit,我们应该已经安装了Testing_Selenium-beta。要检查 Selenium 安装,请从终端运行以下命令,你将能够查看已安装的版本:

**pear info Testing_Selenium-beta** 

否则,运行以下命令以安装 Selenium:

**pear install Testing_Selenium-0.4.4** 

运行 Selenium 测试的时间

让我们通过以下步骤使用 Selenium 运行测试:

  1. 要安装插件,打开工具 | 插件,并为 PHP 安装Selenium 模块

  2. 项目窗口中,右键单击计算器项目的项目节点。选择新建 | 其他新建文件向导打开。选择Selenium,然后点击下一步

第一次创建 Selenium 测试时,会打开一个对话框,询问你为 Selenium 测试文件设置一个目录。这应该是一个与 PHPUnit 测试文件分开的目录;否则,每次运行单元测试时都会运行 Selenium 测试。运行功能测试,如 Selenium,通常比运行单元测试需要更多时间。因此,你可能不想每次运行单元测试时都运行这些测试。

  1. 名称位置页面中接受默认设置,然后点击完成。新的 Selenium 测试文件将在编辑器中打开,并出现在项目窗口中。

  2. 运行 Selenium 测试项现在已添加到项目的上下文菜单中。点击此项,Selenium 测试结果将显示在测试结果窗口中,与 PHPUnit 测试相同。

你还可以修改 Selenium 服务器的设置。Selenium 服务器被添加为服务选项卡中的新服务器。

刚刚发生了什么?

我们刚刚使用了 Selenium 测试框架进行测试,用于 PHP 应用程序。它为开发人员提供了跨多个操作系统、浏览器和编程语言的测试支持,并允许录制、编辑和调试测试。简而言之,这是测试人员的完整测试解决方案。你可以使用 Selenium 随着代码结构的演变来演变你的测试。该软件基于 PHPUnit 框架,并继承了其大部分功能。

注意

你可以从这里了解更多关于 Selenium 测试:seleniumhq.org/

小测验 — 单元测试和代码覆盖率

  1. 什么是单元测试?

  2. 测试代码的最小可测试部分

  3. 测试类的各个方法

  4. 测试,您知道输入和输出是什么

  5. 以上所有

  6. 减去两个数字的测试中哪个断言会失败?

  7. @assert (0, 0) == 0

  8. @assert (2, 3) == -1

  9. @assert (4, 2) == 3

  10. @assert (5, 1) == 4

  11. 如果在一个只包含一个方法的类中测试单元时通过了六个测试并且失败了四个测试,那么代码覆盖百分比将是多少?

  12. 60%

  13. 50%

  14. 100%

  15. 40%

  16. Selenium 测试框架的特性不包括哪个?

  17. 自动化浏览器

  18. 通过手动测试遗漏的缺陷

  19. 观察表达式

  20. 无限次执行测试用例

尝试一下 —— 学习测试依赖关系

一个单元测试通常涵盖一个函数或方法,并且也可以依赖于其他单元测试。现在,使用@depends注释来表示单元测试的依赖关系,并借助www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.test-dependencies进行实践。

注意

查看附录以获取 NetBeans IDE 调试和测试的键盘快捷键。

摘要

在本章中,我们已经学会了使用 NetBeans 调试和测试 PHP 应用程序。该 IDE 已经以有效的方式与这些调试和测试工具集成在一起。此外,对于自动化测试,生成的脚本使该过程变得轻松和简单。

具体来说,我们已经专注于:

  • 各种操作系统上的 XDebug 配置

  • 使用 NetBeans 和 XDebug 运行调试会话

  • 安装 PHPUnit

  • 使用 PHPUnit 进行单元测试

  • 使用 PHPUnit 和 NetBeans 进行代码覆盖率

  • 使用 NetBeans 引入 Selenium 测试框架

现在使用调试和测试工具变得更加容易。在下一章中,我们将强调源代码和 API 文档,以使我们的源代码更易理解。

第五章:使用代码文档

代码告诉你如何做,注释告诉你为什么 - Jeff Atwood

在这一章中,我们将使用 NetBeans IDE 来记录我们的 PHP 源代码。我们将学习如何快速记录变量、方法、类或整个项目,并讨论以下问题:

  • 源文档的约定

  • 如何记录源代码

  • PHP 项目 API 文档

编写优秀的文档

编码是指导机器的艺术,当涉及到人类可读性时,代码应该是表达性的、自解释的和美观的。代码应该是可重用和可理解的,这样你可以在几个月后再次使用它。一个好的实践者会尽可能地简化代码,只在真正需要的地方保留代码文档。

代码文档是编码的激励部分,特别是当你在协作团队环境中工作时;文档应该以一种明智的方式完成,这样学习代码的意图在协作者之间可以更快地进行。

记录源代码的常规做法是在代码中放置符合PHPDoc格式的注释,这样你的代码就变得更有意义,外部文档生成器可以解析这样的注释。

PHPDoc——PHP 的注释标准

PHPDoc 是针对 PHP 编程语言的 Javadoc 的一种适应。由于它是 PHP 代码的标准注释,它允许外部文档生成器,如 phpDocumentor 和 ApiGen 为 API 生成 HTML 文档。它有助于各种 IDE,如 NetBeans,PhpStorm,Zend Studio 和 Aptana Studio,解释变量类型并提供改进的代码完成、类型提示和调试。根据 PHPDoc,文档是使用名为DocBlock的文本块编写的,这些文本块位于要记录的元素之前。作为描述编程构造的一种方式,例如类、接口、函数、方法等,标签注释被用在 DocBlock 内部。

DocBlock 的例子

DocBlock 是一个扩展的 C++风格的 PHP 注释,以"/**"开头,每一行都以"*"开头。

/**
* This is a DocBlock comment
*/

DocBlock 包含三个基本部分,按照以下顺序:

  • 简短描述

  • 长描述

  • 标签

例子:

/**
* Short description
*
* Long description first sentence starts here
* and continues on this line for a while
* finally concluding here at the end of
* this paragraph
*
* The blank line above denotes a paragraph break
*/

简短描述从第一行开始,可以用空行或句号结束。单词中的句号(例如example.com0.1 %)会被忽略。如果简短描述超过三行,那么只有第一行会被采用。长描述可以继续多行,并且可以包含用于显示格式的 HTML 标记。外部文档解析器将在长描述中将所有空格转换为单个空格,并可能使用段落分隔符来定义换行,或者<pre>,如下一节所述。

DocBlock 的长描述和简短描述会被解析为一些选定的 HTML 标签,这些标签使用以下标签进行附加格式化:

  • <b>: 这个标签用于强调/加粗文本

  • <code>: 这个标签用于包围 PHP 代码;一些转换器会对其进行高亮显示

  • <br>: 这个标签用于提供硬换行,并且可能会被一些转换器忽略

  • <i>: 这个标签用于将文本标记为重要的斜体

  • <kbd>: 这个标签用于表示键盘输入/屏幕显示

  • <li>: 这个标签用于列出项目

  • <ol>: 这个标签用于创建有序列表

  • <ul>: 这个标签用于创建无序列表

  • <p>: 这个标签用于包含所有段落;否则,内容将被视为文本

  • <pre>: 这个标签用于保留换行和间距,并假定所有标签都是文本(就像 XML 的 CDATA)

  • <samp>: 这个标签用于表示样本或示例(非 PHP)

  • <var>: 这个标签用于表示变量名

在罕见的情况下,如果需要在 DocBlock 中使用文本"<b>",请使用双定界符,如<<b>>。外部文档生成器将自动将其转换为物理文本"<b>"

熟悉 PHPDoc 标签

PHPDoc 标签是以@符号为前缀的单词,并且只有在它们是 DocBlock 新行上的第一件事情时才会被解析。DocBlock 在结构元素之前,这些元素可以是编程构造,如命名空间、类、接口、特征、函数、方法、属性、常量和变量。

一些常见的标签列表及详细信息已分成组,以便更好地理解,如下所示:

数据类型标签

标签 用法 描述
@param 类型[$varname] 描述 记录函数或方法的参数。
@return 类型描述 文档化函数或方法的返回类型。此标记不应用于构造函数或返回类型为void的方法。
@var 类型 记录类变量或常量的数据类型。

法律标签

标签 用法 描述
@author 作者名称<author@email> 记录当前元素的作者
@copyright 名称日期 记录版权信息
@license URL 名称 用于指示适用于相关结构元素的许可证

版本标签

标签 用法 描述
@version 版本字符串 提供类或方法的版本号
@since 版本字符串 记录发布版本
@deprecated 版本描述 用于指示哪些元素已被弃用并将在将来的版本中被移除
@todo 信息字符串 记录需要在以后的日期对代码进行的事情

其他标签

标签 用法 描述
@example /path/to/example 记录外部保存示例文件的位置
@link URL 链接文本 记录 URL 引用
@see 逗号分隔的元素名称 记录任何元素
@uses 元素名称 记录元素的使用方式
@package 包的名称 记录一组相关的类和函数
@subpackage 子包的名称 记录一组相关的类和函数

在最常用的标签中,@param@return只能用于函数和方法,@var用于属性和常量,@package@subpackage用于过程页面或类,而其他标签,如@author,@version等,可以用于任何元素。除了这些标签,@example@link可以用作内联标签。

注意

您可以在www.phpdoc.org/docs/latest/for-users/list-of-tags.html找到标签列表。

现在,我们将深入使用 NetBeans 来记录我们的 PHP 源代码。

记录源代码

在本节中,我们将学习如何记录函数、方法、类、接口、全局变量、常量等,并讨论使用此类代码文档的好处。如前所述,在协作开发环境中,方法、类等的描述对于了解代码的意图非常重要,我们将在本节中实际实现这一点。

现在,在 NetBeans 中创建一个名为Chapter5的新 PHP 项目,并将其用于所有接下来的教程。

记录函数和方法

在本节中,我们将学习如何在 PHP 函数或方法的开头使用 NetBeans 自动文档功能。

行动时间 - 文档化 PHP 函数或方法

在本教程中,让我们创建一个简单的 PHP 函数或方法,其中传入一些参数,并在其中声明不同类型的变量。我们只是在练习,看看 NetBeans 自动生成文档生成器在这些常用的结构元素上是如何工作的。让我们按照以下步骤进行:

  1. 在项目中添加一个名为sample1.php的 PHP 文件,并输入一个 PHP 函数,如下所示:
function testFunc(DateTime $param1, $param2, string $param3 = NULL)
{
$number = 7;
return $number;
}

在这个函数中,我们可以看到有三个参数传递到testFunc方法中-$param1作为DateTime$param2没有类型提示,因为它可能具有混合类型的值,$param3是可选的,默认值为NULL。此外,在函数体内,函数包含一个整数类型变量,并且也返回该整数类型。

  1. testFunc函数之前的行中键入/**,然后按Enter。您会看到 NetBeans 解析函数并在函数之前根据 PHPDoc 标准生成文档,看起来类似于以下内容:
**/**
*
* @param DateTime $param1
* @param type $param2
* @param string $param3
* @return int
*/**
function testFunc(DateTime $param1, $param2, string $param3 = NULL)
{
$number = 7;
return $number;
}

在前面的代码片段中,我们可以看到 NetBeans 生成了文档,其中提到了参数和返回类型,列举如下:

  • 参数用@param标记注释,并且从给定的类型提示中获取参数类型

返回类型用@return进行注释

您可以看到每个标签旁边的类型和名称之间用空格分隔。如果类型提示不可用,那么 NetBeans 会将其保留为简单的type,例如$param2。在文档中通常使用的词是当真实数据类型未知时使用"mixed",您也可以编辑该"type"

  1. 您可以在文档中为每个变量添加描述;在变量名旁边,只需加上一个前导空格的描述,如下所示:
/**
*
* @param DateTime $param1 this is parameter1
* @param array $param2 this is parameter2
* @param string $param3 this is parameter3 which is optional
* @return int what is returned, goes here
*/

  1. 此外,您可能希望为文档添加一个简短的描述,看起来类似于以下内容:
/**
* a short description goes here
*
* @param DateTime $param1 this is parameter1
* @param array $param2 this is parameter2
* @param string $param3 this is parameter3 which is optional
* @return int what is returned, goes here
*/

  1. 现在,让我们看看这个 NetBeans 生成的文档是什么样子,当有人试图从项目中的任何地方调用这个testFunc时。尝试在任何地方输入函数名。比如,在项目内的index.php文件中开始输入函数名,你会看到 NetBeans 自动提示该函数名以及参数提示和文档,如下所示:操作时间-记录 PHP 函数或方法

如果函数或任何元素有文档可用,那么 NetBeans 在自动建议过程中显示文档,就像前面的截图中所示的那样。

刚刚发生了什么?

我们刚刚学会了如何使用 NetBeans 自动生成文档生成器。通过在函数之前键入/**并按Enter,我们可以解析元数据并生成文档。我们也可以更新文档。同样,外部文档生成器可以提取这样的 DocBlocks 来创建项目 API 文档。现在,我们将在下一节中在 PHP 类之前添加文档。

记录类

在类之前的文档非常重要,可以了解类及其用法。最佳实践是使用适当的注释对前面的文档进行装饰,例如@package, @author, @copyright, @license, @link@version,并对类进行适当的描述。

操作时间-记录 PHP 类和类变量

在这一部分,我们将使用 NetBeans 添加一个 PHP 类,并使用类文档标签更新前面的 DocBlock。所以让我们开始吧...

  1. 右键单击Chapter5项目选择新建|PHP 类...,在文件名框中插入类名Test,然后点击完成,如下所示:操作时间-记录 PHP 类和类变量

  2. Test类应该看起来类似于以下内容:操作时间-记录 PHP 类和类变量

在上一张截图中,您可以看到打开的Test class在顶部添加了一个带有示例类描述和@author标签的 DocBlock。

  1. 您可能希望在包含@author标签的行之前添加 PHPDoc 标签;假设您想要在键入@p时立即添加@package标签。NetBeans 代码自动完成功能显示以@p开头的标签,其描述看起来类似于以下截图:执行时间-记录 PHP 类和类变量

  2. 使用您自己的方式更新 DocBlock,使其看起来类似于以下内容:

**/**
* Short description of the Test Class
*
* Long multiline description of the Test Class goes here
*
* Note: any notes required
* @package Chapter5
* @author M A Hossain Tonu
* @version 1.0
* @copyright never
* @link http://mahtonu.wordpress.com
*/**

  1. 在上述文档中,您可以看到已为类添加了相应的标签,因此在尝试使用代码完成实例化类对象时,可以使用类信息,如下所示:执行时间-记录 PHP 类和类变量

此外,可以使用外部 API 文档生成器提取这样的类 DocBlock。

  1. 现在,按照以下方式在Test类中输入一个名为$variable的类变量:
public $variable;

  1. 要添加类变量文档,请键入/**,并在声明它的行之前按Enter,以便文档看起来类似于以下内容:
/**
*
* @var type
*/

  1. 在这里,您可以按照以下方式更新块:
/**
* example of documenting a variable's type
* @var string
*/

  1. 为了在以后的部分查看类层次结构树,您可以在我们的项目中添加一个名为TestChild的子类,扩展Test类,看起来类似于以下内容:
/**
* Short description of the TestChild Class
*
* Long multiline description of the TestChild Class goes here
*
* Note: any notes required
* @package Chapter5
* @author M A Hossain Tonu
* @version 1.0
* @copyright never
* @link http://mahtonu.wordpress.com
*/
class TestChild extends Test {
}

刚刚发生了什么?

我们已经练习了如何在 PHP 函数、类及其属性之前添加文档,并测试了这些文档信息如何在整个项目中可用。相同风格的 DocBlock 或适当的标签也适用于文档化 PHP 接口。

记录 TODO 任务

您可以使用@todo标签为元素添加计划更改的文档,这些更改尚未实施,该标签几乎可以用于可以文档化的任何元素(全局变量、常量、函数、方法、定义、类和变量)。

执行时间-使用@todo 标签

在本教程中,我们将学习如何使用@todo标签记录我们的未来任务,并将从 NetBeans 任务或操作项窗口查看任务列表:

  1. TestChildPHP 类内或类的前面文档块中,我们可以使用@todo标签;在多行注释或 DocBlock 中,添加类似于以下内容的标签:
/**
* @todo have to add class variable and functions
*/

在上面的文档块中,我们可以看到任务已经被描述在标签旁,用空格分隔。此外,可以使用单行注释添加@todo标签,如下所示:

//TODO need to add class variable and functions

  1. 因此,TestChild类可能看起来类似于以下内容:
class TestChild extends Test {
//TODO have to add class variable and functions
}

  1. 当我们在文件中添加任务时,任务应该在 NetBeans 的任务操作项窗口中可见;按下Ctrl + 6打开窗口,添加的任务应该在任务窗口中列出,如下截图所示:执行时间-使用@todo 标签

刚刚发生了什么?

使用TODO任务标记添加新任务后,NetBeans 会立即更新任务窗口中的任务列表,并且您可以在该窗口中列出整个项目或所有在 NetBeans 中打开的项目的所有任务。当我们有想要实现但没有足够时间编写代码的想法时,可以使用这些标签,考虑到其未来的实现。因此,您可以使用@todo标签在适当的位置放下这个想法。

到目前为止,我们已经学会了如何使用 PHPDoc 标准标签来记录 PHP 源元素,并处理了 DocBlock 来编写源文档。已经讨论了有关源文档的基本概念。因此,在我们的下一节中,我们将学习如何提取这样的 DocBlock,以为整个项目或 API 生成 HTML 文档。

记录 API

正如我们已经讨论过源代码文档的重要性,文档应以一种井然有序的方式呈现给一般用户,或者使用 HTML 页面进行图形化阐述。这样的 API 文档,从源 DocBlocks 转换而来,可以作为了解源代码的技术文档。NetBeans 支持使用ApiGen自动文档工具从整个项目的 PHP 源代码生成 API 文档。

ApiGen 是使用 PHPDoc 标准创建 API 文档的工具,并支持最新的 PHP 5.3 功能,如命名空间、包、文档之间的链接、对 PHP 标准类和一般文档的交叉引用、高亮源代码的创建,以及对 PHP 5.4 traits 的支持。它还为项目生成了一个包含类、接口、traits 和异常树的页面。

提示

查看 ApiGen 的功能:http://apigen.org/##features

在下一节中,我们将讨论如何安装 ApiGen 并在 NetBeans 中配置它。

配置 ApiGen

我们将首先通过 PEAR 安装 ApiGen 并在 NetBeans 中配置它,以便我们可以从 IDE 生成 API 文档。我们可以启用 PEAR 自动发现功能,自动安装 ApiGen 及其所有依赖项。启用发现功能不仅会自动将 ApiGen 添加到系统路径,还允许轻松更新每个 ApiGen 组件。

操作时间-安装 ApiGen 并在 NetBeans 中配置

我们已经熟悉了通过 PEAR 安装 PHP 库(在上一章中讨论过),并且可能已经将 PEAR 配置auto_discover设置为 ON。在本节中,我们将使用以下步骤在 NetBeans 中安装和配置 ApiGen:

  1. 从终端或命令提示符中运行以下命令安装 ApiGen:
**pear config-set auto_discover 1
pear install pear.apigen.org/apigen**

install命令将自动下载并安装 ApiGen 以及其所有依赖项。如果您已经启用了 PEARauto_discover,则跳过第一个命令。

  1. 现在,我们需要将 ApiGen 可执行文件添加到 IDE 中。从工具|选项中打开IDE 选项窗口,选择PHP 选项卡|ApiGen选项卡,然后单击搜索...按钮搜索 ApiGen 脚本。ApiGen 脚本应该会自动列出,如下图所示:操作时间-安装 ApiGen 并在 NetBeans 中配置

  2. 从上一张截图中,选择apigen.bat(Windows 操作系统)或apigen(其他操作系统),然后按确定,将 ApiGen 脚本集成到 IDE 中,如下图所示:操作时间-安装 ApiGen 并在 NetBeans 中配置

您也可以在那里浏览 ApiGen 脚本路径。

  1. 确定保存设置。

刚刚发生了什么?

到目前为止,我们已经在 NetBeans 中配置了 ApiGen 工具,该工具已准备好用于 PHP 项目。一旦您将该工具与 IDE 集成,您可能希望从 IDE 中使用它为您的 PHP 项目生成 HTML 文档。在我们的下一个教程中,我们将学习如何从 IDE 中使用该工具。

生成 API 文档

我们将使用 ApiGen 为示例 PHP 项目Chapter5生成 HTML 文档,并且该工具从项目中可用的 DocBlocks 中提取文档。生成过程可以在 IDE 的输出窗口中查看。最后,生成的 HTML 文档将在 Web 浏览器中打开。

操作时间-使用 ApiGen 生成文档

使用 IDE 集成的 ApiGen,我们将运行文档生成器。请注意,我们需要定义目标目录以存储 HTML 文档。根据以下步骤为我们的示例项目创建 HTML 文档:

  1. 右键单击chapter5项目节点。从上下文菜单中,选择属性 | ApiGen,将显示以下项目属性窗口:Time for action — generating documentation using ApiGen

  2. 从上一个项目属性窗口中,定义 HTML 页面将存储的目标目录,并取消选中PHP框以排除文档中的 PHP 默认元素。在此项目中,让我们在项目内创建一个名为doc的目录作为目标目录,以便可以在http://localost/chapter5/doc/上浏览文档。

  3. 点击确定保存设置。

  4. 现在,右键单击chapter5项目节点。这将生成一个菜单,看起来类似于以下屏幕截图:Time for action — generating documentation using ApiGen

  5. 从上一个项目上下文菜单中,选择生成文档以开始从给定的 DocBlocks 生成 HTML 文档的过程。

  6. 在上一步中选择生成文档后,HTML 文档生成器开始进行进展,并完成了 HTML 文档。生成过程总结在输出窗口中,如下所示:Time for action — generating documentation using ApiGen

  7. 此外,整个项目的 HTML 文档也已在浏览器中打开,看起来类似于以下内容:Time for action — generating documentation using ApiGen

在上面的屏幕截图中,我们可以看到已为整个项目创建了 HTML 文档。文档按照包、类和函数在左侧框架中的顺序进行组织。

  1. 浏览为项目创建的链接,并探索类和方法在那里是如何表示的。您可以点击上一个窗口中的TestChild类链接,以获取以下屏幕截图:Time for action — generating documentation using ApiGen

  2. 在上面的屏幕截图中,我们可以看到类继承也使用树形图表示,并且根据其 DocBlock 适当装饰了类的文档。

刚刚发生了什么?

我们从源代码注释块中创建了专业的 API 文档,并发现了类在最终文档中是如何被正确组织的。请注意,ApiGen 在生成的 HTML 界面上为类、函数等提供了搜索功能,并提供了可自定义的模板功能,以修改整体文档的外观。我们现在有足够的信心有效地为 PHP 源代码进行文档化。

快速测验 —— 复习标签

  1. 以下哪个标签仅适用于函数或方法?

  2. @author

  3. @package

  4. @param

  5. @link

  6. 以下哪个标签可用于文档化任何元素的发布版本?

  7. @version

  8. @since

  9. @deprecated

  10. @todo

  11. 以下哪个标签可以用作内联标签?

  12. @example

  13. @param

  14. @version

  15. @see

尝试更多的英雄 —— 处理文档

每次运行 NetBeans 文档生成器时,它都会清除目标目录并在那里创建一组新的 HTML 文档。尝试对接口、常量、特性等进行注释,并运行文档生成器以测试生成的 API 文档。

总结

在本章中,我们已经讨论并练习了如何使用 NetBeans 为 PHP 应用程序文档化源代码。

我们特别关注了以下主题:

  • PHPDoc 标准和标签

  • 文档化 PHP 函数/方法、类及其变量

  • 文档化 TODO 任务

  • 使用 NetBeans 配置 ApiGen

  • 使用 ApiGen 进行 API 文档

最后,使用自动文档生成器非常有趣,并且在几秒钟内生成了 HTML 文档。

在我们下一章进行协作 PHP 开发时,需要这样的源代码文档,以便在开发团队内保持良好的实践。在下一章中,我们将学习如何从 NetBeans 使用版本控制系统(Git)。

第六章:了解 Git,NetBeans 方式

尽早提交,经常提交。

在本章中,我们将介绍版本控制系统,以管理我们源代码中的更改。为此,我们将学习使用Git,一个免费的开源分布式版本控制系统。我们将逐步从 NetBeans 中使用 Git。特别是,我们将讨论以下问题:

  • 版本控制系统

  • 分布式版本控制系统DVCS

  • Git-快速和分布式版本控制系统

  • 初始化 Git 存储库

  • 克隆 Git 存储库

  • 将文件暂存到 Git 存储库

  • 将更改提交到 Git 存储库

  • 比较文件修订版,并恢复更改

  • 与远程存储库一起工作-获取、拉取和推送

  • 使用分支-创建、检出、切换、合并和删除

版本控制系统

版本控制系统(源代码管理SCM的一个方面)是一种技术和实践的组合,用于跟踪和控制对项目文件的更改,特别是对于源代码、文档和网页。

版本控制如此普遍的原因是它几乎涵盖了项目运行的每个方面-开发者之间的沟通、发布管理、错误管理、代码稳定性和实验性开发工作,以及特定开发人员的更改归因和授权。版本控制系统在所有这些领域提供了一个中央协调力量。

版本控制的核心活动是变更管理-识别对项目文件所做的每个离散更改,用其元数据注释每个更改,例如更改的时间戳和作者,然后以任何方式回放这些事实给询问的人。这是一种通信机制,其中变更是信息的基本单元,这些变更可以与某些类型的合并文件进行比较和恢复。

版本控制系统

现在让我们讨论常见的版本控制系统术语:

  • 存储库:存储库,也称为repo,是文件的当前和历史数据存储的地方。版本控制系统的核心是存储库,该存储库可以集中或分布式存储该系统的数据。存储库通常以文件系统树的形式存储信息,这是文件和目录的层次结构。

  • 工作副本:工作副本是开发者的私有目录树,包含项目的源代码文件,可能还包括其网页或其他文档。工作副本还包含一些由版本控制系统管理的元数据,告诉工作副本来自哪个存储库,文件的“修订版”是什么,等等。通常,每个开发者都有自己的工作副本,他在其中进行更改和测试,并从中提交。

在分散式版本控制系统中,每个工作副本本身就是一个存储库,更改可以推送到(或拉入)任何愿意接受它们的存储库。

  • 工作树:这是实际的、已检出的文件树。工作树通常等于 HEAD,再加上您所做的但尚未提交的本地更改。

  • Origin:这指的是原始存储库,或默认的上游存储库。大多数项目至少有一个上游项目进行跟踪。默认情况下,origin 用于此目的。

  • Master:这指的是默认的开发分支。

  • HEAD:这是分支中的最新版本。

  • 提交:用于对项目进行更改;更正式地说,以一种可以合并到项目未来发布中的方式将更改存储在版本控制数据库中。提交创建一个新版本,本质上是项目中文件的快照在特定时间点上。

  • 索引:这是一个带有统计信息的文件集,其内容被存储。

索引被用作工作目录和存储库之间的暂存区。您可以使用索引来积累一组要一起提交的更改。当您创建一个提交时,提交的是当前在索引中的内容,而不是在您的工作目录中的内容。

  • 修订:“修订”通常是指特定文件或目录的特定版本。例如,如果项目从文件F的修订6开始,然后有人对F进行了更改,这将产生F的修订7

  • 检出:检出是从存储库获取项目、文件、修订等的过程。检出通常会生成一个名为“工作副本”的目录树,可以将更改提交回原始存储库。

  • 分支:这是项目的一个副本,在版本控制下,但是被隔离了,所以对分支的更改不会影响项目的其余部分。分支也被称为开发线。即使项目没有明确的分支,开发仍然被认为是在“主分支”上进行的,也被称为“主线”或“主干”。

  • 合并:合并需要将一个分支的更改复制到另一个分支。这涉及从主干到其他分支的合并,或者反之亦然。

合并还有第二个相关的含义——当版本控制系统发现两个人以非重叠方式更改了同一个文件时,它会执行合并。由于这两个更改不会相互干扰,当一个人更新他们的文件副本(已包含他们自己的更改)时,另一个人的更改将自动合并进来。这是非常常见的,特别是在多人同时修改同一代码的项目中。当两个不同的更改重叠时,结果就是冲突

  • 冲突:当两个人试图对代码中的同一区域进行不同的更改时,就会发生冲突。所有版本控制系统都会自动检测冲突,并通知至少一个涉及的人,他们的更改与其他人的冲突。然后由该人解决冲突并将解决方案通知给版本控制系统。

  • 还原:为了回滚到上一个修订版本,我们会还原更改;也就是说,我们放弃更改并返回到上次更新的点。当你破坏了本地构建并且无法弄清楚如何让它再次工作时,这是很方便的。有时候还原比调试更快,特别是如果你最近已经检查过。

  • 差异:这是一个可查看的更改表示,它显示了哪些行发生了更改,以及如何更改,以及两侧周围上下文的几行。已经熟悉某些代码的开发人员通常可以阅读针对该代码的差异,理解更改的作用,甚至发现错误。

  • 标签:标签是指定修订的特定文件集的标签。标签通常用于保留项目的有趣快照。例如,通常为每个公共发布制作一个标签,以便可以直接从版本控制系统获取组成该发布的确切文件/修订。

分布式版本控制

一些版本控制系统是集中式的——有一个单一的主存储库,存储了对项目所做的所有更改。其他是分散式的——每个开发者都有自己的存储库,更改可以在存储库之间任意交换。

在分布式版本控制系统(如 Git、Mercurial 或 Bazaar)中,开发者(客户端)不仅仅是检出文件的最新快照,还完全镜像存储库。

让我们看一下分布式版本控制的示意图:

分布式版本控制

Git 快速分布式版本控制系统

Git 是一个免费的开源分布式版本控制系统,旨在以速度和效率处理从小型到非常大型的项目。在 Git 中,您可以拥有自己的本地存储库,并几乎所有操作都在本地进行。

每个 Git 克隆都是一个完整的存储库,具有完整的历史记录和完整的修订跟踪功能,不依赖于网络访问或中央服务器。分支和合并都很快且易于操作。

Git 用于对文件进行版本控制,类似于 Mercurial、Subversion、CVS、Perforce 等工具(git-scm.com/)。

注意

Git 最初是由Linus Torvalds为 Linux 内核开发而设计和开发的。

了解 Git,NetBeans 的方式

NetBeans IDE 为 Git 版本控制客户端提供了出色的支持。IDE 的 Git 支持允许您直接从 IDE 中的项目执行版本控制任务。您可以通过两种方法拥有 Git 存储库,第一种方法是将现有项目或目录导入 Git,第二种方法是从另一台服务器计算机克隆现有的 Git 存储库。

在接下来的章节中,我们将使用 NetBeans 尝试初始化一个 Git 存储库,并学习如何克隆一个 Git 存储库。为此,我们将创建一个名为Chapter6的示例 NetBeans 项目,其中项目元数据存储在一个单独的目录中,因为我们不需要将项目元数据纳入版本控制,并将在项目目录中进行练习。

初始化 Git 存储库

如果您要在 Git 中跟踪现有项目,或者希望将现有项目纳入版本控制,则需要初始化 Git 存储库。

操作时间-初始化 Git 存储库

要从现有项目或尚未纳入版本控制的源文件初始化 Git 存储库,可以按照以下步骤进行:

  1. 右键单击项目Chapter6,然后从上下文菜单中选择版本控制|初始化 Git 存储库操作时间-初始化 Git 存储库

  2. 现在,在初始化 Git 存储库对话框中指定存储库将被创建的目录路径。在我们的情况下,我们选择相同的项目路径。

  3. 点击确定,您可以在输出窗口(Ctrl+4)中检查存储库创建的进度或状态,如下所示:操作时间-初始化 Git 存储库

在您的项目目录下将创建一个.git子目录,其中存储了项目快照的所有数据。Git 开始对指定目录中的所有文件进行版本控制。

您可以看到项目文件都标记为-/Added。要查看文件状态,只需将鼠标悬停在文件名上,如下截图所示:

操作时间-初始化 Git 存储库

我们可以看到文件状态显示为绿色,位于斜杠右侧。

还要注意,index.php文件中新增的行以绿色标记,如前面的截图所示。您可以在绿色高亮上悬停以查看自上一版本以来新增的行数。一旦 Git 存储库创建完成,IDE 中的所有 Git 选项都可以直接在团队菜单或当前项目的团队|Git子菜单下使用。

刚刚发生了什么?

我们已成功使用 NetBeans 初始化了 Git 存储库,将现有项目文件纳入版本控制。因此,我们拥有了自己的完整的本地 Git 存储库。

要使用远程仓库,您可以将远程 Git 仓库添加为此初始化仓库的源。这样,您可以将本地仓库与远程仓库同步。现在,我们可以添加文件或直接将它们提交到本地 Git 仓库;但在此之前,让我们尝试通过克隆 Git 仓库的第二种方法。请注意,除了克隆仓库,我们还可以创建另一个新项目。

克隆 Git 仓库

假设您已被添加为 Git 下维护的现有项目的合作者。如果您想获取现有 Git 仓库的副本或者您想要贡献的项目,您将需要该仓库的 Git 克隆。直接合作者是由仓库所有者添加的值得信赖的有经验的开发者,他们为项目做出贡献并可以在原始仓库中执行常规的 Git 操作。

在本教程中,我们已经在 GitHub.com(免费的 Git 托管)上创建了一个名为chapter6demo的 Git 仓库(github.com/mahtonu/chapter6demo),并且为测试目的,我们已经将另一个账户添加为合作者。现在,我们将从 GitHub.com 克隆该仓库,并使用合作者账户在 NetBeans IDE 中练习常规的 Git 功能。要通过 SSH 进行克隆并作为 GitHub 项目的合作者,您需要一个 GitHub 账户,并且需要被相应项目所有者添加为项目成员。

注意

要在 GitHub.com 上托管您的源代码,请注册并在那里创建您自己的仓库。

此外,您需要在设置 | SSH 密钥github.com/settings/ssh)中添加您的公钥,以便从您的计算机通过安全外壳SSH)进行 Git 操作。

对于 Windows 操作系统,您可以使用PuTTYgen (www.chiark.greenend.org.uk/~sgtatham/putty/download.html) 生成您的密钥,并且在 IDE 中使用之前必须将其转换为OpenSSH格式。

在进行以下教程之前,您可以在 GitHub 上创建一个示例仓库,并将另一个 GitHub 测试账户添加为合作者(从ADMIN | Collaborators),并记得为这些相应的账户添加公钥。

操作时间-通过 SSH 协议从 GitHub 克隆 Git 仓库

在本教程中,我们将作为 GitHub 项目的合作者,并且我们的 SSH 公钥已添加到 GitHub 账户中。我们将使用 NetBeans 添加我们的 SSH 私钥。除了仓库克隆,NetBeans 还提供了创建一个全新项目的选项:

  1. 选择Team | Git | Clone...,将显示克隆仓库向导。

  2. 仓库 URL字段中指定所需仓库的路径,例如git@github.com:mahtonu/chapter6demo.git

  3. 验证用户名git

  4. 浏览私钥文件的位置。

  5. 添加在密钥生成期间创建的Passphrase,并(可选)选择保存 Passphrase复选框。克隆仓库向导中的远程仓库页面看起来类似于以下截图:操作时间-通过 SSH 协议从 GitHub 克隆 Git 仓库

  6. 点击下一步,并在远程分支页面选择需要获取(下载)到本地仓库的仓库分支,例如master操作时间-通过 SSH 协议从 GitHub 克隆 Git 仓库

  7. 点击下一步,并填写或浏览父目录,克隆目录将放置在目标目录页面。仓库名称会自动填写在克隆名称字段中,这将是本地克隆目录的名称。操作时间-通过 SSH 协议从 GitHub 克隆 Git 仓库

  8. 在此屏幕截图中,默认情况下Checkout Branch设置为master*Remote Name设置为origin,这意味着这是我们要克隆的原始存储库。同时,保持克隆后扫描 NetBeans 项目复选框选中。

  9. 单击完成,看看 NetBeans 输出 窗口中发生了什么。您将被提示从克隆源创建一个新的 NetBeans 项目,如下面的屏幕截图所示:行动时间——通过 SSH 协议从 GitHub 克隆 Git 存储库

我们还从克隆的源中创建了 NetBeans 项目,方法是选择新项目并选择现有源选项,并将 NetBeans 项目元数据存储到单独的目录中,因为我们不希望它们在 Git 下。此外,您将在项目中找到一个README文件,它已经被跟踪,并来自远程源存储库。

刚刚发生了什么?

我们已经通过 NetBeans 使用 SSH 协议克隆了一个存储库。这些克隆中的每一个都充当一个完全成熟的存储库,它们内部包含所有的修订信息。因此,现在我们有一个可用的本地存储库,并且也可以使用远程源。我们已经将我们的 GitHub 帐户之一添加到 GitHub 项目中作为协作者,因为我们获得了对该项目的访问权限,所以我们使用 NetBeans IDE 从那里克隆了它。您可以从 IDE 执行大多数 Git 操作,并且可以在输出窗口中看到这些操作的结果。

从这一点开始,我们将学习如何从 IDE 使用 Git 操作。接下来的部分是从协作者的角度进行说明,包括添加、编辑、比较、提交文件、推送更改到远程等等。

小测验——理解 Git

  1. 哪个是 Git 的正确功能?

  2. 分布式版本控制系统

  3. 问题跟踪器

  4. 集中式存储库

  5. 始终依赖网络

  6. 哪个不是 Git 存储库的功能?

  7. 每个 Git 克隆都是一个完全成熟的存储库

  8. 本地 Git 存储库是原始存储库的子集

  9. 所有提交都是本地的

  10. 可能有一个远程源

  11. 在我们之前的部分中,哪个关键文件被添加到 IDE 中?

  12. 公钥文件

  13. 私钥文件

  14. 两个关键文件

  15. 打开 SSH 文件

  16. 在 NetBeans IDE 中,对于新创建的文件,在存储库的上下文中,文件状态符号将是什么?

  17. 已添加/-

  18. -/已添加

  19. 已添加/+

  20. +/已添加

将文件分段到 Git 存储库

要开始跟踪新文件,并且还要对 Git 存储库中已经跟踪的文件进行分段更改,您需要将其添加到存储库中。分段意味着在 Git 下添加新文件或修改文件以进行“待提交的更改”。

将文件添加到 Git 存储库时,IDE 首先在索引中组合和保存项目的快照。在执行提交后,IDE 将这些快照保存在 HEAD 中。

行动时间——将文件分段到 Git 存储库

在本教程中,我们将学习如何将文件分段到我们的本地 Git 存储库。分段是将更改添加到待提交状态。以下文件可以称为分段文件:

  • 向存储库添加了一个新创建的文件

  • 修改并添加到存储库的现有文件

首先,我们将向存储库添加一个新创建的文件,然后我们将向存储库添加一个修改后的文件:

  1. 首先,我们将打开 NetBeans Git 的显示更改查看器窗口。右键单击chapter6demo项目节点,然后选择Git | 显示更改。NetBeans 将扫描存储库并在窗口中显示任何更改。现在,可以实时从此窗口查看存储库中的任何更改。

  2. 现在,以通常的方式将一个新文件添加到 NetBeans 项目中,即test.php。您可以看到新的test.php文件已经在编辑器中打开;在项目窗格上悬停在文件名上会显示 Git 的文件状态。行动时间——将文件分段到 Git 存储库

在这个截图中,我们可以看到 Git 窗口底部显示test.php作为新添加的文件,标记为-/Added,这意味着它尚未添加到仓库中。

  1. 右键单击test.php,然后从上下文菜单中选择Git | Add。现在,test.php文件可以在 Git 下进行跟踪。您可以在 Git 窗口中看到文件状态为Added/-,这意味着文件已准备好提交或已暂存。此外,您还可以在输出窗口中查看 Git 操作状态。

  2. 现在,我们将打开现有的README文件,尝试在其中添加一些行,并保存,以观察其在本地仓库中的影响。请注意,该文件来自原始远程仓库。我们还可以立即在 Git 窗口中查看任何更改。操作时间-将文件暂存到 Git 仓库

在这个截图中,我们可以看到文件中添加了一行新行(标记为绿色),开头说明新行已从较早版本添加。此外,在 Git 窗口中,您可以看到文件状态显示为-/Modified,这意味着文件已被修改,但尚未添加到暂存区。

  1. 右键单击README,然后从上下文菜单中选择Git | Add。现在,README文件的更改已经暂存以进行提交。您可以在 Git 窗口中看到文件状态为Modified/-,这意味着文件已准备好提交或已暂存。请注意,每次完成文件的修改后,您都可以重复此步骤,以将更改暂存以进行下一次提交。此外,修改后的文件名在 NetBeans Projects窗格中变为蓝色,新添加的文件名变为绿色。

刚刚发生了什么?

我们刚刚学习了如何为本地仓库中已做的更改暂存文件,这些更改将被提交。因此,每次有更改时,我们可以对这些文件应用Git | Add,以便使它们可以用于下一次提交。此外,我们已经看到 Git 窗口显示了文件的实时状态,与仓库的状态相对比。

请注意,Team菜单包含了用于当前项目所使用的特定版本控制系统的所有选项。例如,在我们的情况下,我们可以看到所有 Git 选项都在Team菜单和Team | Git子菜单下都可用。

在源代码编辑器中查看更改

当您在 IDE 的源代码编辑器中打开一个有版本的文件时,您可以在修改文件时查看文件发生的实时变化,与 Git 仓库中的基本版本进行对比。当您工作时,IDE 会在源代码编辑器的边距中使用颜色代码传达以下信息:

  • 蓝色:表示自较早版本以来已更改的行

  • 绿色:表示自较早版本以来已添加的行

  • 红色:表示自较早版本以来已删除的行

源代码编辑器的左边缘显示了逐行发生的更改。当您修改给定行时,更改会立即显示在左边缘中。

源代码编辑器的右边缘为您提供了一个概览,显示了对文件的整体所做的更改,从上到下。当您对文件进行更改时,颜色编码会立即生成。您可以在右边缘的特定位置单击,立即将内联光标移动到文件中的该位置。

Git 窗口

您已经在 Git 窗口中实时查看了本地工作树中所选文件夹中文件的所有更改的列表,如下截图所示:

Git 窗口

在此版本窗口中,您可以看到带有按钮的工具栏,它使您能够在列表中显示的所有文件上调用最常见的 Git 任务。使用工具栏中的按钮,您可以选择显示在索引或 HEAD 中具有差异的文件列表,工作树和索引中的文件,或工作树和 HEAD 中的文件。您还可以单击列标题,按名称、状态或位置对文件进行排序。

尝试一下-取消暂存的文件

假设您已更改了两个文件,并希望将它们作为两个单独的更改提交,但无意中将它们都暂存了。尝试使用“Team | Git | Reset...”取消暂存的文件;您可以从那里重置 HEAD。

提交更改到存储库

在本节中,我们将学习如何提交已暂存的更改。在上一节中所做的更改将提交到本地存储库中。

行动时间-将更改提交到本地存储库

要将更改提交到本地存储库,请按照以下步骤进行:

  1. 选择要提交到本地存储库的文件;即test.php。右键单击它们,然后从上下文菜单中选择“Git | Commit...”。将显示提交对话框,如下图所示:行动时间-将更改提交到本地存储库

在此屏幕截图中,您可以看到“提交消息”框。 “要提交的文件”列表显示要提交的暂存文件。

  1. 在“提交消息”文本区域中键入一条消息,描述源代码提交的意图。提交消息应传达更改的有意义描述以及原因。

  2. 您可以通过取消选中行来排除要提交的文件,或者可以通过右键单击行来指定一些附加操作。完成后单击“提交”。

刚才发生了什么?

IDE 执行了提交并将快照存储到存储库中。IDE 的状态栏位于界面右下角,在提交操作发生时显示。成功提交后,“项目、文件”和“收藏夹”窗口中的版本控制徽章消失,已提交文件的颜色代码恢复正常。还要注意,Git 窗口中的文件清除了,这意味着存储库是最新的,没有可用的更改。

尝试一下-一起添加和提交所有文件

我们已经将新文件放入存储库,然后提交了这些更改。现在,直接提交新文件,让它们从 IDE 中自动暂存。您可以将新文件添加到项目中;尝试直接提交它们,然后查看差异。

比较文件修订

比较文件版本是在使用版本控制项目时的常见工作。IDE 使您能够使用Diff命令比较修订版本。文件修订可以进行比较,以查看从一个修订到另一个修订的源更改。

行动时间-使用 IDE 进行差异

为了比较文件修订,您可以使用 IDE 的Diff功能,并按照以下步骤进行:

  1. 选择一个名为README的版本化文件,并修改文件的一些行。

  2. 右键单击文件,然后从上下文菜单中选择“Git | Diff”。IDE 的主窗口中打开了一个图形“Diff”查看器,用于所选文件和修订的比较。 “Diff”查看器在并排面板中显示两个副本。较新的副本显示在右侧。因此,如果您要比较存储库修订与您的工作树,工作树将显示在右侧面板中:行动时间-使用 IDE 进行差异

Diff查看器使用与其他地方相同的颜色代码来显示版本控制更改。在上一张屏幕截图中,绿色块表示已添加到更高版本的内容。红色块表示从较早版本中删除了内容。蓝色块表示在突出显示的行中发生了更改。

刚刚发生了什么?

Diff查看器工具栏还包括按钮,使您能够调用列表中显示的所有文件的最常见的 Git 任务。如果您正在对工作树中的本地副本进行差异比较,编辑器使您能够直接从Diff查看器中进行更改。为此,您可以将光标放在Diff查看器的右窗格内,并相应地修改文件。否则,使用显示在每个突出显示的更改旁边的内联图标。

撤销存储库的本地更改

撤销是为了丢弃对工作树中选定文件所做的本地更改,并用索引或 HEAD 中的文件替换这些文件。

行动时间 - 撤销工作树的更改

要撤销更改,请按以下步骤进行:

  1. 从前一节中,修改后的README文件的Diff窗口提供了一个撤销修改的功能。此外,Git 窗口提供了撤销修改的按钮。

  2. 右键单击README文件,然后从上下文菜单中选择Git | Revert | Revert Modifications,或者从Diff窗口单击Revert Modifications按钮。类似于以下对话框将打开:行动时间 - 撤销工作树的更改

  3. 指定附加选项(例如仅将索引中未提交的更改撤消到 HEAD)

  4. 单击Revert

刚刚发生了什么?

IDE 撤消了指定的更改,并用索引或 HEAD 中的文件替换了这些文件。通过这种方式,您可以轻松地撤销修改或撤销提交。

快速测验 - 使用 Git

  1. 将文件添加到 Git 存储库时,IDE 首先会在以下哪个地方组成并保存项目的快照?

  2. 索引

  3. 存储库

  4. 主分支

  5. 源编辑器左边距中的哪种颜色表示自较早版本以来已更改的行?

  6. 绿色

  7. 蓝色

  8. 红色

  9. 黄色

  10. Diff用于以下哪些操作?

  11. 查看文件历史记录

  12. 比较两个版本

  13. 比较两个文件的两个版本

  14. 以上所有

  15. 在撤销更改的情况下可以做什么?

  16. 撤销工作树和索引中的所有未提交更改

  17. 将未提交的更改撤消到工作树中的 HEAD 状态

  18. 仅将未提交的更改撤消到索引中的 HEAD

  19. 以上所有

英雄尝试者 - 撤销提交

尝试使用提交 ID 从 IDE 中撤销特定的提交。为此,您可以从 IDE 中选择Revert | Revert Commit...

与远程存储库一起工作

与其他开发人员一起工作或在协作开发环境中,每个人都希望分享自己的工作,这涉及到从互联网或网络上托管的远程存储库中获取、推送和拉取数据。

获取源代码更新

获取会从您尚未拥有的原始远程存储库中获取更改。它不会更改任何您的本地分支。获取会获取远程存储库中的所有分支,您可以随时将其合并到您的分支中或仅进行检查。

行动时间 - 获取源代码更新

要获取更新,请按以下步骤进行:

  1. 右键单击项目节点,选择Git | Remote | Fetch,然后显示从远程存储库获取向导。行动时间 - 获取源代码更新

  2. 在向导的远程存储库页面,我们将使用配置的存储库(使用之前配置的存储库路径)并单击下一步

  3. 在向导的远程分支页面上,选择要获取更改的分支,然后单击完成。在存储库浏览器窗口(TEAM | Git | 存储库浏览器)中找到远程分支的本地副本。执行操作的时间-获取源代码更新

刚刚发生了什么?

创建了远程分支的本地副本。所选分支已在Git 存储库浏览器中的分支 | 远程目录中更新。接下来,获取的更新将合并到本地分支中。

从远程存储库拉取更新

从远程 Git 存储库拉取一些更新时,会从中获取更改,并将其合并到本地存储库的当前 HEAD。

执行操作的时间-从远程存储库拉取更新

要执行拉取,完成以下步骤:

  1. 右键单击项目节点,选择Git | 远程 | 拉取,然后显示从远程存储库拉取向导。执行操作的时间-从远程存储库拉取更新

  2. 在向导的远程存储库页面上,我们将使用配置的存储库(使用之前配置的存储库路径),然后单击下一步

  3. 在向导的远程分支页面上,选择分支,即master -> origin/master(远程分支origin/master将合并到当前分支),以拉取更改,然后单击完成

刚刚发生了什么?

您的本地存储库与原始存储库同步。在远程分支页面上,我们选择的分支,即master -> origin/master,将合并到我们的当前分支。您还可以在 IDE 的右下角或输出窗口中看到拉取状态。

简单来说,Git Pull执行Git Fetch,然后执行Git Merge

将源代码更改推送到远程存储库

为了分享到目前为止所做的出色提交,您希望将更改推送到远程存储库。再次,您可以将新分支和数据推送到远程存储库。

执行操作的时间-推送源代码更改

要将本地 Git 存储库中的更改贡献到公共/远程 Git 存储库中,请执行以下步骤:

  1. 右键单击项目节点,选择Git | 远程 | 推送,然后显示推送到远程存储库向导。执行操作的时间-推送源代码更改

  2. 在向导的远程存储库页面上,我们将使用配置的存储库(使用之前配置的存储库路径),然后单击下一步

  3. 在向导的选择本地分支页面上,选择要将更改推送到的本地分支,即master -> master,然后单击完成

  4. 更新本地引用页面上,选择要在本地存储库的远程目录中更新的分支(即master -> origin/master),然后单击完成

刚刚发生了什么?

指定的远程存储库分支已使用本地分支的最新状态进行更新。您的本地存储库的分支 | 远程目录也已更新。因此,您的更改已在远程存储库中生效,其他协作者可以将更改拉取到他们自己的存储库中。

使用分支工作

意图启动一条替代的开发线路会在源代码管理系统中生成一个分支。分支帮助您管理工作上下文并提供单独的工作空间。通常,主分支是最好的代码所在地;除此之外,还可能有一个开发分支,其中可以存放持续开发的代码。同样,明智的软件开发使用分支来维护功能、发布、热修复等。

为了开发新版本和维护旧版本,分支是必不可少的。在下图中,描述了一个通用的 Git 分支模型:

使用分支工作

在这个图表中,我们可以看到开发分支主分支合并为一个新版本,并且一个新功能已经与开发分支合并。

NetBeans 支持您使用 Git 分支执行以下操作:

  • 创建分支

  • 检出分支

  • 切换分支

  • 合并分支

  • 删除分支

创建分支

如果您想要在不干扰主干的情况下为稳定性或实验目的创建文件系统的单独版本,可以创建一个分支。

执行操作-创建分支

要创建一个本地分支,请完成以下步骤:

  1. 右键单击项目节点,选择Git | 分支 | 创建分支,将显示创建分支对话框。执行操作-创建分支

  2. 分支名称字段中,键入要创建的所需分支名称,即development

  3. 您可以通过在修订字段中输入提交 ID、现有分支或标签名称,或按选择查看存储库中维护的修订列表来输入所选项目的特定修订。默认的修订是来自主分支的最新修订。

  4. 可选地,在选择修订对话框中,展开分支并选择所需的分支,在相邻列表中指定提交 ID,并按选择

  5. 查看与分支来源的提交 ID,作者消息字段信息,并单击创建。该分支将添加到Git 存储库浏览器分支 | 本地文件夹中。执行操作-创建分支

刚刚发生了什么?

我们在本地存储库中创建了一个新分支。新分支包含了主分支的最新快照。新创建的分支还不是我们的工作分支。主分支仍然是工作分支;我们将选择检出新分支使其成为工作分支。请注意,我们可以从任何现有修订版本创建新分支。

检出分支

如果您想要编辑已经存在的分支上的文件,可以检出需要使用的分支以将文件复制到您的工作树。这将简单地切换到所需的分支。

执行操作-检出分支

检出修订版本

  1. 右键单击项目节点,选择Git | 检出 | 检出修订版本,将显示检出修订版本对话框。执行操作-检出分支

  2. 再次右键单击分支 | 本地 | 分支名称,在存储库浏览器窗口中从上下文菜单中选择,如下图所示。选择检出修订版本,将显示相同的对话框,并显示从该分支选择的最新修订版本。执行操作-检出分支

  3. 可选地,通过在修订字段中输入提交 ID、现有分支或标签名称,或按选择查看存储库中维护的修订列表来指定所需的修订。请注意,如果指定的修订引用有效的提交但未标记为分支名称,则您的 HEAD 将变为分离状态,您将不再位于任何分支上。

  4. 可选地,在选择修订对话框中,展开分支并选择所需的分支,在相邻列表中指定提交 ID,并按选择

  5. 查看与所检出修订版本相关的提交 ID,作者消息字段信息。

  6. 要从所检出的修订版本创建一个新分支,请选择作为新分支检出选项,并在分支名称字段中输入名称。

  7. 检出以检出修订版本。

刚刚发生了什么?

工作树和索引中的文件已更新以匹配指定修订版本中的版本。

切换到分支

如果要将文件切换到已经存在的分支(例如,到一个不在您的分支顶部的提交),可以使用Team | Git | Branch | 切换到分支命令,在切换到所选分支对话框中指定分支,作为新分支进行检出(可选),然后按切换

切换到分支

检出文件

IDE 支持对 IDE 中当前选择的文件、文件夹或项目进行上下文敏感的检出。要从索引中检出一些文件(而不是分支),请从主菜单中选择Team | Git | Checkout | Checkout Files,然后显示Checkout Selected Paths对话框。

检出文件

从此对话框中,选择使用条目更新索引所选修订版选项。如果选择,索引将在检出本身之前使用所选修订版的状态进行更新(即,工作树和索引中的所选文件都将得到更新)。

指定所需的属性并进行检出。

合并

将分支上下文合并到当前分支。一旦在分支中隔离了工作,您最终会想要将其合并到主分支中。您可以将任何分支合并到当前分支。

行动时间-合并到当前分支

要将修改从存储库修订版传输到工作树,请执行以下操作:

  1. 从主菜单中选择Team | Git | 合并修订版。显示合并修订版对话框。行动时间-合并到当前分支

  2. (可选)在修订字段中输入所需的修订版,或按选择以查看存储库中维护的修订版列表。

  3. (可选)在选择修订版对话框中,展开分支并选择所需的分支,指定相邻列表中的提交 ID,然后按选择

  4. 查看与正在合并的修订版相关的提交 ID、作者消息字段信息。

  5. 合并

刚刚发生了什么?

在当前分支、您的工作树内容和指定分支之间进行三向合并。如果发生合并冲突,冲突文件将标有红色标记以指示这一点。合并后,您仍然可以提交更改,以便将其添加到 HEAD 中。

删除分支

要删除不必要的本地分支,请从主菜单中选择Team | Git | Repository Browser。在Git 存储库浏览器中,选择需要删除的分支。请注意,该分支应为非活动状态,这意味着它当前未在工作树中检出。

右键单击所选分支,并从弹出菜单中选择删除分支。在删除分支对话框中,按确定以确认删除分支。该分支将从本地存储库以及Git 存储库浏览器中删除。

远程存储库和分支的工作小测验

  1. 哪些 Git 操作对于远程存储库最相关?

  2. 提交、合并和还原

  3. 获取、拉取和推送

  4. 获取、拉取、推送和检出

  5. 添加、提交和推送

  6. 从远程存储库拉取更改后会发生什么?

  7. 从远程存储库获取更改

  8. 从中获取更改并将其合并到本地存储库的当前 HEAD 中

  9. 从中获取更改并将其合并到远程存储库的当前 HEAD 中

  10. 以上都不是

  11. 检出分支后会发生什么?

  12. 它立即切换到该分支,并且分支文件将可用于您的工作树

  13. 它将文件复制到您的工作树

  14. 创建一个新分支,并将其作为您的工作分支

  15. 以上所有

尝试一下英雄-创建标签

Git 使用两种主要类型的标签——轻量级和注释型。轻量级标签非常像一个不会改变的分支——它只是指向一个特定的提交。然而,注释型标签存储为 Git 数据库中的完整对象。这些标签检查总和包含标记者的姓名、电子邮件和日期以及标记消息。通常建议创建注释型标签,这样你就可以获得所有这些信息。现在,创建一个新标签,你可以从 IDE 中选择Git | Tag | Create Tag...

良好的实践和工作流程

以下讨论了一些指导方针和工作流程,以维护 Git 的良好实践:

  • 无论你在做什么,都要保持一个单独的分支。现在,当你想要将你的更改合并回主分支时,只需进行 Git 合并。

  • 尽可能保持你的分支最新,这涉及到检出或拉取更改。

  • 分支可以推送到原始仓库。这样做有几个原因。首先,如果你的工作站崩溃了,你不会丢失你的更改——这是版本控制系统的一个主要原因。其次,其他开发人员可以在需要时快速切换到你的分支。

  • 经常提交你的更改;当然,应该总是以逻辑片段提交更改。由于你的更改是在本地提交的,而不是到原始/主服务器(可以通过推送完成),你应该以有组织的方式提交更改。

  • 为每个提交消息和对修订历史进行更改的每个操作提供消息/注释。

  • 经常推送你的更改。如果你在自己的分支上开发,与其他人分开,你的更改不会影响其他人。

首选的 Git 工作流程:

  • 从主节点创建一个分支,检出它,并进行你的工作

  • 测试并提交你的更改

  • 可选地,将你的分支推送到远程仓库(origin)

  • 检出主分支,确保它与上游更改保持最新

  • 将你的分支合并到主分支

  • 再次测试(再次再次)

  • 将你的本地主分支推送到远程仓库的主分支(origin/master)

  • 删除你的分支(如果发布了,也要删除远程分支)

此外,即使是对于本地独立项目,使用版本控制系统也是值得的,因为代码更改可以很容易地在本地进行审查、回滚和备份。

总结

在本章中,我们讨论了版本控制系统以及它为何如此重要。此外,我们选择了 Git 作为分布式版本控制系统,并学习了如何在 NetBeans 中使用它。

我们特别关注了以下内容:

  • 分布式版本控制系统或 DVCS

  • 初始化一个 Git 仓库

  • 克隆一个 Git 仓库

  • 将文件暂存到 Git 仓库

  • 提交更改到 Git 仓库

  • 比较文件修订版本并撤销更改

  • 与远程仓库一起工作——获取、拉取和推送

  • 使用分支——创建、检出、切换、合并和删除。

最后,我们讨论了 Git 的实践和首选工作流程。现在,我们更有信心加入使用 Git 和 NetBeans 进行协作开发。

在下一章中,我们将创建一个新的 PHP 项目,包括用户注册、登录和注销,以提升我们的 PHP 应用程序开发技能到一个新的水平。

第七章:构建用户注册,登录和注销

提前规划。当诺亚建造方舟时,天空并没有下雨 - 理查德 C.库欣。

从本章开始,我们将亲自动手进行专业的 PHP 项目。我们将设计和开发一个 Web 应用程序,用户可以在其中注册自己,注册后可以登录到应用程序,查看和更新自己的配置文件等。

在本章中,我们将解决以下主题:

  • 应用程序架构

  • 设计 API

  • 用户注册

  • 用户登录和注销

  • 用户配置文件查看和更新

规划项目

项目规划总是被视为对未来的规划,这意味着项目应该被规划,因为它可以很容易地扩展或可重用,更模块化,甚至可扩展。对于这个项目,我们将以现实的方式设计应用程序架构,以便用户注册,登录和注销应用程序也可以在我们未来的项目中轻松使用。

我们将设计应用程序编程接口API)并使用该 API 构建应用程序。该 API 将为任何类型的用户注册或登录相关任务提供便利,因此项目的核心是 API。一旦 API 准备就绪,我们就可以轻松地使用该 API 构建多个应用程序。

首先,让我们考虑 API 设计。记住,我们将使用一些架构模式,即数据访问对象DAO)模式用于我们的项目。

提示

强烈建议在此项目中具有面向对象编程OOP)概念的先验知识。

理解应用程序架构

架构需要在数据存储,数据访问,应用服务和应用程序中构建层。这在以下截图中有所体现:

理解应用程序架构

每个层都可以被指定为一组类似的逻辑任务,因为数据存储层充当数据源,如关系数据库,文件系统或任何其他数据源。数据访问层与数据源通信,从存储层获取或存储数据,并在数据源中提供良好的抽象以交付给服务层。服务层是与应用层进行数据持久化的媒介,并提供其他服务,如验证服务。数据访问对象位于数据访问层,业务对象位于服务层。最后,应用程序位于应用层,直接与最终用户打交道。因此,在这样的分层设计中,服务层可以成为我们 API 的表面层。

现在,让我们考虑特定的功能,比如注册,登录,验证和数据抽象到每个单元或模块中。因此,每个层都将有强制单元,如下图所示:

理解应用程序架构

我们可以很容易地理解每个层都包含其适当的模块。例如,DAO 模块位于数据访问层,服务层具有其服务单元,如验证和用户服务模块,应用层包含用户登录,用户注册,用户配置文件和管理员模块。为了快速掌握架构概念,我们将尝试将每个模块保持为一个简单的 PHP 类,并附带代码。

因此,让我们快速看一下最终我们将要构建的内容。

以下截图代表了用户注册屏幕,包括姓名,电子邮件,密码电话字段:

理解应用程序架构

以下截图代表了用户登录屏幕,包括下次记住我选项:

理解应用程序架构

以下截图代表了用户配置文件视图,顶部有注销编辑帐户菜单:

理解应用程序架构

理解 DAO 模式

DAO 用于抽象和封装对数据源的所有访问。DAO 管理与数据源的连接,以获取和存储数据。

“DAO 实现了与数据源一起工作所需的访问机制。数据源可以是像 RDBMS 这样的持久存储,像 B2B 交换这样的外部服务,像 LDAP 数据库这样的存储库,或者像业务服务或低级套接字这样的业务服务。依赖 DAO 的业务组件使用 DAO 为其客户端提供的更简单的接口。

DAO 完全隐藏了数据源的实现细节,使其客户端(数据客户端)无法看到。因为 DAO 向客户端公开的接口在基础数据源实现更改时不会改变,所以该模式允许 DAO 适应不同的存储方案,而不会影响其客户端或业务组件。基本上,DAO 充当组件与数据源之间的适配器。该模式源自核心 J2EE 模式。”

java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

使用 DAO 的目的相对简单,如下所示:

  • 它可以在大部分应用程序中使用,无论何时需要数据存储

  • 它将所有数据存储的细节隐藏在应用程序的其余部分之外

  • 它充当您的应用程序与数据库之间的中介

  • 它允许将可能的持久性机制更改的连锁效应限制在特定区域

审查面向对象编程问题

让我们来看一下一些面向对象编程关键字,用于访问修饰符或属性:

  • 公共:此属性或方法可以在脚本的任何地方使用。

  • 私有:此属性或方法只能被其所属的类或对象使用;它不能在其他地方被访问。

  • 受保护:此属性或方法只能由其所属的类中的代码或该类的子类使用。

  • 最终:此方法或类不能在子类中被覆盖。

  • 摘要:这种方法或类不能直接使用,你必须对其进行子类化;它不能被实例化。

  • 静态:此属性或方法属于类本身,而不属于其任何实例。您也可以将静态属性视为全局变量,它们位于类内部,但可以通过类从任何地方访问。可以使用::运算符在类名之后访问静态成员。

命名空间

“命名空间(有时也称为名称范围)是一个抽象的容器或环境,用于保存一组唯一标识符或符号(即名称)。在命名空间中定义的标识符仅与该命名空间相关联。相同的标识符可以在多个命名空间中独立定义。”

  • 维基百科

命名空间从 PHP 5.3 版本开始引入。在 PHP 中,命名空间是使用命名空间块定义的。

在 PHP 世界中,命名空间旨在解决两个问题,即库和应用程序的作者在创建可重用的代码元素(如类或函数)时遇到的问题:

  • 能够避免您创建的代码与内部 PHP 类/函数/常量或第三方类/函数/常量之间的名称冲突

  • 能够别名(或缩短)设计用于缓解第一个问题的超长名称,提高源代码的可读性

PHP 命名空间提供了一种将相关类、接口、函数和常量分组的方法。以下是 PHP 中命名空间使用的示例:

namespace My;
class Foo {
...
}
namespace Your;
class Foo {
...
}

我们可以使用相同名称的类,并通过 PHP 命名空间引用,如下所示:

$myFoo = new \My\Foo();
$yourFoo = new \Your\Foo();

提示

我们将使用My作为整个应用程序的常见根命名空间,My\Dao用于我们的数据访问层类,My\Service用于我们的服务层类。

API

在面向对象的语言中,API 通常包括一组类定义的描述,以及与这些类相关联的一组行为。行为是指对象从该类派生后在特定情况下的行为规则。这个抽象概念与类方法(或更一般地说,与所有公共组件,因此所有公共方法,但也可能包括任何公开的内部实体,如字段、常量和嵌套对象)实现的真实功能相关联。

例如,表示堆栈的类可以简单地公开两个方法-push()(向堆栈添加新项)和pop()(提取最后一项,理想情况下放在堆栈顶部)。

在这种情况下,API 可以解释为两种方法-pop()push()。更一般地说,这个想法是,可以使用实现堆栈行为的Stack类的方法(堆栈暴露其顶部以添加/删除元素)。

到目前为止,一切都很好。我们对项目的概念有了了解,并且我们对 NetBeans 的功能非常了解。现在,让我们开始开发...

设计数据库

在本节中,我们将设计我们的 MySQL 数据库。由于我们已经学会了如何在 NetBeans 中创建数据库连接、新数据库、新表,以及如何运行 MySQL 查询,我们不会再讨论它们,但我们会看一下数据库模式定义。

CREATE TABLE 'users' (
'id' bigint(20) NOT NULL AUTO_INCREMENT,
'useremail' varchar(50) NOT NULL,
'password' char(32) NOT NULL,
'userhash' char(32) NOT NULL,
'userlevel' tinyint(4) NOT NULL,
'username' varchar(100) NOT NULL,
'phone' varchar(20) NULL,
'timestamp' int(11) unsigned NOT NULL,
PRIMARY KEY ('id'),
UNIQUE KEY 'useremail' ('useremail')
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

如您所见,我们在users表中有id(每个条目自动递增)作为主键,useremail作为唯一键。我们有一个password字段,用于存储用户的密码,最多 32 个字符;userhash有 32 个字符,用于存储用户的登录会话标识符;userlevel用于定义用户的访问级别,例如,普通用户为1,管理员用户为9,等等;一个username字段,支持最多 100 个字符;一个phone字段,用于存储用户的联系电话;以及一个timestamp字段,用于跟踪用户的注册时间。所选择的数据库引擎是InnoDB,因为它支持事务和外键,而MyISAM引擎不支持在任何用户进行insertupdate操作时避免表锁定。

因此,您只需要创建一个名为user的新数据库,只需在 NetBeans 查询编辑器中键入 MySQL 查询,然后运行查询,即可在user数据库中准备好您的表。

现在,创建一个 NetBeans PHP 项目,并开始用户 API 开发以及后续部分。

创建数据访问层

数据访问层将包括一个 User DAO 类,用于提供数据库抽象,以及一个抽象的 Base DAO 类,用于提供抽象方法,这是 User DAO 类需要实现的。此外,我们将创建抽象类,以提供 DAO 类在我们未来项目中创建的抽象方法。请注意,我们将使用 PHP 命名空间My\Dao用于数据访问层类。

创建 BaseDao 抽象类

这个抽象类将用于为子类实现方法提供基本框架。简单地说,基本的数据库操作是CRUDcreate, read, updatedelete。因此,抽象类将提供这些类型的抽象方法,以及每个子类都需要的方法。BaseDao抽象类将包含用于数据库连接的final方法,因此子类不需要再次编写它。为了更好地理解这一点,我们将把我们的 DAO 类放在一个名为Dao的单独目录中。

行动时间-创建 BaseDao 类

为了与数据库连接一起使用,我们将保留数据库访问凭据作为它们自己的类常量。此外,我们将使用 PDO 进行各种数据库操作。要创建Base类,请按照以下步骤进行:

  1. Dao目录中创建一个名为BaseDao.php的新 PHP 文件,并键入以下类:
<?php
namespace My\Dao;
abstract class BaseDao {
private $db = null;
const DB_SERVER = "localhost";
const DB_USER = "root";
const DB_PASSWORD = "root";
const DB_NAME = "user";
}
?>

您可以看到这个类使用命名空间 My\Dao;,并且在类名之前还有一个abstract关键字,这将类定义为抽象类。这意味着该类不能被实例化,或者至少在内部有一个抽象方法。此外,您可以看到添加的类常量,其中包含数据库信息和一个私有类变量$db来保存数据库连接。您可以根据需要修改这些常量。

  1. 现在,在类中添加以下getDb()方法:
protected final function getDb(){
$dsn = 'mysql:dbname='.self::DB_NAME.';host='.self::DB_SERVER;
try {
$this->db = new \PDO($dsn, self::DB_USER, self::DB_PASSWORD);
} catch (PDOException $e) {
throw new \Exception('Connection failed: ' . $e->getMessage());
}
return $this->db;
}

protected final function getDb()函数使用 PDO 连接到 MySQL 数据库。类的私有变量存储了可以用于数据库连接的 PDO 实例。此外,getDb()方法是finalprotected的,因此子类继承此方法并且无法覆盖它。

$dsn变量包含数据源名称(DSN),其中包含连接到数据库所需的信息。以下行创建一个 PDO 实例,表示与请求的数据库的连接,并在成功时返回一个 PDO 对象:

$this->db = new \PDO($dsn, self::DB_USER, self::DB_PASSWORD);

请注意,如果尝试连接到请求的数据库失败,DSN 会抛出PDOException异常。我们在 PDO 前面加上反斜杠\,这样 PHP 就知道它在全局命名空间中。

  1. 在类中添加以下abstract方法:
abstract protected function get($uniqueKey);
abstract protected function insert(array $values);
abstract protected function update($id, array $values);
abstract protected function delete($uniqueKey);

您可以看到子类要实现的方法被标记为abstract protectedget()方法将用于根据唯一表键从表中选择单个条目,insert()将在表中插入一行,update()将用于更新表中的一行,delete()将用于删除一个条目。因此,所有这些方法都被保留为抽象方法(没有方法体),因为它们将通过子类来实现。

刚刚发生了什么?

我们已经准备好继承 DAO 类的BaseDao抽象类。从该方法中创建并返回 PDO 实例,因此所有子类都将具有getDb()方法,并且可以使用此返回的实例来执行某种数据库任务。最后,子类将根据需要实现abstract方法。例如,在下一个教程中,User DAO 类将实现get()方法,以选择并返回与用户的电子邮件地址匹配的单个用户注册信息,或者 Product DAO 类将实现get()方法,以选择并返回与产品 ID 匹配的单个产品信息。因此,使用这样的抽象类的意图是为 Dao 类提供基本框架。

提示

使用 PDO 的最大优势之一是,如果我们想迁移到其他 SQL 解决方案,我们只需要调整 DSN 参数。

创建 User DAO 类

在本教程中,我们将创建 User DAO 类,该类将在其中提供各种数据库任务。这个类将隐藏数据库,不让连续的层次访问到它,也就是服务层类。因此,所有连续的层次类将调用这个类的方法,并且由这个类完成所有必要的数据库工作,而数据存储细节对它们完全隐藏。因此,这个类将充当数据库和应用程序之间的中介。

行动时间——创建 User Dao 类

我们将保留相关的用户常量作为类常量。我们将在这个类中编写BaseDao抽象类中方法的实现。简单地说,我们将把这些抽象方法的主体和我们自己所需的方法添加到类中。因此,请按照以下步骤进行操作:

  1. Dao目录中创建一个名为UserDao.php的新 PHP 文件,并键入以下代码:
<?php
namespace My\Dao;
class UserDao extends BaseDao {
private $db = null;
public function __construct() {
$this->db = $this->getDb();
}
}
$userDao = new \My\Dao\UserDao;
?>

正如您所看到的,该类位于My\Dao命名空间下,并扩展到BaseDao类,因此该类将具有从父类继承的方法。Dao 类有自己的私有$db,它存储了从继承的getDb()方法返回的 PDO 实例;正如您所看到的,这个$db变量被分配给了类构造函数。

另外,您可能已经注意到UserDao类已在底部实例化。

  1. 键入get()方法的实现(将该方法添加到类中),使其看起来类似于以下内容:
public function get($useremail) {
$statement = $this->db->prepare("SELECT * FROM users WHERE useremail = :useremail LIMIT 1 ");
$statement->bindParam(':useremail', $useremail);
$statement->execute();
if ($statement->rowCount() > 0) {
$row = $statement->fetch();
return $row;
}
}

您可以看到,prepare()方法准备了要由PDOStatement::execute()方法执行的 SQL 语句。正如您所看到的,以下语句查询用于从users表中选择一行的所有列,而:useremail中的给定电子邮件地址(与bindParam()绑定的参数)匹配useremail列。

SELECT * FROM users WHERE useremail = :useremail LIMIT 1;

最后,如果找到匹配的行,则获取包含用户详细信息的数组并返回。

  1. 键入insert()方法的实现,使其看起来类似于以下内容:
public function insert(array $values) {
$sql = "INSERT INTO users ";
$fields = array_keys($values);
$vals = array_values($values);
$sql .= '('.implode(',', $fields).') ';
$arr = array();
foreach ($fields as $f) {
$arr[] = '?';
}
$sql .= 'VALUES ('.implode(',', $arr).') ';
$statement = $this->db->prepare($sql);
foreach ($vals as $i=>$v) {
$statement->bindValue($i+1, $v);
}
return $statement->execute();
}

该方法接受传入的用户信息数组,准备users表的 MySQL insert查询,并执行该查询。请注意,我们已经将字段名称保留在$fields数组中,并将字段值保留在$vals数组中,这些值分别从传递的数组的键和值中提取。我们在准备的语句中使用?代替所有给定值,这些值将被绑定到PDOStatement::bindValue()方法中。bindValue()将一个值绑定到一个参数。

  1. update()方法的实现中键入代码,使其看起来类似于以下内容:
public function update($id, array $values) {
$sql = "UPDATE users SET ";
$fields = array_keys($values);
$vals = array_values($values);
foreach ($fields as $i=>$f) {
$fields[$i] .= ' = ? ';
}
$sql .= implode(',', $fields);
$sql .= " WHERE id = " . (int)$id ." LIMIT 1 ";
$statement = $this->db->prepare($sql);
foreach ($vals as $i=>$v) {
$statement->bindValue($i+1, $v);
}
$statement->execute();
}

它以与步骤 3相同的方式准备了 MySQL UPDATE查询语句,并执行了该查询以更新具有给定 ID 的行中的相应列值。

  1. 您可以将其他实现留空,如下所示,或根据需要添加自己的代码:
public function delete($uniqueKey) { }

由于我们可能会在将来实现删除用户的方法,因此我们留空了delete()方法的主体。

  1. 现在,我们需要在类中编写一些额外的方法。在注册用户时,我们可以检查我们的数据库,看看表中是否已经存在该电子邮件地址。键入以下方法:
public function useremailTaken($useremail) {
$statement = $this->db->prepare("SELECT id FROM users WHERE useremail = :useremail LIMIT 1 ");
$statement->bindParam(':useremail', $useremail);
$statement->execute();
return ($statement->rowCount() > 0 );
}

useremailTaken()方法接受一个电子邮件地址作为参数,以检查该电子邮件 ID 是否存在。它通过在WHERE子句中使用给定的电子邮件地址运行SELECT查询来执行该任务。如果找到任何行,则意味着该电子邮件地址已经存在,因此该方法返回true,否则返回false。通过这种方法,我们可以确保系统中一个电子邮件地址只能使用一次,并且不允许重复的电子邮件地址,因为这是一个唯一的字段。

  1. 为了在登录时确认用户的密码,请键入以下checkPassConfirmation()方法:
public function checkPassConfirmation($useremail, $password) {
$statement = $this->db->prepare("SELECT password FROM users WHERE useremail = :useremail LIMIT 1 ");
$statement->bindParam(':useremail', $useremail);
$statement->execute();
if ($statement->rowCount() > 0) {
$row = $statement->fetch();
return ($password == $row['password']);
}
return false;
}

该方法以$useremail$password作为参数,并选择password列以匹配用户的电子邮件。现在,如果找不到匹配条件的行,则意味着用户的电子邮件在表中不存在,并返回false 1;如果找到匹配的行,则从结果中获取数组以获得密码。最后,将从数据库中获取的密码与第二个参数中给定的密码进行比较。如果它们匹配,则返回true。因此,我们可以使用这个方法来确认给定对应用户的电子邮件的密码,当用户尝试使用它们登录时,可以轻松跟踪返回的布尔值的状态。

  1. 此外,我们已经在users表中添加了一个名为userhash的字段。该字段存储每个登录会话的哈希值(随机的字母数字字符串),因此我们希望确认userhash,以验证用户当前是否已登录。输入以下方法:
public function checkHashConfirmation($useremail, $userhash) {
$statement = $this->db->prepare("SELECT userhash FROM users WHERE useremail = :useremail LIMIT 1");
$statement->bindParam(':useremail', $useremail);
$statement->execute();
if ($statement->rowCount() > 0) {
$row = $statement->fetch();
return ($userhash == $row['userhash']);
}
return false;
}

checkHashConfirmation()方法与步骤 7中的先前方法相同,以$useremail$useremail作为参数,为给定的电子邮件地址获取useremail,并将其与给定的useremail进行比较。因此,可以用来比较useremail的方法对于会话和数据库都是相同的。如果相同,则意味着用户当前已登录,因为每次新登录都会更新表中对应的useremail

刚刚发生了什么?

对于多次使用不同参数值发出的语句,调用PDO::prepare()PDOStatement::execute()可以优化应用程序的性能,通过允许驱动程序协商查询计划和元信息的客户端和/或服务器端缓存,并帮助防止 SQL 注入攻击,通过消除手动引用参数的需要。

现在,我们已经准备好了 User DAO 类,并且 DAO 层也在我们 NetBeans 项目的Dao目录中完成。因此,User DAO 类已准备好提供所需的数据库操作。可以以我们所做的方式处理数据库操作,以便其他后续类不需要访问或重写数据库功能,因此已实现对数据库的抽象。我们可以在这个类中添加任何类型的与数据库相关的方法,以便让它们可用于 Service 类。现在,实例化的对象将作为数据访问对象,这意味着该对象可以访问数据源中的数据,任何人都可以通过该对象读取或写入数据。

小测验-回顾 PDO

  1. bindValue()bindParam()方法的哪一个是正确的?

  2. 您只能使用bindParam传递变量,使用bindValue可以同时传递值和变量

  3. 您只能使用bindParam传递值,只能使用bindValue传递变量

  4. 您可以使用bindParam传递变量,使用bindValue可以传递值

  5. 两者是相同的

现在,让我们为我们的 API 创建 Service 层。

创建 Service 层

Service 层包含用于为应用程序提供服务的类,或者简单地为应用程序提供框架。应用程序层将与该层通信,以获得各种应用程序服务,例如用户身份验证、用户信息注册、登录会话验证和表单验证。为了更好地理解,我们将把我们的服务类放在一个名为Service的单独目录中,并为该层的类使用命名空间My\Service

创建 ValidatorService 类

该类将执行验证任务,例如表单验证和登录信息验证,并保存以提供表单错误消息和字段值。

行动时间-创建 ValidatorService 类

我们将在类本身中保留一些验证常量,并且该类将使用My\Service作为其命名空间。按照以下步骤创建ValidatorService类:

  1. 在项目目录下创建一个名为Service的新目录。Service 类将位于此目录中。

  2. Service目录中创建一个名为ValidatorService.php的新 PHP 文件,并输入以下类:

<?php
namespace My\Service;
use My\Dao\UserDao;
class ValidatorService {
private $values = array();
private $errors = array();
public $statusMsg = null;
public $num_errors;
const NAME_LENGTH_MIN = 5;
const NAME_LENGTH_MAX = 100;
const PASS_LENGTH_MIN = 8;
const PASS_LENGTH_MAX = 32;
public function __construct() {
}
public function setUserDao(UserDao $userDao){
$this->userDao = $userDao;
}
}
$validator = new \My\Service\ValidatorService;
$validator->setUserDao($userDao);
?>

请注意,该类位于My\Service命名空间下,并导入My\Dao\UserDao类。

您可以看到类变量$values,它保存了提交的表单数值;$errors,它保存了提交的表单错误消息;$statusMsg,它保存了提交的状态消息,可以是成功或临时信息;以及$num_errors,它保存了提交表单中的错误数量。

我们还为验证目的添加了类常量。我们将用户名长度保持在 5 到 100 个字符之间,将password字段长度保持在 8 到 32 个字符之间。

由于该类依赖于 UserDao 类,我们使用setter方法setUserDao()$userDao对象注入其中;传递的$userDao对象存储在一个类变量中,以便 DAO 也可以在其他方法中使用。

  1. 现在,填写类构造函数,使其看起来类似于以下内容:
public function __construct() {
if (isset($_SESSION['value_array']) && isset($_SESSION['error_array'])) {
$this->values = $_SESSION['value_array'];
$this->errors = $_SESSION['error_array'];
$this->num_errors = count($this->errors);
unset($_SESSION['value_array']);
unset($_SESSION['error_array']);
} else {
$this->num_errors = 0;
}
if (isset($_SESSION['statusMsg'])) {
$this->statusMsg = $_SESSION['statusMsg'];
unset($_SESSION['statusMsg']);
}
}

您可以看到,$_SESSION['value_array']$_SESSION['error_array']都已经被最初检查。如果它们有一些值设置,那么将它们分配给相应的类变量,如下例所示:

$this->values = $_SESSION['value_array'];
$this->errors = $_SESSION['error_array'];
$this->num_errors = count($this->errors);

还调整了num_errorserrors数组的计数。请注意,$_SESSION['value_array']$_SESSION['error_array']中的值将由应用程序类设置,该类将使用此服务 API。立即在抓取其值后取消设置这些会话变量,以便为下一个表单提交做好准备。如果这些变量尚未设置,则num_errors应为0(零)。

它还检查$_SESSION['statusMsg']变量。如果已设置任何状态消息,请将消息抓取到相应的类变量中并取消设置。

  1. 现在,按照以下方式在类中输入表单和错误处理方法:
public function setValue($field, $value) {
$this->values[$field] = $value;
}
public function getValue($field) {
if (array_key_exists($field, $this->values)) {
return htmlspecialchars(stripslashes($this->values[$field]));
} else {
return "";
}
}
private function setError($field, $errmsg) {
$this->errors[$field] = $errmsg;
$this->num_errors = count($this->errors);
}
public function getError($field) {
if (array_key_exists($field, $this->errors)) {
return $this->errors[$field];
} else {
return "";
}
}
public function getErrorArray() {
return $this->errors;
}

在这些类方法中,您可以看到setValue($field, $value)getValue($field)方法分别用于设置和获取单个相应字段的值。同样,setError($field, $errmsg)getError($field)在验证时设置和获取相应表单字段值的错误消息,同时 setError 增加num_errors的值。最后,getErrorArray()返回完整的错误消息数组。

  1. 现在,按照以下方式输入表单字段的值验证方法:
public function validate($field, $value) {
$valid = false;
if ($valid == $this->isEmpty($field, $value)) {
$valid = true;
if ($field == "name")
$valid = $this->checkSize($field, $value, self::NAME_LENGTH_MIN, self::NAME_LENGTH_MAX);
if ($field == "password" || $field == "newpassword")
$valid = $this->checkSize($field, $value, self::PASS_LENGTH_MIN, self::PASS_LENGTH_MAX);
if ($valid)
$valid = $this->checkFormat($field, $value);
}
return $valid;
}
private function isEmpty($field, $value) {
$value = trim($value);
if (empty($value)) {
$this->setError($field, "Field value not entered");
return true;
}
return false;
}
private function checkFormat($field, $value) {
switch ($field) {
case 'useremail':
$regex = "/^[_+a-z0-9-]+(\.[_+a-z0-9-]+)*"
. "@[a-z0-9-]+(\.[a-z0-9-]{1,})*"
. "\.([a-z]{2,}){1}$/i";
$msg = "Email address invalid";
break;
case 'password':
case 'newpassword':
$regex = "/^([0-9a-z])+$/i";
$msg = "Password not alphanumeric";
break;
case 'name':
$regex = "/^([a-z ])+$/i";
$msg = "Name must be alphabetic";
break;
case 'phone':
$regex = "/^([0-9])+$/";
$msg = "Phone not numeric";
break;
default:;
}
if (!preg_match($regex, ( $value = trim($value)))) {
$this->setError($field, $msg);
return false;
}
return true;
}
private function checkSize($field, $value, $minLength, $maxLength) {
$value = trim($value);
if (strlen($value) < $minLength || strlen($value) > $maxLength) {
$this->setError($field, "Value length should be within ".$minLength." & ".$maxLength." characters");
return false;
}
return true;
}

验证方法可以描述如下:

  • validate($field, $value)是验证的入口函数。可以从该方法调用输入验证的方法,例如空字符串检查、正确的输入格式或输入大小范围,并且如果验证通过,则返回true,否则返回false

  • isEmpty($field, $value)检查字符串是否为空,然后为该字段设置错误消息并返回falsetrue

  • checkFormat($field, $value)测试字段的值是否符合为每个字段格式编写的适当正则表达式,设置错误(如果有),并返回false,否则返回true

  • checkSize($field, $value, $minLength, $maxLength)检查输入是否在给定的最小大小和最大大小之间。

  1. 我们希望验证登录凭据,以检查用户电子邮件是否存在,或者密码是否属于与该用户电子邮件匹配的用户。因此,按照以下方式添加validateCredentials()方法
public function validateCredentials($useremail, $password) {
$result = $this->userDao->checkPassConfirmation($useremail, md5($password));
if ($result === false) {
$this->setError("password", "Email address or password is incorrect");
return false;
}
return true;
}

该方法接受$useremail$password作为登录凭据验证。您可以看到以下行使用user Dao来确认与useremail关联的密码。Dao 的checkPassConfirmation()方法返回true表示确认,返回false表示电子邮件地址或密码不正确。

$result = $this->userDao->checkPassConfirmation($useremail, md5($password));

  1. 当用户想要注册到我们的应用程序时,我们可以验证电子邮件地址是否已经存在。如果电子邮件地址在数据库中尚未注册,则用户可以自由注册该电子邮件。因此,输入以下方法:
public function emailExists($useremail) {
if ($this->userDao->useremailTaken($useremail)) {
$this->setError('useremail', "Email already in use");
return true;
}
return false;
}

您可以看到该方法在$this->userDao->useremailTaken($useremail);中使用userDao来检查用户电子邮件是否已被使用。如果已被使用,则设置错误,并返回true表示该电子邮件已存在。

  1. 当用户想要更新当前密码时,再次需要密码确认。因此,让我们添加另一个方法来验证当前密码:
public function checkPassword($useremail, $password) {
$result = $this->userDao->checkPassConfirmation($useremail, md5($password));
if ($result === false) {
$this->setError("password", "Current password incorrect");
return false;
}
return true;
}

刚刚发生了什么?

我们已经准备好支持表单、登录凭据和密码验证,甚至通过userDao与数据库通信的验证器服务类。此外,验证器服务允许应用程序检索用于访客或用户的临时状态消息,以及表单输入字段的错误消息。因此,它处理各种验证任务,并且如果发现错误,则验证方法设置错误,并在成功时返回true,在失败时返回false。这样的错误消息可以在相应的表单字段旁边查看,以及字段值。因此,它还有助于创建数据持久性表单。

尝试英雄-添加多字节编码支持

现在,我们的验证器服务无法支持多字节字符编码。为了使应用程序能够支持不同的字符编码,如 UTF-8,您可以在验证方法中实现多字节支持,例如设置内部编码、多字节字符串的正则表达式匹配,以及使用mb_strlen()而不是strlen()。多字节字符串函数可以在php.net/manual/en/ref.mbstring.php找到。

创建 UserService 类

UserService类支持所有应用程序任务,如登录、注册或更新用户详细信息。它与UserDao类对应于任何类型的数据相关函数,并与ValidatorService服务类对应于任何类型的验证函数。应用程序要求任务,如登录或注册,首先调用验证,然后执行任务,同时可能根据需要使用 DAO。最后,如果任务已完成,则返回true,如果失败,则返回false,例如验证失败或其他任何模糊性。简单地说,应用程序将从UserService类调用方法来登录、注册等,并可以了解操作的状态。

行动时间-创建 UserService 类

我们将使用My\Service作为该类的命名空间,并将任何常量保留在类中。UserService类属性将包含用户信息,如用户电子邮件、用户 ID、用户名或电话,并且构造函数将检查已登录用户和从会话加载的类变量中的用户详细信息。此外,该类将利用 PHP cookie 来存储用户的登录数据。该类将充当登录会话管理器。因此,最初,该类将检查会话中或 cookie 中的登录数据,以确定用户是否已登录。

提示

建议您熟悉 PHP 会话和 cookie。

因此,让我们按照以下步骤创建UserService类:

  1. Service目录中创建一个名为UserService.php的新 PHP 文件,并输入以下类:
<?php
namespace My\Service;
use My\Dao\UserDao;
use My\Service\ValidatorService;
class UserService {
public $useremail;
private $userid;
public $username;
public $userphone;
private $userhash;
private $userlevel;
public $logged_in;
const ADMIN_EMAIL = "admin@mysite.com";
const GUEST_NAME = "Guest";
const ADMIN_LEVEL = 9;
const USER_LEVEL = 1;
const GUEST_LEVEL = 0;
const COOKIE_EXPIRE = 8640000;
const COOKIE_PATH = "/";
public function __construct(UserDao $userDao, ValidatorService $validator) {
$this->userDao = $userDao;
$this->validator = $validator;
$this->logged_in = $this->isLogin();
if (!$this->logged_in) {
$this->useremail = $_SESSION['useremail'] = self::GUEST_NAME;
$this->userlevel = self::GUEST_LEVEL;
}
}
}
$userService = new \My\Service\UserService($userDao, $validator);
?>

您可以看到该类使用namespace My\Service;,并且可以使用\My\Service\UserService来访问 Service User 类。

检查存储用户数据的类变量,如果用户已登录,则 $logged_intrue

为了区分用户,已添加了与用户相关的常量。用你自己的邮箱更新 ADMIN_EMAIL;用户中的管理员将由 ADMIN_EMAILADMIN_LEVEL 等于 9 来定义。一般注册用户将被定义为 USER_LEVEL 等于 1,非注册用户将被定义为 GUEST_LEVEL 等于 0GUEST_NAME 为 Guest。因此,使用邮箱地址 <admin@mysite.com> 注册的用户在我们实现管理员功能时将具有管理员访问权限。

在 cookie 常量部分,COOKIE_EXPIRE 默认将 cookie 过期时间设置为 100 天(8640000 秒),COOKIE_PATH 表示 cookie 将在整个应用程序域中可用。

cookie(用户计算机上的文本文件)将用于将 useremail 存储为 cookname,将 userhash 存储为 cookid。这些 cookie 将在用户启用“记住我”选项的情况下设置。因此,我们将首先检查用户本地计算机上是否存在与数据库匹配的 cookie,如果是,则将用户视为已登录用户。

请注意,构造函数注入了 UserDaoValidatorService 对象,因此类可以在内部使用这些依赖项。

现在,通过 $this->logged_in = $this->isLogin(); 这一行,构造函数检查用户是否已登录。private 方法 isLogin() 检查登录数据,如果找到则返回 true,否则返回 false。实际上,isLogin() 检查会话和 cookie 是否有用户的登录数据,如果有,则加载类变量。

未登录用户将是访客用户,因此 useremailuserlevel 分别设置为 GuestGuest Level 0

if (!$this->logged_in) {
$this->useremail = $_SESSION['useremail'] = self::GUEST_NAME;
$this->userlevel = self::GUEST_LEVEL;
}

  1. 现在,让我们创建 isLogin() 方法,如下所示:
private function isLogin() {
if (isset($_SESSION['useremail']) && isset($_SESSION['userhash']) &&
$_SESSION['useremail'] != self::GUEST_NAME) {
if ($this->userDao->checkHashConfirmation($_SESSION['useremail'], $_SESSION['userhash']) === false) {
unset($_SESSION['useremail']);
unset($_SESSION['userhash']);
unset($_SESSION['userid']);
return false;
}
$userinfo = $this->userDao->get($_SESSION['useremail']);
if(!$userinfo){
return false;
}
$this->useremail = $userinfo['useremail'];
$this->userid = $userinfo['id'];
$this->userhash = $userinfo['userhash'];
$this->userlevel = $userinfo['userlevel'];
$this->username = $userinfo['username'];
$this->userphone = $userinfo['phone'];
return true;
}
if (isset($_COOKIE['cookname']) && isset($_COOKIE['cookid'])) {
$this->useremail = $_SESSION['useremail'] = $_COOKIE['cookname'];
$this->userhash = $_SESSION['userhash'] = $_COOKIE['cookid'];
return true;
}
return false;
}

如果 $_SESSION 具有 useremail, userhash,useremail 不是 guest,则意味着用户已经登录到数据中。如果是这样,我们希望使用 UserDaocheckHashConfirmation() 方法来确认 userhash 和关联的 useremail 的安全性。如果未确认,则取消设置 $_SESSION 变量,并将其视为未登录,返回 false。

最后,如果一切顺利,使用 Dao 加载已登录用户的详细信息,$userinfo = $this->userDao->get($_SESSION['useremail']); 加载类和会话变量,并将其返回为 true。

同样,如果 $_SESSION 没有已登录的数据,那么我们将选择检查 cookie,因为用户可能已启用“记住我”选项。如果在 cookie 变量中找到必要的数据,则从中加载类和会话变量。

  1. 现在,为应用程序创建登录服务如下:
public function login($values) {
$useremail = $values['useremail'];
$password = $values['password'];
$rememberme = isset($values['rememberme']);
$this->validator->validate("useremail", $useremail);
$this->validator->validate("password", $password);
if ($this->validator->num_errors > 0) {
return false;
}
if (!$this->validator->validateCredentials($useremail, $password)) {
return false;
}
$userinfo = $this->userDao->get($useremail);
if(!$userinfo){
return false;
}
$this->useremail = $_SESSION['useremail'] = $userinfo['useremail'];
$this->userid = $_SESSION['userid'] = $userinfo['id'];
$this->userhash = $_SESSION['userhash'] = md5(microtime());
$this->userlevel = $userinfo['userlevel'];
$this->username = $userinfo['username'];
$this->userphone = $userinfo['phone'];
$this->userDao->update($this->userid, array("userhash" => $this->userhash));
if ($rememberme == 'true') {
setcookie("cookname", $this->useremail, time() + self::COOKIE_EXPIRE, self::COOKIE_PATH);
setcookie("cookid", $this->userhash, time() + self::COOKIE_EXPIRE, self::COOKIE_PATH);
}
return true;
}

这个方法接受登录详情,比如 useremail, password,rememberme,并将它们传递到应用程序的 $values 数组中。它调用给定输入的验证,如果发现错误则返回 false,并在之后验证访问凭证的关联。如果所有情况都通过了验证,它将从 Dao 中加载用户信息。请注意,在下一行中,md5(microtime()) 创建一个随机的包含字母数字字符的字符串,并分配给类变量。

$this->userhash = $_SESSION['userhash'] = md5(microtime());

最后,为了启动新的登录会话,更新表中对应用户的 userhash,这将是当前会话的标识符。

$this->userDao->update($this->userid, array("userhash" => $this->userhash));

因此,$_SESSION userhash 和数据库 userhash 应该对于一个活跃的、已登录的会话是相同的。

此外,您可以看到,如果 $remembermetrue,则使用 PHP 的 setcookie() 方法设置 cookie,并设置名称、值和过期时间。

  1. 现在,添加用户注册服务方法如下:
public function register($values) {
$username = $values['name'];
$useremail = $values['useremail'];
$password = $values['password'];
$phone = $values['phone'];
$this->validator->validate("name", $username);
$this->validator->validate("useremail", $useremail);
$this->validator->validate("password", $password);
$this->validator->validate("phone", $phone);
if ($this->validator->num_errors > 0) {
return false;
}
if($this->validator->emailExists($useremail)) {
return false;
}
$ulevel = (strcasecmp($useremail, self::ADMIN_EMAIL) == 0) ? self::ADMIN_LEVEL : self::USER_LEVEL;
return $this->userDao->insert(array(
'useremail' => $useremail, 'password' => md5($password),
'userlevel' => $ulevel, 'username' => $username,
'phone' => $phone, 'timestamp' => time()
));
}

该方法接受用户注册的详细信息,将它们传递到$values数组中,并对其进行验证。如果验证通过,它将用户注册详细信息打包到一个数组中,并使用 User Dao 的insert()方法将其保存到数据库中。

请注意,用户级别是通过将注册者的电子邮件地址与ADMIN_EMAIL进行比较来确定的。

  1. 添加getUser()方法如下,以提供与给定的useremail参数匹配的用户信息:
public function getUser($useremail){
$this->validator->validate("useremail", $useremail);
if ($this->validator->num_errors > 0) {
return false;
}
if (!$this->validator->emailExists($useremail)) {
return false;
}
$userinfo = $this->userDao->get($useremail);
if($userinfo){
return $userinfo;
}
return false;
}

请注意,在提供用户信息之前,useremail已经过验证。因此,每当需要用户信息时,应用程序将使用此方法。

  1. 现在,添加update()方法来修改用户的详细信息。
public function update($values) {
$username = $values['name'];
$phone = $values['phone'];
$password = $values['password'];
$newPassword = $values['newpassword'];
$updates = array();
if($username) {
$this->validator->validate("name", $username);
$updates['username'] = $username;
}
if($phone) {
$this->validator->validate("phone", $phone);
$updates['phone'] = $phone;
}
if($password && $newPassword){
$this->validator->validate("password", $password);
$this->validator->validate("newpassword", $newPassword);
}
if ($this->validator->num_errors > 0) {
return false;
}
if($password && $newPassword){
if ($this->validator->checkPassword($this->useremail, $password)===false) {
return false;
}
$updates['password'] = md5($newPassword);
}
$this->userDao->update($this->userid, $updates);
return true;
}

请注意,该方法首先验证给定的信息(如果有)。如果它通过了验证标准,相应的列值将通过 User Dao 更改到数据库表中。

  1. logout()方法可以添加如下:
public function logout() {
if (isset($_COOKIE['cookname']) && isset($_COOKIE['cookid'])) {
setcookie("cookname", "", time() - self::COOKIE_EXPIRE, self::COOKIE_PATH);
setcookie("cookid", "", time() - self::COOKIE_EXPIRE, self::COOKIE_PATH);
}
unset($_SESSION['useremail']);
unset($_SESSION['userhash']);
$this->logged_in = false;
$this->useremail = self::GUEST_NAME;
$this->userlevel = self::GUEST_LEVEL;
}

logout方法取消所有 cookie 和会话变量,将$this->logged_in设置为false,用户再次成为访客用户。

刚刚发生了什么?

现在我们可以检查用户是否已登录,以及用户是否被要求记住登录详细信息,这样用户就不需要再次使用记住我选项登录。该类用于登录、注销、用户注册以及更新或检索用户信息到应用程序层。它在进行 Dao 层之前使用验证器服务。因此,该类还确保了数据安全性,使得UserService类在服务层准备就绪。

最后,我们的 API 已经准备好工作,通过使用这个 API,我们可以构建一个用户注册、用户资料更新、登录和注销的应用程序。我们有我们的数据访问层和服务层正在运行。现在,让我们来看看我们的 NetBeans 项目目录。

刚刚发生了什么?

为了更好地理解,我们为每个层使用了一个单独的目录和一个单独的命名空间。现在,我们将在我们的应用程序文件中包含 API,并通过使用 User Service 对象,我们将实现我们应用程序的目标。

快速测验——使用命名空间

  1. PHP 命名空间支持哪些特性?

  2. 给类名取别名

  3. 给接口名称取别名

  4. 给命名空间名称取别名

  5. 导入函数或常量

  6. 哪一个将导入名为foo的全局类?

  7. 命名空间 foo;

  8. 使用 foo;

  9. 导入 foo;

  10. 以上都不是

构建应用程序

在本教程中,我们将构建一个能够处理用户注册任务的应用程序,例如处理注册表单、通过 API 保存用户数据或显示错误消息,以及用户登录和注销任务。在下一节中,我们将构建 PHP 应用程序,然后添加应用程序用户界面。

在继续之前,请记住我们只有服务层类。我们将选择以这样的方式构建应用程序,使我们的应用程序建立在服务层之上。对于本节,我们不需要考虑底层数据库或 Dao,而是需要从应用程序开发人员的角度思考。

行动时间——创建用户应用程序

我们将把 API 集成到我们的用户应用程序文件中,这将是主要的应用程序文件;每个应用程序目的可能会有接口或视图文件。让我们按照以下步骤进行:

  1. 在项目目录中创建一个名为UserApplication.php的新 PHP 文件,并输入以下UserApplication类:
<?php
namespace My\Application;
use My\Service\UserService;
use My\Service\ValidatorService;
session_start();
require_once "Dao/BaseDao.php";
require_once "Dao/UserDao.php";
require_once "Service/ValidatorService.php";
require_once "Service/UserService.php";
class UserApplication {
public function __ construct (UserService $userService, ValidatorService $validator) {
$this->userService = $userService;
$this->validator = $validator;
if (isset($_POST['login'])) {
$this->login();
}
else if (isset($_POST['register'])) {
$this->register();
}
else if (isset($_POST['update'])) {
$this->update();
}
else if ( isset($_GET['logout']) ) {
$this->logout();
}
}
}
$userApp = new \My\Application\UserApplication($userService, $validator);
?>

在文件顶部,您可以看到在构造函数声明之后,PHP 会话以session_start()开始。API 文件已被包含,并且类构造函数已注入了UserValidator Service对象,因此这些对象在整个应用程序中都可用。

您可以看到,根据用户的请求,适当的方法是从构造函数中调用的,例如如果设置了$_POST['login'],则调用$this->login();。因此,所有方法都是从构造函数中调用的,并且应具有以下功能:

  • login()

  • register()

  • update()

  • logout()

在文件底部,我们有一行$userApp = new \My\Application\UserApplication($userService, $validator);,它实例化了UserApplication类以及依赖注入。

  1. 在下面键入以下login()方法:
public function login() {
$success = $this->userService->login($_POST);
if ($success) {
$_SESSION['statusMsg'] = "Successful login!";
} else {
$_SESSION['value_array'] = $_POST;
$_SESSION['error_array'] = $this->validator->getErrorArray();
}
header("Location: index.php");
}

您可以看到,该方法调用用户服务,并使用用户界面发布的登录凭据在以下行中:

$success = $this->userService->login($_POST);

如果登录尝试成功,则在$_SESSION['statusMsg']会话变量中设置成功状态消息,如果失败,则将用户通过$_POST数组设置为$_SESSION['value_array'],并将从验证器对象中获取的错误数组设置为$_SESSION['error_array']。最后,它将重定向到index.php页面。

  1. 在下面键入以下register()方法:
public function register() {
$success = $this->userService->register($_POST);
if ($success) {
$_SESSION['statusMsg'] = "Registration was successful!";
header("Location: index.php");
} else {
$_SESSION['value_array'] = $_POST;
$_SESSION['error_array'] = $this->validator->getErrorArray();
header("Location: register.php");
}
}

您可以看到,如果注册尝试失败,则会重置相应的会话变量,并重定向到register.php页面,这是用户注册页面。

  1. 在下面键入以下update()方法:
public function update() {
$success = $this->userService->update($_POST);
if ($success) {
$_SESSION['statusMsg'] = "Successfully Updated!";
header("Location: profile.php");
} else {
$_SESSION['value_array'] = $_POST;
$_SESSION['error_array'] = $this->validator->getErrorArray();
header("Location: profileedit.php");
}
}

您可以看到,如果用户资料更新尝试失败,则会重置相应的会话变量,并重定向到profileedit.php页面,这是资料编辑页面,或者在成功时重定向到profile.php。因此,这些页面将是我们的用户资料查看和更新页面。

  1. 在下面键入以下logout()方法,它只是调用注销服务:
public function logout(){
$success = $this->userService->logout();
header("Location: index.php");
}

刚刚发生了什么?

现在我们的主应用程序类已经准备就绪,功能也已经准备就绪。因此,我们可以使用应用程序注册、登录、更新和注销用户。请注意,我们的应用程序只是通过服务对象进行通信,您可以感觉到应用程序对数据源不感兴趣;它所做的只是利用为其设计的服务。通过这种方式,我们可以为用户编写更有趣的应用程序,例如查看注册用户列表;开发管理员功能,例如更新任何用户或删除任何用户,甚至通过更新userlevel将用户从普通用户提升为管理员。通过在不同层中编写更多有趣的方法,我们可以获得特定的应用程序。

在我们的下一个和最后一节中,我们将为特定功能添加用户界面或页面。

创建用户界面

我们将为用户注册和登录创建简单的用户界面和表单。此外,我们还将为查看用户资料、更新资料和注销提供一些用户菜单。我们将在我们的界面文件的顶部集成UserApplication.php。我们的界面文件将包含简单的 HTML,其中包含内部集成的 PHP 代码。

行动时间-创建用户界面

我们将在每个界面文件的开头集成用户应用程序文件。因此,请按照以下步骤创建各种用户界面:

  1. 打开index.php并集成UserApplication类,使其如下所示:
<?php
require_once 'UserApplication.php';
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
</body>
</html>

所有界面代码都可以在 body 标记内。

  1. 现在,让我们创建一个已登录用户菜单,显示状态消息(如果有),已登录用户名,并在每个页面顶部显示菜单。创建一个名为menu.php的新 PHP 文件,并键入以下代码:
<?php
if (isset($validator->statusMsg)) {
echo "<span style=\"color:#207b00;\">" . $validator->statusMsg . "</span>";
}
if ($userService->logged_in) {
echo "<h2>Welcome $userService->username!</h2>";
echo "<a href='profile.php'>My Profile</a> | "
. "<a href='profileedit.php'>Edit Profile</a> | "
. "<a href='UserApplication.php?logout=1'>Logout</a> ";
}
?>

您可以看到,如果$validator->statusMsg可用,则我们将其显示在彩色的span标记内。此外,如果用户已登录,则它会在<h2>标记内显示用户名,并显示用于查看资料、编辑资料和注销的anchor标记。现在,在我们的页面中,我们将在<body>标记内包含此菜单,如下所示:

include 'menu.php';

  1. 现在,让我们创建用户注册页面register.php,并键入以下代码:
<?php
require_once 'UserApplication.php';
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
<?php
include 'menu.php';
if (!$userService->logged_in) {
?>
<h2>User Registration</h2><br />
<?php
if ($validator->num_errors > 0) {
echo "<span style=\"color:#ff0000;\">" . $validator->num_errors . " error(s) found</span>";
}
?> **<form action="UserApplication.php" method="POST">
Name: <br />
<input type="text" name="name" value="<?= $validator->getValue("name") ?>"> <? echo "<span style=\"color:#ff0000;\">".$validator->getError("name")."</span>"; ?>
<br />
Email: <br />
<input type="text" name="useremail" value="<?= $validator->getValue("useremail") ?>"> <? echo "<span style=\"color:#ff0000;\">".$validator->getError("useremail")."</span>"; ?>
<br />
Password:<br />
<input type="password" name="password" value=""> <? echo "<span style=\"color:#ff0000;\">".$validator->getError("password")."</span>"; ?>
<br />
Phone: <br />
<input type="text" name="phone" value="<?= $validator->getValue("phone") ?>"> <? echo "<span style=\"color:#ff0000;\">".$validator->getError("phone")."</span>"; ?>
<br /><br />
<input type="hidden" name="register" value="1">
<input type="submit" value="Register">
</form>**
<br />
Already registered? <a href="index.php">Login here</a>
<?php
}
?>
</body>
</html>

您可以看到,当用户未登录时,显示了用户注册表单。如果有任何错误,则在表单之前显示错误数量,使用$validator->num_errors

在下一行中,您可以看到表单将被发布到 UserApplication.php 文件:

<form action="UserApplication.php" method="POST">

该表单由四个输入框组成,用于姓名、电子邮件、密码和电话号码,以及一个提交按钮用于提交表单。表单带有一个隐藏的输入字段,其中包含预加载的值。这个隐藏字段的值将用于通过UserApplication类构造函数来识别登录任务,以便调用适当的方法。

  1. 现在,让我们看一个输入字段,如下所示:
Name: <br />
<input type="text" name="name" value="<?= $validator-> getValue("name") ?>"> <? echo "<span style= \"color:#ff0000;\">".$validator->getError("name")."</span>"; ?>

您可以看到字段值已被转储(如果可用,则使用$validator->getValue("name")),并显示在value属性中。在表单验证期间,可以使用字段名称在validator方法中找到字段值。此外,通过使用$validator->getError("name"),可以显示与name字段相关的任何错误。因此,其余字段被设计为相似。

  1. 要测试表单验证,请使用register.php指向您的浏览器;单击注册按钮以在不填写任何字段的情况下提交表单。表单看起来类似于以下屏幕截图,每个字段旁边都有一个错误指示。操作时间-创建用户界面

您可以看到,表单显示了每个字段的错误,并在表单顶部显示了错误数量。因此,我们的验证器和用户服务正在工作。因此,您可以测试注册表单以进行书面验证,并最终填写表单以注册自己,并检查数据库表以获取您提交的信息。

  1. 现在,让我们在index.php文件中的<body>标记内创建登录表单,并选择记住我选项,以便body标记包含以下代码:
<?php
include 'menu.php';
if (!$userService->logged_in) {
?>
<h2>User Login</h2>
<br />
<?php
if ($validator->num_errors > 0) {
echo "<span style=\"color:#ff0000;\">" . $validator->num_errors . " error(s) found</span>";
}
?> **<form action="UserApplication.php" method="POST">
Email: <br />
<input type="text" name="useremail" value="<?= $validator->getValue("useremail") ?>"> <? echo "<span style=\"color:#ff0000;\">".$validator->getError("useremail")."</span>"; ?>
<br />
Password:<br />
<input type="password" name="password" value=""> <? echo "<span style=\"color:#ff0000;\">".$validator->getError("password")."</span>"; ?>
<br />
<input type="checkbox" name="rememberme" <?=($validator->getValue("rememberme") != "")?"checked":""?>>
<font size="2">Remember me next time </font>
<br />
<input type="hidden" name="login" value="1">
<input type="submit" value="Login">
</form>**
<br />
New User? <a href="register.php">Register here</a>
<?php
}
?>

  1. 查看登录表单;字段已以与注册表单相同的方式组织。表单包含一个名为login的隐藏字段和值设置为1。因此,当表单被发布时,应用程序类可以确定已提交登录表单,因此调用了应用程序登录方法。登录表单页面看起来类似于以下内容:操作时间-创建用户界面

  2. 使用您注册的数据测试登录表单并登录。成功登录后,您将被重定向到同一页,如下所示:操作时间-创建用户界面

您可以看到页面顶部显示了绿色的成功登录状态,并且用户已登录,因此不再需要登录表单。

  1. 现在,创建profile.php个人资料页面(您可以通过选择文件|另存为...从菜单中的任何界面页面创建文件,并在body标记内进行修改),因为它应该在body标记内包含以下代码:
<?php
include 'menu.php';
if ($userService->logged_in) {
echo '<h2>User Profile</h2>';
echo "Name : " . $userService->username . "<br />";
echo "Email: " . $userService->useremail . "<br />";
echo "Phone: " . $userService->userphone . "<br />";
}
?>

在此代码片段中,您可以看到已转储已登录用户的个人资料信息,看起来类似于以下内容:

操作时间-创建用户界面

  1. 现在,创建个人资料编辑页面profileedit.php,并键入以下代码:
<?php
include 'menu.php';
if ($userService->logged_in) {
?>
<h2>Edit Profile</h2><br />
<?php
if ($validator->num_errors > 0) {
echo "<span style=\"color:#ff0000;\">" . $validator->num_errors . " error(s) found</span>";
}
?> **<form action="UserApplication.php" method="POST">
Name: <br />
<input type="text" name="name" value="<?= ($validator->getValue("name") != "") ? $validator->getValue("name") : $userService->username ?>"> <? echo "<span style=\"color:#ff0000;\">" . $validator->getError("name") . "</span>"; ?>
<br />
Password:<br />
<input type="password" name="password" value=""> <? echo "<span style=\"color:#ff0000;\">" . $validator->getError("password") . "</span>"; ?>
<br />
New Password: <font size="2">(Leave blank to remain password unchanged)</font><br />
<input type="password" name="newpassword" value=""> <? echo "<span style=\"color:#ff0000;\">" . $validator->getError("newpassword") . "</span>"; ?>
<br />
Phone: <br />
<input type="text" name="phone" value="<?= ($validator->getValue("phone") != "") ? $validator->getValue("phone") : $userService->userphone ?>"> <? echo "<span style=\"color:#ff0000;\">" . $validator->getError("phone") . "</span>"; ?>
<br /><br />
<input type="hidden" name="update" value="1">
<input type="submit" value="Save">
</form>**
<?php
}
?>

该表单包含用户个人资料更新字段,如姓名、密码和电话;请注意,如果任何字段(如密码)保持空白,则该字段将不会被更新。最后,在测试时,表单看起来类似于以下内容:

操作时间-创建用户界面

  1. 现在我们可以测试注销功能。查看菜单文件以获取注销的anchor标签,如下所示:
<a href='UserApplication.php?logout=1'>Logout</a>

您可以看到它直接将UserApplication.php文件与logout=1 URL 段锚定,因此UserApplication构造函数发现已使用$_GET['logout']调用了注销,并调用应用程序注销。注销后将重定向到索引页面。

刚刚发生了什么?

我们刚刚创建并测试了我们新建的用户界面。在注册用户、登录或更新用户资料时,测试非常有趣。请记住,我们可以在未来的项目中使用这个登录应用,或者可以以最小成本轻松集成新功能。我们的目标是创建分层架构,并根据该设计构建应用程序已经实现。

注意

本章的完整项目源代码可以从 Packt Publishing 网站下载。您还可以在 GitHub 上 fork 这个项目的扩展版本:github.com/mahtonu/login-script

小测验——应用架构

  1. 我们的应用架构中有多少层?

  2. 2

  3. 3

  4. 4

  5. 5

  6. 数据库抽象是在哪一层实现的?

  7. 数据存储层

  8. 数据访问层

  9. 抽象层

  10. 以上所有内容

  11. 在我们的应用程序中,哪个方法直接与数据库通信以检查电子邮件地址是否存在?

  12. useremailTaken()

  13. emailExists()

  14. checkEmail()

  15. 确认电子邮件()

尝试一下——创建管理员功能

正如您已经注意到的,我们已经创建了一个数据库表列来定义管理员用户。因此,在用户服务中实现管理员功能,比如一个方法来确定用户是否是管理员;如果他/她是管理员,那么添加管理员页面/接口方法来从用户 Dao 获取所有用户列表,并显示这些用户详情,等等。同样,您可以实现管理员功能,将普通用户提升为管理员用户,通过更新userlevel列。

摘要

在本章中,我们使用分层设计开发了用户注册、登录和注销应用。我们现在对企业系统架构有信心,并且可以轻松地向开发的应用程序中添加或删除功能。

我们特别关注了:

  • 设计应用架构

  • 理解 DAO 模式

  • 创建 DAO 类

  • 创建服务类

  • 创建用户注册、登录和注销应用

  • 开发用户界面

因此,我们已经进入了专业的 PHP 项目开发和 IDE 功能的实践,这帮助了我们很多。我们可以在未来的 Web 应用程序中使用这个项目,其中需要用户登录功能;这就是“开发一次,稍微更新,一直使用”的优势。

附录 A. 在 NetBeans 7.2 中引入 Symfony2 支持

Symfony 是一个用于开发 Web 应用程序的 PHP 框架。它在构建 PHP 中的复杂 Web 应用程序方面非常有帮助。虽然 Symfony 是设计用于从命令行工作,但 NetBeans 7.2 对 Symfony 的支持允许您在 NetBeans 图形用户界面中使用它。

本教程演示了 NetBeans IDE 7.2 对 PHP 中 Symfony 框架的内置支持。它展示了如何设置 IDE 以使用 Symfony,如何创建使用 Symfony 框架的 PHP 项目,以及有关导航项目和设置 IDE 选项的一些提示。

下载和集成最新的 Symfony 标准版

Symfony 标准版是启动新项目时使用的最佳发行版。它包含最常见的 bundles,并配有一个简单的配置系统。

创建 Symfony2 与 NetBeans 的时间

在本节中,我们将下载标准版并将存档集成到 IDE 中。所以让我们试一试。

  1. symfony.com/download下载最新的 Symfony 标准 2.x.x.zip。将.zip存档保存到您的磁盘上;您不需要解压.zip文件。

  2. 检查已添加到 IDE 的所有项目的 PHP 5 解释器。选择工具 | 选项 | PHP | 通用,并验证PHP 5 解释器字段中添加的解释器路径。需要添加 PHP 解释器以从 NetBeans 运行 Symfony 命令。

  3. 现在,在 IDE 中提供 Symfony 标准版(.zip文件)的路径。选择工具 | 选项 | PHP | Symfony2。浏览下载的symfony2 .zip存档,并按确定保存设置。

刚刚发生了什么?

IDE 将每次使用添加的symfony2存档来提取和转储新的 Symfony 项目。下载的框架版本包含演示 Symfony 应用程序。我们可以稍后玩这些演示应用程序,以更好地掌握 Symfony 框架。

注意

您可以从symfony.com/download中选择多个下载选项。

创建一个新的 Symfony2 项目

由于我们已经将 Symfony2 框架安装存档与 IDE 集成,因此创建新的 Symfony2 项目与在 NetBeans 中创建新的 PHP 项目完全相同。IDE 使用安装存档并在其中创建一个带有 Symfony 框架的新 PHP 项目。

创建 Symfony2 项目使用 NetBeans 的时间

我们将创建一个具有 Symfony2 框架支持的新 PHP 项目。在 IDE 创建项目目录结构之后,我们将配置我们的 Symfony2 网站。所以让我们按照以下步骤进行:

  1. 以通常的方式创建一个全新的 PHP 项目,在要求选择PHP 框架的步骤中,勾选Symfony2 PHP Web Framework复选框,如下截图所示:创建 Symfony2 项目使用 NetBeans 的时间

  2. 一旦在新项目创建对话框中单击完成,IDE 将生成一个新的 Symfony 项目并将提取的框架转储到其中。创建的项目目录可能类似于以下内容:创建 Symfony2 项目使用 NetBeans 的时间

  3. 现在,将浏览器指向http://localhost/symfony2/web/config.php(将symfony2替换为您的项目目录名称)。新的 Symfony2 项目配置页面将类似于以下截图:创建 Symfony2 项目使用 NetBeans 的时间

您应该看到来自 Symfony 的欢迎消息,可能还有一些它检测到的问题列表。在继续之前,尝试解决建议部分下列出的任何主要环境问题。

  1. Symfony 框架提供了一个网站配置向导。要进入向导,请访问Configure your Symfony Application online链接,并为应用程序配置数据库凭据。在此页面,您可以选择您的数据库驱动程序(MySQL - PDO),更新您的数据库信息,如主机名、数据库名称、用户名和密码,并继续下一步。

如果您已经配置了应用程序,可以选择Bypass configuration and go to the Welcome page链接。

  1. 在下一步中,您可以为您的 Web 应用程序生成和更新全局秘密代码(随机字母数字字符串)。此秘密代码用于安全目的,如 CSRF 保护。

  2. 最后一步显示了一个成功的配置消息,例如Your distribution is configured!实际上,这样的配置已经覆盖了/app/config/目录中的parameters.ini文件。

  3. 现在,将浏览器指向http://localhost/symfony2/web/app_dev.php/(将symfony2替换为您的项目目录名称)。新的 Symfony2 项目登陆页面将类似于以下截图:Time for action — creating a Symfony2 project using NetBeans

刚刚发生了什么?

我们已成功创建和配置了一个新的 Symfony 项目以及演示应用程序。Symfony2 项目的基本目录结构如下所述:

  • app/: 这包括应用程序配置文件、日志、缓存等。

  • src/: 这包括项目的 PHP 代码和您的代码所在的目录。很可能里面已经有一个演示。

  • vendor/: 这包括第三方依赖项。

  • web/: 这包括 web 根目录。

注意

开始使用 Symfony:

symfony.com/get_started

了解 Symfony 目录结构:

symfony.com/doc/current/quick_tour/the_architecture.html

在 NetBeans 中运行 Symfony2 控制台命令

NetBeans IDE 支持运行 Symfony2 命令。要从 IDE 中运行命令,请从项目的上下文菜单中选择Symfony2 | Run Command...以启动Run Symfony2 Command对话框。在对话框中,您可以选择所需的 Symfony 命令并添加参数。

例如:

generate:bundle [--namespace="..."] [--dir="..."] [--bundle-name="..."] [--format="..."] [--structure]

generate:bundle命令帮助您生成新的 bundle。默认情况下,该命令与开发人员交互以调整生成。任何传递的选项都将用作交互的默认值(如果遵循约定,则只需要--namespace):

php app/console generate:bundle --namespace=Acme/BlogBundle

在这里,Acme是您的标识符或公司名称,BlogBundle是以Bundle字符串为后缀的 bundle 名称。

创建一个 bundle

bundle类似于其他软件中的插件,但更好。关键区别在于 Symfony2 中的一切都是 bundle,包括核心框架功能和为您的应用程序编写的代码。bundle 在 Symfony2 中是一等公民。这使您可以灵活地使用打包在第三方 bundle 中的预构建功能,或者分发您自己的 bundle。这使得您可以轻松地选择要在应用程序中启用的功能,并按照您想要的方式对其进行优化。

bundle 只是一个实现单个功能的目录中的一组结构化文件。您可以创建BlogBundleForumBundle或用于用户管理的 bundle(许多这样的 bundle 已经存在作为开源 bundle)。每个目录包含与该功能相关的所有内容,包括 PHP 文件、模板、样式表、JavaScript、测试等。功能的每个方面都存在于 bundle 中,每个功能都存在于 bundle 中。

采取行动 — 使用 Symfony2 控制台命令创建一个 bundle

我们将使用 IDE 的Run Symfony2 Command对话框使用generate:bundle命令创建一个新的 bundle。所以让我们试试看...

  1. 项目窗格中,右键单击项目节点,从上下文菜单中选择Symfony2 | Run Command...以启动Run Symfony2 Command对话框,如下所示:Time for action — creating a bundle using the Symfony2 console command

您将能够在匹配任务框中看到可用命令的列表。您可以为这些命令添加参数,并在命令对话框中查看完整的命令。

  1. 从前面的对话框中,选择generate:bundle命令,然后单击Run,或双击列出的名称以运行命令。IDE 的图形控制台打开以提示命名空间。Time for action — creating a bundle using the Symfony2 console command

  2. 输入Bundle namespace的值,比如Application/FooBundle

  3. 输入Bundle name的值,或按Enter接受默认的 bundle 名称为ApplicationFooBundle

  4. Target目录处按Enter接受默认的 bundle 路径为/src

  5. 您可以输入Configuration format的值(yml, xml, php,或annotation)yml;默认值为annotation

  6. 输入Yes以生成 bundle 的整个目录结构[no]?**,以生成 bundle 的整个目录结构;默认为 no。

  7. 再次输入Yes确认 bundle 生成。

  8. 确认自动更新您的内核 [yes]?确认自动更新路由 [yes]?处,按Enter接受默认值,即 yes。这样 bundle 就可以在 Symfony 内核中注册,并且 bundle 路由文件链接到默认的路由配置文件。

  9. 现在,正如你所看到的,在/src目录内创建了一个新的 bundle;bundle目录结构看起来类似于以下内容:Time for action — creating a bundle using the Symfony2 console command

请注意,默认的控制器、路由文件、模板等与 bundle 同时创建。

  1. 现在,要测试您的 bundle,请将浏览器指向http://localhost/symfony2/web/app_dev.php/hello/tonu,您可能会看到类似Hello Tonu!的输出。

  2. /src/Application/FooBundle/Resources/config/routing.yml处查看 bundle 路由文件,您将看到 URL 与默认控制器的索引操作(ApplicationFooBundle:Default:index)映射的模式/hello/{name}。在这个例子中,该操作显示作为 URL 参数传递的名称,而不是{name}

刚刚发生了什么?

每个 bundle 都托管在一个命名空间下(例如Acme/Bundle/BlogBundleAcme/BlogBundle)。命名空间应以“供应商”名称开头,例如您的公司名称、项目名称或客户名称,后面跟着一个或多个可选的类别子命名空间,最后以 bundle 名称本身结尾(必须以Bundle作为后缀)。

注意

请参阅http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1,了解有关 bundle 命名约定的更多详细信息。

我们已经看到了交互式控制台,它要求参数并自动创建整个bundle目录结构。此外,它将 bundle 注册到 Symfony 的/app/AppKernel.php中,并将 bundle 路由配置文件链接到default/app/config/routing.yml中。

注意

Symfony 学习资源:

symfony.com/doc/current/book/index.html

附录 B. NetBeans 键盘快捷键

NetBeans IDE 的常用键盘快捷键如下。

文件菜单

命令 动作
Ctrl + Shift + N 新建 使用新建项目向导创建新项目
Ctrl + N 新建 使用新建文件向导创建新文件
Ctrl + Shift + O 打开文件 打开现有项目
Ctrl + S 保存 保存当前文件
Ctrl + Shift + S 全部保存 保存所有文件

编辑菜单

命令 动作
Ctrl + Z 撤销 撤销(一次)编辑操作序列,除了保存
Ctrl + Y 重做 撤销(一次)撤销命令序列
Ctrl + X 剪切 删除当前选择并将其放置在剪贴板上
Ctrl + C 复制 将当前选择复制到剪贴板
Ctrl + V 粘贴 将剪贴板的内容粘贴到插入点
Ctrl + Shift + V 粘贴格式 将剪贴板的格式内容粘贴到插入点
Delete 删除 删除当前选择
Ctrl + A 全选 选择当前文档或窗口中的所有内容
Alt + Shift + J 选择标识符 选择当前标识符
Ctrl + F3 查找选择 查找当前选择的实例
F3 查找下一个 查找下一个找到的文本实例
Shift + F3 查找上一个 查找上一个找到的文本实例
Ctrl + F 查找 查找文本字符串
Ctrl + H 替换 查找文本字符串并用指定的字符串替换它
Alt + F7 查找用法 查找所选代码的用法和子类型
Ctrl + Shift + F 在项目中查找 在项目中查找指定的文本、对象名称和对象类型
Ctrl + Shift + H 在项目中替换 替换项目中的文本、对象名称和对象类型

视图菜单

命令 动作
Ctrl + - (减号) 折叠折叠 如果插入点在可折叠的文本部分中,则将这些行折叠成一行
Ctrl + + (加号) 展开折叠 如果源编辑器窗口中当前选择的行代表几行折叠的文本,则展开折叠以显示所有行
Ctrl + Shift + - (减号) 折叠全部 折叠源编辑器窗口中所有可折叠的文本部分
Ctrl + Shift + + (plus) 展开全部 展开源编辑器窗口中所有可折叠的文本部分
Alt + Shift + Enter 全屏 将窗口展开到屏幕的全长和全宽

导航菜单

命令 动作
Alt + Shift + O 转到文件 查找并打开特定文件
Ctrl + O 转到类型 查找并打开特定的类或接口
Ctrl + Alt + Shift + O 转到符号 查找并打开特定符号
Ctrl + Shift + T 转到测试 查找并打开特定测试
Ctrl + 反引号 转到上一个文档 打开当前文档之前打开的文档
Ctrl + Shift + B 转到源 显示包含所选类定义的源文件
Ctrl + B 转到声明 跳转到光标下项目的声明
Ctrl + Shift + P 转到超级实现 跳转到光标下项目的超级实现
Ctrl + Q 上次编辑位置 将编辑器滚动到上次编辑发生的地方
Alt + 左箭头键 返回 后退
Alt + 右箭头键 前进 前进
Ctrl + G 转到行 跳转到指定行
Ctrl + Shift + M 切换书签 在代码行上设置书签
Ctrl + Shift +. (句号) 下一个书签 通过书签向前循环
Ctrl + Shift + , (逗号) 上一个书签 通过书签向后循环
Ctrl +. (句号) 下一个错误 源代码编辑器窗口滚动到包含下一个构建错误的行
Ctrl +, (逗号) 上一个错误 源代码编辑器窗口滚动到包含上一个构建错误的行
Ctrl + Shift + 1 在项目中选择 打开项目窗口并在其中选择当前文档
Ctrl + Shift + 2 在文件中选择 打开文件窗口并在其中选择当前文档
Ctrl + Shift + 3 在收藏夹中选择 打开收藏夹窗口并在其中选择当前文档

源菜单

命令 动作
Alt + Shift + F 格式 格式化所选代码或整个文件(如果未选择任何内容)
Alt + Shift + 左箭头键 向左移动 将所选行或多行向左移动一个制表符
Alt + Shift + 右箭头键 向右移动 将所选行或多行向右移动一个制表符
Alt + Shift + 上箭头键 向上移动 将所选行或多行向上移动一行
Alt + Shift + 下箭头键 向下移动 将所选行或多行向下移动一行
Ctrl + Shift + 上箭头键 向上复制 复制所选行或多行一行向上
Ctrl + Shift + 下箭头键 向下复制 复制所选行或多行一行向下
Ctrl + / (斜杠) 或 Ctrl + Shift + C 切换注释 切换当前行或所选行的注释
Ctrl + 空格键 完成代码 显示代码完成框
Alt + 插入 插入代码 弹出一个上下文感知菜单,您可以使用它来生成常见结构,如构造函数、getter 和 setter
Alt + Enter 修复代码 显示编辑器提示,并在显示灯泡时,IDE 会在提示可用时通知您
Ctrl + Shift + I 修复导入 生成文件中指定类所需的导入语句
Ctrl + P 显示方法参数 选择下一个参数;您必须选择(高亮显示)一个参数,此快捷键才能起作用
Ctrl + Shift + 空格 显示文档 显示光标下项目的文档
Ctrl + Shift + K 插入下一个匹配的单词 当您键入其开始字符时,生成代码中其他地方使用的下一个单词
Ctrl + K 插入上一个匹配的单词 当您键入其开始字符时,生成代码中其他地方使用的上一个单词

重构菜单

命令 动作
Ctrl + R 重命名 原地重命名
Ctrl + M 移动 原地移动
Alt + 删除 安全删除 删除之前,显示引用

运行菜单

命令 动作
F6 运行主项目 运行主项目
Alt + F6 测试项目 为项目启动 PHP 单元测试
Shift + F6 运行文件 运行当前选择的文件
Ctrl + F6 测试文件 为当前文件启动 PHP 单元测试
F11 构建主项目 编译文件;如果选择文件夹,IDE 将编译所有文件,而不管它们自上次编译以来是否发生了更改
Shift + F11 清理并构建主项目 编译文件;如果选择文件夹,IDE 将编译所有文件,而不管它们自上次编译以来是否发生了更改
F9 编译文件 编译文件;如果选择文件夹,IDE 仅编译自上次编译以来新的或已更改的文件

调试菜单

命令 操作
Ctrl + F5 调试主项目 调试主项目
Ctrl + Shift + F5 调试文件 开始当前选定文件的调试会话
Ctrl + Shift + F6 为文件调试测试 开始 PHPUnit 中文件的调试测试
Shift + F5 结束调试会话 结束调试会话
F5 继续 恢复调试直到下一个断点或程序结束
F8 跳过 执行程序的一行源代码。如果该行是一个方法调用,执行整个方法然后停止
Shift + F8 跳过表达式 跳过表达式然后停止调试
F7 步入 执行程序的一行源代码;如果该行是一个方法调用,执行程序直到方法的第一条语句然后停止
Ctrl + F7 跳出 执行程序的一行源代码;如果该行是一个方法调用,执行该方法并返回控制权给调用者
F4 运行到光标 运行当前项目到文件中光标的位置,然后停止程序执行
Shift + F7 运行到方法 运行当前项目到指定方法,然后进入该方法
Ctrl + Alt + 上箭头键 使被调用者为当前 使被调用方法成为当前调用;仅在调用堆栈窗口中选择调用时可用
Ctrl + Alt + 下箭头键 使调用者为当前 使调用方法成为当前调用;仅在调用堆栈窗口中选择调用时可用
Ctrl + F8 切换行断点 在程序中光标位置添加或移除断点
Ctrl + Shift + F8 新断点 在指定行、异常或方法设置新断点
Ctrl + Shift + F7 新建监视 添加指定变量以监视
Ctrl + F9 评估表达式 打开评估表达式对话框

窗口菜单

命令 操作
Ctrl + 0 源代码编辑器 切换到源代码编辑器窗口
Ctrl + 1 项目 打开项目窗口
Ctrl + 2 文件 打开文件窗口
Ctrl + 3 收藏夹 打开收藏夹窗口
Ctrl + 4 输出窗口 打开输出窗口
Ctrl + 5 服务 打开服务窗口
Ctrl + Shift + 5 HTTP 监视器 打开HTTP 监视器窗口
Ctrl + 6 任务列表 打开任务列表窗口
Ctrl + 7 导航器 打开导航器
Alt + Shift + 1 调试 | 变量 打开变量调试器窗口
Alt + Shift + 2 调试 | 监视 打开监视调试器窗口
Alt + Shift + 3 调试 | 调用堆栈 打开调用堆栈调试器窗口
Alt + Shift + 4 调试 | 类 打开类调试器窗口
Alt + Shift + 5 调试 | 断点 打开断点调试器窗口
Alt + Shift + 6 调试 | 会话 打开会话调试器窗口
Alt + Shift + 7 调试 | 线程 打开线程调试器窗口
Alt + Shift + 8 调试 | 源代码 打开源代码窗口
Ctrl + W 关闭 关闭当前窗口中的当前选项卡;如果窗口没有选项卡,则关闭整个窗口
Shift + Esc 最大化窗口 最大化源代码编辑器窗口或当前窗口
Alt + Shift + D 取消停靠窗口 从 IDE 中分离窗口
Ctrl + Shift + W 关闭所有文档 关闭源代码编辑器窗口中的所有打开文档
Shift + F4 文档 打开文档对话框,您可以在其中保存和关闭打开的文档组
Ctrl + Tab (Ctrl + ') 切换到最近的窗口 以它们最后使用的顺序切换打开的窗口;对话框显示所有打开的窗口和源编辑器窗口中的每个打开文档

滚动和选择

动作
Ctrl + 下箭头键 在不移动插入点的情况下向上滚动窗口
Ctrl + 上箭头键 在不移动插入点的情况下向下滚动窗口
Ctrl + [ 将插入点移动到突出显示的匹配括号处;此快捷键仅在插入点紧跟在开放或关闭括号之后时才起作用
Ctrl + Shift + [ 选择一对括号之间的代码块;此快捷键仅在插入点紧跟在开放或关闭括号之后时才起作用
Ctrl + G 跳转到指定行
Ctrl + A 选择文件中的所有文本

修改文本

动作
Insert 在插入文本和覆盖文本模式之间切换
Ctrl + Shift + J 打开国际化对话框,您可以在插入点插入国际化字符串
Ctrl + U, U 将所选字符或插入点右侧的字符转换为大写
Ctrl + U, L 将所选字符或插入点右侧的字符转换为小写
Ctrl + U, S 反转所选字符的大小写或插入点右侧的字符的大小写

代码折叠

动作
Ctrl + - (减号) 折叠插入点所在的代码块
Ctrl + + (加号) 展开插入点旁边的代码块
Ctrl + Shift + - (减号) 折叠所有代码块
Ctrl + Shift + + (加号) 展开所有代码块

搜索文本

动作
Ctrl + F3 搜索插入点所在的单词并突出显示该单词的所有出现
F3 选择当前搜索中单词的下一个出现
Shift + F3 选择当前搜索中单词的上一个出现
Alt + Shift + H 打开或关闭搜索结果的高亮显示
Ctrl + F 打开查找对话框
Ctrl + H 打开查找和替换对话框

设置制表位

动作
Tab 将插入点右侧的所有文本向右移动
Alt + Shift + Right 将包含插入点的行中的文本向右移动
Alt + Shift + Left 将包含插入点的行中的文本向左移动

IDE 还为那些习惯于其他编辑器和 IDE 键盘快捷键的用户提供了不同的预配置快捷键配置文件。您可以复制和修改任何键盘快捷键配置文件。IDE 提供以下快捷键配置文件:

  • Eclipse

  • Emacs

  • IDEA

  • NetBeans

  • NetBeans 5.5

由于 NetBeans IDE 5.5 和 NetBeans IDE 6.0 之间的快捷键映射发生了重大变化,您可以选择切换回 NetBeans IDE 5.5 中可用的快捷键。要这样做,请从工具 | 选项 | 键盘映射中选择 NetBeans 5.5 快捷键配置文件。

注意

有关 Mac OS 键盘快捷键,请参阅NetBeans 帮助 | IDE 基础知识 | 键盘快捷键 | Mac OS 键盘快捷键

附录 C. 弹出测验答案

每章的弹出测验答案都在这里提供,供您参考。你得了多少分?

第二章,使用 PHP 编辑器提高编码效率

弹出测验 - 熟悉基本 IDE 功能

1 d
2 d
3 c
4 c
5 b

弹出测验 - 探索 PHP 编辑器

1 d
2 d
3 b
4 d

弹出测验 - 使用重命名重构和即时重命名

1 a
2 b

弹出测验 - 使用代码补全

1 c
2 d
3 b

弹出测验 - 使用代码生成器

1 a
2 d

第三章,使用 NetBeans 构建类似 Facebook 的状态发布器

弹出测验 - 理解 PDO

1 c

弹出测验 - 理解 CSS

1 b
2 a
3 b

弹出测验 - 复习 jQuery 知识

1 c
2 d
3 c
4 c
5 b
6 d

第四章,使用 NetBeans 进行调试和测试

弹出测验 - 使用 XDebug 进行调试

1 a, c, d
2 b
3 b

弹出测验 - PEAR

1 b

弹出测验 - 单元测试和代码覆盖率

1 d
2 c
3 c
4 c

第五章,使用代码文档

弹出测验 - 复习标签

1 c
2 b
3 a

第六章,了解 Git,NetBeans 方式

弹出测验 - 理解 Git

1 a
2 b
3 b
4 b

弹出测验 - 使用 Git

1 a
2 b
3 b
4 d

弹出测验 - 使用远程存储库和分支

1 b
2 b
3 d

第七章,构建用户注册、登录和注销

弹出测验 - 复习 PDO

1 c

弹出测验 - 使用命名空间

1 a, b, c
2 b

弹出测验 - 应用架构

1 c
2 b
3 a
posted @ 2024-05-05 00:13  绝不原创的飞龙  阅读(10)  评论(0编辑  收藏  举报