PHP8 解决方案(一)

原文:PHP 8 Solutions

协议:CC BY-NC-SA 4.0

一、PHP 8 是什么?

2020 年 11 月下旬发布的 PHP 8 是最流行的编程语言之一的重大更新。根据网络技术调查( https://w3techs.com/technologies/details/pl-php/all/all ),使用服务器端语言的网站中,超过五分之四部署了 PHP。尽管 PHP 很受欢迎,但也有很多批评者,主要是因为这种语言在早期的发展方式。这导致相关函数的名称和参数的顺序有时不一致。而且它的一些特性对没有经验的人来说会带来安全风险。自 2012 年以来,为改善语言所做的共同努力已经消除了大部分问题。

PHP 现在是一种成熟、强大的语言,已经成为创建动态网站最广泛使用的技术。它被大型企业使用,包括 Wikipedia、Mailchimp 和 Tumblr,也支持流行的 WordPress、Drupal 和 Joomla 内容管理系统。PHP 通过以下方式赋予网站生命:

  • 将您网站的反馈直接发送到您的邮箱

  • 通过网页上传文件

  • 从较大的图像生成缩略图

  • 读取和写入文件

  • 动态显示和更新信息

  • 使用数据库来显示和存储信息

  • 使网站可搜索

  • 还有更多…

通过阅读这本书,你将能够做到这一切。PHP 不仅简单易学;它是平台中立的,所以相同的代码可以在 Windows、macOS 和 Linux 上运行。你需要用 PHP 开发的所有软件都是开源免费的。

在本章中,您将了解以下内容:

  • PHP 是如何发展成为动态网站最广泛使用的技术的

  • PHP 如何使网页动态化

  • 学习 PHP 有多难或多容易

  • PHP 是否安全

  • PHP 8 的新特性

  • 写 PHP 需要什么软件

PHP 是如何发展的

PHP 始于 1995 年,当时的野心并不大。最初叫个人主页工具(PHP Tools)。其主要目标之一是通过从在线表格中收集信息并将其显示在网页上来创建一个留言簿。不到三年,它就决定从名字中去掉个人主页,因为它听起来像是为业余爱好者准备的,而且与后来添加的一系列复杂功能不相称。这就留下了首字母 PHP 应该代表什么的问题。最终决定称之为 PHP 超文本预处理器;但是大多数人都简单的叫它 PHP。

这些年来,PHP 一直在发展,不断增加新的特性。这种语言最吸引人的地方之一是它忠实于自己的根源。尽管它支持复杂的面向对象编程,但您可以在不深入复杂理论的情况下开始使用它。PHP 的创始人拉斯马斯·勒德尔夫曾将其描述为“一种对程序员非常友好的脚本语言,适合没有编程经验的人以及需要快速完成工作的经验丰富的 web 开发人员。”您可以立即开始编写有用的脚本,同时确信您正在使用一种能够开发工业级应用的技术。

Note

本书中的大部分代码都使用了 PHP 8 的新特性。它不能保证在旧版本的 PHP 上工作。

PHP 如何使页面动态化

PHP 最初被设计成嵌入在网页的 HTML 中,这也是它经常被使用的方式。例如,要在版权声明中显示当前年份,您可以将其放在页脚中:

<p>&copy; <?php echo date('Y'); ?> PHP 8 Solutions</p>

在支持 PHP 的 web 服务器上,<?php?>标记之间的代码被自动处理,并显示年份,如下所示:

这只是一个小例子,但它说明了使用 PHP 的一些优点:

  • 新年午夜钟声敲响时,年份会自动更新。

  • 日期是由 web 服务器计算的,因此如果用户电脑中的时钟设置不正确,也不会受到影响。然而,正如您稍后将了解到的,PHP 遵循服务器的时区;但是这可以通过编程来调整。

虽然像这样在 HTML 中嵌入 PHP 代码很方便,但是这是重复的,而且会导致错误。它还会使您的网页难以维护,尤其是当您开始使用更复杂的 PHP 代码时。因此,通常的做法是将大量动态代码存储在单独的文件中,然后使用 PHP 从不同的组件构建页面。单独的文件——或通常所说的包含文件——可以只包含 PHP、HTML 或两者的混合。

举个简单的例子,你可以把网站的导航菜单放在一个包含文件中,用 PHP 把它包含在每个页面中。每当需要更改菜单时,只需编辑包含文件,更改会自动反映在包含菜单的每个页面中。想象一下,对于一个有几十个页面的网站来说,这能节省多少时间!

对于普通的 HTML 页面,内容是由 web 开发人员在设计时确定的,并上传到 web 服务器。当有人访问页面时,web 服务器只需发送 HTML 和其他资产,如图像和样式表。这是一个简单的事务—请求来自浏览器,固定内容由服务器发回。当你用 PHP 创建网页时,更多的事情会发生。图 1-1 显示了发生的情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-1

web 服务器动态构建每个 PHP 页面来响应请求

当一个 PHP 驱动的网站被访问时,它会启动以下事件序列:

  1. 浏览器向 web 服务器发送请求。

  2. web 服务器将请求传递给嵌入在服务器中的 PHP 引擎。

  3. PHP 引擎处理请求页面中的代码。在许多情况下,它还可能在构建页面之前查询数据库。

  4. 服务器将完成的页面发送回浏览器。

这个过程通常只需要几分之一秒,所以 PHP 网站的访问者不太可能注意到任何延迟。因为每个页面都是单独构建的,所以 PHP 站点可以响应用户输入,在用户登录时显示不同的内容,或者显示数据库搜索的结果。

创建独立思考的页面

PHP 是一种服务器端语言。PHP 代码保留在 web 服务器上。经过处理后,服务器只发送脚本的输出。通常情况下,这是 HTML,但 PHP 也可以用于生成其他 web 语言,如 JSON (JavaScript 对象表示法)或 XML(可扩展标记语言)。

PHP 允许你在你的网页中引入基于选择的逻辑。有些决策是使用 PHP 从服务器收集的信息做出的:日期、时间、星期几、页面 URL 中的信息等等。如果是星期三,它会显示星期三的电视时间表。在其他时候,决策是基于用户输入的,这是 PHP 从在线表单中提取的。如果你注册了一个网站,它会显示个性化信息——诸如此类。

PHP 使用和学习有多难?

PHP 不是火箭科学,但是不要期望在 5 分钟内成为专家。也许对新来者来说最大的震惊是 PHP 对错误的容忍度远远低于浏览器对 HTML 的容忍度。如果在 HTML 中省略了结束标记,大多数浏览器仍然会呈现页面。如果您在 PHP 中省略了右引号、分号或大括号,您将得到如图 1-2 所示的错误信息。这影响了所有的编程语言,比如 JavaScript 和 C#,而不仅仅是 PHP。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-2

像 PHP 这样的服务器端语言不能容忍大多数编码错误

如果你使用可视化设计工具,却从来不看底层代码,那么是时候重新考虑你的方法了。将 PHP 与结构不良的 HTML 混合在一起很可能会导致问题。PHP 使用循环来执行重复的任务,比如显示数据库搜索的结果。一个循环重复相同的代码段——通常是 PHP 和 HTML 的混合——直到所有结果都显示出来。如果你把循环放在错误的地方,或者你的 HTML 结构不良,你的页面很可能会像纸牌搭的房子一样倒塌。

如果你还没有这样做的习惯,使用万维网联盟(W3C)的 Nu HTML 检查器( https://validator.w3.org/nu/ )来检查你的页面是个好主意。

Note

W3C 是开发 HTML 和 CSS 等标准以确保网络长期发展的国际组织。它是由万维网的发明者蒂姆·伯纳斯·李领导的。要了解 W3C,请参见 www.w3.org/Consortium/mission

我可以复制并粘贴代码吗?

抄这本书里的代码没有错。这就是它存在的目的。我已经把这本书组织成了一系列实践项目。我解释了代码的用途以及它为什么会在那里。即使您不完全理解它是如何工作的,这也应该给了您足够的信心,让您知道代码的哪些部分适合您自己的需要,哪些部分最好不要去管。但是要想从这本书中获得最大收益,你需要开始尝试,然后想出自己的解决方案。

PHP 有数以千计的内置函数,可以执行各种任务,比如将文本转换为大写,从全尺寸图像生成缩略图,或者连接到数据库。真正的力量来自于以不同的方式组合这些函数,并添加您自己的条件逻辑。

PHP 有多安全?

PHP 就像你家里的电或菜刀:处理得当,非常安全;不负责任的处理,会造成很大的伤害。这本书第一版的灵感之一是大量攻击,这些攻击利用了电子邮件脚本中的漏洞,将网站变成了垃圾邮件中继站。解决方案很简单,你将在第六章中了解到,但即使多年后,我仍然看到有人使用同样不安全的技术,将他们的网站暴露在攻击之下。

PHP 并非不安全,也不需要每个人都成为安全专家才能使用。重要的是理解 PHP 安全的基本原理:在处理用户输入之前总是检查它。你会发现这是本书的永恒主题。大多数安全风险都可以不费吹灰之力消除。

保护自己的最好方法是理解你正在使用的代码。

PHP 8 有什么新特性?

PHP 8 中的新特性和变化非常广泛;但最重要的是采用了实时(JIT)编译器。这改变了 PHP 代码转换成服务器可以理解的机器码的方式。顾名思义,JIT 旨在提高性能。JIT 提案的作者之一 Zeev Surasky 制作了一个短片( https://youtu.be/dWH65pmnsrI )来展示 JIT 能够带来的巨大改进。然而,这样的速度提升只会影响处理器密集型计算——至少目前是这样。WordPress 在 PHP 8 上的运行速度并不比在 PHP 7 上快,这也是它在速度上超越之前版本的地方。

PHP 8 中的许多新特性旨在使代码更加简洁高效。例如,如果您只想更改一个函数的多个参数中的一个,那么使用命名参数就不需要重复这些参数的默认值。构造函数属性提升极大地简化了类定义中的属性声明,通常会将行数减少三分之一。新的 nullsafe 操作符同样减少了调用方法或获取非 null 表达式结果的属性所需的代码量。这些和其他新特性的细节在第 3 和 4 章中介绍。

将现有代码迁移到新版本 PHP 的一个重要考虑是,不兼容的更改是否会破坏您的应用。如果你一直遵循推荐的最佳实践,你不太可能有问题。但是,您应该了解一些重要的变化,如下所示:

  • 错误控制操作符(@)将不再沉默致命错误。

  • 使用两个等号(==)的数字和非数字字符串之间的非严格比较现在将数字转换为字符串并比较字符串。这意味着以前等同于true的一些比较现在是false

  • 与类同名的方法不再被解释为构造函数。你必须用__construct()来代替。

  • 您不能再定义不区分大小写的常量。

  • match现在是保留关键字。

  • #[不再被认为是注释的开始,因为这个语法被用于一个叫做属性的新特性。这只影响#后的左方括号。

Note

参见 www.php.net/manual/en/migration80.incompatible.php 获得 PHP 8 中向后不兼容的变化的完整列表。

写 PHP 需要什么软件?

严格来说,你不需要任何特殊的软件来编写 PHP 脚本。PHP 代码是纯文本,可以在任何文本编辑器中创建,例如 Windows 上的记事本或 macOS 上的 TextEdit。话虽如此,如果你使用一个具有加速开发过程的特性的程序,你的生活会变得容易得多。有很多可用的——既有免费的,也有付费的。

选择 PHP 编辑器时要注意什么

如果你的代码中有一个错误,你的页面可能永远不会到达浏览器,你只会看到一个错误信息。您应该选择具有以下功能的脚本编辑器:

  • PHP 语法检查:这曾经只能在昂贵的专用程序中找到,但是现在它是几个免费程序的一个特性。语法检查器会在您键入代码时监控代码并突出显示错误,从而节省大量时间并减少挫败感。

  • PHP 语法着色:代码根据所扮演的角色用不同的颜色突出显示。如果你的代码是一种意想不到的颜色,这肯定是你犯了一个错误。

  • PHP 代码提示:PHP 有如此多的内置函数,以至于即使对于一个有经验的用户来说,也很难记住如何使用它们。许多脚本编辑器会自动显示工具提示,提醒您某段代码是如何工作的。

  • 线路编号:快速找到特定线路,使故障排除变得更加简单。

  • 一个“平衡大括号”特征:圆括号(())、方括号([])和花括号({})必须总是成对出现。很容易忘记关闭一对。所有好的脚本编辑器都有助于找到匹配的圆括号、方括号或大括号。

您正在使用的构建网页的程序可能已经具备了这些功能中的部分或全部。即使您不打算进行大量的 PHP 开发,如果您的 web 开发程序不支持语法检查,您也应该考虑使用专用的脚本编辑器。下面的专用脚本编辑器拥有所有的基本特性,比如语法检查和代码提示。这不是一个详尽的列表,而是基于个人经验的列表:

  • PhpStorm ( www.jetbrains.com/phpstorm/ ):虽然这是一个专用的 PHP 编辑程序,但它对 HTML、CSS、JavaScript 都有极好的支持。这是我最喜欢的用 PHP 开发的程序。它是按年订阅出售的。如果您在至少 12 个月后取消,您将获得旧版本的永久许可证。

  • Visual Studio Code(https://code.visualstudio.com/):微软的一款优秀的代码编辑器,不仅可以在 Windows 上运行,也可以在 macOS 和 Linux 上运行。它是免费的,并且内置了对 PHP 的支持。

  • Sublime Text ( www.sublimetext.com/ ):如果你是 Sublime Text 的粉丝,这里有 PHP 语法着色、语法检查和文档化的插件。免费评估,但你应该购买相对便宜的许可证继续使用。

  • Zend Studio ( www.zend.com/products/zend-studio ):由 Zend 创建的强大的专用 PHP 编辑器,该公司由 PHP 开发的主要贡献者运营。它可以在 Windows、macOS 和 Linux 上运行。不同的价格适用于个人和商业用途。

  • Eclipse PHP 开发工具(PDT) ( https://projects.eclipse.org/projects/tools.pdt ):类似 Zend Studio 但优势是免费。它运行在 Eclipse 上,Eclipse 是支持多种计算机语言的开源 IDE。如果您已经将 Eclipse 用于其他语言,您应该会发现它相对容易使用。PDT 在 Windows、macOS 和 Linux 上运行。

所以让我们继续吧…

这一章仅仅提供了一个 PHP 可以做些什么来给你的网站添加动态特性的简要概述,以及你需要什么样的软件来这样做。使用 PHP 的第一步是建立一个测试环境。下一章将介绍你在 Windows 和 macOS 上需要的东西。

二、准备使用 PHP

既然您已经决定使用 PHP 来丰富您的网页,那么您需要确保您已经拥有了继续阅读本书其余部分所需的一切。虽然您可以在远程服务器上测试所有内容,但是在本地计算机上测试 PHP 页面通常更方便。你需要安装的一切都是免费的。在这一章中,我将解释 Windows 和 macOS 的各种选项。必要的组件通常默认安装在 Linux 上。

本章涵盖

  • 检查你的网站是否支持 PHP

  • 在 Windows 和 macOS 中使用现成的包创建本地测试设置

  • 决定存储 PHP 文件的位置

  • 检查本地和远程服务器上的 PHP 配置

检查你的网站是否支持 PHP

要想知道你的网站是否支持 PHP,最简单的方法就是询问你的托管公司。另一种方法是上传一个 PHP 页面到你的网站,看看它是否有效。即使您知道您的站点支持 PHP,也要做以下测试来确认运行的是哪个版本:

  1. 打开脚本编辑器,在空白页中键入以下代码:

  2. 将文件另存为phpversion.php。确保你的操作系统没有在.php后面添加.txt文件扩展名是很重要的。如果您在 Mac 上使用“文本编辑”,请确定它没有以 RTF 格式存储文件。如果你完全不确定,使用本书随附文件中的ch02文件夹中的phpversion.php

  3. 像上传 HTML 页面一样上传phpversion.php到你的网站,然后在浏览器中输入 URL。假设您将文件上传到站点的顶层,URL 将类似于 www.example.com/phpversion.php

<?php echo phpversion();

如果您看到屏幕上显示一个由三部分组成的数字,如 8.0.3,那么您就成功了:PHP 已启用。这个数字告诉你你的服务器上运行的是哪个版本的 PHP。

  1. 如果您收到类似“解析错误”的消息,这意味着 PHP 是受支持的,但您在文件中键入代码时出错了。请使用ch02文件夹中的版本。

  2. 如果只是看到原代码,说明不支持 PHP。

Caution

本书中的代码使用了 PHP 8 的新特性。如果您的 web 服务器运行的是旧版本的 PHP,本书中描述的许多技术将无法工作。

决定在哪里测试你的页面

与普通网页不同,你不能在 Mac 上的 Windows 文件资源管理器或 Finder 中双击 PHP 页面,然后在浏览器中查看它们。它们需要通过支持 PHP 的网络服务器进行解析或处理。如果你的托管公司支持 PHP,你可以上传你的文件到你的网站并在那里测试。但是,每次进行更改时,您都需要上传文件。在早期,您会发现由于代码中的一个小错误,您不得不经常这样做。随着你越来越有经验,你仍然需要经常上传文件,因为你会想尝试不同的想法。

使用本地测试环境是用 PHP 开发的最有效的方式。本章的其余部分将向你展示如何做到这一点,包括对 Windows 和 macOS 的说明。

本地测试环境需要什么

要在本地计算机上测试 PHP 页面,您需要安装以下软件:

  • 网络服务器,它是一个显示网页的软件,而不是一台独立的计算机

  • 服务器端编程语言(Professional Hypertext Preprocessor 的缩写)

  • 一个 MySQL 或 MariaDB 数据库和 phpMyAdmin,一个用于管理数据库的基于 web 的前端

Tip

MariaDB ( https://mariadb.org/ )是一个由社区开发的替代 MySQL 的插件。本书中的代码完全兼容 MySQL 和 MariaDB。

你需要的所有软件都是免费的。对你来说,唯一的代价是下载必要的文件所花费的时间,当然,还有确保一切设置正确的时间。在大多数情况下,您应该在不到一个小时的时间内启动并运行,可能更短。只要你有至少 1 GB 的空闲磁盘空间,你应该能够在你的计算机上安装所有的软件——即使是一个普通的软件。

Tip

如果您的本地计算机上已经有了 PHP 8 测试环境,就没有必要重新安装。只需查看本章末尾的“检查 PHP 设置”一节。

设置测试环境最简单的方法是使用一个包,在一个操作中安装 Apache、PHP、MySQL(或 MariaDB)和 phpMyAdmin。在我的电脑上,我用 XAMPP 操作系统( www.apachefriends.org/index.html )和 MAMP 操作系统( www.mamp.info/en/ )。还提供其他软件包;你选择哪一个都没关系。

在 Windows 上设置

在继续之前,请确保您以管理员身份登录。

让窗口显示文件扩展名

默认情况下,大多数 Windows 计算机隐藏常见的三或四个字母的文件扩展名,如.doc.html,所以你在对话框和 Windows 文件资源管理器中看到的都是thisfile,而不是thisfile.docthisfile.html

使用以下说明在 Windows 10 中启用文件扩展名显示:

  1. 打开文件资源管理器(Windows 键+ E)。

  2. 选择“查看”以展开文件资源管理器窗口顶部的功能区。

  3. 选择“文件扩展名”复选框。

显示文件扩展名更加安全——您可以判断病毒作者是否在看似无害的文档中附加了.exe.scr可执行文件。

选择 Web 服务器

大多数 PHP 安装运行在 Apache web 服务器上。两者都是开源的,可以很好地协同工作。但是,Windows 有自己的 web 服务器,Internet 信息服务(IIS),它也支持 PHP。微软与 PHP 开发团队密切合作,将 PHP 在 IIS 上的性能提高到与 Apache 大致相同的水平。那么应该选择哪个呢?

除非您需要 ASP 或 ASP.NET 的 IIS,否则我建议您使用 XAMPP 或其他一体化软件包安装 Apache,如下一节所述。如果需要使用 IIS,可以从 https://php.iis.net/ 安装 PHP。

在 Windows 上安装一体化软件包

有两个流行的 Windows 软件包可以在一次操作中在你的电脑上安装 Apache、PHP、MySQL 或 MariaDB、phpMyAdmin 和其他几个工具:XAMPP ( www.apachefriends.org/index.html )和 EasyPHP ( www.easyphp.org )。安装过程通常只需几分钟。安装完软件包后,您可能需要更改一些设置,这将在本章后面解释。

在印刷书籍的生命周期中,版本很容易改变,所以我不会描述安装过程。每个包装的网站上都有说明。

在 macOS 上设置

Apache web 服务器和 PHP 预装在 macOS 上,但默认情况下它们是不启用的。我建议您不要使用预装的版本,而是使用 MAMP,它可以在一次操作中安装 Apache、PHP、MySQL、phpMyAdmin 和其他几个工具。

为了避免与预装的 Apache 和 PHP 版本冲突,MAMP 将所有应用放在硬盘上的专用文件夹中。如果您决定不再需要电脑上的 MAMP,只需将 MAMP 文件夹拖到废纸篓,就可以轻松卸载所有内容。

安装 MAMP

开始之前,请确定您已使用管理权限登录到电脑:

  1. 前往 www.mamp.info/en/downloads/ ,选择 MAMP & MAMP PRO 的链接。这会下载一个磁盘映像,其中包含免费和付费版本的 MAMP。

  2. 下载完成后,启动磁盘映像。您将看到一份许可协议。您必须点按“同意”以继续装载磁盘映像。

  3. 按照屏幕上的说明进行操作。

  4. 验证 MAMP 已安装在您的应用文件夹中。

Note

MAMP 自动将免费版和付费版安装在分别名为MAMPMAMP PRO的文件夹中。付费版本使得配置 PHP 和使用虚拟主机更加容易,但是免费版本已经足够了,尤其是对于初学者。如果你想删除MAMP PRO文件夹,不要把它拖到垃圾桶里。打开文件夹,双击MAMP PRO卸载图标。付费版本需要这两个文件夹。

测试和配置 MAMP

默认情况下,MAMP 为 Apache 和 MySQL 使用非标准端口。除非您使用 Apache 和 MySQL 的多个安装,否则请按照以下步骤更改端口设置:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1

在 MAMP 控制面板中选择 PHP 版本

  1. 双击应用/MAMP 中的 MAMP 图标。如果您看到一个面板,邀请您了解有关标准视图的更多信息,这是仅在付费版本中新增的功能。要防止每次启动 MAMP 时都显示该面板,请取消选择面板左下角的复选框。然后单击左上角的关闭按钮关闭面板。

  2. 在 MAMP 控制面板中,将 PHP 版本下拉菜单设置为 8.0.2 或更高版本(参见图 2-1 )。如果你得到一个警告,说你的站点在 PHP 8 中可能没有预期的行为,点击 OK。通过选中复选框,可以防止此警告再次出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2

更改 Apache 和 MySQL 端口

  1. 单击 MAMP 控制面板右上角的开始图标。您的默认浏览器最终会启动并向您显示 MAMP 欢迎页面。

  2. 如果您的浏览器没有自动启动,请单击 MAMP 控制面板顶部的 WebStart 图标。

  3. 检查浏览器地址栏中的 URL。从localhost:8888开始。:8888表示 Apache 正在监听非标准端口 8888 上的请求。

  4. 最小化浏览器,然后单击 MAMP 控制面板左上角的首选项图标。

  5. 在打开的面板顶部选择端口。显示 Apache 和 MySQL 运行在端口 8888 和 8889 上(见图 2-2 )。

  6. 如图 2-2 所示,点击 80 & 3306 按钮,将 web 和 MySQL 端口更改为标准值:Apache 为 80,MySQL 为 3306。

  7. 当提示重新启动服务器时,点按“好”并输入您的 Mac 密码。

    提示如果任何其他程序正在使用端口 80,Apache 将不会重新启动。如果您找不到阻止 Apache 使用端口 80 的原因,请打开 MAMP 偏好设置面板,然后单击 MAMP 默认按钮。然后再次单击确定。

  8. 当服务器再次启动时,单击 MAMP 控制面板中的 WebStart 按钮,将欢迎页面加载到浏览器中。这一次,URL 不应该有一个冒号,后跟一个出现在localhost后面的数字,因为 Apache 现在正在监听默认端口。

在哪里可以找到你的 PHP 文件(Windows 和 Mac)

您需要在 web 服务器可以处理文件的位置创建文件。通常,这意味着文件应该位于服务器的文档根目录或文档根目录的子文件夹中。对于最常见的设置,文档根目录的默认位置如下:

  • XAMPP : C:\xampp\htdocs

  • EasyPHP : C:\EasyPHP\www

  • IISC:\inetpub\wwwroot

  • MAMP : /Applications/MAMP/htdocs

要查看 PHP 页面,您需要使用 URL 将其加载到浏览器中。在您的本地测试环境中,web 服务器的文档根的 URL 是http://localhost/

Caution

如果您需要将 MAMP 重置回其默认端口,您将需要使用http://localhost:8888而不是http://localhost

如果您将这本书的文件存储在名为php8sols的文档根目录的子文件夹中,URL 是http://localhost/php8sols/,后跟文件夹(如果有)和文件的名称。

Tip

如果您对http://localhost/有疑问,请使用http://127.0.0.1/127.0.0.1是所有计算机用来指代本地机器的环回 IP 地址。

检查你的 PHP 设置

安装 PHP 后,最好检查一下它的配置设置。除了核心特性,PHP 还有大量可选的扩展。一体化软件包安装了这本书所需的所有扩展。但是,一些基本配置设置可能会略有不同。为避免意外问题,请调整您的配置,以匹配以下页面中推荐的设置。

用 phpinfo()显示服务器配置

PHP 有一个内置命令phpinfo(),显示 PHP 在服务器上如何配置的细节。由phpinfo()产生的大量细节可能让人感觉像是大量信息过载,但是它对于确定为什么某些东西在您的本地计算机上完美地工作而在您的实时网站上却不工作是无价的。问题通常在于远程服务器禁用了某个功能或者没有安装可选的扩展。

一体式软件包使运行phpinfo()变得简单:

  • XAMPP :点击 XAMPP 控制面板中的 Apache 管理按钮,在浏览器中启动 XAMPP 欢迎页面。然后点击页面顶部的 PHPInfo 按钮。

  • MAMP:向下滚动到 MAMP 欢迎页面的 PHP 部分,点击 phpinfo 链接。

或者,创建一个简单的测试文件,并按照以下说明将其加载到您的浏览器中:

  1. 确保 Apache 或 IIS 正在本地计算机上运行。

  2. 在脚本编辑器中键入以下内容:

<?php phpinfo();

文件里应该没别的了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3

运行phpinfo()命令显示 PHP 配置的全部细节。

  1. 将文件保存为服务器文档根目录中的phpinfo.php(参见本章前面的“在哪里定位您的 PHP 文件(Windows 和 Mac)”。

    小心确保你的编辑器没有在.php后添加.txt.rtf扩展名。

  2. 在浏览器地址栏中键入http://localhost/phpinfo.php,然后按回车键。

  3. 您应该会看到一个类似于图 2-3 的页面,显示 PHP 的版本,后面是您的 PHP 配置的详细信息。

  4. 记下加载的配置文件项目的值。这告诉你在哪里可以找到php.ini,你需要编辑这个文本文件来改变 PHP 中的大多数设置。

  5. 向下滚动到标记为 Core 的部分,将设置与表 2-1 中推荐的设置进行比较。记下任何差异,以便您可以按照本章后面的描述进行更改。

    表 2-1

    推荐的 PHP 配置设置

    |

    管理的

    |

    本地值

    |

    评论

    |
    | — | — | — |
    | display_errors | 在 | 对于调试脚本中的错误至关重要。如果设置为 Off,错误会导致完全空白的屏幕,让您对可能的原因一无所知。 |
    | error_reporting | Thirty-two thousand seven hundred and sixty-seven | 这将错误报告设置为最高级别。 |
    | file_uploads | 在 | 允许您使用 PHP 将文件上传到网站。 |
    | log_errors | 离开 | 当display_errors设置为 On 时,您不需要用错误日志来填充硬盘。 |

  6. 配置页面的其余部分显示了启用了哪些 PHP 扩展。虽然页面看起来会一直延续下去,但是扩展都是按字母顺序排列的。要使用这本书,请确保启用了以下扩展:

    • gd :使 PHP 能够生成和修改图像和字体。

      注意如果您的测试环境运行在 Windows 上,并且没有列出gd扩展,您可以按照下一节中的说明轻松打开它。

    • MySQL:连接到 MySQL/MariaDB。(请注意“I”,它代表“改进的”。从 PHP 7 开始,不再支持旧的mysql版本。)

    • PDO:为数据库提供软件中立的支持(可选)。

    • pdo_mysql :连接 MySQL/MariaDB 的替代方法(可选)。

    • 会话:会话维护与用户相关的信息,并用于用户认证等。

您还应该在远程服务器上运行phpinfo()来检查哪些特性被启用了。如果不支持列出的扩展名,当您将文件上传到网站时,本书中的某些代码将无法工作。如果PDOpdo_mysql没有列出,可以用mysqli代替。

Caution

phpinfo()显示的输出揭示了大量信息,恶意黑客可以利用这些信息来攻击您的网站。检查配置后,请务必从远程服务器上删除该文件。

如果您的设置中有任何设置与这些建议不同,您将需要编辑 PHP 配置文件php.ini,如下一节所述。

编辑 php.ini

PHP 配置文件php.ini是一个非常长的文件,这往往会让编程新手感到紧张,但没什么好担心的。它是用纯文本编写的,其长度的一个原因是它包含大量解释各种选项的注释。也就是说,在编辑php.ini之前做个备份是个好主意,以防你出错。

如何打开php.ini取决于您的操作系统和 PHP 的安装方式:

  • 如果您在 Windows 上使用了一体化软件包,例如 XAMPP,请在 Windows 文件资源管理器中双击php.ini。该文件将在记事本中自动打开。

  • 如果您在 IIS 上安装了 PHP,php.ini通常位于 Program Files 的子文件夹中。虽然您可以通过双击打开php.ini,但是您将无法保存您所做的任何更改。相反,右键单击记事本并选择以管理员身份运行。在记事本中,选择文件➤打开,并设置选项显示所有文件().导航到php.ini所在的文件夹,选择文件,点击打开。

  • 在 macOS 上,使用纯文本编辑器打开php.ini。如果您使用“文本编辑”,请确定它将文件存储为纯文本格式,而不是多信息文本格式。

以分号(;)开头的行是注释。除了在 Windows 上打开gd扩展名,需要编辑的行不用分号开头。

使用您的文本编辑器的查找功能找到您需要更改的指令,以匹配表 2-1 中的建议。大多数指令前面都有一个或多个应该如何设置的示例。确保你没有错误地编辑其中一个被评论的例子。

对于使用OnOff的指令,只需将值更改为推荐值。例如,如果需要打开错误消息的显示,请编辑这一行

display_errors = Off

通过将其更改为:

display_errors = On

要设置错误报告的级别,您需要使用 PHP 常量,这些常量以大写形式编写,并且区分大小写。该指令应该如下所示:

error_reporting = E_ALL

如果您的测试环境运行在 Windows 上,并且在运行phpinfo()时没有列出gd扩展,那么在php.ini中找到下面一行:

;extension=gd

删除该行开头的分号。

Note

在 macOS 和 Linux 上,PHP 通常需要在启用 gd 扩展的情况下进行编译,所以这个简单的修复不起作用。查看您下载 PHP 安装的网站,了解任何可用的选项。

编辑php.ini后,保存文件,然后重启 Apache 或 IIS,使更改生效。如果 web 服务器无法启动,请检查服务器的错误日志文件。可以在以下位置找到它:

  • XAMPP :在 XAMPP 控制面板,点击 Apache 旁边的日志按钮,然后选择 Apache (error.log)。

  • MAMP :在/Applications/MAMP/logs 中,双击 apache_error.log 在控制台中打开。

  • EasyPHP :右击系统托盘中的 EasyPHP 图标,选择日志文件➤ Apache。

  • IIS :日志文件的默认位置是 C:\inetpub\logs。

错误日志中的最新条目应该会告诉您是什么阻止了服务器重新启动。使用该信息来更正您对php.ini所做的更改。如果这不起作用,感谢你在编辑之前备份了php.ini。重新开始,仔细检查你的编辑。

下一步是什么?

现在,您已经有了一个 PHP 测试平台,毫无疑问,您已经迫不及待了。我最不想做的事情就是挫伤任何热情,但是在实际的网站中使用 PHP 之前,您应该对这门语言的规则有一个基本的了解。所以,在开始做这些有趣的事情之前,请阅读下一章,它解释了如何编写 PHP 脚本。即使你有丰富的 PHP 经验,也一定要查看 PHP 8 中的变化部分。

三、如何编写 PHP 脚本

这一章提供了 PHP 工作原理的快速概述,并给出了基本规则。它主要针对那些以前没有 PHP 或编码经验的读者。即使你以前使用过 PHP,也要检查一下主要的标题,看看这一章包含了什么,并复习一下你不太清楚的方面的知识。

本章涵盖

  • 理解 PHP 的结构

  • 在网页中嵌入 PHP

  • 将数据存储在变量和数组中

  • 让 PHP 做决定

  • 循环重复的任务

  • 使用预设任务的功能

  • 显示 PHP 输出

  • 理解 PHP 错误消息

PHP:大图

乍一看,PHP 代码可能看起来很吓人,但是一旦你理解了基础,你会发现结构非常简单。如果你使用过其他计算机语言,比如 JavaScript 或 jQuery,你会发现它们有很多共同点。

每个 PHP 页面必须具有以下内容:

  • 正确的文件扩展名,通常是.php

  • 每个 PHP 代码块周围的 PHP 标记(如果文件只包含 PHP 代码,通常省略结束 PHP 标记)

典型的 PHP 页面将使用以下部分或全部元素:

  • 变量作为未知或变化值的占位符

  • 用于保存多个值的数组

  • 做出决策的条件语句

  • 执行重复任务的循环

  • 执行预设任务的功能或对象

让我们依次快速浏览一下,从文件名和开始、结束标记开始。

告诉服务器处理 PHP

PHP 是一种服务器端语言。web 服务器——通常是 Apache——处理您的 PHP 代码,并只将结果(通常是 HTML 格式)发送给浏览器。因为所有的操作都在服务器上进行,所以您需要告诉它您的页面包含 PHP 代码。这包括两个简单的步骤:

  • 给每个页面一个 PHP 文件扩展名;默认为.php。只有当你的主机公司明确要求你使用不同的扩展名时,才使用不同的扩展名。

  • 用 PHP 标签识别所有的 PHP 代码。

开始标签是<?php,结束标签是?>。如果你把标签和周围的代码放在同一行,那么在开始标签之前或者结束标签之后不需要有空格,但是在开始标签中的php之后必须有一个空格,就像这样:

<p>This is HTML with embedded PHP<?php //some PHP code ?>.</p>

当插入多行 PHP 代码时,为了清晰起见,最好将开始和结束标记放在不同的行上:

<?php
// some PHP code
// more PHP code
?>

你可能会遇到<?作为开始标签的另一个简短版本。但是,<?并未在所有服务器上启用。坚持使用保证有效的<?php

当一个文件只包含 PHP 代码时,强烈建议省略结束 PHP 标签。这避免了使用包含文件时的潜在问题(参见第五章)。

Note

为了节省空间,本书中的大多数例子都省略了 PHP 标签。当您编写自己的脚本或将 PHP 嵌入到网页中时,您必须始终使用它们。

在网页中嵌入 PHP

PHP 可以作为一种嵌入式语言。这意味着您可以在普通网页中插入 PHP 代码块。当有人访问您的站点并请求一个 PHP 页面时,服务器会将其发送到 PHP 引擎,该引擎会从上到下读取页面,寻找 PHP 标签。HTML 和 JavaScript 原封不动地通过,但是每当 PHP 引擎遇到一个<?php标签时,它就开始处理您的代码,并继续处理,直到到达结束的?>标签(或者如果 PHP 代码后面没有任何内容,则到达脚本的结尾)。如果 PHP 产生了任何输出,它就在那个点被插入。

Tip

一个页面可以有多个 PHP 代码块,但是它们不能嵌套在一起。

图 3-1 显示了嵌入在普通网页中的一段 PHP 代码,以及它通过 PHP 引擎后在浏览器和页面源代码视图中的样子。该代码计算当前年份,检查它是否不同于固定年份(在图左侧代码的第 26 行用$startYear表示),并在版权声明中显示适当的年份范围。从图右下方的页面源代码视图中可以看到,发送到浏览器的内容中没有 PHP 的痕迹。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-1

PHP 代码保留在服务器上;只有输出被发送到浏览器

Tip

PHP 并不总是为浏览器产生直接输出。例如,它可以在发送电子邮件或将信息插入数据库之前检查表单输入的内容。因此,一些代码块被放在主 HTML 代码的上面或下面,或者放在外部文件中。但是,产生直接输出的代码会出现在您希望显示输出的地方。

将 PHP 存储在外部文件中

除了在 HTML 中嵌入 PHP,通常的做法是将常用代码存储在单独的文件中。当一个文件只包含 PHP 代码时,开始的<?php标签是强制的,但是结束的?>标签是可选的。事实上,推荐的做法是省去结束的 PHP 标签。然而,如果外部文件在 PHP 代码后包含 HTML,你必须使用结束?>标签。

使用变量来表示变化的值

图 3-1 中的代码可能看起来像是一种非常冗长的显示一年或一系列年份的方式。但是从长远来看,PHP 解决方案可以节省您的时间。你不需要每年更新版权声明,PHP 代码会自动更新。你写一次代码就忘了。更重要的是,正如你将在第五章中看到的,如果你将代码存储在一个外部文件中,对外部文件的任何更改都会反映在你网站的每一页上。

这种自动显示年份的能力依赖于 PHP 的两个关键方面:变量函数。顾名思义,函数做事情;它们执行预设的任务,例如获取当前日期并将其转换为人类可读的形式。稍后我将介绍函数,所以让我们先研究变量。图 3-1 中的脚本包含两个变量:$startYear$thisYear

Tip

一个变量仅仅是你给一个可能改变或者你事先不知道的东西起的名字。PHP 中的变量总是以$(一个美元符号)开头。

我们在日常生活中无时无刻不在使用变量,而不去思考它。当你第一次见到某人时,你可以问“你叫什么名字?”这个人是叫汤姆、迪克还是哈里特并不重要;“名”字不变。同样,你的银行账户也是,钱一直在进进出出(看起来大部分是出了),但是如图 3-2 所示,无论你是穷困潦倒还是腰缠万贯都没关系。可用的金额总是被称为余额。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2

你的银行对账单上的余额是一个变量的日常例子——标签保持不变,尽管值可能每天都在变化

所以“名字”和“余额”是日常变量。只要在它们前面放一个美元符号,就有了两个现成的 PHP 变量,就像这样:

$name
$balance

很简单。

命名变量

只要记住以下规则,您可以随意命名变量:

  • 变量总是以美元符号($)开头。

  • 有效字符包括字母、数字和下划线。

  • 美元符号后的第一个字符必须是字母或下划线(_)。

  • 除下划线外,不允许有空格或标点符号。

  • 变量名区分大小写:$startYear$startyear不一样。

给变量命名时,选择能告诉你它的用途的东西。到目前为止,你所看到的变量——$startYear$thisYear$name$balance——就是很好的例子。在组合单词时,最好将第二个或后续单词的第一个字母大写(有时称为 camel case )。或者,您可以使用下划线($start_year$this_year等)。).

Tip

西欧语言中常用的重音字符在变量中是有效的。比如$prénom$förnamn都是可以接受的。在实践中,您也可以在变量名中使用其他字母,如西里尔字母和非字母文字,如日本汉字;但是在撰写本文时,这种使用还没有文档记录,所以我建议坚持前面的规则。

不要试图通过使用非常短的变量来节省时间。使用$sy$ty$n$b而不是更具描述性的会让代码更难理解——这也让代码更难写。更重要的是,这使得错误更难发现。和往常一样,规则也有例外。按照惯例,$i$j$k经常用于记录循环运行的次数,而$e$t用于错误检查。在本章的后面你会看到这些例子。

Caution

虽然你在变量名的选择上有相当大的自由度,但是不能用$this,因为它在 PHP 面向对象编程中有特殊的含义。建议避免 www.php.net/manual/en/reserved.php 中列出的任何关键词。

给变量赋值

变量从各种来源获取值,包括:

  • 通过在线表单的用户输入

  • 一个数据库

  • 外部源,如新闻源或 XML 文件

  • 计算的结果

  • PHP 代码中的直接赋值

无论该值来自何处,通常都会被赋予一个等号(=),如下所示:

$variable = value;

变量在等号的左边,值在右边。因为等号赋值,所以称之为赋值运算符

Caution

从小对等号的熟悉,让我们很难走出认为它表示“等于”的习惯。然而,PHP 使用两个等号(==)来表示相等。这是初学者犯错误的主要原因,而且有时也会让更有经验的开发人员犯错误。===之间的区别将在本章后面详细介绍。

以分号结束命令

PHP 被写成一系列的命令或语句。每个语句通常告诉 PHP 引擎执行一个操作,并且它后面必须总是跟一个分号,就像这样:

<?php
do this;
now do something else;
?>

与所有规则一样,有一个例外:您可以省略代码块中最后一条语句后的分号。然而,不要这样做,除非使用短的echo标签,如本章后面所述。与 JavaScript 不同,PHP 不会假定如果你省略分号,行尾就应该有分号。这有一个很好的副作用:你可以将长语句分散在几行中,以便于阅读。PHP 和 HTML 一样,忽略代码中的空白。相反,它依靠分号来指示一个命令的结束位置和下一个命令的开始位置。

Tip

缺少分号会让你的脚本嘎然而止。

注释脚本

PHP 将所有东西都视为要执行的语句,除非您将一段代码标记为注释。以下三个原因解释了您为什么想要这样做:

  • 插入脚本功能的提醒

  • 为以后添加的代码插入占位符

  • 暂时禁用一段代码

当您对脚本记忆犹新时,似乎没有必要插入任何不会被处理的内容。然而,如果您需要在几个月后修改脚本,您会发现注释比试图单独遵循代码更容易阅读。当你在团队中工作时,评论也是至关重要的。他们帮助你的同事理解代码的意图。

在测试过程中,阻止一行代码,甚至整个部分运行通常是有用的。PHP 忽略任何标记为注释的东西,所以这是一种打开和关闭代码的有用方法。

添加注释有三种方式:两种用于单行注释,一种用于跨越多行的注释。

单行注释

最常见的单行注释以两个正斜杠开头,如下所示:

// this is a comment and will be ignored by the PHP engine

PHP 忽略从双斜线到行尾的所有内容,所以您也可以在代码旁边放置注释(但只能放在右边):

$startYear = 2018; // this is a valid comment

注释不是 PHP 语句,所以它们不以分号结尾。但是不要忘记 PHP 语句末尾的分号,它和注释在同一行。

另一种样式使用散列或井号(#),如下所示:

# this is another type of comment that will be ignored by the PHP engine
$startYear = 2018; # this also works as a comment

这种注释风格通常表示较长脚本的各个部分,如下所示:

##################
## Menu section ##
##################

Caution

PHP 8 使用#[作为一个名为属性的新特性的开始语法,该特性为类、函数和一些其他特性的声明提供元数据(参见 www.php.net/manual/en/language.attributes.overview.php )。如果你在#后面使用一个方括号作为注释,PHP 8 将会产生一个解析错误。

多行注释

对于跨越几行的注释,使用与级联样式表(CSS)和 JavaScript 相同的样式。/**/之间的任何内容都被视为注释,如下所示:

/* This is a comment that stretches
   over several lines. It uses the same
   beginning and end markers as in CSS. */

多行注释在测试或故障排除时特别有用,因为它们可以用来禁用脚本的长部分,而无需删除它们。

Tip

好的注释和精心选择的变量名使代码更容易理解和维护。

使用数组存储多个值

PHP 允许你在一个特殊类型的变量中存储多个值,这个变量被称为一个数组。一种简单的思考数组的方式是,它们就像一个购物清单。虽然每个项目可能不同,但您可以用一个名称来统称它们。图 3-3 展示了这个概念:变量$shoppingList指的是所有五个项目——酒、鱼、面包、葡萄和奶酪。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-3

数组是存储多个项目的变量,就像购物清单一样

单个项目——或数组元素——通过紧跟在变量名后面的方括号中的数字来标识。PHP 自动分配编号,但是需要注意的是编号总是从 0 开始。所以数组中的第一项,在我们的例子中是葡萄酒,被称为$shoppingList[0],而不是$shoppingList[1]。虽然有五个项目,但最后一个(奶酪)是$shoppingList[4]。这个数字被称为数组索引,这种类型的数组被称为索引数组

Caution

PHP 8 改变了自动编号的工作方式。如果你创建一个数组,第一个键是负数(如第四章所述),后续的键将在前一个数上加 1。在 PHP 8 之前,负数之后的后续键总是从零开始。

PHP 使用另一种类型的数组,其中的关键字是一个单词(或者是字母和数字的任意组合)。例如,包含这本书的详细信息的数组可能如下所示:

$book['title'] = 'PHP 8 Solutions: Dynamic Web Design and Development Made Easy';
$book['author'] = 'David Powers';
$book['publisher'] = 'Apress';

这种类型的数组称为关联数组。注意数组键是用引号括起来的(单引号还是双引号,无所谓)。

数组是 PHP 重要而有用的一部分。你会经常用到它们,从第五章开始,你将在一个数组中存储图像的细节,以在网页上显示一个随机的图像。当您在一系列数组中获取搜索结果时,数组也广泛用于数据库。

Note

你可以在第四章中学习创建数组的各种方法。

PHP 内置的超级全局数组

PHP 有几个内置的数组,可以自动填充有用的信息。它们被称为超全局数组,通常以美元符号后跟下划线开始。唯一的例外是$GLOBALS,它包含对脚本的全局作用域中所有变量的引用(参见第四章中的“变量作用域:作为黑盒的功能”了解作用域的描述)。

你会经常看到的两个超级全局变量是$_POST$_GET。它们包含分别通过超文本传输协议(HTTP) postget方法从表单传递的信息。超全局变量都是关联数组,$_POST$_GET的键是从 URL 末尾的查询字符串中的表单元素或变量的名称自动派生出来的。

假设您在一个表单中有一个名为“address”的文本输入字段;当表单通过post方法提交时,PHP 自动创建一个名为$_POST['address']的数组元素,或者如果使用get方法,PHP 自动创建一个名为$_GET['address']的数组元素。如图 3-4 所示,$_POST['address']包含了访问者在文本字段中输入的任何值,使你能够在屏幕上显示它,将其插入数据库,发送到你的电子邮箱,或者做任何你想做的事情。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-4

您可以通过$_POST数组检索用户输入的值,该数组是在使用post方法提交表单时自动创建的

当您通过电子邮件将在线反馈表的内容发送到您的收件箱时,您将使用第六章中的$_POST数组。你将在本书中使用的其他超全局数组是$_SERVER,在第 5 、 14 和 15 中从 web 服务器获取信息,在第 8 和$_SESSION章中上传文件到你的网站,在第 11 和 19 中创建一个简单的登录系统。

Caution

不要忘记 PHP 中的变量名是区分大小写的。所有超全局数组名都是大写的。比如$_Post或者$_Get就不行。

了解何时使用引号

如果你仔细观察图 3-1 中的 PHP 代码块,你会注意到赋给第一个变量的值没有用引号括起来。看起来是这样的:

$startYear = 2021;

然而“使用数组存储多个值”中的所有例子都使用了引号,就像这样:

$book['title'] = 'PHP 8 Solutions: Dynamic Web Design and Development Made Easy';

简单的规则如下:

  • 数字:无引号

  • 文本:需要引号

一般来说,不管是用单引号还是双引号括住文本还是用字符串括住文本,都没有关系,因为文本是在 PHP 和其他计算机语言中调用的。情况有点复杂,如第四章中所解释的,因为 PHP 引擎处理单引号和双引号的方式有细微的差别。

Note

“字符串”这个词是从计算机和数学科学中借来的,它的意思是一系列简单的对象——在这里是文本中的字符。

引号必须总是成对出现,所以在单引号字符串中包含撇号或者在双引号字符串中包含双引号时需要小心。检查以下代码行:

$book['description'] = 'This is David's latest book on PHP.';

乍一看,似乎没什么毛病。然而,PHP 引擎看到的东西与人眼不同,如图 3-5 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-5

单引号字符串中的撇号会使 PHP 引擎混淆

有两种方法可以解决这个问题:

  • 如果文本包含撇号,请使用双引号。

  • 在撇号前加一个反斜杠(这被称为转义)。

因此,以下两种情况都是可以接受的:

$book['description'] = "This is David's latest book on PHP.";
$book['description'] = 'This is David\'s latest book on PHP.';

这同样适用于双引号字符串中的双引号(尽管规则相反)。以下代码导致了一个问题:

$play = "Shakespeare's "Macbeth"";

在这种情况下,撇号没问题,因为它与双引号不冲突,但是 Macbeth 前面的左引号使字符串过早结束。要解决该问题,以下任一方法都是可以接受的:

$play = 'Shakespeare\'s "Macbeth"';
$play = "Shakespeare's \"Macbeth\"";

在第一个示例中,整个字符串都用单引号括起来。这绕过了麦克白的双引号问题,但是引入了对 的莎士比亚的 中的撇号进行转义的需要。撇号在双引号字符串中没有问题,但是麦克白两边的双引号都需要转义。所以,总结一下

  • 单引号和撇号在双引号字符串中是可以的。

  • 双引号可以出现在单引号字符串中。

  • 其他任何内容都必须用反斜杠进行转义。

Tip

大多数情况下使用单引号,保留双引号用于有特殊含义的情况,如第四章所述。

特殊情况:真、假和空

尽管文本应该用引号括起来,但是三个关键字——truefalsenull——永远不应该用引号括起来,除非您想将它们视为字符串。前两个意味着你所期望的;null表示“不存在的价值”

Note

Truefalse被称为布尔值。它们是以 19 世纪数学家乔治·布尔的名字命名的,他的逻辑运算系统成为许多现代计算的基础。

正如下一节所解释的,PHP 根据某个东西是等于true还是false来做出决定。给false加上引号会产生意想不到的后果。考虑以下代码:

$OK = 'false';

这与你可能期望的正好相反:它使$OK成为真实!为什么呢?因为false周围的引号把它变成了一个字符串,而 PHP 把字符串当成了true。(在第四章的“PHP 真相”中有更详细的解释。)

关键字truefalsenull不区分大小写。下面的例子都是有效的:

$OK = TRUE;
$OK = tRuE;
$OK = true;

因此,概括一下,PHP 将truefalsenull视为特例:

  • 不要用引号将它们括起来。

  • 它们不区分大小写。

做决定

决定,决定,决定…生活充满了决定。PHP 也是。它们使它能够根据情况改变输出。PHP 中的决策使用条件语句。这些用法中最常见的是if,它严格遵循正常语言的结构。在现实生活中,你可能会面临以下决定(诚然,在英国不太常见):如果天气热,我会去海滩。

在 PHP 伪代码中,相同的决定如下所示:

if (the weather's hot) {
    I'll go to the beach;
}

被测试的条件放在括号内,结果动作放在花括号内。这是基本的决策模式:

if (condition is true) {
    // code to be executed if condition is true
}

Tip

条件语句是控制结构,后面没有分号。花括号将一个或多个单独的语句放在一起,这些语句将作为一个组来执行。

如果条件是true,花括号内的代码只在执行*。如果是false,PHP 将忽略括号之间的所有内容,并继续下一段代码。PHP 如何确定条件是true还是false将在下一节描述。*

有时,if语句就是您所需要的,但是如果条件不满足,您通常希望调用默认操作。为此,使用else,就像这样:

if (condition is true) {
    // code to be executed if condition is true
} else {
    // default code to run if condition is false
}

如果你想要更多的选择,你可以像这样添加更多的条件语句:

if (condition is true) {
    // code to be executed if condition is true
} else {
    // default code to run if condition is false
}
if (second condition is true) {
    // code to be executed if second condition is true
} else {
    // default code to run if second condition is false
}

在这种情况下,两个条件语句都将运行。如果您只想执行一个代码块,可以像这样使用elseif:

if (condition is true) {
    // code to be executed if first condition is true
} elseif (second condition is true) {
    // code to be executed if first condition fails
    // but second condition is true
} else {
    // default code if both conditions are false
}

您可以在条件语句中使用任意多的elseif子句。*只执行第一个等于真的条件;所有其他的都将被忽略,即使它们也是真的。*这意味着你需要按照你希望它们被评估的优先顺序来构建条件语句。这是严格的先来先服务的等级制度。

Note

虽然elseif通常被写成一个单词,但是你也可以把else if作为单独的单词。

进行比较

条件语句只对一件事感兴趣:被测试的条件是否等于true。如果不是true,那一定是false。没有折衷或可能的余地。条件通常取决于两个值的比较。这个比那个大吗?它们都一样吗?诸如此类。

为了测试相等性,PHP 使用两个等号(==),如下所示:

if ($status == 'administrator') {
    // send to admin page
} else {
    // refuse entry to admin area
}

Caution

在第一行使用一个等号($status = 'administrator')向所有人打开你网站的管理区。为什么呢?因为这样会自动将$status的值设置为administrator;它不会比较这两个值。这是一个常见的错误,但可能会带来灾难性的后果。要比较值,必须使用两个等号。一个更强大的比较运算符使用三个等号;在第四章中有描述。

使用小于(<)和大于(>)的数学符号进行数值比较。假设您在允许文件上传到您的服务器之前检查文件的大小。您可以将最大大小设置为 50 KB,如下所示(1 千字节= 1024 字节):

if ($bytes > 51200) {
    // display error message and abandon upload
} else {
    // continue upload
}

Note

第四章描述了如何同时测试多个条件。

为了清晰起见,使用缩进和空白

缩进代码有助于将语句放在逻辑组中,从而更容易理解脚本的流程。PHP 忽略代码中的任何空白,所以你可以采用任何你喜欢的风格。保持一致,这样你就能发现任何看起来不合适的地方。

大多数人发现缩进四到五个空格有助于提高代码的可读性。也许风格上最大的区别在于花括号的位置。通常将左大括号放在与前面代码相同的行上,右大括号放在代码块后的新行上,如下所示:

if ($bytes > 51200) {
    // display error message and abandon upload
} else {
    // continue upload
}

然而,其他人更喜欢这种风格:

if ($bytes > 51200)
{
    // display error message and abandon upload
}
else
{
    // continue upload
}

风格并不重要。重要的是你的代码是一致的,易读的。

对重复性任务使用循环

循环可以节省大量时间,因为它们一遍又一遍地执行相同的任务,而只涉及很少的代码。它们经常用于数组和数据库结果。您可以逐一查看每个项目,查找匹配项或执行特定任务。循环在与条件语句结合使用时特别强大,允许您在一次扫描中对大量数据选择性地执行操作。在真实的环境中使用循环可以更好地理解它们。所有循环结构的细节和示例在第四章中。

使用预设任务的功能

函数做事情……很多事情,令人难以置信的是在 PHP 中。典型的 PHP 设置允许您访问数千个内置函数。您只需要使用少数几种,但是知道 PHP 是一种全功能语言是令人放心的。

您将在本书中使用的函数确实非常有用,例如获取图像的高度和宽度,从现有图像创建缩略图,查询数据库,发送电子邮件,等等。您可以在 PHP 代码中识别函数,因为它们后面总是跟有一对括号。有时候,括号是空的,就像你在上一章的phpversion.php中使用的phpversion()的情况。不过,括号中通常包含变量、数字或字符串,如图 3-1 中的这行代码:

$thisYear = date('Y');

这段代码计算当前年份,并将其存储在变量$thisYear中。它通过向内置的 PHP 函数date()提供字符串'Y'来工作。像这样在括号之间放置一个值被称为向函数传递一个参数。该函数获取参数中的值,并对其进行处理以产生(或返回)结果。例如,如果您将字符串'M'作为参数传递给date()而不是'Y',它将返回三个字母缩写形式的当前月份(例如,三月、四月、五月)。如下例所示,通过将函数的结果赋给适当命名的变量,可以捕获函数的结果:

$thisMonth = date('M');

Note

第十六章深入介绍了 PHP 如何处理日期和时间。

有些函数有多个参数。出现这种情况时,请在括号内用逗号分隔参数,如下所示:

$mailSent = mail($to, $subject, $message);

不需要天才就能发现,这将发送一封电子邮件到第一个参数中存储的地址,主题行存储在第二个参数中,消息存储在第三个参数中。大多数函数都返回值,所以结果存储在变量$mailSent(本例中为true或 f alse,取决于成功或失败)。你会在第六章中看到这个函数是如何工作的。

Tip

您经常会遇到术语“参数”而不是“参数”从技术上讲,parameter 指的是函数定义中使用的变量,而 argument 指的是传递给函数的实际值。实际上,这两个术语往往可以互换使用。

似乎所有的内置函数还不够,PHP 允许您构建自己的自定义函数,如下一章所述。即使你不喜欢创建自己的函数,在本书中你也会用到一些我自己做的函数。您可以像使用内置函数一样使用它们。

显示 PHP 输出

除非你能在你的网页上显示结果,否则在幕后进行的所有这些魔术没有多大意义。在 PHP 中实现这一点的两种主要方式是使用echoprint。这两者之间有一些细微的差别,但它们是如此的细微,以至于你可以把echoprint视为相同。我更喜欢echo,原因很简单,因为这样可以少打一个字母。

可以将echo与变量、数字、字符串一起使用;简单地把它放在你想展示的东西前面,就像这样:

$name = 'David';
echo $name;   // displays David
echo 5;       // displays 5
echo 'David'; // displays David

对变量使用echoprint时,变量必须只包含一个值。您不能使用它们来显示数组或数据库结果的内容。这就是循环如此有用的地方:在循环中使用echoprint来单独显示每个元素。在本书的其余部分,您将会看到大量这样的例子。

您可能会看到使用圆括号将echoprint括起来的脚本,如下所示:

echo('David'); // displays David

括号没什么区别。除非你喜欢为了打字而打字,否则就不要用它们。

使用短回显标签

当您想要显示单个变量或表达式的值(除此之外别无其他)时,您可以使用简短的echo标记,它由一个左尖括号、一个问号和等号组成,如下所示:

<p>My name is <?= $name ?>.</p>

这会产生与以下内容相同的输出:

<p>My name is <?php echo $name ?>.</p>

因为它是echo的简写,所以没有其他代码可以在同一个 PHP 块中,但是当在网页中嵌入数据库结果时,它特别有用。不用说,在使用这个快捷方式之前,变量的值必须在前面的 PHP 块中设置。

Tip

因为没有其他代码可以在同一个 PHP 块中,所以在使用短的echo标记时,通常会省略结束 PHP 标记前的分号。

将字符串连接在一起

虽然许多其他计算机语言使用加号(+)来连接文本(字符串),但是 PHP 使用句点、圆点或句号(.),如下所示:

$firstName = 'David';
$lastName = 'Powers';
echo $firstName.$lastName; // displays DavidPowers

正如最后一行代码的注释所示,当两个字符串像这样连接在一起时,PHP 不会在它们之间留下任何空隙。不要误以为在句号后面加个空格就行了。不会的。你可以在周期的任何一边放尽可能多的空间;结果总是一样的,因为 PHP 会忽略代码中的空格。事实上,为了可读性,建议在句号的两边留一个空格。

要在最终输出中显示一个空格,必须在其中一个字符串中包含一个空格,或者将空格作为单独的字符串插入,如下所示:

echo $firstName . ' ' . $lastName; // displays David Powers

Tip

句点——或者说串联操作符,给它一个正确的名字——可能很难在其他代码中找到。确保编辑器中的字体足够大,可以看到句号和逗号之间的区别。

使用数字

PHP 可以处理很多数字,从简单的加法到复杂的数学。下一章包含了可以在 PHP 中使用的算术运算符的细节。在这个阶段,你需要记住的是,除了小数点之外,数字不能包含任何标点符号。这必须是一个点或句号。在许多欧洲国家,逗号是不允许使用的。类似地,如果您使用逗号或空格作为千位分隔符,PHP 将会阻塞(尽管从 PHP 7.4.0 开始,为了可读性,您可以使用下划线——PHP 在处理代码时会去掉下划线)。

理解 PHP 错误消息

错误消息是生活中不幸的事实,所以你需要理解它们试图告诉你什么。下图显示了一条典型的错误消息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PHP 错误消息报告 PHP 发现问题的行。大多数新人——很自然地——认为这是他们犯错的地方。错了…

大多数时候,PHP 会告诉你发生了意想不到的事情。换句话说,错误在于先于那一点。前面的错误消息意味着 PHP 发现了一个不应该存在的echo命令。

不要担心echo命令可能出了什么问题(可能没什么问题),开始向后工作,寻找任何遗漏的东西,可能是前一行的分号或右引号。

有时,消息会在脚本的最后一行报告错误。这通常意味着你已经在页面的某个地方省略了右花括号。

这些是主要的错误类别,按重要性降序排列如下:

  • 致命错误:错误之前的任何 HTML 输出都将被显示,但是一旦遇到错误——顾名思义——其他的都将被彻底杀死。致命错误通常是由引用不存在的文件或函数引起的。

  • 解析错误:这意味着你的代码语法中有错误,比如不匹配的引号,或者缺少分号或右括号。它停止脚本运行,甚至不允许显示任何 HTML 输出。

  • 警告:警告表示严重的问题,比如缺少包含文件。(包含文件是第五章的主题。)但是,该错误通常不会严重到阻止脚本的其余部分被执行。

  • Deprecated :警告你 PHP 未来版本中计划删除的特性。如果您看到这种类型的错误消息,您应该认真考虑更新您的脚本,因为如果您的服务器升级,它可能会突然停止工作。

  • Strict :这种类型的错误消息警告您使用不被认为是良好实践的技术。

  • 注意:这是对相对较小问题的建议,比如使用未声明的变量。虽然这种类型的错误不会阻止页面的显示(并且您可以关闭通知的显示),但是您应该总是尝试消除它们。任何错误都是对您输出的威胁。

为什么我的页面是空白的?

当许多初学者将一个 PHP 页面加载到浏览器中时,却什么也看不到,这让他们摸不着头脑。没有错误信息,只有一个空白页。当有一个解析错误——换句话说,代码中的一个错误——并且php.ini中的display_errors指令被关闭时,就会发生这种情况。

如果您遵循了前一章中的建议,那么应该在您的本地测试环境中启用display_errors。但是,大多数托管公司都关闭了display_errors。这有利于安全,但会使远程服务器上的故障排除变得困难。除了解析错误之外,缺少包含文件通常会导致空白页。

通过在页面顶部添加以下代码,可以打开单个脚本的错误显示:

ini_set('display_errors', '1');

将这段代码放在开始的 PHP 标签后的第一行,或者如果 PHP 在页面下方,则放在页面顶部的一个单独的 PHP 块中。当您上传页面并刷新浏览器时,您应该会看到 PHP 生成的任何错误消息。

如果在添加这行代码后,您仍然看到一个空白页,这意味着您的语法有错误。在打开display_errors的情况下本地测试页面,找出导致问题的原因。

Caution

更正错误后,删除显示错误的代码。如果在以后的阶段脚本中出现了其他问题,您不希望在您的 web 站点上暴露潜在的漏洞。

PHP 快速检查表

这一章包含了很多信息,但是希望它已经给了你一个 PHP 如何工作的广泛概述。以下是一些要点的提示:

  • 总是给 PHP 页面正确的文件扩展名,通常是.php

  • 将 PHP 代码放在正确的标签之间:<?php?>

  • 避免开头标签的缩写形式:<?。用<?php比较靠谱。

  • 在只包含 PHP 代码的文件中省略结束 PHP 标记。

  • PHP 变量以$开头,后跟一个字母或下划线字符。

  • 选择有意义的变量名,并记住它们区分大小写。

  • 使用注释来提醒您脚本的作用。

  • 数字不需要引号,但是字符串(文本)需要。

  • 小数点是数字中唯一允许的标点符号。

  • 您可以在字符串周围使用单引号或双引号,但外部的一对必须匹配。

  • 使用反斜杠对字符串中相同类型的引号进行转义。

  • 若要将相关项存储在一起,请使用数组。

  • 使用条件语句,如ifif . . . else,进行决策。

  • 循环简化了重复性任务。

  • 函数执行预设任务。

  • echoprint显示 PHP 输出。

  • 对于大多数错误信息,从指示的位置向后操作*。*

  • 保持微笑——记住 PHP 是而不是难的。

下一章将填充一些必要的细节,你可以在阅读本书时参考。

四、PHP 快速参考

前一章为初学者提供了 PHP 的鸟瞰图,而这一章则深入细节。不要试图一口气读完。当您需要了解如何做特定的事情时,比如构建一个数组或使用一个循环来重复一个动作,就可以使用它。下面几节没有涵盖 PHP 的每个方面,但是它们将有助于扩展您对本书其余部分的理解。

本章涵盖

  • 理解 PHP 中的数据类型

  • 使用算术运算符进行计算

  • 理解 PHP 如何处理字符串中的变量

  • 创建索引和关联数组

  • 理解 PHP 认为什么是真什么是假

  • 用比较来做决定

  • 在循环中重复执行相同的代码

  • 用函数模块化代码

  • 使用生成器生成一系列值

  • 理解类和对象

  • 动态创建新变量

在现有网站中使用 PHP

PHP 代码通常只在使用.php文件扩展名的页面中处理。虽然您可以在同一个网站中混合使用.html.php页面,但是最好只使用.php,即使不是每个页面都包含动态特性。这让你可以灵活地将 PHP 添加到页面中,而不会破坏现有的链接或丢失搜索引擎排名。

PHP 中的数据类型

PHP 是众所周知的弱类型语言。实际上,这意味着,与其他一些计算机语言(如 Java 或 C#)不同,PHP 不关心在变量中存储什么类型的数据。

大多数情况下,这非常方便,尽管您需要小心用户输入,因为来自在线表单的数据总是以文本形式传输。仔细检查用户输入是后面章节的主题之一。

尽管 PHP 是弱类型的,但它使用以下数据类型:

  • 整数:这是一个整数,比如 1,25,42,或者 2006。整数不能包含逗号作为千位分隔符。但是,从 PHP 7.4.0 开始,为了提高可读性,可以在数字之间使用下划线,例如 1_234_567。PHP 引擎会自动删除下划线。

  • 浮点数:包含小数点的数字,如 9.99、98.6 或 2.1。PHP 不支持使用逗号作为小数点,这在许多欧洲国家是很常见的。您必须使用句号。和整数一样,从 PHP 7.4.0 开始,浮点数可以包含下划线作为千位分隔符。(这种类型也被称为浮子。)

Caution

以前导零开头的整数被视为八进制数。例如,08 会产生一个解析错误,因为它不是一个有效的八进制数。另一方面,在浮点数中使用前导零没有问题,例如 0.8。

  • String :字符串是任意长度的文本。它可以短到零个字符(空字符串),并且在 64 位版本上没有上限。在实践中,其他考虑因素,比如可用内存或通过表单传递值,都会施加限制。

  • 布尔:这个类型只有两个值,truefalse。然而,PHP 将其他值视为隐式真或假。请参阅本章后面的“PHP 的真相”。

  • 数组:数组是一个能够存储多个值的变量,尽管它可能根本不包含任何值(空数组)。数组可以保存任何数据类型,包括其他数组。一个数组的数组叫做多维数组

  • 对象:对象是一种复杂的数据类型,能够存储和操作值。请参阅本章后面的“理解 PHP 类和对象”。

  • Resource :当 PHP 连接到一个外部数据源时,比如一个文件或数据库,它将一个对它的引用存储为一个资源。

  • Null :这是一种特殊的数据类型,表示变量值不存在。

Note

PHP 在线文档列出了另外两种描述结构行为而不是数据类型的类型。一个 iterable 是一个结构,比如一个数组或生成器,它可以在一个循环中使用,通常在每次循环运行时提取或生成一个序列中的下一个值。一个可调用的是被另一个函数调用的函数。

PHP 弱类型的一个重要副作用是,如果您用引号将整数或浮点数括起来,PHP 会自动将其从字符串转换为数字,从而允许您执行计算,而无需任何特殊处理。这可能会产生意想不到的后果。当 PHP 看到加号(+)时,它假设您想要执行加法,因此它尝试将字符串转换为整数或浮点数,如下例所示(代码在ch04文件夹的data_conversion_01.php中):

$fruit = '2 apples ';
$veg = '2 carrots';
echo $fruit + $veg;  // displays 4

PHP 看到$fruit$veg都以一个数字开始,所以它提取这个数字并忽略其余的。

Caution

尽管自动转换有效,但 PHP 8 会生成关于“非数值”的警告消息。引号中的数字本身没有问题。

但是,如果字符串不是以数字开头,PHP 8 会触发致命的 TypeError,因为+不能用于组合两个字符串,如下例所示(代码在data_conversion_02.php):

$fruit = '2 apples ';
$veg = 'and 2 carrots';
echo $fruit + $veg;  // displays warning about "non-numeric value" followed by fatal error

检查变量的数据类型

测试脚本时,检查变量的数据类型通常很有用。这有助于解释为什么脚本会产生意想不到的结果。要检查变量的数据类型和内容,只需将它传递给var_dump()函数,如下所示:

var_dump($variable_to_test);

使用本章文件中的data_tests.php查看var_dump()为不同类型的数据生成的输出。只需更改最后一行括号中的变量名称。

显式更改变量的数据类型

大多数时候,PHP 会自动将变量的数据类型转换为适合当前上下文的类型。这就是所谓的式杂耍。然而,有时有必要使用转换操作符显式地改变数据类型。表 4-1 列出了 PHP 中最常用的造型运算符。

表 4-1

常用的 PHP 强制转换运算符

|

铸造操作员

|

可供选择的事物

|

操作

|
| — | — | — |
| (array) |   | 强制转换为数组 |
| (bool) | (boolean) | 强制转换为布尔值 |
| (float) | (double), (real) | 强制转换为浮点数 |
| (int) | (integer) | 转换为整数 |
| (string) |   | 转换为字符串 |

要转换变量的数据类型,请在它前面加上适当的转换运算符,如下所示:

$input = 'coffee';
$drinks = (array) $input;

这将把$input的值作为一个数组分配给$drinks,包含字符串'coffee'作为其唯一的元素。当函数需要数组而不是字符串作为参数时,像这样将字符串转换为数组会很有用。在这个例子中,$input的数据类型仍然是字符串。要使造型永久,请将造型值重新分配给原始变量,如下所示:

$input = (array) $input;

检查变量是否已经定义

条件语句中最常见的测试之一是检查变量是否已定义。简单地将变量传递给isset()函数,如下所示:

if (isset($name)) {
    //do something if $name has been defined
} else {
    //do something else, such as give $name a default value
}

Tip

请参阅本章后面的“使用空值合并运算符设置默认值”,以了解为尚未定义的变量赋值的更简单的方法。

用 PHP 做计算

PHP 可以执行各种各样的计算,从简单的算术到复杂的数学。本章只讲述标准算术运算符。PHP 支持的数学函数和常数详见 www.php.net/manual/en/book.math.php

Note

一个常数代表一个不能改变的固定值。所有 PHP 预定义的常量都是大写的。与变量不同,它们不以美元符号开头。例如,π (pi)的常数是M_PI。您可以在 www.php.net/manual/en/reserved.constants.php 找到完整列表。

算术运算符

标准的算术运算符都按照您预期的方式工作,尽管其中一些看起来与您在学校学到的略有不同。例如,星号(*)用作乘法符号,正斜杠(/)用于表示除法。表 4-2 展示了标准算术运算符如何工作的例子。为了展示它们的效果,$x被设定为 20。

表 4-2

PHP 中的算术运算符

|

操作

|

操作员

|

例子

|

结果

|
| — | — | — | — |
| 添加 | + | $x + 10 | 30 |
| 减法 | - | $x - 10 | 10 |
| 增加 | * | $x * 10 | 200 |
| 分开 | / | $x / 10 | 2 |
| 以…为模 | % | $x % 3 | 2 |
| 增量(加 1) | ++ | $x++ | 21 |
| 减量(减 1) | -- | $x-- | 19 |
| 幂运算 | ** | $x**3 | 8000 |

模运算符通过在处理前去除小数部分,将两个数字转换为整数,并返回除法的余数,如下所示:

5 % 2.5    // result is 1, not 0 (the decimal fraction is stripped from 2.5)
10 % 2     // result is 0

模运算对于计算一个数是奇数还是偶数很有用。$number % 2总是产生 0 或 1。如果结果为 0,则没有余数,所以数字是偶数。

使用递增和递减运算符

递增(++)和递减(--)运算符可以出现在变量之前或之后。它们的位置对计算有重要影响。

当运算符出现在变量之前时,在执行任何进一步的计算之前会加上或减去 1,如下例所示:

$x = 5;
$y = 6;
--$x * ++$y // result is 28 (4 * 7)

他们来了之后,先进行主计算,然后要么加 1,要么减 1,像这样:

$x = 5;
$y = 6;
$x-- * $y++ // result is 30 (5 * 6), but $x is now 4, and $y is 7

确定计算顺序

PHP 中的计算遵循与标准算术相同的优先级规则。表 4-3 按优先级顺序列出了算术运算符,优先级最高的在顶部。

表 4-3

算术运算符的优先级

|

|

经营者

|

规则

|
| — | — | — |
| 圆括号 | () | 首先计算括号内的运算。如果这些表达式是嵌套的,则最里面的表达式最先被计算。 |
| 幂运算 | ** |   |
| 递增/递减 | ++ -- |   |
| 乘法和除法 | * / % | 如果表达式包含两个或更多这样的运算符,则从左到右计算它们。 |
| 加法和减法 | + - | 如果表达式包含两个或更多这样的运算符,则从左到右计算它们。 |

结合计算和赋值

PHP 提供了一种在变量上执行计算并通过组合赋值操作符将结果重新分配给变量的简便方法。主要的列于表 4-4 中。

表 4-4

PHP 中使用的组合算术赋值运算符

|

操作员

|

例子

|

等于

|
| — | — | — |
| += | $a += $b | $a = $a + $b |
| -= | $a -= $b | $a = $a - $b |
| *= | $a *= $b | $a = $a * $b |
| /= | $a /= $b | $a = $a / $b |
| %= | $a %= $b | $a = $a % $b |
| **= | $a **= $b | $a = $a ** $b |

添加到现有字符串

同样方便的简写允许您通过组合句点和等号向现有字符串的末尾添加新内容,如下所示:

$hamlet = 'To be';
$hamlet .= ' or not to be';

请注意,您需要在附加文本的开头创建一个空格,除非您希望两个字符串不间断地运行。这种简写被称为组合 串联运算符,在组合许多字符串时非常有用,例如在构建电子邮件消息的内容或循环数据库搜索的结果时。

Tip

在复制代码时,等号前面的句点很容易被忽略。当你看到同一个变量在一系列语句的开头重复出现时,这通常是一个明确的信号,表明你需要单独使用.=而不是=。但是,在使用组合串联运算符之前,变量必须已经存在。如果你试图用.=初始化一个变量,它会产生一个关于未定义变量的警告。

你曾经想知道的关于报价的一切,以及更多

计算机总是将第一个匹配的引号作为字符串的结尾。由于字符串可能包含撇号,单引号和双引号的组合是不够的。此外,PHP 在双引号内对变量和转义序列(某些字符前面有反斜杠)进行了特殊处理。在接下来的几页中,我将解开这个谜团,为你解释清楚。

PHP 如何处理字符串中的变量

选择使用双引号还是单引号可能看起来只是个人偏好的问题,但是 PHP 处理它们的方式有一个重要的区别:

  • 单引号之间的任何内容都被视为文本。

  • 双引号作为处理变量和特殊字符的信号,称为转义序列

在下面的示例中,$name被赋值,然后在单引号字符串中使用。所以$name被当作普通文本对待(代码在quotes_01.php):

$name = 'Dolly';
echo 'Hello, $name';  // Hello, $name

如果将第二行的单引号替换为双引号(参见quotes_02.php),则会处理$name,其值会显示在屏幕上:

$name = 'Dolly';
echo "Hello, $name";  // Hello, Dolly

Note

在这两个示例中,第一行的字符串都在单引号中。导致变量被处理的原因是它嵌入在双引号字符串中,而不是它最初是如何获得值的。

在双引号中使用转义序列

双引号还有另一个重要的作用:它们以一种特殊的方式处理转义序列。所有的转义序列都是在字符前加一个反斜杠形成的。表 4-5 列出了 PHP 支持的主要转义序列。

表 4-5

主要的 PHP 转义序列

|

转义序列

|

用双引号字符串表示的字符

|
| — | — |
| \" | 双引号 |
| \n | 换行 |
| \r | 回车 |
| \t | 标签 |
| \\ | 反斜线符号 |
| \$ | 美元符 |

Caution

除了\\,表 4-5 中列出的转义序列只在双引号字符串中有效。在单引号字符串中,它们被视为文字反斜杠后跟第二个字符。字符串末尾的反斜杠总是需要转义。否则,它将被解释为对下面的引号进行转义。

在字符串中嵌入关联数组元素

双引号字符串中的关联数组元素有一个令人讨厌的“陷阱”。下面一行代码试图从名为$book的关联数组中嵌入几个元素:

echo "$book['title'] was written by $book['author'].";

看起来还行。数组元素的键使用单引号,所以不会出现引号不匹配的情况。然而,如果你将quotes_03.php加载到浏览器中,你会得到这个神秘的错误信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方案是将关联数组元素用花括号括起来,就像这样(见quotes_04.php):

echo "{$book['title']} was written by {$book['author']}.";

这些值现在可以正确显示,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

索引数组元素,比如$shoppingList[2],不需要这种特殊处理,因为数组索引是一个数字,没有用引号括起来。

避免使用 Heredoc 语法对引号进行转义

使用反斜杠来转义一两个引号并不是很大的负担,但是我经常看到反斜杠似乎不受控制的代码示例。PHP heredoc 语法提供了一种相对简单的将文本赋给变量的方法,不需要任何特殊的引号处理。

Note

“heredoc”这个名字来源于 here-document,这是 Unix 和 Perl 编程中使用的一种技术,用于向命令传递大量文本。

使用 heredoc 将字符串赋给变量包括以下步骤:

  1. 键入赋值运算符,后跟<<<和一个标识符。标识符可以是字母、数字和下划线的任意组合,但不能以数字开头。稍后使用相同的组合来标识 heredoc 的结尾。

  2. 在新的一行开始字符串。它可以包括单引号和双引号。任何变量都将以与双引号字符串中相同的方式处理。

  3. 将标识符放在字符串末尾后的新行上。为了确保 heredoc 在 PHP 的所有版本中都能工作,标识符必须位于行首;除了最后一个分号,其他任何东西都不应该在同一行。

Note

在 PHP 8 中,结束标识符可以缩进。

实践中看到就轻松很多了。以下简单示例可在本章文件的heredoc.php中找到:

$fish = 'whiting';
$book['title'] = 'Alice in Wonderland';
$mockTurtle = <<< Gryphon
"Oh, you sing," said the Gryphon. "I've forgotten the words."
So they began solemnly dancing round and round Alice, every now and then treading on her toes when they passed too close, and waving their fore-paws to mark the time, while the Mock Turtle sang this, very slowly and sadly:"Will you walk a little faster?" said a $fish to a snail.
"There's a porpoise close behind us, and he's treading on my tail."
(from {$book['title']})
Gryphon;
echo $mockTurtle;

在本例中,Gryphon是标识符。字符串从下一行开始,双引号被视为字符串的一部分。所有内容都包括在内,直到新行开始处的标识符。然而,在 heredoc 的主体中重复的标识符被视为文本的一部分。如下图所示,Gryphon 的第一个实例被视为字符串的一部分,因为它不在新行的开头。此外,heredoc 显示双引号并处理$fish$book['title']变量:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Caution

尽管 heredoc 语法避免了对引号的转义,但是关联数组元素$book[‘title’]仍然需要用大括号括起来,如前一节所述。或者,在双引号字符串或 heredoc 中使用它之前,将其赋给一个更简单的变量。

为了在不使用 heredoc 语法的情况下达到相同的效果,您需要添加双引号并像这样对它们进行转义:

$mockTurtle = "\"Oh, you sing,\" said the Gryphon. \"I've forgotten the words.\" So they began solemnly dancing round and round Alice, every now and then treading on her toes when they passed too close, and waving their fore-paws to mark the time, while the Mock Turtle sang this, very slowly and sadly:— \"Will you walk a little faster?\" said a $fish to a snail. \"There's a porpoise close behind us, and he's treading on my tail.\" (from {$book['title']})";

当您有一个长字符串和/或许多引号时,heredoc 语法主要是有价值的。如果您想将一个 XML 文档或一段很长的 HTML 赋给一个变量,这也很有用。

创建数组

有两种类型的数组:索引数组,它使用数字来标识每个元素;关联数组,它使用字符串。您可以通过直接为每个元素赋值来构建这两种类型。例如,$book关联数组可以这样定义:

$book['title'] = 'PHP 8 Solutions: Dynamic Web Design and Development Made Easy';
$book['author'] = 'David Powers';
$book['publisher'] = 'Apress';

要直接构建索引数组,请使用数字而不是字符串作为数组键。默认情况下,索引数组从 0 开始编号,因此要构建上一章图 3-3 中描述的$shoppingList数组,您应该像这样声明它:

$shoppingList[0] = 'wine';
$shoppingList[1] = 'fish';
$shoppingList[2] = 'bread';
$shoppingList[3] = 'grapes';
$shoppingList[4] = 'cheese';

尽管这两种方法都是创建数组的非常有效的方法,但是还有更短的方法。

构建索引数组

最快的方法是使用简写语法,这与 JavaScript 中的数组文字相同。您可以通过在一对方括号之间括起逗号分隔的值列表来创建数组,如下所示:

$shoppingList = ['wine', 'fish', 'bread', 'grapes', 'cheese'];

Caution

逗号必须在引号之外,这与美国印刷惯例不同。为了便于阅读,我在每个逗号后面插入了一个空格,但这不是必须的。

另一种方法是将逗号分隔的列表传递给array(),如下所示:

$shoppingList = array('wine', 'fish', 'bread', 'grapes', 'cheese');

PHP 自动对每个数组元素进行编号,从 0 开始,所以这两种方法创建的是同一个数组,就好像您对它们分别进行了编号一样。

要在数组末尾添加新元素,请使用一对空方括号,如下所示:

$shoppingList[] = 'coffee';

PHP 使用下一个可用的数字,所以这变成了$shoppingList[5]

Note

在 PHP 8 之前,只有当现有数组中的最后一个数字为正数时,添加到索引数组中的项才会取下一个可用的数字。如果最后一个数字是负数,则新的加法被设置为 0。现在,如果最后一个数字是负数,新的索引将增加 1。例如,如果最后一个索引是–4,那么下一个索引将是–3。

构建关联数组

关联数组使用=>操作符(等号后面跟一个大于号)给每个数组键赋值。使用速记方括号语法,结构如下所示:

$arrayName = ['key1' => 'element1', 'key2' => 'element2'];

使用array()获得相同的结果:

$arrayName = array('key1' => 'element1', 'key2' => 'element2');

所以这是构建$book数组的简化方法:

$book = [
    'title'           => 'PHP 8 Solutions: Dynamic Web Design and Development Made Easy',
    'author'      => 'David Powers',
    'publisher'  => 'Apress'
];

不一定要把开始和结束括号放在不同的行上,也不一定要像我所做的那样对齐=>操作符,但是这使得代码更容易阅读和维护。

Tip

速记语法和array()都允许在最后一个数组元素后面有一个逗号。这同样适用于索引数组和关联数组。

创建空数组

您可能希望创建空数组有两个原因,如下所示:

  • 创建(或初始化)一个数组,这样它就可以在一个循环中添加元素了

  • 清除现有数组中的所有元素

要创建空数组,只需使用一对空方括号:

$shoppingList = [];

或者,使用圆括号之间不带任何内容的array(),如下所示:

$shoppingList = array();

$shoppingList数组现在不包含任何元素。如果您使用$shoppingList[]添加一个新的,它将自动从 0 开始重新编号。

多维数组

数组元素可以存储任何数据类型,包括其他数组。您可以用几本书的详细信息创建一个数组的数组,换句话说,一个多维数组,如下所示(使用速记语法):

$books = [
    [
        'title'     => 'PHP 8 Solutions: Dynamic Web Design and Development Made Easy',
        'author'    => 'David Powers'
    ],
    [
        'title'     => 'PHP 8 Revealed',
        'author'    => 'Gunnard Engebreth'
    ]
];

此示例显示了嵌套在索引数组中的关联数组,但是多维数组可以嵌套任何一种类型。要引用特定元素,请使用两个数组的键,例如:

$books[1]['author']  // value is 'Gunnard Engebreth'

使用多维数组并不像看起来那么困难。秘诀是使用一个循环来到达嵌套数组。然后,您可以像处理普通数组一样处理它。这是您处理数据库搜索结果的方式,它通常包含在多维数组中。

使用 print_r()检查数组

像这样将数组传递给print_r(),以在测试期间检查其内容(参见inspect_array.php):

print_r($books);

通常,切换到源代码视图来检查细节会有所帮助,因为浏览器会忽略底层输出中的缩进:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Tip

始终使用print_r()来检查阵列。echoprint不起作用。要在网页上显示数组的内容,使用一个foreach循环,如本章后面所述。

PHP 的真相

PHP 条件语句中的决策是基于互斥的布尔值truefalse。如果条件等于true,则执行条件块中的代码。如果false,则忽略。条件是true还是false通过以下方式之一确定:

  • 显式设置为布尔值之一的变量

  • PHP 将一个值隐式解释为truefalse

  • 两个非布尔值的比较

显式布尔值

如果一个变量被赋值为truefalse并在条件语句中使用,则决定基于该值。关键字truefalse不区分大小写,不能用引号括起来,例如:

$ok = false;
if ($ok) {
    // do something
}

条件语句里面的代码不会被执行,因为$okfalse

隐式布尔值(“真”和“假”)值

使用隐式布尔值提供了一种方便的速记方法,尽管它有一个缺点——至少对初学者来说——就是不够清晰。隐式布尔值——或者有时被称为“真”和“假”值——依赖于 PHP 对其视为false的相对狭窄的定义,即:

  • 不区分大小写的关键字 false 和 null

  • 整数(0)、浮点数(0.0)或字符串('0'"0")形式的零

  • 空字符串(单引号或双引号,中间没有空格)

  • 空数组

  • 从空标记创建的 SimpleXML 对象

其他都是true

Tip

这解释了为什么 PHP 将"false"(在引号中)解释为true。这是一个字符串,所有的字符串——除了一个空字符串——都是true。还要注意,–1被认为是true,就像任何其他非零数字一样。

通过比较两个值做出决策

许多true/false决策是基于使用比较运算符对两个值进行的比较。表 4-6 列出了 PHP 中使用的比较运算符。

表 4-6

用于决策的 PHP 比较运算符

|

标志

|

名字

|

例子

|

结果

|
| — | — | — | — |
| == | 平等 | $a == $b | 如果$a$b相等,则返回true;否则,返回false. |
| != | 不平等 | $a != $b | 如果$a$b不同,则返回true;否则,返回false. |
| === | 同一的 | $a === $b | 确定$a$b是否相同。它们不仅必须具有相同的值,而且必须具有相同的数据类型(例如,都是整数)。 |
| !== | 不相同 | $a !== $b | 确定$a$b是否不相同(根据与前一个运算符相同的标准)。 |
| > | 大于 | $a > $b | 如果$a大于$b.,则返回true |
| >= | 大于或等于 | $a >= $b | 如果$a大于或等于$b.,则返回true |
| < | 不到 | $a < $b | 如果$a小于$b.,则返回true |
| <= | 小于或等于 | $a <= $b | 如果$a小于或等于$b.,则返回true |
| <=> | 宇宙飞船 | $a <=> $b | 如果$a小于$b,则返回一个小于零的整数;如果$a大于$b,则返回一个大于零的整数;如果$a$b等于.,则返回零 |

正如你将在第九章中看到的,飞船操作符对于定制排序很有用。它的名字来自 Perl 书籍的作者,也是操作符的发源地。他认为这比经常提到“小于等于或大于运算符”要容易得多。

重要的是要记住,一个等号只分配一个值。比较两个值时,使用相等运算符()、相同运算符(=)或它们的负等效运算符(!=而且!==).

Caution

PHP 8 改变了等号运算符(==)比较数字和字符串的方式,将数字转换成字符串,并测试它们是否相同。在以前的版本中,比较是通过将字符串转换为数字以相反的方式进行的。这导致一些边缘情况现在返回到它们先前返回的true的地方false。详见 www.php.net/manual/en/migration80.incompatible.php

测试多个条件

通常,比较两个值是不够的。PHP 允许你设置一系列条件,使用逻辑操作符来指定是全部还是部分需要满足。

PHP 中最重要的逻辑运算符在表 4-7 中列出。逻辑 Not 运算符适用于单个条件,而不是一系列条件。

表 4-7

PHP 中用于决策的主要逻辑运算符

|

标志

|

名字

|

例子

|

结果

|
| — | — | — | — |
| && | 和 | $a && $b | 如果$a$b都是true,则等同于true |
| &#124;&#124; | 或者 | $a &#124;&#124; $b | 如果$a$btrue,则等同于true;否则,false |
| ! | 不 | !$a | 如果$a而不是 true,则等同于true |

从技术上讲,可以测试的条件数量没有限制。从左到右依次考虑每个条件,一旦达到定义点,就不再进行进一步的测试。使用&&时,每个条件都必须满足,因此一旦其中一个变成false,测试就停止。同样,当使用||时,只需要满足一个条件,因此一旦其中一个变成true,测试就停止:

$a = 10;
$b = 25;
if ($a > 5 && $b > 20) // returns true
if ($a > 5 || $b > 30) // returns true, $b never tested

总是设计测试来提供最快的结果。如果所有条件都必须满足,首先评估最有可能失败的条件。如果只需要满足一个条件,首先评估最有可能成功的一个。如果需要将一组条件视为一组,请将它们括在括号中,如下所示:

if (($a > 5 && $a < 8) || ($b > 20 && $b < 40))

Note

PHP 也用AND代替&&,用OR代替||。然而,ANDOR的优先级要低得多,这可能会导致意想不到的结果。为了避免问题,建议坚持使用&&||

将 switch 语句用于决策链

switch陈述为决策提供了if . . . else的替代方案。基本结构是这样的:

switch(variable being tested) {
    case value1:
        statements to be executed
        break;
    case value2:
        statements to be executed
        break;
    default:
        statements to be executed
}

case关键字表示传递给switch()的变量的可能匹配值。每个可选值必须以case开头,后跟一个冒号。当匹配成功时,执行每一行后续代码,直到遇到breakreturn关键字,此时switch语句结束。下面是一个简单的例子:

switch($myVar) {
    case 1:
        echo '$myVar is 1';
        break;
    case 'apple':
    case 'orange':
        echo '$myVar is a fruit';
        break;
    default:
        echo '$myVar is neither 1 nor a fruit';
}

关于switch的注意要点如下:

  • case关键字后面的表达式通常是数字或字符串。不能使用数组或对象这样的复杂数据类型。

  • 要使用带有case的比较运算符,您必须重复被测试的表达式。例如,case > 100:不会工作,但是case $myVar > 100:会。在第八章的“PHP 解决方案 8-4:用逗号连接数组”中有一个这种情况的实际例子。

  • 每一个后续的案件也会被执行,除非你用break或者return结束一个案件。

  • 您可以将case关键字的几个实例组合在一起,对它们应用相同的代码块。因此,在前面的例子中,如果$myVar是“apple”或“orange”,下面的行将被执行。

  • 如果没有匹配,则执行任何跟在关键字default后面的语句。如果没有设置默认值,那么switch语句会自动退出,并继续执行下一个代码块。

对决策链使用匹配表达式

PHP 8 引入了将一个值与多个选项进行比较的match表达式。它类似于switch,但有一些重要的区别。基本语法如下所示:

$return_value = match($value) {
    single_conditional_expression => return_expression,
    conditional_expression1, conditional_expression2 => return_expression,
} ;

上一节中的开关示例将改写如下:

$result = match ($myVar) {
    1 => '$myVar is 1',
    'apple', 'orange' => '$myVar is a fruit',
    default => '$myVar is neither 1 nor a fruit'
};
echo $result;

这不仅比 switch 更简洁;还有其他重大差异,即:

  • match返回一个值。不能用echoprint直接输出数值。

  • 在右花括号后面必须有一个分号。

  • match使用标识运算符(===)执行严格的比较,而switch使用宽松的相等运算符(==)。这意味着被比较的值必须与条件表达式的类型相同。在前面的示例中,如果传递给 match 的值是字符串“1”(在引号中),则它不会与第一个条件表达式中的数字 1 匹配。

  • 如果没有匹配的,PHP 抛出一个UnhandledMatchError。您可以通过在末尾设置默认值来避免这种情况。

  • 没必要用break。一旦找到匹配项,match表达式就停止计算条件表达式。

您还可以通过将true作为参数来使用match表达式测试不相同的条件。以下示例通过在每个条件表达式中重复一个值,将该值与一个整数范围进行比较:

$age = 23;

$result = match (true) {
    $age >= 65 => 'senior',
    $age >= 25 => 'adult',
    $age >= 18 => 'young adult',
    default => 'child',
};  // $result is 'young adult'

Note

所有在 www.php.net/manual/en/control-structures.match.php 的例子在最后一个表达式后面都有一个尾随逗号。这在实践中是可选的。

使用三元运算符

三元运算符 ( ?:)是一种表示条件语句的简写方法。它的名字来源于它通常使用三个操作数。基本语法如下所示:

condition ? value if true : value if false;

下面是一个使用中的例子:

$age = 17;
$fareType = $age >= 16 ? 'adult' : 'child';

第二行测试$age的值。如果大于等于 16,$fareType设置为adult;否则,$fareType被设置为child。使用if . . . else的等效代码如下所示:

if ($age >= 16) {
    $fareType = 'adult';
} else {
    $fareType = 'child';
}

您可以省略问号和冒号之间的值。如果条件为真,这会将条件值赋给变量。前面的示例可以改写如下:

$age = 17;
$adult = $age >= 16 ?: false; // $adult is true

在这种情况下,问号前的表达式是一个比较,所以它只能等同于truefalse。但是,如果问号前的表达式为“truthy”(隐式 true),则返回值本身。例如:

$age = 17;
$years = $age ?: 'unknown';  // $years is 17

前面例子的问题是,如果用作条件的变量还没有定义,就会产生一个错误。一个更好的解决方案是使用零合并操作符,如下一节所述。

Caution

不建议链接或嵌套三元表达式,因为代码可能难以理解,结果也难以预测。PHP 8 要求嵌套的三元表达式用圆括号括起来,以指示它们的求值顺序。否则会产生致命错误。

使用空合并运算符设置默认值

当另一个变量——比如一个包含来自在线表单的用户输入的变量——没有被定义时,空合并操作符是一种为变量分配默认值的便捷方式。运算符由两个问号(??)组成,用法如下:

$greeting = $_GET['name'] ?? 'guest';

这试图将$greeting的值设置为$_GET['name']中存储的任何值。但是如果$_GET['name']没有被定义——换句话说,它是空的——则使用?? ( 'guest')之后的值。零合并运算符可以像这样链接:

$greeting = $_GET['name'] ?? $nonexistent ?? $undefined ?? 'guest';

PHP 依次测试每个值,并将第一个非空值赋给变量。

Caution

空合并操作符只拒绝空值——换句话说,不存在的变量或者被显式设置为 的变量。在前面的例子中,如果提交的表单没有在名为name的字段中输入值,那么$_GET['name']将被设置为空字符串。虽然 PHP 将此视为false,但它不是null。因此,$greeting将被设置为空字符串。

用循环重复执行代码

一个循环是一段重复的代码,直到满足某个条件。通常通过设置一个计算迭代次数的变量来控制循环。通过每次递增变量,当变量达到一个预置数时,循环停止。循环也是通过遍历数组的每一项来控制的。当没有更多的项目要处理时,循环停止。循环经常包含条件语句,所以尽管它们在结构上非常简单,但它们可以用来创建以复杂方式处理数据的代码。

使用 while 和 do 循环。。。正在…

最简单的循环称为while循环。它的基本结构是这样的:

while (condition is true) {
    do something
}

以下代码在浏览器中显示从 1 到 100 的每个数字(您可以在本章文件的while.php中测试它)。它首先将变量($i)设置为 1,然后使用变量作为计数器来控制循环,并在屏幕上显示当前数字:

$i = 1;  // set counter
while ($i <= 100) {
    echo "$i<br>";
    $i++; // increase counter by 1
}

Tip

在前一章中,我警告过不要使用带有神秘名称的变量。但是,使用$i作为计数器是一种常见的约定。如果$i已经被使用,通常的做法是使用$j$k作为计数器。

while循环的一个变体使用关键字do并遵循以下基本模式:

do {
    code to be executed
} while (condition to be tested);

不同之处在于,do 块中的代码至少执行一次,即使条件从不为真。以下代码(在dowhile.php中)显示一次$i的值,即使它大于在该条件下测试的最大值:

$i = 1000;
do {
    echo "$i<br>";
    $i++; // increase counter by 1
} while ($i <= 100);

危险在于忘记设置一个结束循环的条件,或者设置一个不可能的条件。这就是所谓的无限循环,它会冻结你的电脑或者导致浏览器崩溃。

多功能 for 循环

for循环不太容易产生无限循环,因为循环的所有条件都在第一行声明。for循环使用以下基本模式:

for (initialize loop; condition; code to run after each iteration) {
    code to be executed
}

以下代码的输出与前面的while循环相同,显示从 1 到 100 的每个数字(见forloop.php):

for ($i = 1; $i <= 100; $i++) {
    echo "$i<br>";
}

括号内的三个表达式控制循环的动作(注意,它们是用分号分隔的,而不是逗号):

  • 第一个表达式在循环开始前执行。在这种情况下,它将计数器变量$i的初始值设置为 1。

  • 第二个表达式设置了确定循环应该运行多长时间的条件。这可以是固定的数字、变量或计算值的表达式。

  • 第三个表达式在循环的每次迭代结束时执行。在这种情况下,它将$i增加 1,但是没有什么可以阻止您使用更大的增量。例如,在这个例子中用$i+=10替换$i++将会显示 1、11、21、31 等等。

注意for 循环开始处括号内的第一个和第三个表达式可以包含多个用逗号分隔的语句。例如,循环可能使用两个独立递增或递减的计数器。

用 foreach 遍历数组和对象

PHP 中循环的最后一种类型与数组、对象和生成器一起使用(参见本章后面的“生成器:一种特殊类型的不断给出的函数”)。它有两种形式,都使用临时变量来处理每个元素。如果您只需要对元素的值做一些事情,foreach循环采用以下形式:

foreach (variable_name as element) {
    do something with element
}

下面的示例遍历$shoppingList数组并显示每个项目的名称(代码在foreach_01.php中):

$shoppingList = ['wine', 'fish', 'bread', 'grapes', 'cheese'];
foreach ($shoppingList as $item) {
    echo $item.'<br>';
}

Caution

foreach关键字必须而不是foreach之间有一个空格。

虽然前面的例子使用了一个索引数组,但是您也可以使用带有关联数组的基本形式的foreach循环来访问每个元素的值。

另一种形式的foreach循环提供了对每个元素的键和值的访问。它的形式略有不同:

foreach (variable_name as key => value) {
    do something with key and value
}

下一个例子使用了本章前面“创建数组”一节中的$book关联数组,并将每个元素的键和值合并到一个简单的字符串中,如下面的屏幕截图所示(参见foreach_02.php):

foreach ($book as $key => $value) {
    echo "$key: $value<br>";
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Note

除了数组,foreach循环的主要用途是与迭代器生成器一起使用。你将在第 8 和 9 章看到如何使用迭代器和生成器。

打破循环

要在满足特定条件时提前结束循环,请在条件语句中插入关键字break。脚本一遇到break,就退出循环。

要在满足特定条件时跳过循环中的代码,请使用continue关键字。它没有退出,而是立即返回到循环的顶部(忽略循环体中跟在continue后面的代码)并处理下一个元素。例如,下面的循环从一个条件开始,如果$photo没有值,则跳过当前元素(如果变量不存在或为假,则empty()函数返回true):

foreach ($photos as $photo) {
    if (empty($photo)) continue;
    // code to display a photo
}

用函数模块化代码

除了大量的内置函数,PHP 还允许您创建自己的函数。您只需编写一次代码,而不需要在任何需要的地方重新键入。如果函数中的代码有问题,您可以只在一个地方更新它,而不是搜索整个站点。

用 PHP 构建自己的函数很容易。您只需将一段代码放在一对花括号中,并使用function关键字来命名新函数。函数名后面总是跟着一对括号。下面的例子展示了一个定制函数的基本结构(参见本章文件中的functions_01.php):

function sayHi() {
    echo 'Hi!';
}

简单地将sayHi();放入 PHP 代码块中会导致 Hi!显示在屏幕上。这种类型的功能就像无人机:它总是执行相同的操作。为了让函数对环境做出响应,您需要将值作为参数传递给它们。

向函数传递值

假设您想修改sayHi()函数来显示某人的名字。您可以通过在函数声明的括号之间插入一个变量来实现这一点(从技术上来说,这被称为在函数签名中插入一个参数)。然后在函数内部使用同一个变量来存储传递给函数的任何值。functions_02.php中的修改版看起来是这样的:

function sayHi($name) {
    echo "Hi, $name!";
}

您现在可以在页面中使用这个函数来显示传递给sayHi()的任何变量或文字字符串的值。例如,如果你有一个在线表单,将某人的名字保存在一个名为$visitor的变量中,马克访问你的网站,你可以通过将sayHi($visitor);放在你的页面中,给他如下图所示的那种个人问候。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PHP 弱类型的一个缺点是,如果 Mark 不合作,他可能会在表单中键入 5 而不是他的名字,这样就不会出现您所期望的击掌。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Tip

在任何关键情况下使用用户输入之前,一定要检查它。随着这本书的深入,你会学到如何做到这一点。

要向函数传递多个参数,请在函数签名中用逗号分隔变量(参数)。

设置参数的默认值

要为传递给函数的参数设置默认值,在函数签名中为变量赋值,如下所示(见functions_04.php):

function sayHi($name = 'bashful') {
    echo "Hi, $name!";
}

这使得参数是可选的,允许您像这样调用函数:

sayHi();

以下截图显示了结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是,您仍然可以向该函数传递一个不同的值来代替默认值。

Tip

可选参数必须始终位于必需参数之后的函数签名的末尾。这个不行:function sayHi($name = 'bashful', $title);。这个会:function sayHi($title, $name = 'bashful');

可变范围:充当黑盒

函数创建了一个独立的环境,有点像黑匣子。正常情况下,函数内部发生的事情对脚本的其余部分没有影响,除非它返回一个值,如下一节所述。函数内部的变量仍然是函数专有的。这个例子应该说明这一点(见functions_05.php):

function doubleIt($number) {
    $number *= 2;
    echo 'Inside the function, $number is ' . $number . '<br>';  // number is doubled
}
$number = 4;
doubleIt($number);
echo 'Outside the function $number is still ' . $number;   // not doubled

前四行定义了一个名为doubleIt()的函数,它接受一个数字,将它加倍,并显示在屏幕上。脚本的其余部分将值 4 赋给$number。然后它将$number作为参数传递给doubleIt()。该功能处理$number并显示 8。功能结束后,echo在屏幕上显示$number。这次是 4 而不是 8,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这表明主脚本中的$number与函数中同名的变量完全无关。这就是所谓的变量的范围。即使变量的值在函数内部发生了变化,在函数外部同名的变量也不会受到影响,除非变量的值通过引用传递给函数,如本章后面所述。

Tip

尽可能避免在脚本的其余部分使用与函数内部相同的变量名。它使您的代码更容易理解和调试。

PHP 超全局变量( www.php.net/manual/en/language.variables.superglobals.php ),如$_POST``$_GET,不受变量作用域的影响。它们总是可用的,这就是它们被称为超级全球的原因。

从函数返回值

有多种方法可以让函数改变作为参数传递给它的变量值,但最重要的方法是使用return关键字,并将结果赋给同一个变量或另一个变量。这可以通过如下修改doubleIt()函数来演示(代码在functions_06.php):

function doubleIt($number) {
    return $number *= 2;
}
$num = 4;
$doubled = doubleIt($num);
echo '$num is: ' . $num . '<br>';  // remains unchanged
echo '$doubled is: ' . $doubled;   // original number doubled

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这一次,我为变量使用了不同的名称,以避免混淆。我还将doubleIt($num)的结果赋给了一个新变量。这样做的好处是原始值和计算结果现在都是可用的。您不会总是希望保持原始值,但它有时会非常有用。

Tip

函数并不总是需要返回值。关键字return可以单独用来停止任何进一步的处理。

生成器:一种特殊类型的函数,它不断地给出

当一个函数遇到return时,它立即终止并返回值或不返回值。生成器是创建简单迭代器的特殊函数,用于在循环中产生一系列值。他们不使用关键字return,而是使用yield。这使得生成器一次产生一个值,跟踪序列中的下一个值,直到它被再次调用或用完所有值。

生成器可以使用内部循环来生成它所产生的值,或者它可以有一系列的yield语句。generator.php中的简单例子使用了这两种技术:

function counter($num) {
    $i = 1;
    while ($i < $num) {
        yield $i++;
    }
    yield $i;
    yield $i + 10;
    yield $i + 20;
}

counter()生成器接受一个参数$num。它将计数器$i初始化为 1,然后使用一个循环,当$i小于$num时继续运行。循环产生$i,并将其递增 1。在循环结束后,一系列的yield语句产生另外三个值。

通过将生成器赋给变量来初始化生成器后,可以在 foreach 循环中使用它,如下所示:

$numbers = counter(5);
foreach ($numbers as $number) {
    echo $number . ' ';
}

这将产生一系列数字,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于这个简单的例子,创建一个值数组并在循环中直接使用它会更简单。生成器的主要优点是,对于大量的值,它们使用的内存比数组少得多。第九章给出了一个生成器处理文件内容的实例。

通过引用传递:更改参数的值

尽管函数通常不改变作为参数传递给它们的变量值,但有时您确实想改变原始值而不是捕获返回值。为此,在定义函数时,在要更改的参数前加一个&符号,如下所示:

function doubleIt(&$number) {
    $number *= 2;
}

注意,这个版本的doubleIt()函数不echo计算$number的值,也不返回计算的值。因为圆括号之间的参数以&为前缀,所以作为参数传递给函数的变量的原始值将会改变。这就是所谓的通过引用

以下代码(可在functions_07.php中找到)演示了这种效果:

$num = 4;
echo '$num is: ' . $num . '<br>';
doubleIt($num);
echo '$num is now: ' . $num;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

&符号仅在函数定义中使用,而不是在调用函数时使用。

一般来说,使用函数来更改作为参数传递给它们的变量的原始值并不是一个好主意,因为如果在脚本的其他地方使用该变量,可能会产生意想不到的后果。然而,有些情况下这样做很有意义。例如,内置的数组排序函数使用按引用传递来影响原始数组。

Note

对象总是通过引用传递,即使函数定义没有在参数前加上&符号。这也适用于迭代器和生成器,它们实现内置的 PHP 类。

接受可变数量参数的函数

名字有点不雅的 splat 操作符允许你定义一个接受任意数量参数的函数(技术上称为变量函数)。它由函数签名中最后一个(或唯一一个)参数前的三个点或句点组成。splat 运算符将传递给函数的值转换成一个数组,然后可以在函数内部使用该数组。functions_08.php中的代码包含以下简单的例子:

function addEm(...$nums) {
    return array_sum($nums);
}
$total = addEm(1, 2, 3, 4, 5);
echo '$total is ' . $total;

传递给函数的逗号分隔的数字被转换成一个数组,然后传递给内置的array_sum()函数,该函数将数组中的所有值相加。以下屏幕截图显示了输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自动解包传递给函数的数组

当 splat 运算符位于作为参数传递给函数的数组之前时,它会产生相反的效果:它会对数组进行解包,以便每个元素都被视为一个单独的参数。以下 unpack.php 的例子说明了它是如何运作的:

function add ($a, $b) {
    return $a + $b;
}
$nums = [1,2,4,7,9];
echo 'The result is ' . add(...$nums);

add()函数期望两个独立的值,并将它们相加。$nums是五个整数的数组。当 splat 操作符前面的数组被传递给函数时,前两个元素被自动提取并相加,然后返回结果。多余的参数被忽略,产生如下屏幕截图所示的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Caution

尽管多余的元素会被忽略,但数组必须至少包含函数期望的那么多的值。

可选地指定数据类型

随着 PHP 的成熟,许多开发人员寻求对函数接受和返回的数据类型的更大控制。这在社区中引发了激烈的争论,因为 PHP 薄弱的数据类型是其成功的主要原因之一——不必担心数据类型使这门语言对初学者来说更容易学习。折衷的办法是引入可选的类型声明

要指定参数必须是特定类型,请在函数签名中的参数前加上表 4-8 中列出的类型之一。

表 4-8

类型声明

|

类型

|

描述

|
| — | — |
| Class/interface name | 必须是给定类或接口的实例 |
| self | 必须是同一类的实例 |
| parent | 必须是当前类的父类的实例 |
| array | 必须是数组 |
| callable | 必须是有效的可调用函数 |
| bool | 必须是布尔值 |
| float | 必须是浮点数 |
| int | 必须是整数 |
| string | 必须是字符串 |
| iterable | 必须是数组或实现Traversable接口 |
| object | 必须是一个对象 |
| mixed | 可以是任何值 |

Note

表 4–8 中的前三个类型声明仅用于类,这将在本章后面的“理解 PHP 类和对象”中描述。一个接口指定了一个类必须实现哪些方法。

类、接口、数组、可调用函数和对象的类型声明通过在使用不同类型时引发错误来强制使用正确的数据类型。然而,boolfloatintstring类型声明的行为不同。它们不会抛出错误,而是悄悄地将参数转换为指定的数据类型。functions_09.php中的代码修改了本章前面“从函数返回值”中的doubleIt()函数,添加了如下类型声明:

function doubleIt(int $number) {
    return $number *= 2;
}

下面的屏幕截图显示了当传递给函数的值是 4.9 时会发生什么:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该数字在被处理之前被转换成整数。甚至没有四舍五入到最接近的整数。小数部分被简单地去掉了。

Tip

您可以通过在每个脚本中启用严格类型来更改boolfloatintstring类型声明的行为。但是,严格类型的实现可能会令人困惑。我个人建议仅对类、接口和数组使用类型声明,除非您有意要将提交的值转换为指定的类型。在 www.php.net/manual/en/functions.arguments.php 可以学习如何在 PHP 文档中启用严格类型。

您还可以指定函数返回的数据类型。可用类型与表 4-8 中所列的相同,但增加了void。返回类型声明由函数签名中右括号和左花括号之间的冒号和 type 组成。functions_10.php中的例子像这样修改了doubleIt()函数:

function doubleIt(int $number) : float {
    return $number *= 2;
}

我特意选择了这个不合逻辑的例子来演示将float设置为返回类型会悄悄地将函数返回的值转换为浮点数。但是它不重写参数的类型声明。将 4.9 作为参数传递给函数仍然返回 8;但是var_dump()揭示了 PHP 将其作为浮点数处理,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用boolintstring作为返回类型声明也会执行静默数据类型转换,除非启用了严格类型。如果函数返回错误的数据类型,其他返回类型声明会抛出错误。

指定多种数据类型

以前的 PHP 版本只允许声明一种数据类型。PHP 8 现在允许联合类型,这允许您在一个声明中组合两个或多个数据类型。简单地用一个垂直管道将类型分开,就像这样:

string|array

它接受字符串或数组。联合类型也可以用于返回类型声明。

如果你想指定null也是可以接受的,有两种方法。对于联合类型,将null声明为如下类型之一:

string|array|null

如果您指定值可以是单一类型或null,请在类型声明前加上一个问号,如下所示:

?string

这相当于以下联合类型:

string|null

You不能使用null作为独立类型。它只能是指定类型的替代。

Note

本书中的代码只在真正有好处的情况下才使用类型声明,例如,检查是否将正确的数据类型传递给了函数。

使用命名参数

通常,所有参数都需要按照函数签名中参数的顺序传递给函数,除非它们是可选的。但是,如果函数有几个可选参数,而您只需要更改其中一个选项,这可能会很不方便。PHP 8 通过引入命名参数解决了这个问题,它允许你以任何顺序向函数提交值。

一个有用的命名参数的例子是内置函数htmlentities(),它接受一个字符串并转换所有具有 HTML 实体等价物的字符(比如用& amp;替换&)。函数签名如下所示:

htmlentities ( string $string , int $flags = ENT_COMPAT , string|null $encoding = null , bool $double_encode = true ) : string

最后三个参数有默认值,所以是可选的。最后一个参数转换所有内容,包括现有的 HTML 实体。例如,如果您的字符串包含一个已经被转换成 HTML 实体的&符号,如下所示

Fish &​amp; Chips

当您将它传递给htmlentities()时,默认是再次转换&符号,产生

Fish &​amp;​amp; Chips

为了防止这种情况,您需要将最后一个参数设置为false。在以前的 PHP 版本中,这需要像这样设置所有四个参数:

$output = htmlentities($myText, ENT_COMPAT, null, false);

命名参数是通过在值前面加上参数名称(减去前导$)后跟一个冒号来传递的,代码简化为:

$output = htmlentities($myText, double_encode: false) ;

哪里可以找到定制的函数

如果你的定制函数在被使用的同一个页面上,那么你在哪里声明这个函数并不重要;可以是使用前也可以是使用后。然而,将函数存储在一起是一个好主意,无论是在页面的顶部还是底部。这使得它们更容易找到和维护。

在多个页面中使用的函数最好存储在每个页面包含的外部文件中。包含带有includerequire的外部文件将在下一章详细介绍。当函数在外部文件中时,您必须在调用其任何函数之前包含外部文件*。*

创建匿名函数

匿名函数允许创建没有指定名称的函数。它们在你需要一个只使用一次的函数的情况下很有用,比如一个回调函数

Tip

回调函数是作为参数传递给另一个函数的函数,然后在外部函数中调用该函数来完成某种例程或操作。你会在第八章中看到匿名回调的实际例子。

匿名函数的基本语法与普通函数相同,只是它没有名字。如果您将它作为参数传递给另一个函数,它看起来像这样:

function ($arguments) {
    // body of function
}

举个简单的例子,这个匿名函数将一个数字加倍,并返回结果:

function ($num) {
    return $num * 2;
}

您也可以像这样给变量分配一个匿名函数:

$anon = function ($arguments) {
    // body of function
};

Caution

右花括号后面必须有一个分号,因为这是一个将函数赋给变量的语句。

当函数体包含大量代码,如果直接作为参数传递给另一个函数会使代码难以阅读时,将函数赋给变量非常有用。当作为回调参数传递时,单独传递变量。在任何其他上下文中,通过在变量后附加一对括号来调用匿名函数,并像这样传递参数:

$anon($arguments);

如果您想将值从父作用域传递给匿名函数,可以使用如下的use结构:

function ($arguments) use ($fromParentScope) {
    // body of function
    // do something with $fromParentScope
}

通过use构造传递的值可以被匿名函数修改,方法是在参数前面加上一个&,与本章前面的“通过引用传递:更改参数的值”中描述的方式相同。

使用箭头函数的简洁匿名语法

如果匿名函数节省了输入,箭头函数节省更多。语法如下所示:

fn ($arguments) => expression

上一节中的数字加倍匿名函数可以重写为箭头函数,如下所示:

fn ($num) => $num * 2

function关键字被缩短为fn。没有花括号;并且省略了return关键字。您也可以将箭头函数分配给变量,如下所示:

$anon = fn ($num) => $num * 2;

箭头函数可以自动访问父作用域中的变量。在下面的示例中,arrow 函数将父范围中的 y 值添加到参数 y 值添加到参数 y值添加到参数x 中:

$y = 3;
$anon = fn ($x) => $x + $y;
echo $anon(5);  // displays 8

但是,箭头函数不能修改父作用域中的值。以下内容没有影响:

$y = 3;
$anon = fn () => $y++;
echo $anon();  // displays 3; the value of $y is not changed

要从父作用域中更改一个值,您需要一个匿名函数的更详细的语法,并通过引用前面一节中描述的use构造来传递值。

理解 PHP 类和对象

类是面向对象编程 (OOP)的基本构建模块,面向对象编程是一种旨在使代码可重用且更易于维护的编程方法。PHP 对 OOP 有广泛的支持,新特性经常以面向对象的方式实现。

对象是一种复杂的数据类型,可以存储和操作值。一个是定义一个对象特性的代码,可以看作是制作对象的蓝图。

使用 PHP 内置类

在 PHP 的许多内置类中,两个特别有趣的是处理日期和时区的DateTimeDateTimeZone类。要创建一个对象,可以使用new关键字和类名,如下所示:

$now = new DateTime();

这创建了一个DateTime类的实例,并将其存储在一个名为$nowDateTime对象中,该对象不仅知道其创建的日期和时间,还知道 web 服务器使用的时区。大多数类都有属性和方法,就像变量和函数一样,只是它们与类的特定实例相关。例如,您可以使用DateTime类的方法来更改某些值,比如月、年或时区。DateTime 对象还能够执行日期计算,这在使用普通函数时要复杂得多。

您可以使用->操作符(一个连字符后跟一个大于号)来访问对象的属性和方法。要重置DateTime对象的时区,将DateTimeZone对象作为参数传递给setTimezone()方法,如下所示:

$westcoast = new DateTimeZone('America/Los_Angeles');
$now->setTimezone($westcoast);

这会将$now更改为洛杉矶的当前日期和时间,而不管 web 服务器位于何处,并自动根据夏令时进行调整。

使用->操作符以同样的方式访问对象的属性:

$someObject->propertyName

构建自定义类

你可以用 PHP 定义自己的类,就像定义一个函数一样。不同之处在于,一个类通常包含一组设计用来协同工作的函数(称为方法)和变量(称为属性)。一个类中的每个函数通常应该专注于一个任务。代码也应该是通用的,所以它不依赖于特定的网页。您还可以创建子类(也称为子类)来添加或修改现有类的功能。

定义 PHP 类很简单。您使用class关键字,后跟类名,然后将该类的所有代码放在一对花括号中。按照惯例,类名以大写字母开头,类存储在与类同名的单独文件中。您不能使用 www.php.net/manual/en/reserved.php 中列出的任何保留字作为类的名称。

大多数类都有一个构造函数,用于在创建对象的新实例时初始化任何属性。基本的构造函数语法如下所示:

__construct($arguments) {
    // initialization of object
}

Caution

PHP 8 不再将与类同名的方法视为构造函数。你必须使用__construct()。注意,它以两个下划线开始,而不是一个。

访问类中的方法和属性

PHP 类使用保留变量$this来引用对象的当前实例。要调用类定义中的一个类方法,请使用箭头运算符,如下所示:

$this->myMethod();

类似地,通过使用箭头运算符访问属性并赋值来设置属性的值,如下所示:

$this->myProperty = 4;

设置类方法、属性和常数的可见性

类定义可以通过在声明前添加以下关键字之一来设置方法、属性和常数的可见性:

  • 这使得它在任何地方都是可见的,包括在类定义之外,允许你调用一个方法,访问或者改变一个属性的值,或者使用一个常量的值。

  • protected:这限制了对类定义内部或父类或子类的访问。

  • private:这限制了对定义类的访问。

声明属性时,必须定义它的可见性,后面可以选择数据类型。声明方法和常量的可见性是可选的。没有任何显式可见性的方法和常量被视为public

通常的做法是在类定义的顶部声明属性。如果为属性指定默认值,则该值必须是实际值,而不是从另一个变量派生的表达式的结果。更改默认值的一种方法是将一个参数传递给构造函数,并将其重新分配给属性,如下所示:

class MyClass {
    protected int myValue = 42;

    public function __construct(int $value) {
        $this->myValue = $value;
        // other initialization code
    }
}

使用构造函数属性提升

PHP 8 引入了一种声明和设置属性值的简写语法。当构造函数参数包含可见性修饰符时,PHP 将其解释为对象属性和构造函数参数,并将参数值赋给属性。这避免了单独声明属性的需要。因此,上一节中的示例可以简化如下:

class MyClass {
    public function __construct(protected int myValue = 42) {
        // other initialization code
    }
}

如果没有其他需要初始化的,构造函数方法可以是空的。

声明和使用类常量

要创建一个类常量,在类定义中使用const关键字声明它,可以选择在它前面加上一个可见性声明。例如,这将 42 设置为只能在子类或父类中访问的常量:

protected const ULTIMATE_ANSWER = 42;

通常的惯例是常量名称全部大写,以提醒常量的值不能更改。

Note

虽然常量的值不能在类内部或由类的实例更改,但它可以由子类重新定义。

要在类或子类中访问常量的值,可以使用 self 或 parent 数据类型,后跟范围解析运算符(一对冒号),如下所示:

self::ULTIMATE_ANSWER
parent::ULTIMATE_ANSWER

如果类常量已被显式公开或定义时没有可见性声明,则可以使用范围解析运算符通过类的实例在类定义外部访问其值。例如,它访问一个名为$myObject的对象的类常量的值:

$myObject::ULTIMATE_ANSWER

使用命名空间避免命名冲突

一旦你开始使用别人(包括本书中的人)创建的脚本和类,就会有多个类同名的危险。PHP 通过使用名称空间将相关的类、函数和常数分组来解决这个问题。

一种常见的策略是将类定义存储在描述其功能的文件夹结构中,并根据域名或公司名为顶级文件夹指定一个唯一的名称。名称空间可以有子级别,因此文件夹结构被复制为由反斜杠分隔的子名称空间。命名空间也是单独声明的,允许您使用简单的类名。

例如,在第九章中,你将创建一个名为Upload的类。为了避免命名冲突,它将被创建在一个名为Php8Solutions\File的名称空间中。

使用关键字namespace在文件的顶部声明一个名称空间,后跟如下名称空间:

namespace Php8Solutions\File;

Caution

PHP 在所有操作系统上都使用反斜杠作为名称空间分隔符。不要试图在 Linux 或 macOS 上将其改为正斜杠。

因此,在这个名称空间中,名为Upload的类的完全限定名是Php8Solutions\File\Upload

导入命名空间类

为了避免每次引用命名空间类时都必须使用完全限定名,可以在脚本的开头用关键字use导入类,如下所示:

use Php8Solutions\File\Upload;

Caution

关键字use必须在脚本的顶层声明。它不能嵌套在条件语句中。

然后,您可以将该类称为Upload,而不是使用完全限定名。事实上,您可以使用关键字as为导入的类分配一个别名,如下所示:

use Php8Solutions\File\Upload as FileUploader;

该类可以被称为FileUploader。使用别名主要在大型应用中有用,在这些应用中,来自不同框架的两个类具有相同的名称。用use关键字导入一个类只是声明您想使用一个名字更短的类。你仍然需要包含类定义(包含外部文件是第五章的主题)。

Note

本章只讲述了在 PHP 中使用类和对象的基本知识。更多详情,请查阅 www.php.net/manual/en/language.oop5.php 的文档。

处理错误和异常

从 PHP 7 开始,大多数错误都是通过抛出一个异常来报告的——或者生成一个特殊类型的对象,该对象包含导致错误的原因以及错误出现的位置的详细信息。如果您使用过 PHP 以前的版本,您可能会注意到的唯一区别是错误消息的措辞或错误类型不同。但是,由内部错误(比如解析错误或缺少包含文件)引发的异常和由脚本引发的异常之间有细微的区别。

当 PHP 由于内部错误抛出异常时,它会立即暂停脚本。如果您按照测试环境中的建议打开了错误消息的显示,PHP 会显示一条消息,指出发生了什么。有时这些消息可能很难解释,所以捕捉异常通常是个好主意。您可以通过将主脚本封装在一个名为try的块中,并将错误处理代码放在一个catch块中,如下所示:

try {
    // main script goes here
} catch (Throwable $t) {
    echo $t->getMessage();
}

Tip

catch块中的Throwable类型声明涵盖了内部错误和脚本抛出的异常(用户异常)。

这将产生一条错误消息,通常比某些错误产生的冗长消息更容易理解。

您可以使用关键字throw抛出自定义异常,如下所示:

if (error occurs) {
    throw new Exception('Houston, we have a problem.');
}

括号内的字符串用作错误消息,可以在catch块中捕获。

Caution

错误消息对于帮助您解决开发过程中的问题至关重要。但是,当您在活动的网站上部署脚本时,它们可能会泄露对恶意攻击者有用的信息。上线时,用中性消息替换catch块中的错误消息。或者,使用catch块将访问者重定向到错误页面。

动态创建新变量

PHP 支持创建所谓的变量。那不是印刷错误。变量变量创建一个新的变量,它的名字来源于一个现有的变量。以下示例显示了其工作原理(参见variable_variables.php):

$location = 'city';

前面的语句将字符串“city”赋给一个名为$location的变量。你可以通过使用两个美元符号来创建一个变量,就像这样:

$$location = 'London';

变量 variable 以原始变量的值作为其名称。换句话说,$$location$city相同:

echo $city; // London

您将在第六章的邮件处理脚本中看到这种技术的实际例子。

Tip

为了表明这个双$是有意的,你可以用花括号将变量括起来,这样来创建变量变量:${$location}。大括号是可选的,但是使代码更容易阅读。

现在谈谈解决方案

前四章是关于理论的——很重要,但没什么意思。本书的其余部分涉及到实际问题:让 PHP 解决现实世界的问题。所以,事不宜迟,让我们继续讨论 PHP 8 解决方案。