PHP-MySQL-和-JavaScript-学习指南第六版-一-

70 阅读1小时+

PHP、MySQL 和 JavaScript 学习指南第六版(一)

原文:zh.annas-archive.org/md5/4aa97a1e8046991cb9f8d5f0f234943f

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

PHP 与 MySQL 的结合是实现动态、数据库驱动型网页设计最便捷的方法,面对一些更难学习的集成框架的挑战,它始终占据一席之地。由于其开源根源,它可以免费实现,因此是网页开发的极为流行的选择。

任何想要成为 Unix/Linux 甚至 Windows 平台开发者的人都需要掌握这些技术。再加上 JavaScript、React、CSS 和 HTML5 等合作技术,您将能够创建像 Facebook、Twitter 和 Gmail 这样的行业标准的网站。

受众

本书适合希望学习如何创建有效和动态网站的人士。这可能包括已经掌握创建静态网站或 CMS(如 WordPress)的网站管理员或平面设计师,但希望将他们的技能提升到更高水平的人,以及高中生、大学生、最近毕业生和自学者。

实际上,任何准备学习响应式网页设计背后基础原理的人都将获得 PHP、MySQL、JavaScript、CSS 和 HTML5 核心技术的全面掌握,您还将学习 React 库的基础知识。

本书的假设

本书假定您具有 HTML 的基本理解,并且至少能够编写简单的静态网站,但不假定您具有任何 PHP、MySQL、JavaScript、CSS 或 HTML5 的先前知识——尽管如果您有这些知识,您通过本书的进展会更快。

本书的组织结构

本书的各章按特定顺序编写,首先介绍它涵盖的所有核心技术,然后引导您在 Web 开发服务器上安装这些技术,以便您可以准备好处理示例。

在第一部分中,您将掌握 PHP 编程语言的基础,涵盖语法、数组、函数和面向对象编程。

随后,掌握了 PHP 之后,您将进入 MySQL 数据库系统的介绍,您将从 MySQL 数据库的结构到生成复杂查询的各个方面进行学习。

在此之后,您将学习如何将 PHP 和 MySQL 结合起来,通过整合表单和其他 HTML 特性开始创建自己的动态网页。然后,您将深入了解 PHP 和 MySQL 开发的实际细节,学习各种有用的函数以及如何管理 cookies 和 sessions,以及如何保持高水平的安全性。

在接下来的几章中,您将全面掌握 JavaScript 的基础知识,从简单函数和事件处理到访问文档对象模型、浏览器验证和错误处理。您还将全面了解如何使用流行的 React 库来进行 JavaScript 开发。

理解了这三种核心技术后,您将学习如何进行后台 Ajax 调用,将您的网站转变为高度动态的环境。

接下来,您将花两章时间学习如何使用 CSS 来为您的网页设置样式和布局,然后了解 React 库如何大大简化您的开发工作。然后,您将进入关于 HTML5 中的交互功能的最后一部分,包括地理位置、音频、视频和画布。之后,您将把所学的一切整合在一起,构建一个完整的程序集,这些程序共同构成一个完全功能的社交网络网站。

在此过程中,您将找到许多关于良好编程实践和可以帮助您找到和解决难以检测的编程错误的建议。还有许多链接指向包含所涵盖主题详细信息的网站。

本书使用的约定

本书中使用以下排版约定:

普通文本

指示菜单标题、选项和按钮。

斜体

指示新术语、URL、电子邮件地址、文件名、文件扩展名、路径名、目录和 Unix 实用程序。还用于数据库、表和列名。

常宽

指示命令和命令行选项、变量和其他代码元素、HTML 标签以及文件的内容。

常宽粗体

显示程序输出,并用于突出显示文本中讨论的代码部分。

常宽斜体

显示应替换为用户提供值的文本。

注意

此元素表示一般说明。

警告

此元素表示警告或注意事项。

提示

此元素表示提示或一般说明。

使用代码示例

附加资料(代码示例,练习等)可在GitHub上下载。

本书旨在帮助您完成工作。一般而言,如果本书提供示例代码,则可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需征得我们的许可。例如,编写使用本书中多个代码片段的程序不需要许可。销售或分发从 O’Reilly 书籍中获取的示例集需要许可。通过引用本书并引用示例代码来回答问题不需要许可。将本书中大量示例代码合并到您产品的文档中需要许可。

我们感谢但不要求归属。归属通常包括标题、作者、出版商和 ISBN。例如:“Learning PHP, MySQL & JavaScript 6th Edition by Robin Nixon (O’Reilly). Copyright 2021 Robin Nixon, 9781492093824.”

如果您觉得您对代码示例的使用超出了合理使用范围或以上给出的许可,请随时通过permissions@oreilly.com与我们联系。

奥莱利在线学习

注意

超过 40 年来,奥莱利传媒为企业的技术和业务培训提供了知识和见解,助力它们取得成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专长。奥莱利的在线学习平台为您提供按需访问直播培训课程、深度学习路径、交互式编码环境以及奥莱利和其他 200 多家出版商的大量文本和视频。有关更多信息,请访问http://oreilly.com

如何联系我们

请将关于本书的评论和问题发送给出版商:

  • 奥莱利传媒公司

  • 1005 Gravenstein Highway North

  • 加利福尼亚州,塞巴斯托波尔,95472

  • (800) 998-9938(美国或加拿大)

  • (707) 829-0515(国际或本地)

  • (707) 829-0104(传真)

我们为这本书准备了一个网页,列出了勘误、示例和任何额外信息。你可以访问这个页面,网址为*oreil.ly/learning-ph…

关于本书的问题,请发送电子邮件至bookquestions@oreilly.com

有关我们的书籍和课程的新闻和更多信息,请访问我们的网站http://www.oreilly.com

在 Facebook 上找到我们:http://facebook.com/oreilly

在 Twitter 上关注我们:http://twitter.com/oreillymedia

在 YouTube 上观看我们:http://www.youtube.com/oreillymedia

致谢

我要感谢高级内容采集编辑阿曼达·奎恩、内容开发编辑梅丽莎·波特和所有为这本书努力工作的人,包括米哈尔·斯帕切克和大卫·麦基对技术的全面审查,凯特琳·盖根监督制作,金·科弗进行编辑,金·桑多瓦尔进行校对,朱迪斯·麦康维尔创建索引,卡伦·蒙哥马利设计原始的飞翔树鼠封面,兰迪·科默更新书籍封面,我的最初编辑安迪·奥拉姆监督前五版的出版,以及其他无法一一列举的提供勘误和建议的人。

第一章:动态网页内容介绍

万维网是一个不断发展的网络,早在 1990 年代初期就已经远远超出了其创立时解决特定问题的概念。在欧洲核子研究中心(现已成为大型强子对撞机的运营者)进行的最先进实验产生了大量数据,这些数据分布在全世界各地的参与科学家之间变得异常棘手。

那个时候,互联网已经建立起来,连接了数十万台计算机,因此蒂姆·伯纳斯-李(一位欧洲核子研究中心的研究员)设计了一种使用超链接框架进行导航的方法,后来被称为超文本传输协议,或者 HTTP。他还创建了一种叫做超文本标记语言,或者 HTML 的标记语言。为了将它们结合起来,他写了第一个网页浏览器和网页服务器。

如今,我们认为这些工具理所当然,但在当时,这个概念是革命性的。迄今为止,家用调制解调器用户所体验到的最大连接性是拨号连接到一个公告板,你只能与该服务的其他用户交流和交换数据。因此,你需要成为许多公告板系统的成员,才能有效地通过电子方式与你的同事和朋友进行沟通。

但是伯纳斯-李一举改变了这一切,在 1990 年代中期,已经有三种主要的图形网页浏览器竞争吸引了五百万用户的注意。很快就显而易见,有些东西是缺失的。是的,文本和图片页面与超链接的概念非常出色,但其结果并未反映出计算机和互联网在满足每个用户具体需求方面的即时潜力。使用网络是一种非常枯燥和普通的体验,即使现在我们有了滚动文本和动画 GIF!

购物车、搜索引擎和社交网络明显改变了我们使用网络的方式。在本章中,我们将简要介绍构成网络的各种组件以及帮助使其使用成为丰富和动态体验的软件。

注意

现在我们需要立即开始使用一些缩写词。在继续之前,我已经试图清楚地解释它们,但不要太担心它们代表什么或者这些名称的含义,因为随着你继续阅读,细节会变得清晰起来。

HTTP 和 HTML:伯纳斯-李的基础

HTTP 是一种通信标准,它管理在运行在最终用户计算机上的浏览器和 Web 服务器之间发送的请求和响应。服务器的工作是接受来自客户端的请求并尝试以有意义的方式回复,通常是通过提供请求的 Web 页面 —— 这就是为什么使用术语服务器。与服务器的自然对应物是客户端,因此该术语同时适用于 Web 浏览器和运行它的计算机。

在客户端和服务器之间可能存在多个其他设备,例如路由器、代理、网关等等。它们在确保请求和响应在客户端和服务器之间正确传输方面扮演着不同的角色。通常情况下,它们使用互联网来发送这些信息。这些中间设备中的一些还可以通过在所谓的缓存中本地存储页面或信息,然后直接从缓存向客户端提供这些内容来帮助加速互联网。

Web 服务器通常可以处理多个同时连接,当不与客户端通信时,它会花时间监听传入连接。一旦连接到达,服务器就会发送回复以确认接收到连接。

请求/响应过程

在其最基本的水平上,请求/响应过程包括 Web 浏览器或其他客户端请求 Web 服务器发送 Web 页面,服务器然后发送页面。浏览器然后负责显示或渲染页面(参见图 1-1)。

图 1-1. 基本的客户端/服务器请求/响应序列

请求和响应序列中的步骤如下:

  1. 您在浏览器的地址栏中输入*server.com*。

  2. 您的浏览器查找server.com的 Internet 协议(IP)地址。

  3. 您的浏览器发出请求,请求server.com的主页。

  4. 请求通过互联网传输并到达server.com Web 服务器。

  5. Web 服务器在收到请求后,在其磁盘上查找 Web 页面。

  6. Web 服务器检索页面并将其返回给浏览器。

  7. 您的浏览器显示 Web 页面。

对于平均网页,该过程还会为页面中的每个对象执行一次:图形、嵌入式视频或 Flash 文件,甚至 CSS 模板。

在第 2 步中,请注意浏览器查找server.com的 IP 地址。连接到互联网的每台机器都有一个 IP 地址 —— 包括您的计算机,但我们通常通过名称访问 Web 服务器,例如google.com。浏览器还要查询一个名为域名系统(DNS)的附加互联网服务,以查找服务器关联的 IP 地址,然后使用它与计算机通信。

对于动态网页,过程稍微复杂一些,因为可能会同时涉及 PHP 和 MySQL。例如,您可能会点击一件雨衣的图片。然后 PHP 将使用标准的数据库语言 SQL(本书中您将学习到其许多命令)组装一个请求,并将请求发送到 MySQL 服务器。MySQL 服务器将返回您选择的雨衣的信息,PHP 代码将其包装在一些 HTML 中,服务器将其发送到您的浏览器(见 图 1-2)。

动态客户端/服务器请求/响应序列

图 1-2. 动态客户端/服务器请求/响应序列

步骤如下:

  1. 您在浏览器的地址栏中输入 *server.com*。

  2. 您的浏览器查找 server.com 的 IP 地址。

  3. 您的浏览器向该地址发出请求,请求 Web 服务器的主页。

  4. 请求穿过互联网并到达 server.com Web 服务器。

  5. Web 服务器在收到请求后,从硬盘中获取主页文件。

  6. 现在首页已经加载到内存中,Web 服务器注意到它是一个包含 PHP 脚本的文件,并将页面传递给 PHP 解释器。

  7. PHP 解释器执行 PHP 代码。

  8. 一些 PHP 包含 SQL 语句,这些语句现在由 PHP 解释器传递给 MySQL 数据库引擎。

  9. MySQL 数据库将语句的结果返回给 PHP 解释器。

  10. PHP 解释器将执行的 PHP 代码的结果与来自 MySQL 数据库的结果一起返回给 Web 服务器。

  11. Web 服务器将页面返回给请求的客户端,客户端显示该页面。

虽然了解这个过程对于了解这三个元素如何协同工作是有帮助的,但实际上你不需要关心这些细节,因为它们都会自动发生。

每个示例中返回给浏览器的 HTML 页面可能包含 JavaScript,客户端会本地解释它,可能会发起另一个请求,就像嵌入对象(如图像)一样。

PHP、MySQL、JavaScript、CSS 和 HTML5 的好处

在本章开头,我介绍了 Web 1.0 的世界,但很快人们就开始创造 Web 1.1,随着诸如 Java、JavaScript、JScript(微软的 JavaScript 轻微变体)和 ActiveX 等浏览器增强功能的开发。在服务器端,使用脚本语言如 Perl(PHP 语言的替代方案)和 服务器端脚本(动态地将一个文件的内容(或运行本地程序的输出)插入另一个文件中)正在进行进展。

风云稍定后,三种主要技术脱颖而出。尽管 Perl 仍然是一种流行的脚本语言并拥有强大的追随者群体,PHP 的简单性和与 MySQL 数据库程序的内置链接使其用户数量超过了两倍。而 JavaScript 则已成为动态操作层叠样式表(CSS)和 HTML 的必不可少的组成部分,现在更承担了处理客户端异步通信的更强大任务(在网页加载后在客户端和服务器之间交换数据)。使用异步通信,网页在后台执行数据处理并向 web 服务器发送请求——而用户在浏览网页时并不知道这一切正在进行。

毫无疑问,PHP 和 MySQL 的共生特性帮助它们双双前进,但最初是什么吸引开发者呢?简单答案必须是你可以使用它们快速创建网站动态元素的简便性。MySQL 是一个快速而强大且易于使用的数据库系统,几乎提供了网站在查找和向浏览器提供数据时所需的一切。当 PHP 联合 MySQL 来存储和检索这些数据时,你拥有了构建 Web 2.0 所需的基本部件。

当你把 JavaScript 和 CSS 也加入进来时,你就有了构建高度动态和交互式网站的配方——尤其是现在有许多复杂的 JavaScript 框架可供调用,以加速网页开发。这些包括广为人知的 jQuery,直到最近仍是程序员访问异步通信特性的常见方式,以及近年来快速增长的 React JavaScript 库。它现在是下载和实施最广泛的框架之一,以至于自 2020 年以来,Indeed 网站列出的 React 开发者职位数量超过 jQuery 的两倍以上。

MariaDB:MySQL 的克隆品

在 Oracle 收购 Sun 微系统(MySQL 的所有者)之后,社区担心 MySQL 可能不会完全保持开源,因此从中分叉出了 MariaDB,以在 GNU GPL 下保持其自由。MariaDB 的开发由 MySQL 的一些原始开发者领导,它与 MySQL 保持极其密切的兼容性。因此,在一些服务器上,你可能会遇到 MariaDB 替代 MySQL,但不用担心,本书中的所有内容在 MySQL 和 MariaDB 上同样适用,你可以随意切换而不会有任何区别。

无论如何,事实证明,许多最初的担忧似乎已经消除,因为 MySQL 仍然是开源的,Oracle 只是为支持和提供附加功能(如地理复制和自动扩展)的版本收费。然而,与 MariaDB 不同,MySQL 不再由社区驱动,因此知道如果需要时 MariaDB 始终可用将让许多开发者安心,可能也确保了 MySQL 本身将继续保持开源。

使用 PHP

使用 PHP,将动态活动嵌入网页非常简单。给页面添加*.php*扩展名后,即可立即使用脚本语言。从开发者的角度来看,你只需编写如下的代码:

<?php
 echo " Today is " . date("l") . ". ";
?>

Here's the latest news.

开头的<?php告诉 Web 服务器允许 PHP 程序解释直到?>标签之间的所有代码。在这个结构之外,所有内容都将直接发送给客户端作为 HTML。因此,文本Here's the latest news.将简单地输出到浏览器;在 PHP 标记内,内置的date函数根据服务器系统时间显示当前星期几。

两部分的最终输出如下:

*Today is Wednesday. Here's the latest news.*

PHP 是一种灵活的语言,有些人更喜欢将 PHP 结构直接放在 PHP 代码旁边,如下所示:

Today is <?php echo date("l"); ?>. Here's the latest news.

还有更多格式化和输出信息的方式,我将在有关 PHP 的章节中进行解释。关键是,使用 PHP,Web 开发人员拥有一种脚本语言,虽然不像在 C 或类似语言中编译代码那样快速,但速度非常快,并且与 HTML 标记完美集成。

注意

如果你打算将本书中的 PHP 示例输入到程序编辑器中与我一起工作,你必须记住在它们前面加上<?php,并在后面加上?>,以确保 PHP 解释器可以处理它们。为了方便起见,你可能希望准备一个名为example.php的文件,并在适当位置添加这些标记。

使用 PHP,你可以对你的 Web 服务器进行无限制的控制。无论你需要在页面中动态修改 HTML、处理信用卡、将用户详细信息添加到数据库,还是从第三方网站获取信息,你都可以在同一个 PHP 文件中完成。

使用 MySQL

当然,能够动态更改 HTML 输出的能力没有太大意义,除非你还有一种方法跟踪用户在使用网站时提供的信息。在互联网早期,许多网站使用“平面”文本文件存储用户名和密码等数据。但是,如果文件没有正确锁定以防止多个同时访问导致的损坏,这种方法可能会导致问题。此外,平面文件在变得过大之前就变得难以管理,更不用说尝试合并文件和在合理时间内执行复杂搜索的困难了。

这就是关系型数据库与结构化查询变得至关重要的地方。而 MySQL 作为免费使用并安装在大量互联网服务器上的数据库管理系统,则非常出色地胜任了这一角色。它是一个强大且异常快速的数据库管理系统,使用类似英语的命令。

MySQL 结构的最高级别是数据库,您可以在其中拥有一个或多个包含您数据的表。例如,假设您正在处理一个名为users的表,在其中创建了surnamefirstnameemail列,现在希望添加另一个用户。您可能使用的一条命令如下:

INSERT INTO users VALUES('Smith', 'John', 'jsmith@mysite.com');

之前您已经发出了其他命令来创建数据库和表,并设置了所有正确的字段,但此处的 SQL INSERT命令显示了向数据库添加新数据可以多么简单。SQL 是上世纪 70 年代设计的一种语言,它让人联想起最古老的编程语言之一,COBOL。然而,它非常适合数据库查询,这就是为什么在这么长时间后它仍然在使用中的原因。

查找数据同样简单。假设您有一个用户的电子邮件地址并需要查找该人的姓名。为此,您可以发出如下所示的 MySQL 查询:

SELECT surname,firstname FROM users WHERE email='jsmith@mysite.com';

MySQL 然后将返回Smith, John及数据库中与该电子邮件地址可能关联的任何其他姓名对。

正如您所期望的那样,您可以在 MySQL 中做的远不止简单的INSERTSELECT命令。例如,您可以组合相关数据集以将相关信息片段汇集在一起,请求以各种顺序返回结果,在您仅知道要搜索的字符串的一部分时进行部分匹配,仅返回第n个结果等等。

使用 PHP,您可以直接调用 MySQL 而无需自己直接访问 MySQL 命令行界面。这意味着您可以将结果保存在数组中进行处理,并执行多次查找,每次查找都依赖于从前面的查找返回的结果,以便深入到您需要的数据项。

更有力的是,正如稍后将看到的,MySQL 内置了额外的功能,可以高效地运行 MySQL 内部的常见操作,而不是通过多个 PHP 调用 MySQL 创建它们。

使用 JavaScript

JavaScript 的创建是为了使脚本能够访问 HTML 文档的所有元素。换句话说,它提供了一种动态用户交互的手段,例如在输入表单中检查电子邮件地址的有效性,并显示诸如“您是认真的吗?”之类的提示(尽管不能依赖其用于安全性,这应始终在 Web 服务器上执行)。

结合 CSS(请参阅下一节),JavaScript 是动态网页背后的动力,使页面能够在您眼前变化,而不是在服务器返回新页面时变化。

然而,JavaScript 以前使用起来很棘手,因为不同浏览器设计者选择实现方式存在显著差异。这主要是在一些制造商试图在其浏览器中添加额外功能,却牺牲了与竞争对手的兼容性时出现的问题。

幸运的是,开发者们大多已经理智过来,意识到彼此兼容的必要性,因此在如今不太需要为不同浏览器优化代码。然而,仍然有数百万用户在使用传统浏览器,而且这种情况可能在未来很多年内仍会存在。幸运的是,有解决不兼容性问题的解决方案,本书后面我们会看到一些库和技术,使您能够安全地忽略这些差异。

现在,让我们看看如何使用基本的 JavaScript,所有浏览器都支持:

<script type="text/javascript">
  document.write("Today is " + Date() );
</script>

此代码片段告诉 Web 浏览器解析 <script> 标签中的所有内容为 JavaScript,浏览器通过使用 JavaScript 函数 Date 将文本 Today is 与当前日期一起写入当前文档。结果将看起来像这样:

Today is Wed Jan 01 2025 01:23:45
注意

除非您需要指定确切的 JavaScript 版本,通常可以省略 type="text/javascript",直接使用 <script> 开始 JavaScript 的解析。

如前所述,JavaScript 最初是为了在 HTML 文档中动态控制各种元素而开发的,这仍然是它的主要用途。但是越来越多地,JavaScript 被用于Ajax,即在后台访问 Web 服务器的过程。

异步通信使得网页开始类似独立程序,因为它们不需要完全重新加载以显示新内容。相反,异步调用可以拉取和更新网页上的单个元素,例如在社交网络网站上更改您的照片或替换您点击的按钮与问题答案。这个主题在第十八章中有详尽的介绍。

然后,在第二十二章中,我们仔细研究了 jQuery 框架,您可以在需要快速、跨浏览器的代码来操作网页时使用它,以免重复发明轮子。当然,还有其他可用的框架,因此我们还会看一下 React,在第二十四章中是当今最受欢迎的选择之一。它们都非常可靠,是许多经验丰富的网页开发者工具包中的主要工具。

使用 CSS

CSS 是 HTML 的重要伴侣,确保 HTML 文本和嵌入的图像在用户屏幕上一致且合适地排列。随着近年来 CSS3 标准的出现,CSS 现在提供了以前仅由 JavaScript 支持的动态交互水平。例如,不仅可以为任何 HTML 元素设置样式以更改其尺寸、颜色、边框、间距等,而且现在还可以仅使用几行 CSS 为网页添加动画过渡和转换效果。

使用 CSS 可能就像在网页头部的<style></style>标签之间插入几条规则一样简单,就像这样:

<style>
  p {
    text-align:justify;
    font-family:Helvetica;
  }
</style>

这些规则改变了<p>标签的默认文本对齐方式,使其中包含的段落完全对齐并使用 Helvetica 字体。

正如你将在第十九章中了解的那样,有许多不同的方式可以布置 CSS 规则,你还可以直接将它们包含在标签内部或保存为一个外部文件单独加载。这种灵活性不仅可以让你精确地为你的 HTML 设置样式,还可以(例如)提供内置的悬停功能来在鼠标悬停时为对象添加动画效果。你还将学习如何从 JavaScript 以及 HTML 中访问元素的所有 CSS 属性。

还有 HTML5

尽管所有这些对 Web 标准的增加都非常有用,但对于越来越雄心勃勃的开发者来说还不够。例如,在没有像 Flash 这样的插件的情况下,仍然没有简单的方法在 Web 浏览器中操作图形。同样,插入音频和视频到网页中也是如此。而且,在 HTML 的发展过程中还出现了一些令人讨厌的不一致性。

因此,为了澄清所有这些并将互联网推向 Web 2.0 之外,进入其下一个迭代,创建了一个新的 HTML 标准来解决所有这些缺陷:HTML5。其开发始于 2004 年,当时 Mozilla 基金会和 Opera Software(两个流行的 Web 浏览器开发者)起草了第一个草案,但直到 2013 年初,最终草案才被提交给了万维网联盟(W3C),这是国际上的 Web 标准管理机构。

HTML5 发展了几年,但现在我们有了一个非常稳定和稳健的版本 5.1(自 2016 年以来)。然而,这是一个永无止境的发展循环,随着时间的推移,肯定会向其中添加更多功能,例如计划在 2017 年作为 W3C 推荐发布的 5.2 版(计划使插件系统过时),以及截至 2020 年仍在计划中的 HTML 5.3(具有自动大写功能等提议功能)。HTML5 中用于处理和显示媒体的一些最佳功能包括<audio><video><canvas>元素,它们添加了声音、视频和高级图形。关于 HTML5 的所有这些以及其他方面的详细信息都在第二十五章中详细介绍。

注意

我喜欢 HTML5 规范的一点是不再需要 XHTML 语法来自动关闭元素。过去,你可以使用<br>元素显示一个换行。然后,为了确保与 XHTML(计划中用于替换 HTML 但从未实现的)的未来兼容性,这被改为<br />,其中添加了一个关闭/字符(因为预期所有元素都应包含带有此字符的关闭标记)。但现在情况已经完全变了,你可以使用这些类型元素的任一版本。因此,出于简洁和减少击键次数的考虑,在这本书中,我已经恢复 r>` 等样式。

Apache 网络服务器

除了 PHP、MySQL、JavaScript、CSS 和 HTML5,动态网站还有第六位英雄:网络服务器。在这本书中,这意味着 Apache 网络服务器。我们已经讨论过网络服务器在 HTTP 服务器/客户端交换过程中做了一些什么,但它在幕后做了更多的事情。

例如,Apache 不仅提供 HTML 文件,它还处理各种文件,从图像和 Flash 文件到 MP3 音频文件,RSS(真正简单联合)源等等。这些对象不必是静态文件,比如 GIF 图像。它们都可以由诸如 PHP 脚本之类的程序生成。没错:PHP 甚至可以为您创建图像和其他文件,无论是即时生成还是提前生成以供以后使用。

要做到这一点,通常你要么在 Apache 或 PHP 中预编译模块,要么在运行时调用模块。其中一个这样的模块是 GD(图形绘制)库,PHP 用它来创建和处理图形。

Apache 也支持各种各样的自己的模块。除了 PHP 模块外,作为网络程序员最重要的模块是处理安全性的模块。其他例子包括 Rewrite 模块,它使网络服务器能够处理各种 URL 类型并将它们重写为自己的内部要求,以及 Proxy 模块,你可以用它来从缓存中提供经常请求的页面,以减轻服务器的负载。

本书的后面,你将看到如何使用这些模块来增强三个核心技术提供的功能。

处理移动设备

我们现在完全进入了互联的移动计算设备世界,仅为桌面计算机开发网站的概念已经相当过时。相反,开发人员现在旨在开发响应式网站和 Web 应用程序,以适应它们运行的环境。

因此,在本版本中新加入的内容是,我展示了如何仅使用本书详细介绍的技术以及强大的 jQuery Mobile 库,轻松创建这些类型的产品。借助它,你将能够专注于网站和 Web 应用程序的内容和可用性,知道它们的显示方式将自动优化以适应各种不同的计算设备——这样你就少了一件需要担心的事情。

为了展示如何充分利用它的力量,本书的最后一章创建了一个简单的社交网络示例网站,使用 jQuery 使其完全响应,并确保它在从小型手机屏幕到平板电脑或台式电脑的任何设备上显示良好。我们同样可以使用 React(或其他 JavaScript 库或框架),但也许这是你在完成本书后想要自己尝试的一个练习。

关于开源

这本书中涉及的技术都是开源的:任何人都可以阅读和修改代码。关于这些技术之所以如此流行的原因,常常被争论,但 PHP、MySQL 和 Apache 确实 是它们类别中使用最广泛的三种工具。可以肯定的是,它们的开源性意味着它们是由社区中的团队开发的,这些团队编写了他们自己想要和需要的功能,原始代码对所有人都是开放的,可以随时查看和修改。可以迅速发现错误,并在它们发生之前预防安全漏洞。

还有另一个好处:所有这些程序都可以免费使用。不用担心如果要扩展你的网站并增加更多服务器就必须购买额外的许可证,也无需在决定是否升级到这些产品的最新版本之前检查预算。

将所有内容整合在一起

PHP、MySQL、JavaScript(有时候辅助 React 或其他框架)、CSS 和 HTML5 的真正美妙之处在于它们如何协同工作来生成动态的 Web 内容:PHP 在 Web 服务器上处理所有主要工作,MySQL 管理所有数据,而 CSS 和 JavaScript 的组合则负责网页的呈现。JavaScript 还可以在需要更新某些内容(无论是在服务器上还是在网页上)时与你的 PHP 代码进行通信。借助 HTML5 中强大的功能,如画布、音频和视频以及地理位置,你可以使你的网页高度动态、互动且多媒体丰富。

在不使用程序代码的情况下,让我们通过查看将一些技术组合到每日异步通信功能中的过程来总结本章的内容,这是许多网站在用户注册新帐户时使用的功能的示例:检查所需的用户名是否已存在于站点上。这在 Gmail 中有一个很好的例子(见图 1-3)。

图 1-3. Gmail 使用异步通信来检查用户名的可用性

这个异步过程涉及的步骤将类似于以下内容:

  1. 服务器输出 HTML 创建网页表单,要求输入必要的详细信息,如用户名、名字、姓氏和电子邮件地址。

  2. 同时,服务器将一些 JavaScript 附加到 HTML 上,以监视用户名输入框并检查两件事情:是否已输入了一些文本,以及是否取消了输入选择,因为用户已点击了另一个输入框。

  3. 一旦文本被输入并取消选定该字段,JavaScript 代码会将输入的用户名传回到 Web 服务器上的 PHP 脚本,并等待响应。

  4. Web 服务器查找用户名并回复 JavaScript,告知该名称是否已被占用。

  5. 然后,JavaScript 在用户名输入框旁边放置一个指示,显示用户名对用户是否可用的状态——可能是一个绿色的勾或一个红色的叉,以及一些文本。

  6. 如果用户名不可用,并且用户仍然提交表单,JavaScript 将中断提交并重新强调(可能通过更大的图形和/或警报框)用户需要选择另一个用户名。

  7. 可选地,这个过程的改进版本甚至可以查看用户请求的用户名,并建议当前可用的替代用户名。

所有这些都在后台静静地进行,为用户提供了舒适且无缝的使用体验。没有异步通信,整个表单都必须提交到服务器,然后服务器会返回 HTML,并突出显示任何错误。这也算是一个可行的解决方案,但远不及即时处理表单字段那样整洁和令人愉悦。

然而,异步通信不仅仅可以用于简单的输入验证和处理,我们将在本书的后续部分探讨许多其他可以使用它来实现的功能。

在本章中,您已经读到了 PHP、MySQL、JavaScript、CSS 和 HTML5(以及 Apache)的核心技术的很好介绍,并学习了它们如何共同工作。在第二章中,我们将讨论如何在自己的 Web 开发服务器上安装和练习您将要学习的所有内容。

问题

  1. 至少需要哪四个组件才能创建一个完全动态的网页?

  2. HTML 是什么意思?

  3. 为什么 MySQL 的名称包含字母 SQL

  4. PHPJavaScript 都是能够为网页生成动态结果的编程语言。它们的主要区别是什么,为什么你会同时使用它们呢?

  5. CSS 代表什么?

  6. 列出 HTML5 中引入的三个主要新元素。

  7. 如果你在一个开源工具中遇到一个(很少见的)Bug,你认为你能如何修复它?

  8. 为什么像 jQuery 或 React 这样的框架对于开发现代网站和 Web 应用程序如此重要?

查看 “第一章答案” 在 附录 A 中,获取这些问题的答案。

第二章:建立开发服务器

如果你想开发互联网应用程序但没有自己的开发服务器,你将不得不在能够测试之前将每一次修改上传到 Web 上的某个其他服务器。

即使在快速的宽带连接上,这仍可能导致开发时间显著减慢。然而,在本地计算机上,测试可以像保存更新一样简单(通常只需点击图标一次),然后在浏览器中点击刷新按钮。

另一个开发服务器的优点是,当你编写和测试时,你不必担心尴尬的错误或安全问题,而在公共网站上,你需要意识到人们可能会看到或使用你的应用程序。最好在你仍然在家庭或小办公室系统上,预计受到防火墙和其他保护措施保护的时候,把一切都搞定。

一旦你拥有了自己的开发服务器,你会想知道之前是如何没有它的,而且设置一个是很容易的。只需按照以下各节中的步骤进行操作,根据你使用的是 PC、Mac 还是 Linux 系统,选择适当的说明。

在本章中,我们只涵盖了 Web 体验的服务器端,正如在第一章中描述的那样。但是为了测试你的工作结果——特别是当我们稍后在本书中开始使用 JavaScript、CSS 和 HTML5 时——你最好有一个至少包含 Microsoft Edge、Mozilla Firefox、Opera、Safari 和 Google Chrome 的主要 Web 浏览器的实例运行在方便你的某个系统上。一旦准备好发布产品,你可能需要所有这些,以确保在所有浏览器和平台上一切都如预期运行。如果你计划确保你的网站在移动设备上看起来良好,你应该尽量安排访问各种 iOS 和 Android 设备。

什么是 WAMP, MAMPLAMP

WAMPMAMPLAMP 是 “Windows, Apache, MySQL 和 PHP”,“Mac, Apache, MySQL 和 PHP”,以及 “Linux, Apache, MySQL 和 PHP”的缩写。这些缩写描述了用于开发动态互联网页面的完整功能设置。

WAMPsMAMPsLAMPs 以捆绑程序包的形式出现,这些程序将捆绑的程序一起绑定,以便你不必单独安装和设置它们。这意味着你只需下载和安装一个单一的程序,并按照几个简单的提示进行操作,就可以快速启动和运行你的 Web 开发服务器,几乎没有任何麻烦。

在安装过程中,会为你创建几个默认设置。这样的安装的安全配置不会像生产 Web 服务器上那样严格,因为它被优化用于本地使用。因此,你绝对不应该将这样的设置作为生产服务器安装。

然而,对于开发和测试网站和应用程序来说,其中一个这样的安装完全足够了。

警告

如果您选择不通过 WAMP/MAMP/LAMP 方式构建自己的开发系统,您应知道自行下载和集成各部分可能非常耗时,并可能需要大量研究来完全配置所有内容。但如果您已经安装并集成了所有组件,它们应该可以与本书中的示例一起工作。

在 Windows 上安装 AMPPS

有几个可用的 WAMP 服务器,每个提供稍有不同的配置。在各种开源和免费选项中,最好的之一是 AMPPS。您可以通过点击网站的主页上显示的按钮来下载,如图 2-1 所示。(也有 Mac 和 Linux 版本可用;参见“在 macOS 上安装 AMPPS”和“在 Linux 上安装 LAMP”。)

注意

最近,Chrome 已更新以禁止从混合来源(例如从 https:// 网页下载的 http:// 文件)下载。其他浏览器可能也会遵循这一安全倡议。当前 AMPPS 网站使用混合来源,您可能会遇到此问题。解决方法是当 Chrome(或其他浏览器)提示“无法安全下载 AMPPS”时,不要选择 DISCARD,而是使用上箭头选择 Keep,然后下载将继续。此外,如果单击下载链接似乎无任何反应,您将需要右键单击并选择 另存为 来启动下载。

我建议您始终下载最新的稳定版本(截至我写作时,版本为 3.9,大小约为 114 MB)。各种 Windows、macOS 和 Linux 安装程序列在下载页面上。

AMPPS 网站

图 2-1. AMPPS 网站
注意

在本版本的生命周期内,以下步骤中显示的某些屏幕和选项可能会发生变化。如果确实如此,请凭常识尽量按照所描述的操作顺序进行。

下载安装程序后,请运行它以显示图 2-2 中显示的窗口。不过,在到达该窗口之前,如果您使用防病毒程序或在 Windows 上激活了用户账户控制,可能会首先显示一个或多个提示通知,您需要点击“是”和/或“确定”以继续安装。

单击“下一步”,然后必须接受协议。再次单击“下一步”,然后再次单击以跳过信息屏幕。现在,您需要确认安装位置。这可能会建议为主硬盘的某个字母,但如果您希望,可以更改此选项:

C:\Program Files (x86)\Ampps

图 2-2. 安装程序的初始窗口

接下来,您必须在下一个屏幕上接受协议并点击下一步,然后在阅读信息摘要后再次点击下一步,然后系统会询问您希望将 AMPPS 安装到哪个文件夹。

一旦您决定安装 AMPPS 的位置,请点击下一步,决定保存快捷方式的位置(通常默认即可),然后再次点击下一步选择您希望安装的图标,如图 2-3 所示。在接下来的屏幕上,点击“安装”按钮开始安装过程。

选择要安装的图标

图 2-3. 选择要安装的图标

安装过程将需要几分钟,之后您将看到图 2-4 中的完成屏幕,然后点击“完成”。

AMPPS 现在已安装

图 2-4. AMPPS 现在已安装

最后一件事是安装 Microsoft Visual C++ Redistributable,如果您尚未安装。一个窗口将弹出以提示您,如图 2-5 所示。点击“是”开始安装,或者如果您确定已经安装了它,则点击“否”。或者,您也可以无论如何继续,系统会告知您是否需要重新安装。

图 2-5. 如果尚未安装,请安装 Visual C++ Redistributable

如果您选择继续安装,您需要在弹出的窗口中同意条款和条件,然后点击安装。安装过程应该相当快速。点击关闭以完成。

安装完成后,您的桌面右下角将会显示图 2-6 中显示的控制窗口。如果您允许创建这些图标,您也可以通过“开始”菜单或桌面上的 AMPPS 应用程序快捷方式调出此窗口。

在继续之前,如果您有任何进一步的问题,我建议您熟悉AMPPS 文档,否则您已准备好开始了——控制窗口底部始终有一个支持链接,您可以通过它访问 AMPPS 网站,以便在需要时打开故障申请。

图 2-6. AMPPS 控制窗口
注意

您可能会注意到 AMPPS 中默认的 PHP 版本是 7.3。如果由于任何原因您希望尝试版本 5.6,请在 AMPPS 控制窗口内点击选项按钮(一个方形中的九个白色框),然后选择“更改 PHP 版本”,接着会弹出一个新菜单,您可以在其中选择 5.6 到 7.3 之间的版本。

安装测试

此时,首先要做的一件事是验证一切是否正常工作。为此,请在浏览器的地址栏中输入以下任何一个 URL:

localhost
127.0.0.1

这将调出一个介绍屏幕,在这里您将有机会通过给它设置密码来保护 AMPPS(参见图 2-7)。我建议您不要勾选复选框,只需点击提交按钮即可继续而不设置密码。

图 2-7. 初始安全设置屏幕

一旦完成这些步骤,您将被带到主控页面,位于localhost/ampps/(从现在开始,我假设您通过localhost而不是127.0.0.1访问 AMPPS)。从这里,您可以配置和控制 AMPPS 堆栈的所有方面,所以请记下这一点以备将来参考,或者在浏览器中设置书签。

接下来,键入以下内容以查看新 Apache Web 服务器的文档根目录(在以下部分描述):

localhost

这一次,您不会看到关于设置安全性的初始屏幕,而是应该看到类似于图 2-8 的内容。

图 2-8. 查看文档根目录

访问文档根目录(Windows)

文档根目录是包含域的主要 Web 文档的目录。当在浏览器中键入基本 URL 而没有路径时,例如*yahoo.com*或对于您的本地服务器是*http://localhost*…

默认情况下,AMPPS 将使用以下位置作为文档根目录:

C:\Program Files\Ampps\www

为确保配置正确,现在应创建“Hello World”文件。因此,请使用 Windows 记事本(或 Windows 上的Notepad++,或 Mac 上的Atom,或您选择的其他许多可用选项,但不要使用 Microsoft Word 等富文本编辑器)创建一个如下所示的小型 HTML 文件:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>A quick test</title>
  </head>
  <body>
    Hello World!
  </body>
</html>

输入完毕后,将文件保存到文档根目录,文件名为test.html。如果您使用的是记事本,请确保“另存为类型”框中的值已从文本文档 (.txt) 更改为所有文件 (.*)

您现在可以通过在浏览器地址栏中输入以下 URL 来调用此页面(参见图 2-9):

localhost/test.html

您的第一个网页

图 2-9. 您的第一个网页
注意

请记住,从文档根目录(或子文件夹)提供网页与从计算机文件系统加载网页到 Web 浏览器不同。前者将确保访问 PHP、MySQL 和 Web 服务器的所有功能,而后者仅将文件加载到浏览器中,并尽力显示它,但无法处理任何 PHP 或其他服务器指令。因此,您通常应该从浏览器的地址栏中使用localhost前缀来运行示例,除非您确信文件不依赖于 Web 服务器功能。

替代 WAMPs

当软件更新时,有时它的工作方式可能与您的预期不同,甚至可能会引入错误。因此,如果您在 AMPPS 中遇到无法解决的困难,您可能更喜欢选择网上其他可用的解决方案之一。

您仍然可以使用本书中的所有示例,但您需要按照每个 WAMP 附带的说明进行操作,这可能不像前面的指南那样易于理解。

这是我认为的一些最佳选择:

注意

在本书的整个版本周期内,AMPPS 的开发人员很可能会对软件进行改进,因此安装屏幕和使用方法可能会随时间而演变,Apache、PHP 或 MySQL 的版本也可能会更新。因此,请不要假设某些东西出了问题,如果屏幕和操作看起来不同,请跟随任何提示,并参考网站上的文档

在 macOS 上安装 AMPPS

AMPPS 也可在 macOS 上使用,并且您可以从网站下载它,如前所示在图 2-1 中(在我写作时,当前版本是 3.0,大小约为 270 MB)。

如果您的浏览器在下载完成后未自动打开,请双击*.dmg文件,然后将AMPPS文件夹拖到应用程序*文件夹中(参见图 2-10)。

将 AMPPS 文件夹拖到应用程序

图 2-10. 将 AMPPS 文件夹拖到应用程序

现在以通常的方式打开您的应用程序文件夹,然后双击 AMPPS 程序。如果您的安全设置阻止文件被打开,请按住 Control 键并单击一次图标。将弹出一个新窗口询问您是否确定要打开它。点击打开以继续。启动应用程序时,您可能需要输入 macOS 密码。

一旦 AMPPS 启动,类似于图 2-6 所示的控制窗口将出现在您桌面的左下角。

注意

您可能注意到 AMPPS 中的默认 PHP 版本是 7.3。如果出于任何原因您希望尝试 5.6 版本,请单击 AMPPS 控制窗口内的选项按钮(一个有九个白色方框的正方形),然后选择更改 PHP 版本,这时会出现一个新菜单,您可以在其中选择 5.6 至 7.3 之间的版本。

访问文档根目录(macOS)

默认情况下,AMPPS 将使用以下位置作为文档根目录:

/Applications/Ampps/www

为了确保您已正确配置一切,现在您应该创建强制性的“Hello World”文件。因此,请使用 TextEdit 程序或任何其他程序或文本编辑器创建以下类型的小型 HTML 文件,但不要使用像 Microsoft Word 这样的富文本处理器:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>A quick test</title>
  </head>
  <body>
    Hello World!
  </body>
</html>

输入完毕后,请使用文件名 test.html 将文件保存到文档根目录中。

您现在可以通过在地址栏中输入以下 URL 来在浏览器中打开此页面(以查看与 图 2-9 类似的结果):

localhost/test.html
注意

请记住,从文档根目录(或子文件夹)提供网页与从计算机文件系统加载网页到 Web 浏览器是不同的。前者确保访问 PHP、MySQL 和 Web 服务器的所有功能,而后者仅将文件加载到浏览器中,浏览器尽其所能显示它,但无法处理任何 PHP 或其他服务器指令。因此,通常应从浏览器地址栏中使用 localhost 前缀来运行示例,除非您确信该文件不依赖于 Web 服务器功能。

在 Linux 上安装 LAMP

本书主要面向 PC 和 Mac 用户,但其内容在 Linux 计算机上同样适用。不过,有数十种流行的 Linux 版本,每种版本可能需要以略有不同的方式安装 LAMP,因此我无法在本书中全部涵盖。但是,Linux 也有一种名为 AMPPS 的版本可供使用,可能是您最简单的选择。

尽管如此,一些 Linux 版本预装了 Web 服务器和 MySQL,您可能已经准备就绪。要确认,请在浏览器中输入以下内容,查看是否能获取默认的文档根网页:

localhost

如果此操作成功,您可能已安装 Apache 服务器,并且可能已经运行 MySQL;请向系统管理员确认。

但是,如果您尚未安装 Web 服务器,可以从网站下载 AMPPS 的版本。

安装步骤与前一节中显示的顺序类似。如果需要进一步了解如何使用软件,请参阅文档。

远程工作

如果您已经可以访问配置了 PHP 和 MySQL 的 Web 服务器,您可以随时用于 Web 开发。但是,除非您拥有高速连接,否则这可能并非您的最佳选项。在本地进行开发允许您测试修改,几乎没有上传延迟。

远程访问 MySQL 也许不是件容易的事情。您应该使用安全的 SSH 协议登录服务器,通过命令行手动创建数据库并设置权限。您的 Web 主机公司将为您提供最佳操作建议,并提供 MySQL 访问的任何密码(当然也包括首次登录服务器的密码)。我建议您永远不要使用不安全的 Telnet 协议远程登录任何服务器。

登录

我建议至少 Windows 用户应安装诸如 PuTTY 之类的程序进行 SSH 访问(请记住 SSH 比 Telnet 安全得多)。

在 Mac 上,你已经可以使用 SSH。只需选择应用程序文件夹,然后选择实用工具,然后启动终端。在终端窗口中,使用 SSH 登录服务器如下:

ssh *mylogin*@*server.com*

其中*server.com是你希望登录的服务器名称,mylogin*是你要使用的用户名。然后系统会提示你输入该用户名的正确密码,如果输入正确,你将登录成功。

使用 SFTP 或 FTPS

要在您的 Web 服务器上传输文件,通常需要 FTP、SFTP 或 FTPS 程序。虽然 FTP 不是安全协议,但仍然经常用软件上传和下载文件,称为 FTP,但确保 Web 服务器安全的是 FTPS 或 SFTP。如果你在网上寻找一个好的客户端,你会发现有很多选择,可能需要一段时间才能找到适合你的功能完备的程序。

不要使用 FTP

FTP 是不安全的,不应使用。有比 FTP 更安全的文件传输方法,如使用 Git 或类似技术。此外,基于 SSH 的 SFTP(安全文件传输协议)和 SCP(安全复制协议)也越来越受欢迎。好的 FTP 程序通常也支持 SFTP 和 FTPS(FTP-SSL)。文件传输方式通常取决于你所在公司的政策,但对于个人使用来说,如 FileZilla(下面讨论)这样的 FTP 程序将提供你所需的大部分(如果不是全部)功能和安全性。

我偏爱的 FTP/SFTP 程序是开源软件FileZilla,支持 Windows、Linux 和 macOS 10.5 或更新版本(参见图 2-11)。有关如何使用 FileZilla 的完整说明,请参阅wiki

FileZilla 是一个功能齐全的 FTP 程序

图 2-11. FileZilla 是一个功能齐全的 FTP 程序

当然,如果你已经有一个 FTPS 或 SFTP 程序,那就更好了——坚持你熟悉的工具。

使用代码编辑器

尽管纯文本编辑器可以用于编辑 HTML、PHP 和 JavaScript,但专用代码编辑器的改进显著,现在它们具备了非常实用的功能,如彩色语法突出显示。今天的程序编辑器很智能,甚至可以在你运行程序之前就显示语法错误的位置。一旦你使用了现代编辑器,你会想知道自己以前是怎么没有用它们的。

有很多优秀的程序可供选择,但我选择了微软的 Visual Studio Code(VSC),因为它功能强大,在 Windows、Mac 和 Linux 上都能运行,并且是免费的(参见图 2-12)。尽管每个人的编程风格和偏好不同,如果你对它不习惯,还有很多其他代码编辑器可供选择,或者你可能希望直接选择一个集成开发环境(IDE),正如下一节所述。

图 2-12. 程序编辑器(如 Visual Studio Code)优于纯文本编辑器

如你所见,通过图 2-12,VSC 适当地突出语法,使用颜色帮助澄清正在发生的事情。此外,你可以将光标放在括号或大括号旁边,它会突出显示匹配的括号,这样你可以检查是否有过多或过少。事实上,VSC 还有更多功能,随着你的使用,你会发现并享受它们。你可以从code.visualstudio.com下载副本。

如果你有其他喜欢的编辑器,可以使用它;使用你已经熟悉的程序是个好主意。

使用 IDE

尽管专用程序编辑器对你的编程生产力可能非常有用,但与集成开发环境相比,它们的效用显得微不足道。IDE 提供许多额外功能,如编辑器内调试和程序测试,以及函数描述等,尽管这些功能如今也在一些代码编辑器中慢慢加入,比如之前推荐的 VSC。图 2-13 展示了流行的 Eclipse IDE,主框架加载了一些 HTML(它还允许你使用 PHP、JavaScript 和其他文件类型)。

图 2-13. 使用 IDE 时,代码开发变得更快、更容易

在使用 IDE 进行开发时,你可以设置断点,然后运行所有(或部分)代码,它将在断点处停止,并为你提供关于程序当前状态的信息。

作为学习编程的辅助工具,本书中的示例可以直接输入到集成开发环境(IDE)中运行,无需打开网页浏览器。不同平台都有多种 IDE 可供选择。表 2-1 列出了一些最受欢迎的免费 PHP IDE 以及它们的下载 URL。

表 2-1. 免费 IDE 选择

IDE下载链接 WindowsmacOSLinux
Eclipse PDTeclipse.org/downloads/p…        ✓     ✓    ✓
NetBeanswww.netbeans.org        ✓     ✓    ✓
Visual Studiocode.visualstudio.com        ✓     ✓    ✓

选择一个 IDE 可能是非常个人化的事情,所以如果你打算使用一个,我建议你先下载几个或更多来试用;它们都有试用版或免费使用,所以不会花费你任何费用。

现在花时间安装一个你熟悉的代码编辑器或 IDE,然后你将准备好在接下来的章节中尝试示例。

有了这些工具,现在你已经准备好继续进入《第三章》,我们将深入探讨 PHP,并了解如何使 HTML 和 PHP 协同工作,以及 PHP 语言本身的结构。但在继续之前,我建议你用以下问题来测试你的新知识。

问题

  1. 什么是 WAMP、MAMP 和 LAMP 之间的区别?

  2. IP 地址 127.0.0.1 和 URL http://localhost 有什么共同点?

  3. FTP 程序的目的是什么?

  4. 远程 Web 服务器的主要缺点是什么?

  5. 为什么使用程序编辑器而不是纯文本编辑器更好?

请参阅《第二章答案》在《附录 A》中查看这些问题的答案。

第三章:PHP 简介

在第一章中,我解释了 PHP 是您用来使服务器生成动态输出的语言——每次浏览器请求页面时,输出可能会有所不同。在这一章中,您将开始学习这种简单但功能强大的语言;接下来几章将深入探讨这个主题,直到第七章。

我鼓励您使用第二章中列出的 IDE 之一或良好的代码编辑器来开发您的 PHP 代码。

这些程序中的许多程序将允许您运行 PHP 代码并查看本章讨论的输出。我还将向您展示如何创建 PHP 代码,以便您可以看到 Web 页面中的输出(最终用户将如何看待它)。但是,在这个阶段,这一步骤虽然一开始可能会令人兴奋,但实际上并不重要。

在生产中,您的网页将是 PHP、HTML、JavaScript 和一些 MySQL 语句的组合,使用 CSS 布局。此外,每个页面都可以导向其他页面,以便用户通过链接点击和填写表单。尽管如此,在学习每种语言时,我们可以避开所有这些复杂性。现在专注于编写 PHP 代码,并确保您得到预期的输出,或者至少理解实际得到的输出!

将 PHP 嵌入 HTML 中

默认情况下,PHP 文档以扩展名*.php结尾。当 Web 服务器在请求的文件中遇到此扩展名时,它会自动将其传递给 PHP 处理器。当然,Web 服务器高度可配置,一些 Web 开发者选择强制以.htm.html*结尾的文件也由 PHP 处理,通常是因为他们想隐藏其对 PHP 的使用。

您的 PHP 程序负责传回一个干净的文件,适合在 Web 浏览器中显示。在最简单的情况下,一个 PHP 文档只会输出 HTML。为了证明这一点,您可以拿任何普通的 HTML 文档,并将其保存为 PHP 文档(例如,将index.html另存为index.php),它将与原始文档显示完全一致。

要触发 PHP 命令,您需要学习一个新的标签。这是第一部分:

<?php

您可能注意到的第一件事是标签没有关闭。这是因为整个 PHP 部分可以放置在此标签内,只有当遇到关闭部分时才会结束,其看起来像这样:

?>

一个小的 PHP“Hello World”程序可能看起来像示例 3-1。

示例 3-1. 调用 PHP
<?php
  echo "Hello world";
?>

使用此标签可以非常灵活。一些程序员在文档开头就打开标签,并在文档末尾关闭它,直接从 PHP 命令输出任何 HTML。然而,其他人选择仅在这些标签中插入 PHP 的最小可能片段,只在需要动态脚本时,将文档的其余部分保持为标准 HTML。

后一种类型的程序员通常认为,他们的编码风格会导致更快的代码,而前者则认为速度提升是如此微小,以至于不能证明在单个文档中多次进出 PHP 的额外复杂性。

随着你的学习深入,你肯定会发现自己偏好的 PHP 开发风格,但为了使本书中的示例更易于理解,我采用了尽量减少 PHP 和 HTML 之间转换次数的方法——通常在一个文档中只有一两次。

顺便说一句,PHP 语法还有一种轻微的变体。如果你在互联网上搜索 PHP 示例,你可能会遇到打开和关闭语法看起来像这样的代码:

<?
  echo "Hello world";
?>

尽管 PHP 解析器被调用的情况不太明显,但这是一种有效的替代语法,通常也可以工作。但我不鼓励使用,因为它与 XML 不兼容,现在已经被弃用(意味着不再推荐使用,并且未来版本可能会移除支持)。

注意

如果你的文件中只有 PHP 代码,可以省略结尾的 ?>。这是一个很好的做法,因为它可以确保你的 PHP 文件没有多余的空白字符泄漏(特别是在你编写面向对象的代码时尤为重要)。

本书的示例

为了节省你输入所有内容的时间,本书中的所有示例都已存储在 GitHub 上。你可以通过访问以下链接将存档下载到你的计算机中:GitHub

除了按章节和示例编号列出所有示例(如 example3-1.php),某些示例可能需要显式的文件名,此时示例的副本也将使用相同文件夹中的文件名保存(例如即将出现的 示例 3-4,应保存为 test1.php)。

PHP 的结构

在本节中,我们将涵盖相当广泛的内容。这并不太困难,但我建议你仔细阅读,因为这为本书中的所有其他内容奠定了基础。和往常一样,章节末尾有一些有用的问题,可以用来测试你学到了多少知识。

使用注释

有两种方式可以向你的 PHP 代码添加注释。第一种通过在前面加上一对斜杠将单行变为注释:

// This is a comment

这种版本的注释功能是暂时从一个给你错误的程序中移除一行代码的好方法。例如,你可以使用这样的注释隐藏一个调试代码行,直到你需要它,就像这样:

// echo "X equals $x";

你也可以直接在一行代码后使用这种类型的注释描述它的操作,就像这样:

$x += 10; // Increment $x by 10

当你需要使用多行时,有第二种类型的注释,看起来像 示例 3-2。

示例 3-2. 多行注释
<?php
/* This is a section
 of multiline comments
 which will not be
 interpreted */
?>

您可以使用/**/字符对来在代码的几乎任何位置打开和关闭注释。大多数程序员使用这种结构来暂时注释掉整个不起作用或由于某种原因他们不希望解释的代码部分。

警告

一个常见的错误是使用/**/来注释一个已包含使用这些字符的注释部分的大段代码。您不能以这种方式嵌套注释;PHP 解释器不会知道注释何时结束,并显示错误消息。但是,如果您使用具有语法高亮的编辑器或 IDE,这种错误更容易发现。

基本语法

PHP 是一种相当简单的语言,起源于 C 和 Perl(如果您曾经接触过这些语言),但它看起来更像 Java。它也非常灵活,但您需要学习关于其语法和结构的一些规则。

分号

您可能已经注意到在前面的示例中,PHP 命令以分号结尾,就像这样:

$x += 10;

在 PHP 中,您将遇到的最常见的错误之一是忘记分号。这会导致 PHP 将多个语句视为一个语句,PHP 无法理解,因此会生成一个Parse error消息。

$ 符号

$ 符号已经被许多不同的编程语言以多种方式使用。例如,在 BASIC 语言中,它被用来终止变量名称以表示它们是字符串。

然而,在 PHP 中,您必须在所有变量前面放置一个$。这是必需的,以使 PHP 解析器更快,因为它可以立即知道何时遇到变量。无论您的变量是数字、字符串还是数组,它们都应该看起来像示例 3-3 中的那些。

示例 3-3. 三种不同类型的变量赋值
<?php
  $mycounter = 1;
  $mystring  = "Hello";
  $myarray   = array("One", "Two", "Three");
?>

这实际上就是您必须记住的语法。与 Python 等严格要求缩进和布局的语言不同,PHP 完全自由,您可以随意使用或不使用缩进和空格。事实上,合理使用空白通常是鼓励的(以及全面注释),这有助于您在回顾代码时理解它。它还有助于其他程序员在维护代码时理解。

变量

有一个简单的比喻可以帮助您理解 PHP 变量的含义。只需将它们想象成小(或大)火柴盒!没错,就是您粉刷过并写上名称的火柴盒。

字符串变量

想象一下,您有一盒火柴上面写着用户名。然后,您在一张纸上写下Fred Smith并将其放入盒子中(参见图 3-1)。好吧,这与将字符串值分配给变量的过程相同,就像这样:

$username = "Fred Smith";

引号表明“Fred Smith”是一个由字符组成的字符串。你必须用引号或撇号(单引号)将每个字符串括起来,尽管这两种引号之间存在微妙的差异,稍后会进行解释。当你想看看盒子里面装了什么时,你打开它,拿出那张纸,然后阅读它。在 PHP 中,这样做看起来像这样(显示变量的内容):

echo $username;

或者您可以将其分配给另一个变量(复印纸张并将副本放入另一个火柴盒),如下所示:

$current_user = $username;

您可以将变量视为装有物品的火柴盒

图 3-1. 您可以将变量视为装有物品的火柴盒
示例 3-4. 你的第一个 PHP 程序
<?php // test1.php
  $username = "Fred Smith";
  echo $username;
  echo "<br>";
  $current_user = $username;
  echo $current_user;
?>

现在,您可以通过在浏览器地址栏中输入以下内容来调用它:

http://localhost/test1.php
注意

如果在安装网络服务器期间(详见第二章)不太可能发生,您将服务器分配的端口更改为 80 以外的任何数字,则必须将该端口号放在本书中的所有示例中的 URL 中,例如,如果将端口更改为 8080,则前述 URL 将变成如下所示:

http://localhost:8080/test1.php

我不会再提到这一点了,所以请记住在尝试示例或编写自己的代码时使用端口号(如果需要)。

运行此代码的结果应该是两次出现名称Fred Smith,第一次是echo $username命令的结果,第二次是echo $current_user命令的结果。

数值变量

变量不一定只能包含字符串,它们也可以包含数字。如果我们回到火柴盒类比,要在变量$count中存储数字 17,相当于在火柴盒里放入 17 颗珠子,并在盒子上写上单词count

$count = 17;

你也可以使用浮点数(包含小数点)。语法是一样的:

$count = 17.5;

要查看火柴盒的内容,只需打开它并计算珠子的数量。在 PHP 中,您可以将$count的值分配给另一个变量,或者可能只是将其回显到 Web 浏览器中。

数组

您可以将数组视为几个粘在一起的火柴盒。例如,假设我们要在名为$team的数组中存储一个五人足球队的球员姓名。为此,我们可以侧向粘合五个火柴盒,并在分别的纸片上写下所有球员的名字,将每个名字放入一个火柴盒中。

在整个火柴盒组件的顶部,我们将写下team(参见图 3-2)。在 PHP 中,这的等效物将是以下内容:

$team = array('Bill', 'Mary', 'Mike', 'Chris', 'Anne');

数组就像粘在一起的几个火柴盒

图 3-2. 数组就像粘在一起的几个火柴盒

此语法比您迄今看到的其他示例更复杂。数组构建代码包括以下结构:

array();

内部包含五个字符串。每个字符串用单引号或双引号括起来,并用逗号分隔。

如果我们想知道第四名玩家是谁,我们可以使用这个命令:

echo $team[3]; // Displays the name Chris

前述声明中的数字为 3 而不是 4 的原因是 PHP 数组的第一个元素实际上是零索引,因此玩家编号将从 0 到 4。

二维数组

数组还有更多用途。例如,它们不仅可以是单维线条的火柴盒,还可以是二维矩阵,甚至具有更多维度。

举例说明二维数组,比如我们想要跟踪井字棋游戏,这需要一个 3 × 3 的九个单元格的数据结构。要用火柴盒表示这个结构,想象九个火柴盒按照 3 行 3 列的矩阵排列在一起(见图 3-3)。

用火柴盒模拟的多维数组

图 3-3. 用火柴盒模拟的多维数组

现在,你可以在每个移动中正确的火柴盒上放置一个带有xo的纸条。要在 PHP 代码中实现这一点,你必须设置一个包含三个更多数组的数组,就像示例 3-5 中那样,其中数组已经设置了进行中的游戏。

示例 3-5. 定义一个二维数组
<?php
  $oxo = array(array('x', ' ', 'o'),
               array('o', 'o', 'x'),
               array('x', 'o', ' '));
?>

再次,我们在复杂性上迈出了一步,但是如果你掌握了基本的数组语法,这是很容易理解的。在外部array()结构中嵌套了三个array()结构。我们用一个字符填充了每一行的数组:xo或空格。(我们使用空格以便所有单元格在显示时宽度相同。)

要返回该数组中第二行的第三个元素,你可以使用以下 PHP 命令,它将显示一个x

echo $oxo[1][2];
注意

记住,数组索引(指向数组元素的指针)从零开始,而不是从一开始,所以前一个命令中的[1]指的是三个数组中的第二个,[2]则指的是该数组中的第三个位置。此命令将返回火柴盒中第三行第二列的内容。

正如前文所述,我们可以通过简单地在数组中创建更多数组来支持更多维度的数组。但是,在本书中我们不会涉及超过二维的数组。

如果你仍然难以掌握使用数组,不要担心,因为这个主题在第六章中有详细解释。

变量命名规则

在创建 PHP 变量时,必须遵循以下四条规则:

  • 变量名在美元符号后必须以字母或下划线字符开头。

  • 变量名只能包含字符azAZ09,和_(下划线)。

  • 变量名不得包含空格。如果变量名必须由多个词组成,一个好主意是用下划线 _(下划线)字符分隔单词(例如,$user_name)。

  • 变量名区分大小写。变量 $High_Score 与变量 $high_score 不同。

注意

为了允许包含带重音的扩展 ASCII 字符,PHP 还支持变量名中从 127 到 255 的字节。但除非你的代码只由习惯使用这些字符的程序员维护,否则最好避免使用它们,因为使用英文键盘的程序员将难以访问它们。

运算符

运算符 允许你指定执行的数学运算,比如加法、减法、乘法和除法。但还有其他类型的运算符,如字符串、比较和逻辑运算符。在 PHP 中数学运算看起来很像普通算术运算——例如,以下语句输出 8

echo 6 + 2;

在继续学习 PHP 能为你做什么之前,花点时间了解它提供的各种运算符。

算术运算符

算术运算符执行你所期望的操作——它们用于执行数学运算。你可以用它们进行四个主要运算(加、减、乘、除),以及找到模数(除法后的余数),并对值进行增加或减少(参见 表 3-1)。

表 3-1. 算术运算符

运算符描述示例
+加法$j + 1
减法$j 6
*乘法$j * 11
/除法$j / 4
%取模(除法后的余数)$j % 9
++递增++$j
--递减--$j
**指数(或幂)$j******2

赋值运算符

这些运算符将值赋给变量。它们从简单的 = 开始,然后是 +=-= 等等(参见 表 3-2)。运算符 += 将右侧的值加到左侧的变量上,而不是完全替换左侧的值。因此,如果 $count 初始值为 5,则语句:

$count += 1;

$count 设置为 6,就像更熟悉的赋值语句一样:

$count = $count + 1;

/=*= 运算符类似,但用于除法和乘法,.= 运算符将变量连接起来,使 $a .= "." 将句点附加到 $a 的末尾,而 %= 分配一个百分比值。

表 3-2. 赋值运算符

运算符示例等价于
=$j **`=`** 15$j = 15
+=$j **`+=`** 5$j = $j + 5
–=$j **`-=`** 3$j = $j – 3
*=$j **`*=`** 8$j = $j * 8
/=$j **`/=`** 16$j = $j / 16
.=$j **`.=`** $k$j = $j . $k
%=$j **`%=`** 4$j = $j % 4

比较运算符

比较运算符通常用于诸如 if 语句等结构中,需要比较两个项的情况下。例如,您可能希望知道一个您一直在递增的变量是否达到了特定值,或者另一个变量是否小于一个设定值等(参见 表 3-3)。

表 3-3. 比较运算符

运算符描述示例
==等于$j **==** 4
!=不等于$j **`!=`** 21
>大于$j **`>`** 3
<小于$j **`<`** 100
>=大于或等于$j **`>=`** 15
<=小于或等于$j **`<=`** 8
<>不等于$j **`<>`** 23
===全等于$j **`===`** "987"
!==不全等于$j **`!==`** "1.2e3"

请注意 === 之间的差异。前者是赋值运算符,而后者是比较运算符。即使是高级程序员有时在编码时也可能混淆这两者,所以请小心。

逻辑运算符

如果你以前没有使用过逻辑运算符,刚开始可能会感到有些令人畏惧。但只需像在英语中运用逻辑一样思考即可。例如,你可能会对自己说,“如果现在时间晚于下午 12 点而早于下午 2 点,则吃午饭。”在 PHP 中,这段代码可能看起来像以下示例(使用军事时间):

if ($hour > 12 && $hour < 14) dolunch();

在这里,我们已经将去吃午饭的一系列指令移到一个稍后需要创建的名为 dolunch 的函数中。

如前面的示例所示,通常使用逻辑运算符来组合前一节中显示的比较运算符的结果。逻辑运算符也可以输入到另一个逻辑运算符中:“如果现在时间晚于下午 12 点且早于下午 2 点,或者如果烤肉的香味弥漫在走廊上并且桌子上有盘子。”通常情况下,如果某物具有 TRUEFALSE 值,它可以输入到一个逻辑运算符中。逻辑运算符需要两个真值或假值输入,并生成一个真值或假值结果。

表 3-4 显示了逻辑运算符。

表 3-4. 逻辑运算符

运算符描述示例
&&$j == 3 **&&** $k == 2
and低优先级的 and$j == 3 **and** $k == 2
&#124;&#124;$j < 5 **&#124;&#124;** $j > 10
or低优先级的 or$j < 5 **or** $j > 10
!! ($j **==** $k)
xor异或$j **xor** $k

注意 && 通常可以和 and 互换;同样,||or 也是如此。然而,因为 andor 的优先级较低,除非它们是唯一的选项,否则应避免使用它们,就像以下语句一样,这个语句必须使用 or 操作符(如果第一个失败,|| 不能用来强制执行第二个语句):

$html = file_get_contents($site) `or` die("Cannot download from $site");

这些运算符中最不寻常的是 xor,它代表排他或,如果任一值为 TRUE,则返回 TRUE 值,但如果两个输入都为 TRUE 或都为 FALSE,则返回 FALSE 值。为了理解这一点,想象一下你想要自己调配家庭用品的清洁剂。氨水和漂白剂都是良好的清洁剂,所以你希望你的清洁剂有其中之一。但是清洁剂不能同时有两种,因为这种组合是危险的。在 PHP 中,你可以这样表示:

$ingredient = $ammonia xor $bleach;

在这个例子中,如果 $ammonia$bleach 的任一值为 TRUE$ingredient 也将被设置为 TRUE。但是如果两者都为 TRUE 或都为 FALSE$ingredient 将被设置为 FALSE

变量赋值

将值分配给变量的语法始终为 variable = value。或者,要将值重新分配给另一个变量,则为 other_variable = variable

还有一些其他的赋值运算符可能会对你有用。例如,我们已经看到了这个:

$x += 10;

这告诉 PHP 解析器将右侧的值(在本例中为值 10)添加到变量 $x 中。同样,我们可以进行减法如下:

$y –= 10;

变量递增和递减

增加或减去 1 是一种常见的操作,PHP 提供了特殊的运算符来实现。你可以使用以下之一来代替 +=-= 运算符:

++$x;
--$y;

与测试(if 语句)结合使用的语法如下:

if (++$x == 10) echo $x;

这告诉 PHP 首先 递增 $x 的值,然后测试它是否为 10,如果是,则输出其值。但是你也可以要求 PHP 在测试值之后(或者,如下例中,递减)递增变量的值,就像这样:

if ($y-- == 0) echo $y;

这给出了一个略有不同的结果。假设 $y 在执行该语句之前初始值为 0。比较将返回一个 TRUE 结果,但是在进行比较后,$y 将被设置为 -1。那么 echo 语句会显示 0 还是 -1?试着猜一下,然后在 PHP 处理器中尝试这个语句以确认。因为这些语句的组合很令人困惑,应该只作为教育例子而不是良好编程风格的指南。

简而言之,如果运算符放在变量之前,则变量在测试之前被递增或递减,而如果运算符放在变量之后,则变量在测试之后被递增或递减。

顺便说一句,前面问题的正确答案是 echo 语句将显示结果 –1,因为 $yif 语句中被访问后立即递减,然后在 echo 语句之前。

字符串连接

连接 是一个有些古怪的术语,用于将某物放在另一物后面。因此,字符串连接使用句点(.)将一个字符串附加到另一个字符串。这样做的最简单方法如下:

echo "You have " . $msgs . " messages.";

假设变量 $msgs 被设置为值 5,那么这行代码的输出将是:

You have 5 messages.

就像你可以使用 += 运算符向数字变量添加值一样,你可以使用 .= 将一个字符串附加到另一个字符串上,像这样:

$bulletin .= $newsflash;

在这种情况下,如果 $bulletin 包含新闻公告,而 $newsflash 包含新闻快讯,这条命令将新闻快讯附加到新闻公告中,使得 $bulletin 现在包含了两个文本字符串。

字符串类型

PHP 支持两种类型的字符串,根据你使用的引号类型来表示。如果你希望赋值一个字面字符串,保留其确切内容,你应该使用单引号(撇号),像这样:

$info = 'Preface variables with a $ like this: $variable';

在这种情况下,单引号字符串中的每个字符都被赋值给 $info。如果你使用了双引号,PHP 将尝试将 $variable 解析为一个变量。

另一方面,当你想在字符串中包含变量的值时,你可以使用双引号字符串:

echo "This week $count people have viewed your profile";

正如你将会意识到的,这种语法还提供了一个更简单的选项,用于连接字符串而无需使用句点或关闭和重新打开引号来附加一个字符串到另一个字符串上。这称为 变量替换,一些程序员广泛使用它,而其他人则根本不使用它。

转义字符

有时字符串需要包含具有特殊含义的字符,这些字符可能会被错误地解释。例如,以下代码行将无法工作,因为在 spelling’s 中遇到的第二个引号告诉 PHP 解析器字符串已经结束。因此,剩余的部分将被拒绝作为错误:

$text = 'My spelling's atroshus'; // Erroneous syntax

要纠正这个问题,你可以直接在有问题的引号前加上反斜杠,告诉 PHP 将该字符视为字面量,而不解释它:

$text = 'My spelling\'s still atroshus';

你可以在 PHP 几乎所有可能导致解释字符错误的情况下使用这个技巧。例如,以下双引号字符串将被正确赋值:

$text = "She wrote upon it, \"Return to sender\".";

此外,你可以使用转义字符插入各种特殊字符到字符串中,如制表符、换行符和回车符。你可能猜到,它们分别用 \t\n\r 表示。这里有一个使用制表符布局标题的示例—这仅用于说明转义,因为在网页中总有更好的布局方式:

$heading = "Date\tName\tPayment";

这些特殊的反斜杠前缀字符只在双引号包围的字符串中起作用。在单引号包围的字符串中,前面的字符串将显示为丑陋的\t序列而不是制表符。在单引号包围的字符串中,只有转义的撇号(\')和转义的反斜杠本身(\\)被识别为转义字符。

多行命令

有时你需要从 PHP 中输出大量文本,使用多个echo(或print)语句会耗时且混乱。为了解决这个问题,PHP 提供了两个便利的方法。第一个是将多行放在引号中,如示例 3-6 所示。变量也可以像在示例 3-7 中那样赋值。

示例 3-6. 多行字符串echo语句
<?php
  $author = "Steve Ballmer";

  echo "Developers, developers, developers, developers, developers,
 developers, developers, developers, developers!

 - $author.";
?>
示例 3-7. 多行字符串赋值
<?php
  $author = "Bill Gates";

  $text = "Measuring programming progress by lines of code is like
 Measuring aircraft building progress by weight.

 - $author.";
?>

PHP 还提供了使用<<<运算符的多行序列——通常称为here-documentheredoc——作为指定字符串文字的一种方式,保留文本中的换行和其他空白(包括缩进)。其使用方法可以在示例 3-8 中看到。

示例 3-8. 替代的多行echo语句
<?php
  $author = "Brian W. Kernighan";

  echo <<<_END
 Debugging is twice as hard as writing the code in the first place.
 Therefore, if you write the code as cleverly as possible, you are,
 by definition, not smart enough to debug it.

 - $author.
_END;
?>

这段代码告诉 PHP 输出两个_END标签之间的所有内容,就好像它是一个双引号包围的字符串(但在 heredoc 中,引号不需要转义)。这意味着开发者可以直接在 PHP 代码中编写整个 HTML 部分,然后只需用 PHP 变量替换特定的动态部分。

重要的是要记住,关闭的_END; 必须 出现在新行的开头,并且它必须是那一行中唯一 的内容—甚至不能在它后面添加注释(甚至是一个空格)。一旦你关闭了一个多行块,你可以自由地再次使用相同的标签名。

注意

记住:使用<<<_END..._END;这种 heredoc 结构时,你不需要添加\n换行符来换行,只需按回车键并开始新的一行。与双引号或单引号包围的字符串不同的是,在 heredoc 中,你可以自由地使用所有单引号和双引号,无需通过在它们前面加上反斜杠(\)进行转义。

示例 3-9 展示了如何使用相同的语法将多行赋值给一个变量。

示例 3-9. 多行字符串变量赋值
<?php
  $author = "Scott Adams";

  $out = <<<_END
 Normal people believe that if it ain't broke, don't fix it.
 Engineers believe that if it ain't broke, it doesn't have enough
 features yet.

 - $author.
_END;
echo $out;
?>

变量$out将被填充为两个标签之间的内容。如果你是在追加而不是赋值,你也可以使用.=来将字符串追加到$out而不是使用=

要小心不要在第一次出现的_END后面直接加上分号,因为这会在多行块甚至开始之前终止它,并导致Parse error消息。

顺便说一句,_END 标签只是我为这些示例选择的一个,因为在 PHP 代码中不太可能使用它,因此是唯一的。你可以使用任何你喜欢的标签,比如 _SECTION1_OUTPUT 等等。此外,为了区分这样的标签和变量或函数,一般的做法是在它们之前加上一个下划线。

注意

将文本布置在多行上通常只是为了使你的 PHP 代码更易于阅读,因为一旦它在网页中显示,HTML 格式规则就接管了,空白会被抑制(但是在我们的例子中,$author 仍然会被替换为变量的值)。

因此,例如,如果你将这些多行输出示例加载到浏览器中,它们将不会显示在几行上,因为所有浏览器都像对待空格一样对待换行符。但是,如果你使用浏览器的查看源代码功能,你会发现换行符被正确放置,PHP 保留了换行符。

变量类型

PHP 是一种弱类型语言。这意味着变量在使用之前不必声明,并且 PHP 总是在访问时根据上下文需要的类型来转换变量。

例如,你可以创建一个多位数,并通过假设它为字符串来提取它的第n位数字。在示例 3-10 中,数字1234567890相乘,返回结果838102050,然后放入变量$number中。

示例 3-10. 数字自动转换为字符串
<?php
  $number = 12345 * 67890;
  echo substr($number, 3, 1);
?>

在赋值点,$number是一个数值变量。但是在第二行,调用了 PHP 函数substr,它要求从$number中返回一个字符,从第四个位置开始(记住 PHP 偏移从零开始)。为此,PHP 将$number转换为一个九个字符的字符串,以便substr可以访问并返回字符,而在本例中是1

同样适用于将字符串转换为数字,等等。在示例 3-11 中,变量$pi被设置为一个字符串值,然后通过计算圆的面积的方程,在第三行自动转换为浮点数,输出值78.5398175

示例 3-11. 将字符串自动转换为数字
<?php
  $pi     = "3.1415927";
  $radius = 5;
  echo $pi * ($radius * $radius);
?>

实际上,这一切的意思是你不必过于担心你的变量类型。只需给它们赋予对你来说有意义的值,PHP 将根据需要自动转换它们。然后,当你想检索值时,只需请求它们,例如,使用echo语句,但请记住,有时自动转换的操作可能不像你期望的那样运行。

常量

常量与变量类似,用于保存稍后访问的信息,但它们就像它们听起来的那样——常量。换句话说,一旦定义了常量,其值在程序的其余部分设置,并且无法更改。

例如,你可以使用一个常量来保存服务器根目录的位置(网站主文件夹的位置)。你可以像这样定义这样一个常量:

define("ROOT_LOCATION", "/usr/local/www/");

然后,要读取变量的内容,只需像普通变量一样引用它(但不需要前面加上美元符号):

$directory = ROOT_LOCATION;

现在,每当你需要在不同配置的服务器上运行你的 PHP 代码时,你只需要改变一行代码。

注意

关于常量,你必须记住的两件主要事情是它们不应该以$开头(不像普通变量),并且只能使用define函数来定义它们。

通常被认为是一个良好的实践是只为常量变量名使用大写字母,特别是如果其他人也会阅读你的代码。

预定义常量

PHP 准备了几十个预定义的常量,作为初学者通常不会使用。然而,有一些被称为魔术常量,你会发现它们非常有用。魔术常量的名称总是以两个下划线开头和两个下划线结尾,这样你就不会意外地尝试使用已经被占用的名称来定义自己的常量。它们在 Table 3-5 中有详细介绍。表中提到的概念将在后续章节中介绍。

表 3-5. PHP 的魔术常量

魔术常量描述
__LINE__文件的当前行号。
__FILE__文件的完整路径和文件名。如果在include中使用,则返回被包含文件的文件名。一些操作系统允许目录的别名,称为符号链接;在 __FILE__ 中,这些始终会被更改为实际的目录。
__DIR__文件所在的目录。如果在include中使用,返回被包含文件的目录。这相当于*dirname*(__FILE__)。这个目录名没有尾随的斜杠,除非它是根目录。
__FUNCTION__函数名。以声明时的大小写形式返回函数名。在 PHP 4 中,其值始终是小写的。
__CLASS__类名。以声明时的大小写形式返回类名。在 PHP 4 中,其值始终是小写的。
__METHOD__类方法名。方法名将以声明时的大小写形式返回。
__NAMESPACE__当前命名空间的名称。此常量在编译时定义(区分大小写)。

这些变量的一个方便用法是调试时,当你需要插入一行代码以查看程序流是否到达它:

echo "This is line " . __LINE__ . " of file " . __FILE__;

这会将当前文件中的当前程序行(包括路径)打印到 Web 浏览器。

echo 和 print 命令之间的区别

到目前为止,您已经看到了echo命令在多种不同方式下从服务器向浏览器输出文本。在某些情况下,已输出字符串文字。在其他情况下,首先连接字符串或评估变量。我还展示了分布在多行的输出。

但是有一种可以使用的echo替代方案:print。这两个命令非常相似,但print是一个类似函数的结构,接受一个参数并返回一个值(始终为1),而echo纯粹是 PHP 语言的构造。由于这两个命令都是构造,所以都不需要括号。

总的来说,echo命令通常比print命令稍快一点,因为它不设置返回值。另一方面,由于它不像函数实现,echo不能作为更复杂表达式的一部分使用,而print可以。下面是一个使用print输出变量值为TRUEFALSE的示例——使用echo无法以同样的方式执行,因为它将显示Parse error消息:

$b ? print "TRUE" : print "FALSE";

问号仅仅是一种询问变量$b是否为TRUEFALSE的方法。如果$bTRUE,则执行冒号后面左侧的命令,而如果$bFALSE,则执行冒号后面右侧的命令。

尽管如此,本书中的示例通常使用echo,我建议您在 PHP 开发中达到需要使用print的阶段之前也这样做。

函数

函数分离执行特定任务的代码部分。例如,也许您经常需要查找日期并以特定格式返回。这将是一个将其转换为函数的好例子。执行此操作的代码可能只有三行,但如果您不使用函数,则不得不在程序中粘贴它十几次,这将使您的程序变得不必要地庞大和复杂。而且,如果以后决定更改日期格式,将其放在函数中意味着只需更改一个地方。

将代码放入函数中不仅可以缩短程序并提高可读性,还可以增加额外的功能(双关语),因为函数可以接受参数以使其执行不同的操作。它们还可以将值返回给调用代码。

要创建函数,请按照示例 3-12 中所示的方式声明它。

示例 3-12. 一个简单的函数声明
<?php
  function longdate($timestamp)
  {
    return date("l F jS Y", $timestamp);
  }
?>

此函数以2025 年 5 月 2 日星期五的格式返回日期。初始括号之间可以传递任意数量的参数;我们选择只接受一个。花括号包围后续调用该函数时执行的所有代码。请注意,在此示例中date函数调用中的第一个字母是小写字母 L,不要与数字 1 混淆。

要使用这个函数输出今天的日期,请在你的代码中放置以下调用:

echo longdate(time());

如果你需要打印出 17 天前的日期,现在只需要发出以下调用:

echo longdate(time() - 17 * 24 * 60 * 60);

它传递给longdate当前时间减去 17 天前的秒数(17 天 × 24 小时 × 60 分钟 × 60 秒)。

函数也可以接受多个参数并返回多个结果,使用我将在接下来的章节中介绍的技术。

变量作用域

如果你有一个非常长的程序,很可能你会开始用尽好的变量名,但是在 PHP 中你可以决定变量的作用域。换句话说,你可以告诉它你想要变量$temp仅在特定函数内部使用,并在函数返回时忘记它曾经被使用过。事实上,这是 PHP 变量的默认作用域。

或者,你可以告诉 PHP 一个变量的作用域是全局的,因此可以被程序中的任何其他部分访问。

局部变量

局部变量是仅在函数内部创建并且只能被函数访问的变量。它们通常是临时变量,用于存储函数返回之前部分处理的结果。

一组局部变量是函数的参数列表。在前一节中,我们定义了一个接受名为$timestamp参数的函数。这仅在函数体中有意义;你不能在函数外部获取或设置它的值。

对于局部变量的另一个例子,请再看一下稍微修改过的longdate函数,在示例 3-13 中。

示例 3-13。longdate函数的扩展版本
<?php
  function longdate($timestamp)
  {
    $temp = date("l F jS Y", $timestamp);
    return "The date is $temp";
  }
?>

在这里,我们将date函数返回的值赋给临时变量$temp,然后将其插入到函数返回的字符串中。一旦函数返回,$temp变量及其内容就会消失,就像它们从未被使用过一样。

现在,为了看到变量作用域的影响,让我们看一些类似的代码,如示例 3-14。在这里,$temp在调用longdate函数之前被创建。

示例 3-14。试图在函数longdate中访问$temp将失败
<?php
  $temp = "The date is ";
  echo longdate(time());

  function longdate($timestamp)
  {
    return $temp . date("l F jS Y", $timestamp);
  }
?>

然而,因为$temp既不是在longdate函数内创建的,也没有作为参数传递给它,所以longdate无法访问它。因此,这段代码仅输出日期,而不是前面的文本。事实上,根据 PHP 的配置方式,它可能首先显示错误消息Notice: Undefined variable: temp,这是你不希望用户看到的。

这是因为,默认情况下,函数内创建的变量仅在该函数内部可用,而在任何函数之外创建的变量只能被非函数代码访问。

修复 示例 3-14 的一些方法出现在示例 3-15 和 3-16 中。

示例 3-15. 重新编写以在其本地作用域内引用 $temp 修复了问题
<?php
  $temp = "The date is ";
  echo $temp . longdate(time());

  function longdate($timestamp)
  {
    return date("l F jS Y", $timestamp);
  }
?>

示例 3-15 将对 $temp 的引用移出函数。引用出现在变量定义的同一作用域内。

示例 3-16. 另一种解决方案:将 $temp 作为参数传递
<?php
  $temp = "The date is ";
  echo longdate($temp, time());

  function longdate($text, $timestamp)
  {
    return $text . date("l F jS Y", $timestamp);
  }
?>

示例 3-16 中的解决方案将 $temp 作为额外参数传递给 longdate 函数。longdate 函数将其读入一个临时变量 $text 中,并输出所需的结果。

注意

忘记变量作用域是一个常见的编程错误,所以记住变量作用域是如何工作的将有助于你调试一些非常晦涩的问题。可以说,除非你另有声明,否则变量的作用域仅限于局部:要么是当前函数内,要么是在任何函数外的代码中,这取决于它是在函数内首次创建还是访问。

全局变量

有些情况下,你需要一个具有全局范围的变量,因为你希望所有的代码都能访问它。此外,有些数据可能又大又复杂,你不想把它作为函数的参数来回传递。

要从全局作用域访问变量,需添加关键字 global。假设你有一种方法将用户登录到网站,并希望所有代码都知道它是与已登录用户还是访客进行交互。一种方法是在变量前使用 global 关键字,如 $is_logged_in

global $is_logged_in;

现在你的登录函数只需在成功登录尝试时将该变量设置为 1,失败时设置为 0。由于变量的作用域设置为全局,你程序中的每一行代码都可以访问它。

尽管如此,你应该谨慎使用给予全局访问权限的变量。我建议只有在完全找不到其他实现结果的方法时才创建它们。通常,将程序分成小部分并隔离数据会减少 bug 并更容易维护。如果你有一个千行的程序(总有一天你会有这样的程序),发现某个全局变量在某个时刻有错误的值,你将需要多长时间才能找到设置它错误的代码?

另外,如果你有太多全局作用域的变量,你就有可能在本地再次使用其中的一个变量名,或者至少认为你已经在本地使用过它,实际上它已经声明为全局。这种情况可能导致各种奇怪的 bug。

注意

我通常采用所有需要全局访问的变量名大写的约定(就像建议将常量设为大写一样),这样我一眼就能看到变量的作用域。

静态变量

在“本地变量”章节(#local_variables)中,我提到当函数结束时,局部变量的值将被清除。如果函数运行多次,它将以变量的新副本开始,并且先前的设置不会产生影响。

这里有一个有趣的案例。如果您在函数内部有一个局部变量,不希望代码的其他部分访问它,但又希望保留其值以供下次调用函数时使用怎么办?为什么?也许是因为您想要一个计数器来跟踪函数调用的次数。解决方案是声明一个 静态 变量,如 示例 3-17 所示。

示例 3-17. 使用静态变量的函数
<?php
  function test()
  {
    static $count = 0;
    echo $count;
    $count++;
  }
?>

函数 test 的第一行创建了一个名为 $count 的静态变量,并将其初始化为 0。下一行输出变量的值;最后一行增加变量的值。

下次调用该函数时,由于 $count 已经声明,函数的第一行将被跳过。然后显示 $count 先前增加的值,然后再次增加变量。

如果您计划使用静态变量,应注意不能在定义中分配表达式的结果。它们只能用预定义的值初始化(参见 示例 3-18)。

示例 3-18. 允许和不允许的静态变量声明
<?php
  static $int = 0;         // Allowed
  static $int = 1+2;       // Correct (as of PHP 5.6)
  static $int = sqrt(144); // Disallowed
?>

超全局变量

从 PHP 4.1.0 开始,几个预定义变量可用。它们被称为 超全局变量,这意味着它们由 PHP 环境提供,但在程序中是全局的,可在任何地方访问。

这些超全局变量包含关于当前运行程序及其环境的大量有用信息(见 表 3-6)。它们结构化为关联数组,这是第六章讨论的主题(参见 第 6 章)。

表 3-6. PHP 超全局变量

超全局变量名称内容
$GLOBALS当前脚本全局范围内定义的所有变量。数组的键是变量名。
$_SERVER包含头信息、路径和脚本位置等信息。此数组的条目由 Web 服务器创建,并不能保证每个 Web 服务器都提供所有或部分条目。
$_GET通过 HTTP GET 方法传递给当前脚本的变量。
$_POST通过 HTTP POST 方法传递给当前脚本的变量。
$_FILES通过 HTTP POST 方法上传到当前脚本的项目。
$_COOKIE通过 HTTP cookie 传递给当前脚本的变量。
$_SESSION当前脚本可用的会话变量。
$_REQUEST浏览器传递的信息的内容;默认包括 $_GET$_POST$_COOKIE
$_ENV通过环境方法传递给当前脚本的变量。

所有超全局变量(除了$GLOBALS)都以单个下划线和大写字母命名;因此,应避免以此方式命名您自己的变量,以避免潜在的混淆。

为了说明您如何使用它们,让我们看一个常见的例子。超全局变量提供的众多信息中,有一个是指向当前网页的引用页面的 URL。可以这样访问这些引用页面信息:

$came_from = $_SERVER['HTTP_REFERER'];

就是这么简单。哦,如果用户直接访问您的网页,比如直接在浏览器中输入其 URL,那么$came_from将被设置为空字符串。

超全局变量和安全性

在您开始使用超全局变量之前,需要注意一点,因为黑客经常利用它们来寻找可能破坏您网站的漏洞。他们会通过加载$_POST$_GET或其他超全局变量来注入恶意代码,如 Unix 或 MySQL 命令,如果您天真地访问它们,可能会导致损坏或显示敏感数据。

因此,在使用它们之前,您应始终对超全局变量进行清理。通过 PHP 的htmlentities函数是一种方法。它将所有字符转换为 HTML 实体。例如,小于号和大于号(<>)被转换为字符串&lt;&gt;,这样它们就变得无害了,所有的引号和反斜杠等也是如此。

因此,更好的访问$_SERVER(以及其他超全局变量)的方法是:

$came_from = htmlentities($_SERVER['HTTP_REFERER']);
警告

在任何需要处理用户或其他第三方数据以进行输出的情况下,使用htmlentities函数进行清理是一种重要的实践,而不仅仅是超全局变量。

本章为你提供了使用 PHP 的坚实基础。在第四章中,你将开始运用所学内容来构建表达式和控制程序流程,换句话说,进行实际编程。

但在继续之前,我建议你通过以下(如果可能的话,全部都做)问题来测试自己,确保你已经完全消化了本章的内容。

问题

  1. 用于启动 PHP 解释程序代码的标记是什么?它的简写形式是什么?

  2. 有哪两种类型的注释标签?

  3. 每个 PHP 语句的结尾必须放置哪个字符?

  4. 哪个符号用于定义所有 PHP 变量的前缀?

  5. 变量可以存储什么?

  6. $variable = 1$variable == 1之间有什么区别?

  7. 为什么变量名允许使用下划线($current_user),而不允许使用连字符($current-user)?

  8. 变量名区分大小写吗?

  9. 变量名可以包含空格吗?

  10. 如何将一个变量类型转换为另一个变量类型(比如从字符串到数字)?

  11. ++$j$j++之间有什么区别?

  12. 运算符&&and可以互换使用吗?

  13. 如何创建多行的echo或赋值?

  14. 能重新定义一个常量吗?

  15. 如何转义引号?

  16. echoprint 命令有什么区别?

  17. 函数的目的是什么?

  18. 如何使一个变量在 PHP 程序的所有部分都可访问?

  19. 如果在函数内生成数据,有哪些方法可以将数据传递给程序的其余部分?

  20. 将字符串和数字结合的结果是什么?

请参阅“第三章答案”,在附录 A 中找到这些问题的答案。

第四章:PHP 中的表达式和控制流

前一章只是简单介绍了一些这一章更全面涵盖的主题,如做出选择(分支)和创建复杂表达式。在前一章中,我想专注于 PHP 中最基本的语法和操作,但无法避免触及更高级的主题。现在我可以填补你需要正确使用这些强大 PHP 功能的背景。

在本章中,你将深入了解 PHP 编程的实践工作以及如何控制程序的流程。

表达式

让我们从任何编程语言中最基础的部分开始:表达式

表达式是值、变量、运算符和函数的组合,其结果是一个值。对于任何已经学过高中代数的人来说,这是熟悉的。这里有一个例子:

*`y`* = 3 (|2*`x`*| + 4)

在 PHP 中,它将是:

$y = 3 * (abs(2 * $x) + 4);

这个数学声明中返回的值(y在这个数学声明中,或者在 PHP 中是$y)可以是一个数字,一个字符串,或者一个布尔值(以 19 世纪英国数学家和哲学家乔治·布尔命名)。到现在为止,你应该熟悉前两种值类型,但我将解释第三种。

真还是假?

基本的布尔值可以是TRUEFALSE。例如,表达式20 > 9(20 大于 9)是TRUE,表达式5 == 6(5 等于 6)是FALSE。(你可以使用其他经典布尔运算符如ANDORXOR来结合这些操作,这些在本章后面会介绍。)

注意

请注意,我在TRUEFALSE的名称中使用了大写字母。这是因为它们在 PHP 中是预定义常量。如果你愿意,你也可以使用小写版本,因为它们也是预定义的。事实上,小写版本更稳定,因为 PHP 不允许重新定义它们;大写的可能会被重新定义,这是在导入第三方代码时需要注意的事情。

如果你要求 PHP 打印预定义的常量,它实际上不会这样做,就像在示例 4-1 中一样。对于每一行,示例打印出一个字母,后面跟着一个冒号和一个预定义常量。当示例运行时,PHP 将数字值1随意分配给TRUE,因此在a:之后显示1。更神秘的是,因为b:评估为FALSE,它不显示任何值。在 PHP 中,常量FALSE被定义为NULL,另一个表示无值的预定义常量。

示例 4-1。输出TRUEFALSE的值
<?php // test2.php
  echo "a: [" . TRUE  . "]<br>";
  echo "b: [" . FALSE . "]<br>";
?>

<br>标签用于创建换行符,从而在 HTML 中将输出分为两行。这是输出:

a: [1]
b: []

转向布尔表达式,示例 4-2 展示了一些简单的表达式:前面提到的两个,再加上另外两个。

示例 4-2。四个简单的布尔表达式
<?php
  echo "a: [" . (20 > 9) . "]<br>";
  echo "b: [" . (5 == 6) . "]<br>";
  echo "c: [" . (1 == 0) . "]<br>";
  echo "d: [" . (1 == 1) . "]<br>";
?>

这段代码的输出是:

a: [1]
b: []
c: []
d: [1]

顺便说一句,在某些语言中,FALSE 可能被定义为 0 或者甚至是 -1,所以在使用每种语言时,检查其定义是很重要的。幸运的是,布尔表达式通常深藏在其他代码中,因此你通常不必担心 TRUEFALSE 在内部的具体表现。事实上,这些名称在代码中很少出现。

文字和变量

这些是编程的最基本元素,也是表达式的构建块。文字 简单地意味着评估为其自身的东西,比如数字 73 或字符串 "Hello"。变量,我们已经看到其名称以美元符号开头,评估为已分配给它的值。最简单的表达式只是一个单一的文字或变量,因为两者都返回一个值。

示例 4-3 显示了三个文字和两个变量,它们都返回值,尽管类型不同。

示例 4-3. 文字和变量
<?php
  $myname = "Brian";
  $myage  = 37;

  echo "a: " . 73      . "<br>"; // Numeric literal
  echo "b: " . "Hello" . "<br>"; // String literal
  echo "c: " . FALSE   . "<br>"; // Constant literal
  echo "d: " . $myname . "<br>"; // String variable
  echo "e: " . $myage  . "<br>"; // Numeric variable
?>

正如你所期望的那样,你会从所有这些运算中看到返回值,除了 c:,它评估为 FALSE,在以下输出中不返回任何内容:

a: 73
b: Hello
c:
d: Brian
e: 37

结合运算符,可以创建更复杂的表达式,评估为有用的结果。

程序员将表达式与我们早些时候看到的赋值操作符等其他语言结构结合起来,形成语句。 示例 4-4 显示了两个语句。第一个将表达式 366 - $day_number 的结果赋给变量 $days_to_new_year,第二个仅在表达式 $days_to_new_year < 30 评估为 TRUE 时输出友好消息。

示例 4-4. 表达式和语句
<?php
  $days_to_new_year = 366 - $day_number; // Expression

  if ($days_to_new_year < 30)
  {
     echo "Not long now till new year";  // Statement
  }
?>

运算符

PHP 提供了许多强大的不同类型的运算符——算术、字符串、逻辑、赋值、比较等等(参见 表 4-1)。

表 4-1. PHP 运算符类型

运算符描述示例
算术基本数学运算$a + $b
数组数组并集$a + $b
赋值赋值操作$a = $b + 23
位运算在字节内操作位12 ^ 9
比较比较两个值$a < $b
执行执行反引号内容`ls -al`
增量/减量加或减 1$a++
逻辑布尔运算$a and $b
字符串连接$a . $b

每个运算符接受不同数量的操作数:

  • 一元 运算符,如递增($a++)或否定(!$a),接受一个操作数。

  • 二元 运算符,代表大多数 PHP 运算符(包括加法、减法、乘法和除法),接受两个操作数。

  • 唯一的三元 运算符,采用 expr ? x : y 的形式,需要三个操作数。它是一个简洁的单行 if 语句,如果 exprTRUE,则返回 x,如果 exprFALSE,则返回 y

运算符优先级

如果所有运算符的优先级相同,则按遇到的顺序处理它们。实际上,许多运算符确实具有相同的优先级。看看 示例 4-5。

示例 4-5. 三个等价表达式
1 + 2 + 3 - 4 + 5
2 - 4 + 5 + 3 + 1
5 + 2 - 4 + 1 + 3

在这里,您会看到尽管数字(及其前面的运算符)已经移动,但每个表达式的结果是值7,因为加号和减号运算符具有相同的优先级。我们可以用乘法和除法来尝试同样的事情(参见 示例 4-6)。

示例 4-6. 三个同等表达式
1 * 2 * 3 / 4 * 5
2 / 4 * 5 * 3 * 1
5 * 2 / 4 * 1 * 3

这里的结果值始终为7.5。但是当我们在一个表达式中混合具有不同优先级的运算符时,情况就会改变,就像 示例 4-7 中一样。

示例 4-7. 使用混合优先级运算符的三个表达式
1 + 2 * 3 - 4 * 5
2 - 4 * 5 * 3 + 1
5 + 2 - 4 + 1 * 3

如果没有运算符优先级,这三个表达式的计算结果将分别为25–2912。但是因为乘法和除法优先于加法和减法,这些表达式被计算为如果在表达式的这些部分周围有括号,就像数学表示法一样(参见 示例 4-8)。

示例 4-8. 显示隐含括号的三个表达式
1 + (2 * 3) - (4 * 5)
2 - (4 * 5 * 3) + 1
5 + 2 - 4 + (1 * 3)

PHP 首先计算括号内的子表达式,以得出半完成的表达式在 示例 4-9 中的结果。

示例 4-9. 在评估括号中的子表达式后
1 + (6) - (20)
2 - (60) + 1
5 + 2 - 4 + (3)

这些表达式的最终结果分别为–13–576(与没有运算符优先级时得到的25–2912完全不同)。

当然,您可以通过插入自己的括号并强制希望的任何顺序来覆盖默认的运算符优先级(参见 示例 4-10)。

示例 4-10. 强制左到右的评估
((1 + 2) * 3 - 4) * 5
(2 - 4) * 5 * 3 + 1
(5 + 2 - 4 + 1) * 3

正确插入括号后,现在可以看到值分别为25–2912

表 4-2 按优先级从高到低列出了 PHP 的运算符。

表 4-2. PHP 运算符的优先级(从高到低)

运算符类型
()括号
++ --自增/自减
!逻辑
* / %算术
+ - .算术和字符串
<< >>
< <= > >= <>比较
== != === !==比较
&位(及引用)
^
&#124;
&&逻辑
&#124;&#124;逻辑
? :三元
= += -= *= /= .= %= &= != ^= <<= >>=赋值
and逻辑
xor逻辑
or逻辑

此表中的顺序并非任意排列,而是精心设计,以便您可以在不使用括号的情况下获取最常见和直观的优先级。例如,您可以使用andor分隔两个比较,以获得预期的结果。

结合性

我们一直在讨论从左到右处理表达式,除非运算符优先级生效。但是有些运算符要求从右到左处理,这种处理方向称为运算符的结合性。对于某些运算符,没有结合性。

结合性(如表 4-3 所详细说明的)在不显式强制优先级的情况下变得重要,因此您需要了解运算符的默认操作。

表 4-3. 运算符结合性

Operator描述Associativity
< <= >= == != === !== <>比较None
!逻辑 NOTRight
~NOTRight
++ --增加和减少Right
(int)转换为整数Right
(double) (float) (real)转换为浮点数Right
(string)转换为字符串Right
(array)转换为数组Right
(object)转换为对象Right
@抑制错误报告Right
= += -= *= /=赋值Right
.= %= &= &#124;= ^= <<= >>=赋值Right
+加法和一元加Left
-减法和取反Left
*乘法Left
/除法Left
%取模Left
.字符串连接Left
<< >> & ^ &#124;位运算Left
?:三元运算符Left
&#124;&#124; && and or xor逻辑Left
,分隔符Left

例如,让我们看一下赋值运算符在示例 4-11 中的应用,其中三个变量都设置为值0

示例 4-11. 多重赋值语句
<?php
  $level = $score = $time = 0;
?>

只有在先评估表达式的最右部分,然后继续从右到左处理时,才可能进行多重赋值。

注意

作为 PHP 的新手,您应该避免运算符结合性的潜在陷阱,始终将子表达式嵌套在括号中以强制评估顺序。这也将有助于其他可能需要维护您代码的程序员理解正在发生的事情。

关系运算符

关系运算符回答诸如“这个变量的值是否为零?”和“哪个变量的值更大?”的问题。这些运算符测试两个操作数并返回布尔结果,要么是TRUE要么是FALSE。关系运算符有三种类型:相等性比较逻辑

相等性

就像我们在本章中已经看到的几次一样,等号运算符是==(两个等号)。重要的是不要将它与=(单个等号)赋值运算符混淆。在例子 4-12 中,第一条语句分配一个值,第二条测试它是否相等。

例子 4-12. 分配一个值并测试相等性
<?php
  $month = "March";

  if ($month == "March") echo "It's springtime";
?>

如你所见,通过返回TRUEFALSE,等号运算符使你能够使用例如if语句来测试条件。但这并不是全部,因为 PHP 是一种弱类型语言。如果等号表达式的两个操作数类型不同,PHP 会将它们转换为它认为最合适的类型。很少使用的全等运算符由三个连续的等号组成,可以用来比较项目而不进行转换。

例如,任何完全由数字组成的字符串在与数字比较时将被转换为数字。在例子 4-13 中,$a$b是两个不同的字符串,因此我们预期if语句都不会输出结果。

例子 4-13. 等号和全等运算符
<?php
  $a = "1000";
  $b = "+1000";

  if ($a == $b)  echo "1";
  if ($a === $b) echo "2";
?>

然而,如果你运行这个例子,你会看到它输出数字1,这意味着第一个if语句评估为TRUE。这是因为两个字符串首先被转换为数字,并且1000+1000是相同的数值。相比之下,第二个if语句使用全等运算符,因此它将$a$b作为字符串进行比较,发现它们不同,因此不输出任何内容。

就像强制操作符优先级一样,每当你对 PHP 如何转换操作数类型感到怀疑时,你可以使用全等运算符来关闭这种行为。

就像你可以使用等号运算符来测试操作数是否相等一样,你也可以使用!=,不等运算符来测试它们相等。看一下例子 4-14,它是例子 4-13 的重写,其中等号和全等运算符已被它们的反向替换。

例子 4-14. 不等和非全等运算符
<?php
  $a = "1000";
  $b = "+1000";

  if ($a != $b)  echo "1";
  if ($a !== $b) echo "2";
?>

正如你可能期望的那样,第一个if语句不会输出数字1,因为代码询问$a$b是否在数值上相等。

相反,这段代码输出数字2,因为第二个if语句询问$a$b是否在它们实际的字符串类型上相同,答案是TRUE;它们不同。

比较运算符

使用比较运算符,你不仅可以测试相等和不相等,PHP 还提供了>(大于)、<(小于)、>=(大于等于)和<=(小于等于)等来使用。例子 4-15 展示了它们的使用。

示例 4-15. 四个比较运算符
<?php
  $a = 2; $b = 3;

  if ($a > $b)  echo "$a is greater than $b<br>";
  if ($a < $b)  echo "$a is less than $b<br>";
  if ($a >= $b) echo "$a is greater than or equal to $b<br>";
  if ($a <= $b) echo "$a is less than or equal to $b<br>";
?>

在此示例中,其中$a2$b3,输出如下:

2 is less than 3
2 is less than or equal to 3

请尝试自己运行此示例,改变$a$b的值,看看结果。尝试将它们设置为相同的值,看看会发生什么。

逻辑运算符

逻辑运算符产生truefalse的结果,因此也称为布尔运算符。共有四种运算符(请参见表 4-4)。

表 4-4. 逻辑运算符

逻辑运算符描述
AND如果两个操作数都为TRUE则为TRUE
OR如果任一操作数为TRUE则为TRUE
XOR如果两个操作数中的一个为TRUE则为TRUE
! (NOT)如果操作数为FALSE则为TRUE,如果操作数为TRUE则为FALSE

您可以在示例 4-16 中看到这些运算符的使用。请注意,PHP 要求使用!符号代替NOT。此外,这些运算符可以是小写或大写。

示例 4-16. 使用的逻辑运算符
<?php
  $a = 1; $b = 0;

  echo ($a AND $b) . "<br>";
  echo ($a or $b)  . "<br>";
  echo ($a XOR $b) . "<br>";
  echo !$a         . "<br>";
?>

逐行分析,此示例输出空值、11和空值,意味着只有第二个和第三个echo语句评估为TRUE。(请记住,NULL—或空值—表示FALSE的值。)这是因为AND语句要求如果要返回TRUE值,那么两个操作数都必须为TRUE,而第四个语句对$a的值执行NOT操作,将其从TRUE(值为1)转换为FALSE。如果你想要试验一下,请尝试运行代码,并为$a$b赋予不同的10值。

注意

在编码时,请记住ANDOR的优先级低于操作符的其他版本,如&&||

OR运算符可能会在if语句中造成意外问题,因为如果第一个操作数被评估为TRUE,则不会评估第二个操作数。在示例 4-17 中,如果$finished的值为1,则函数getnext将永远不会被调用。

示例 4-17. 使用OR运算符的语句
<?php
  if ($finished == 1 OR getnext() == 1) exit;
?>

如果需要在每个if语句中调用getnext,可以像示例 4-18 中所做的那样重写代码。

示例 4-18. 修改后的if...OR语句以确保调用getnext
<?php
  $gn = getnext();

  if ($finished == 1 OR $gn == 1) exit;
?>

在这种情况下,在执行getnext函数并将返回的值存储在$gn之前,会执行if语句。

注意

另一个解决方案是交换两个子句以确保执行getnext,因为它将首先出现在表达式中。

表 4-5 显示了使用逻辑运算符的所有可能变体。还应注意,!TRUE等于FALSE!FALSE等于TRUE

表 4-5. 所有可能的 PHP 逻辑表达式

输入 运算符和结果
abANDORXOR
TRUETRUETRUETRUEFALSE
TRUEFALSEFALSETRUETRUE
FALSETRUEFALSETRUETRUE
FALSEFALSEFALSEFALSEFALSE

条件语句

条件语句 改变程序流。它们使您能够对某些事物提出问题,并以不同的方式响应您得到的答案。条件语句是创建动态网页的核心(使用 PHP 的初衷),因为它们使得每次查看页面时都可以轻松地呈现不同的输出。

在本节中,我将介绍三种基本的条件语句:if语句、switch语句和?运算符。此外,循环条件语句(我们马上会讲到)会反复执行代码,直到满足条件为止。

if语句

想象一下程序流就像是你驾驶沿着的一条单车道公路。它基本上是一条笔直的路线,但偶尔会遇到各种指示牌,告诉你应该往哪个方向走。

对于if语句,你可以想象遇到一个绕行标志,如果某个条件为TRUE,你必须按照绕行标志的指示行驶。如果是这样,你会驶出去,沿着绕行路线直到回到主路,然后继续沿着原来的方向前进。或者,如果条件不为TRUE,你将忽略绕行路线,继续驾驶(见图 4-1)。

程序流就像是一条单车道公路

图 4-1。程序流就像是一条单车道公路

if条件的内容可以是任何有效的 PHP 表达式,包括相等测试、比较表达式、对0NULL的测试,甚至函数(无论是内置函数还是你自己编写的函数)。

if条件为TRUE时要执行的操作通常放在大括号内({ })。如果只有一个语句要执行,可以忽略大括号,但如果始终使用大括号,将避免因缺少大括号而导致的难以追踪的错误,例如当您向条件添加额外的行时,该行不会被评估。

注意

多年来,“goto fail”漏洞一直困扰着苹果产品中安全套接层(SSL)代码,因为一位程序员忘记在if语句周围加上大括号,导致有时函数会错误地报告成功连接,而实际上并非总是如此。这使得恶意攻击者能够让一个安全证书被接受,而本应被拒绝。如果有疑问,请在你的if语句周围加上大括号。

然而,出于简洁和清晰的考虑,本书中的许多示例忽略了这个建议,并省略了单语句的大括号。

在示例 4-19 中,想象一下现在是月底,你所有的账单都已经支付,所以你正在进行一些银行账户的维护工作。

示例 4-19。带有大括号的if语句
<?php
  if ($bank_balance < 100)
  {
    $money         = 1000;
    $bank_balance += $money;
  }
?>

在本例中,您正在检查您的余额,以查看是否少于100(或您的货币单位)。如果是,则支付自己100(或您的货币单位)。如果是,则支付自己1,000,然后将其添加到余额中。(如果赚钱那么简单!)

如果银行余额为$100 或更高,则忽略条件语句,程序流将跳到下一行(未显示)。

在本书中,开放的花括号通常从新行开始。有些人喜欢将第一个花括号放在条件表达式的右侧;其他人则从新行开始。无论哪种方式都可以,因为 PHP 允许您按照自己的方式设置空白字符(空格、换行和制表符)。但是,如果您每个条件级别都用制表符缩进,您会发现代码更易于阅读和调试。

else语句

有时,当条件不为TRUE时,您可能不希望立即继续主程序代码,而是希望执行其他操作。这就是else语句的用处。借助它,您可以在公路上设置第二个分支,就像图 4-2 中那样。

使用if...else语句,如果条件为TRUE,则执行第一个条件语句。但如果为FALSE,则执行第二个条件语句。两者之一 必须 被执行。在任何情况下都不能两者都执行(或都不执行)。示例 4-20 展示了使用if...else结构。

现在的公路有一个 if 分支和一个 else 分支

图 4-2. 现在的公路有一个if分支和一个else分支
示例 4-20. 使用花括号的if...else语句
<?php
  if ($bank_balance < 100)
  {
    $money         = 1000;
    $bank_balance += $money;
  }
  else
  {
    $savings      += 50;
    $bank_balance -= 50;
  }
?>

在这个例子中,如果您确定银行账户中有$100 或更多的金额,则执行else语句,将一部分资金存入您的储蓄账户。

if语句一样,如果您的else只有一个条件语句,可以选择省略花括号。(但建议始终使用花括号。首先,它们使代码更易于理解。其次,它们让您可以轻松地在以后的分支中添加更多语句。)

elseif语句

有时您希望根据一系列条件发生不同的可能性。您可以使用elseif语句来实现这一点。正如您可能想象的那样,它类似于else语句,只是您在条件代码之前放置了进一步的条件表达式。在示例 4-21 中,您可以看到完整的if...elseif...else结构。

示例 4-21. 使用花括号的if...elseif...else语句
<?php
  if ($bank_balance < 100)
  {
    $money         = 1000;
    $bank_balance += $money;
  }
  elseif ($bank_balance > 200)
  {
    $savings      += 100;
    $bank_balance -= 100;
  }
  else
  {
    $savings      += 50;
    $bank_balance -= 50;
  }
?>

在本例中,在ifelse语句之间插入了一个elseif语句。它检查您的银行余额是否超过200,并且如果是,则决定您本月可以存下200,并且如果是,则决定您本月可以存下100。

虽然我可能有些过度使用这个比喻,但你可以把它想象成一个多路的绕道 (见 图 4-3)。

带有 if、elseif 和 else 的公路

图 4-3. 带有 ifelseifelse 的公路
注意

else 语句关闭 if...elseif...elseif...else 语句。如果不需要最终的 else,可以省略它,但不能在 elseif 之前留有 else;在 if 语句之前也不能有 elseif

你可以有任意多个 elseif 语句。但是随着 elseif 语句的数量增加,如果符合你的需求,可能更适合考虑使用 switch 语句。接下来我们将详细讨论这一点。

switch 语句

switch 语句在一个变量或表达式的结果可能有多个值,并且每个值应该触发不同活动的情况下非常有用。

例如,考虑一个由 PHP 驱动的菜单系统,根据用户的请求向主菜单代码传递单个字符串。假设选项是主页、关于、新闻、登录和链接,我们将变量 $page 设置为其中一个,根据用户的输入。

如果我们使用 if...elseif...else 来编写这段代码,可能会像 示例 4-22。

示例 4-22. 多行 if...elseif...else 语句
<?php
  if     ($page == "Home")  echo "You selected Home";
  elseif ($page == "About") echo "You selected About";
  elseif ($page == "News")  echo "You selected News";
  elseif ($page == "Login") echo "You selected Login";
  elseif ($page == "Links") echo "You selected Links";
  else                      echo "Unrecognized selection";
?>

如果我们使用 switch 语句,代码可能看起来像 示例 4-23。

示例 4-23. switch 语句
<?php
  switch ($page)
  {
    case "Home":
        echo "You selected Home";
        break;
    case "About":
        echo "You selected About";
        break;
    case "News":
        echo "You selected News";
        break;
    case "Login":
        echo "You selected Login";
        break;
    case "Links":
        echo "You selected Links";
        break;
  }
?>

正如你所见,$page 只在 switch 语句的开头提到。此后,case 命令检查是否匹配。一旦匹配,执行匹配的条件语句。当然,在实际程序中,你会在这里编写代码来显示或跳转到一个页面,而不仅仅是告诉用户选择了什么。

注意

switch 语句中,你不使用花括号来定义 case 命令。相反,它们以冒号开头,并以 break 语句结束。整个 switch 语句中的所有 case 都包含在一对花括号中。

退出

如果你希望在 switch 语句中因为条件已满足而跳出,使用 break 命令。这个命令告诉 PHP 退出 switch 并跳转到下一条语句。

如果你在 示例 4-23 中省略了 break 命令,并且 Home 的情况被评估为 TRUE,那么所有五个情况都会被执行。或者,如果 $page 的值是 News,那么之后的所有 case 命令都会被执行。这是有意为之的,允许一些高级编程,但通常你应该记住每次一组 case 条件执行完毕时都要发出 break 命令。事实上,忽略 break 语句是一个常见的错误。

默认操作

switch语句中的典型要求是在未满足任何case条件时返回默认操作。例如,在示例 4-23 中的菜单代码中,你可以在最后一个大括号之前立即添加示例 4-24 中的代码。

示例 4-24. 添加到示例 4-23 的默认语句
default:
    echo "Unrecognized selection";
    break;

这复制了示例 4-22 中else语句的效果。

尽管此处不需要break命令,因为默认是最终的子语句,并且程序流会自动继续到结束大括号,但如果你决定将default语句放在较高位置,那么肯定需要一个break命令来防止程序流陷入后续语句。通常,最安全的做法是始终包含break命令。

替代语法

如果你愿意,可以在switch语句中用单个冒号替换第一个大括号,并用endswitch命令替换最后一个大括号,就像示例 4-25 中所示。然而,这种方法并不常用,仅在你在第三方代码中遇到时才会提到。

示例 4-25. 备选switch语句语法
<?php
  switch ($page):
    case "Home":
        echo "You selected Home";
        break;

    // etc

    case "Links":
        echo "You selected Links";
        break;
  endswitch;
?>

?(或三元)操作符

避免使用ifelse语句的冗长方法之一是使用更紧凑的三元操作符?,这在使用中有些不同,因为它需要三个操作数而不是通常的两个。

我们在第三章中简要提到了这一点,讨论了printecho语句之间的区别,作为与print良好配合但不与echo良好配合的操作符类型的示例。

?操作符接受一个必须评估的表达式,以及两个要执行的语句:一个是当表达式评估为TRUE时,另一个是当它为FALSE时。示例 4-26 展示了我们可能会用于向汽车数字仪表板写入有关燃油水平警告的代码。

示例 4-26. 使用?操作符
<?php
  echo $fuel <= 1 ? "Fill tank now" : "There's enough fuel";
?>

在此语句中,如果燃料少于或等于一加仑(换句话说,$fuel设置为1或更少),则将字符串Fill tank now返回给前面的echo语句。否则,将返回字符串There's enough fuel。你也可以将?语句返回的值赋给一个变量(参见示例 4-27)。

示例 4-27. 将?条件结果赋给变量
<?php
  $enough = $fuel <= 1 ? FALSE : TRUE;
?>

在这里,只有当燃料超过一加仑时,$enough才会被赋值为TRUE;否则,它将被赋值为FALSE

如果您觉得?运算符令人困惑,可以继续使用if语句,但您应该熟悉这个运算符,因为您会在其他人的代码中看到它。它可能很难阅读,因为它经常混合多个相同变量的出现。例如,以下代码是相当流行的:

$saved = $saved >= $new ? $saved : $new;

如果仔细拆解,您可以弄清楚此代码的作用:

$saved =                // Set the value of $saved to...
        $saved >= $new  // Check $saved against $new
    ?                   // Yes, comparison is true...
        $saved          // ... so assign the current value of $saved
    :                   // No, comparison is false...
        $new;           // ... so assign the value of $new

这是在程序进行时跟踪所见到的最大值的简洁方法。您将最大值保存在$saved中,并在每次获得新值时将其与$new进行比较。熟悉?运算符的程序员发现它比用于这样的短比较的if语句更为方便。当不用于编写紧凑的代码时,它通常用于在行内做出一些决策,例如在将变量设置为函数参数之前测试是否已设置。

循环

计算机的一大优点是它们可以快速且不知疲倦地重复计算任务。通常情况下,您可能希望程序重复执行相同的代码序列,直到发生某些事件,如用户输入值或达到自然结束。PHP 的循环结构为实现这一点提供了完美的方式。

要想象这是如何工作的,请看图 4-4。这与用于说明if语句的公路隐喻大致相同,只是这条路也有一个循环部分,一旦车辆进入其中,只有在正确的程序条件下才能退出。

将循环想象为程序高速公路布局

图 4-4. 将循环想象为程序高速公路布局的示意图

while循环

让我们将示例 4-26 中的数字汽车仪表板转变为一个循环,以在驾驶时持续检查燃油水平,使用一个while循环(示例 4-28)。

示例 4-28. 一个while循环
<?php
  $fuel = 10;

  while ($fuel > 1)
  {
    // Keep driving...
    echo "There's enough fuel";
  }
?>

实际上,您可能更喜欢保持绿灯亮起,而不是输出文本,但关键是您希望对燃油水平做出的任何正面指示都放在while循环内部。顺便说一句,如果您尝试这个示例,注意它会持续打印字符串,直到您在浏览器中单击停止按钮。

注意

if语句一样,您会注意到while语句内部需要用花括号来容纳语句,除非只有一个语句。

查看另一个显示 12 倍表的while循环的示例,请参阅示例 4-29。

示例 4-29. 打印 12 倍表的while循环
<?php
  $count = 1;

  while ($count <= 12)
  {
    echo "$count times 12 is " . $count * 12 . "<br>";
    ++$count;
  }
?>

在这里,变量$count被初始化为1的值,然后开始一个while循环,其比较表达式为$count <= 12。此循环将继续执行,直到变量大于 12。从这段代码输出如下:

1 times 12 is 12
2 times 12 is 24
3 times 12 is 36
*and so on...*

在循环内部,打印一个字符串以及$count乘以 12 的值。为了整洁起见,接下来是一个<br>标签,强制换行。然后递增$count,准备最后的大括号,告诉 PHP 回到循环的起始处。

此时,再次测试$count是否大于 12。它不是,但现在它的值是2,再绕过循环 11 次后,它的值将变为13。当发生这种情况时,跳过while循环内的代码,执行转到循环后的代码,本例中即为程序的末尾。

如果没有++$count语句(同样也可以是$count++),这个循环就像本节中的第一个循环。它永远不会结束,只会一遍又一遍地打印1 * 12的结果。

但是有一种更整洁的方式可以编写这个循环,我想你会喜欢。看看示例 4-30。

示例 4-30。示例 4-29 的简化版本
<?php
  $count = 0;

  while (++$count <= 12)
    echo "$count times 12 is " . $count * 12 . "<br>";
?>

在这个例子中,可以将++$count语句从while循环内的语句移动到循环的条件表达式中。现在发生的是,PHP 在每次迭代循环开始时遇到变量$count,注意到它前面有递增操作符,首先递增变量,然后再将其与值12进行比较。因此,你可以看到现在必须将$count初始化为0,而不是1,因为一进入循环就会递增。如果保持初始化为1,则只会输出 2 到 12 之间的结果。

do...while 循环

while循环的轻微变化是do...while循环,当您希望一段代码至少执行一次并在此后进行条件检查时使用。示例 4-31 展示了使用这种循环的修改版本的 12 乘法表代码。

示例 4-31。用于打印 12 乘法表的do...while循环
<?php
  $count = 1;
  do
    echo "$count times 12 is " . $count * 12 . "<br>";
  while (++$count <= 12);
?>

注意,我们又回到了将$count初始化为1(而不是0)的地方,因为在我们有机会递增变量之前,循环的echo语句就已执行了。尽管如此,代码看起来还是相当相似的。

当然,如果在do...while循环内有多个语句,记得使用大括号,就像示例 4-32 中那样。

示例 4-32。扩展示例 4-31 以使用大括号
<?php
  $count = 1;

  do {
    echo "$count times 12 is " . $count * 12;
    echo "<br>";
  } while (++$count <= 12);
?>

for 循环

最后一种循环语句是for循环,也是最强大的,因为它结合了在进入循环时设置变量、在迭代循环时测试条件以及在每次迭代后修改变量的能力。

示例 4-33 展示了如何使用for循环编写乘法表程序。

示例 4-33. 输出 12 乘法表的for循环
<?php
  for ($count = 1 ; $count <= 12 ; ++$count)
    echo "$count times 12 is " . $count * 12 . "<br>";
?>

看看代码是如何被简化为一个包含单个条件语句的for语句?这里发生了什么。每个for语句都有三个参数:

  • 一个初始化表达式

  • 一个条件表达式

  • 一个修改表达式

这些用分号分隔开,像这样:for (expr1 ; expr2 ; expr3)。在循环的第一次迭代开始时,初始化表达式被执行。在乘法表代码中,$count被初始化为值1。然后,在每次循环中,条件表达式(在本例中是$count <= 12)被测试,只有在条件为TRUE时才进入循环。最后,在每次迭代结束时,修改表达式被执行。在乘法表代码中,变量$count被递增。

所有这些结构都清晰地消除了将循环控制放置在其主体内的要求,仅仅为你希望循环执行的语句保留空间。

如果for循环包含多个语句,记得要使用大括号,就像示例 4-34 中那样。

示例 4-34. 示例 4-33 中的for循环,加入了大括号
<?php
  for ($count = 1 ; $count <= 12 ; ++$count)
  {
    echo "$count times 12 is " . $count * 12;
    echo "<br>";
  }
?>

让我们比较何时使用for循环和while循环。for循环是专门设计用于一个在规律性变化的单一值上。通常情况下,你有一个增量值,例如你得到一个用户选择列表并想逐个处理每个选择。但你可以随意变换这个变量。for语句的更复杂形式甚至允许在每个参数中执行多个操作:

for ($i = 1, $j = 1 ; $i + $j < 10 ; $i++ , $j++)
{
  // ...
}

尽管这很复杂且不建议首次使用者使用。关键是要区分逗号和分号。这三个参数必须用分号分隔。在每个参数内部,多个语句可以用逗号分隔。因此,在前面的示例中,第一个和第三个参数每个包含两个语句:

$i = 1, $j = 1  // Initialize $i and $j
$i + $j < 10    // Terminating condition
$i++ , $j++     // Modify $i and $j at the end of each iteration

从这个例子中要理解的主要内容是,你必须使用分号来分隔这三个参数部分,而不是逗号(逗号只能用来分隔参数部分内的语句)。

那么,什么情况下使用while语句比for语句更合适?当你的条件不依赖于变量的简单、规律性变化时。例如,如果你想要检查某些特殊输入或错误,并在其发生时结束循环,就使用while语句。

退出循环

就像你看到如何跳出switch语句一样,你也可以使用相同的break命令跳出for循环(或任何循环)。当例如其中一个语句返回错误且循环无法继续安全执行时,这一步就是必要的。可能出现这种情况的一个案例是写入文件返回错误,可能是因为磁盘已满(参见示例 4-35)。

示例 4-35. 使用for循环编写文件并进行错误处理
<?php
  $fp = fopen("text.txt", 'wb');

  for ($j = 0 ; $j < 100 ; ++$j)
  {
    $written = fwrite($fp, "data");

    if ($written == FALSE) break;
  }

  fclose($fp);
?>

这是迄今为止你见过的最复杂的代码片段,但你已经准备好了。我们将在第七章中探讨文件处理命令,但现在你只需要知道,第一行以二进制模式打开文件text.txt进行写入,并将文件指针返回到变量$fp中,稍后用于引用打开的文件。

然后,循环迭代 100 次(从 0 到 99),将字符串data写入文件。每次写入后,变量$writtenfwrite函数赋值,表示正确写入的字符数。但如果发生错误,fwrite函数则赋值为FALSE

fwrite的行为使得代码可以轻松检查变量$written是否设置为FALSE,如果是,则跳出循环,并进入关闭文件的下一条语句。

如果你希望改进代码,可以简化这行代码:

if ($written == FALSE) break;

使用NOT运算符,像这样:

if (!$written) break;

实际上,内部循环语句对可以缩短为单个语句:

if (!fwrite($fp, "data")) break;

换句话说,你可以消除$written变量,因为它只是用来检查fwrite返回的值。你可以直接测试返回值而不是用$written变量。

break命令比你想象的更加强大,因为如果你的代码嵌套超过一层需要跳出,你可以在break命令后跟一个数字来指示跳出多少层:

break 2;

continue 语句

continue语句有点像break语句,不过它指示 PHP 停止处理当前循环的迭代,并直接进入下一次迭代。因此,PHP 不会跳出整个循环,而是仅退出当前迭代。

在某些情况下,这种方法非常有用,例如你知道在当前循环中继续执行没有意义,并且你想要节省处理器周期,或者通过直接进入循环的下一次迭代来防止发生错误。在示例 4-36 中,使用continue语句可以防止当变量$j的值为0时发生除以零的错误。

示例 4-36. 使用continue捕获除零错误
<?php
  $j = 11;

  while ($j > -10)
  {
    $j--;

    if ($j == 0) continue;

    echo (10 / $j) . "<br>";
  }
?>

对于 $j10-10 之间的所有值(0 除外),计算 10 除以 $j 的结果会显示出来。但是当 $j0 时,会执行 continue 语句,并立即跳到循环的下一个迭代。

隐式和显式转换

PHP 是一种弱类型语言,允许你在使用变量时简单地声明其类型。它还会根据需要自动将值从一种类型转换为另一种类型。这称为隐式转换

然而,有时 PHP 的隐式转换可能不是你想要的。在 示例 4-37 中,请注意除法的输入是整数。默认情况下,PHP 将输出转换为浮点数,以便提供最精确的值——4.66 循环。

示例 4-37. 这个表达式返回一个浮点数
<?php
  $a = 56;
  $b = 12;
  $c = $a / $b;

  echo $c;
?>

但是如果我们希望 $c 是一个整数呢?我们可以通过各种方式来实现这一点,其中一种方法是使用整数转换类型 (int) 强制将 $a / $b 的结果转换为整数值,就像这样:

$c = (int) ($a / $b);

这称为显式转换。请注意,为了确保整个表达式的值被转换为整数,我们将表达式置于括号中。否则,只有变量 $a 会被转换为整数,这将是毫无意义的练习,因为除以 $b 仍将返回一个浮点数。

您可以将变量和文字显式转换为 表 4-6 中所示的类型。

表 4-6. PHP 的类型转换

类型转换描述
(int) (integer)通过舍弃小数部分转换为整数。
(bool) (boolean)转换为布尔值。
(float) (double) (real)转换为浮点数。
(string)转换为字符串。
(array)转换为数组。
(object)转换为对象。
注意

通常可以通过调用 PHP 的内置函数避免使用转换。例如,要获取整数值,可以使用 intval 函数。正如本书中的其他部分一样,本节主要是为了帮助您理解可能偶尔遇到的第三方代码。

PHP 动态链接

因为 PHP 是一种编程语言,其输出对于每个用户可能完全不同,因此一个完整的网站可以仅由单个 PHP 网页运行。每当用户点击某些内容时,细节可以发送回同一个网页,根据各种 cookie 和/或其他会话详细信息决定下一步该执行什么操作。

尽管可以通过这种方式构建整个网站,但并不推荐,因为您的源代码会越来越庞大,并开始变得难以管理,因为它必须考虑用户可能采取的每一个可能的操作。

相反,将您的网站开发分成不同的部分更加明智。例如,一个明确的过程是注册网站,以及所有这些操作的检查以验证电子邮件地址,确定用户名是否已被使用等等。

第二个模块可能是登录用户后将其传递到网站的主要部分之前的模块。然后您可能有一个具有用户留言功能的消息模块,一个包含链接和有用信息的模块,另一个允许上传图片的模块等等。

只要您已经通过 cookie 或会话变量创建了跟踪用户在网站上行动的方式(我们将在后面的章节中更详细地讨论这两者),您可以将您的网站分成 PHP 代码的合理部分,每个部分都是自包含的,因此在未来开发每个新功能和维护旧功能时会变得更加轻松。如果您有一个团队,不同的人可以分别工作在不同的模块上,这样每个程序员只需彻底了解一个部分。

动态链接实例

如今 Web 上更受欢迎的基于 PHP 的应用之一是内容管理系统(CMS)WordPress(见图 4-5)。您可能没有意识到,但每个主要部分都有自己的主 PHP 文件,并且一整套通用的共享函数已放置在根据需要由主 PHP 页面包含的单独文件中。

图 4-5. WordPress CMS

整个平台通过幕后的会话追踪来紧密连接,以便在从一个子部分过渡到另一个子部分时几乎感觉不到。因此,希望调整 WordPress 的 Web 开发人员可以轻松找到他们需要的特定文件,修改它,测试和调试它,而不必去破坏程序的不相关部分。下次您使用 WordPress 时,请关注您浏览器的地址栏,您会注意到它使用的一些不同的 PHP 文件。

本章内容涵盖了相当多的内容,到目前为止,您应该能够编写自己的小型 PHP 程序。但在开始下一章关于函数和对象之前,您可能希望通过回答以下问题来测试您的新知识。

问题

  1. TRUEFALSE分别表示什么实际底层值?

  2. 什么是最简单的两种表达形式?

  3. 什么是一元、二元和三元运算符的区别?

  4. 强制自己操作符优先级的最佳方法是什么?

  5. 运算符结合性是什么意思?

  6. 何时使用===(身份)运算符?

  7. 命名三种条件语句类型。

  8. 您可以使用哪个命令来跳过当前循环迭代并继续下一个迭代?

  9. 为什么for循环比while循环更强大?

  10. ifwhile语句如何解释不同数据类型的条件表达式?

请查看“第四章答案”,在附录 A 中可以找到这些问题的答案。