PHP-编程指南第四版-一-

53 阅读41分钟

PHP 编程指南第四版(一)

原文:zh.annas-archive.org/md5/516bbc09499c161bb049b4edb114d468

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

很难相信将近 20 年前,我第一次拿起了我的第一本 PHP 书。我对编程有兴趣,超越了 Netscape Composer 和静态 HTML。我知道 PHP 能让我创建动态、更智能的网站,并且存储和获取数据以创建交互式的 Web 应用程序。

我不知道的是,解锁 PHP 的这些新能力将带我走向何方,或者 PHP 将如何在 20 年后演变成为支配大约 80%的 Web 的编程语言,并得到一个友善、友好和充满活力的社区的支持。

千里之行始于足下。通过选择 Peter MacIntyre 和 Kevin Tatroe 的Programming PHP,你不仅迈出了进入 PHP 及其基础的第一步,还迈出了进入网站和 Web 应用程序开发未来的一步。凭借可用的工具和对 PHP 编程语言的深入理解,唯一的限制将是你的想象力和继续成长并沉浸在社区中的意愿。这段旅程属于你,可能性无限,未来由你定义。

当你准备开始这段旅程时,我想分享几条建议。首先,把每一章节实践,尝试不同的方法,不要害怕出错或失败。虽然Programming PHP会为你奠定坚实的基础,但探索语言并找到汇集所有这些组成部分的新创意方式,仍由你来决定。

我的第二条建议是:成为 PHP 社区的积极一员。尽可能利用在线社区、用户组和 PHP 会议。当你尝试新事物时,与社区分享并获取他们的反馈和建议。

不仅可以找到一个支持的社群——一群最友善的人,他们希望你成功,并乐意花时间帮助你在编程旅程中取得进展——而且你还将建立一个持续学习的基础,帮助你更快掌握 PHP 的核心技能,并保持对新的编程理论、技术、工具和变化的了解。更不用说,你将会遭遇一连串的糟糕双关语(包括我自己的)。

因此,我很荣幸能够成为第一批欢迎你并祝愿你旅程顺利的人之一——而这本书无疑是开启这段旅程的最好开始!

迈克尔·斯托,作家、演讲家和技术专家

2020 年冬,加利福尼亚州旧金山

前言

现在比以往任何时候,网络是企业和个人沟通的主要工具。网站携带地球全景卫星图像;寻找外太空生命;存放个人照片相册、企业购物车和产品列表等等!许多这样的网站都是由 PHP 驱动的,这是一种主要用于生成 HTML 内容的开源脚本语言。

自 1994 年成立以来,PHP 已经席卷了互联网,今天仍然保持其惊人的增长。由 PHP 驱动的数百万个网站证明了它的流行和易用性。普通人可以学习 PHP,并用它构建强大的动态网站。

核心 PHP 语言(版本 7+)提供了强大的字符串和数组处理功能,以及对面向对象编程的大大改进的支持。通过使用标准和可选的扩展模块,PHP 应用程序可以与数据库(如 MySQL 或 Oracle)交互、绘制图形、创建 PDF 文件和解析 XML 文件。你可以在 Windows 上运行 PHP,这让你可以控制其他 Windows 应用程序(例如使用 COM 控制 Word 和 Excel)或者通过 ODBC 与数据库交互。

本书是关于 PHP 语言的指南。当你完成它(我们不会告诉你它怎么结束!),你将了解 PHP 语言的工作原理,如何使用 PHP 标配的许多强大扩展,并设计和构建你自己的 PHP Web 应用程序。

受众

PHP 是一个融合了多种文化的技术。网页设计师喜欢它的易用性和便利性,而程序员则赞赏它的灵活性、强大性、多样性和速度。两种文化都需要对这门语言有清晰准确的参考。如果你是(Web)程序员,那么这本书适合你。我们展示了 PHP 语言的整体架构,然后深入讨论细节,不浪费你的时间。大量的示例澄清了文本解释;实用的编程建议和许多风格提示将帮助你不仅成为 PHP 程序员,而且是优秀的PHP 程序员。

如果你是网页设计师,你将喜欢特定技术的清晰和实用指南,如 JSON、XML、会话、PDF 生成和图形处理。而且你可以从语言章节中迅速获取所需的信息,这些章节以简单的术语解释基本编程概念。

本版已完全修订,以涵盖 PHP 7.4 版本的最新功能。

本书的假设

本书假定你具有 HTML 的工作知识。如果你不懂 HTML,在尝试学习 PHP 之前,你应该先对简单的网页有所了解。关于 HTML 的更多信息,我们推荐查看HTML & XHTML: The Definitive Guide(由 Chuck Musciano 和 Bill Kennedy(O'Reilly)著)。

本书内容

我们安排了本书的内容,以便你可以从头到尾阅读,也可以跳跃阅读只关注你感兴趣的主题。本书分为 18 章和 1 个附录,具体如下:

第一章,PHP 简介

探讨 PHP 的历史,并快速概述了 PHP 程序的可能性。

第二章,语言基础

是 PHP 程序元素的简明指南,如标识符、数据类型、运算符和流控制语句。

第三章,函数

讨论用户定义的函数,包括作用域、可变长度参数列表以及可变和匿名函数。

第四章,字符串

涵盖了在 PHP 代码中构建、分解、搜索和修改字符串时将使用的函数。

第五章,数组

详细介绍了在 PHP 代码中构造、处理和排序数组的符号和函数。

第六章,对象

涵盖了 PHP 更新的面向对象特性。在本章中,您将了解类、对象、继承和内省的内容。

第七章,日期和时间

讨论日期和时间操作,如时区和日期计算。

第八章,Web 技术

讨论大多数 PHP 程序员最终想要使用的技术,包括处理 Web 表单数据、维护状态以及处理 SSL。

第九章,数据库

讨论 PHP 处理数据库的模块和函数,以 MySQL 数据库为例。同时也涵盖了 SQLite 和 PDO 数据库接口。此外还介绍了 NoSQL 概念。

第十章,图形

演示如何在 PHP 内部创建和修改各种格式的图像文件。

第十一章,PDF

解释如何从 PHP 应用程序创建动态 PDF 文件。

第十二章,XML

介绍 PHP 的扩展,用于生成和解析 XML 数据。

第十三章,JSON

涵盖了 JavaScript 对象表示法(JSON),这是一种设计成极其轻量和人类可读的标准化数据交换格式。

第十四章,安全

为编写安全脚本的程序员提供宝贵的建议和指导。您将学习编程最佳实践,帮助您避免可能导致灾难的错误。

第十五章,应用技巧

讨论编码技术,如实现代码库、以独特方式处理输出和错误处理。

第十六章,Web 服务

描述了通过 REST 工具和云连接进行外部通信的技术。

第十七章,调试 PHP

讨论调试 PHP 代码的技术以及编写可调试 PHP 代码的方法。

第十八章,不同平台上的 PHP

讨论 PHP Windows 版本的技巧和陷阱。还讨论了一些 Windows 特有的功能,如 COM。

附录

用作 PHP 所有核心函数的便捷快速参考。

本书中使用的约定

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

斜体

指示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽字体

用于程序清单,以及段落内引用程序元素如变量或函数名、数据库、数据类型、环境变量、语句和关键字。

粗体等宽字体

显示用户应按字面输入的命令或其他文本。

斜体等宽字体

显示应替换为用户提供的值或由上下文确定值的文本。

此图标表示提示、建议、一般注意事项、警告或注意。

O’Reilly 在线学习

超过 40 年来,O’Reilly Media 提供技术和商业培训、知识和洞见,帮助公司取得成功。

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

如何联系我们

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

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

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

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

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

  • 707-829-0104(传真)

我们为本书设立了一个网页,列出勘误、示例和任何额外信息。您可以访问此页面:https://oreil.ly/programming-PHP-4e

发送电子邮件至bookquestions@oreilly.com以评论或提出关于本书的技术问题。

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

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

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

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

致谢

Kevin Tatroe

再次感谢所有曾经为 PHP 贡献过代码、为 PHP 生态系统做出贡献或编写过 PHP 代码的个人。正是你们使 PHP 成为了现在和将来的样子。

致我的父母,他们曾在一次漫长而惊险的飞行旅程中为我购买了一个小的乐高套装,开始了一个创造力和组织的痴迷,这至今仍然放松和激励着我。

最后,对 Jenn 和 Hadden 的大量感激,感谢他们每天帮助启发和鼓励我。

Peter MacIntyre

我想赞美万军之耶和华,祂赐给我面对每一天的力量!祂通过电力创造了我谋生的方式;感谢和赞美祂为祂创造的这完全独特而迷人的一部分!

对于再次成为我在这版主要合著者的 Kevin,感谢你的努力,再次专注于这个项目直至出版。

致力于筛选我们的代码示例并测试它们确保我们“说真话”的技术编辑们——Lincoln、Tanja、Jim 和 James——谢谢你们!

最后,对 O’Reilly 的所有那些经常被忽略的人们——我不知道你们所有人的名字,但我知道你们为了让这样一个项目最终“上线”所做的努力。编辑、图形工作、布局、规划、营销等等,所有这些工作都必须完成,我非常感激你们为此目标所做的一切辛勤工作。

第一章:PHP 简介

PHP 是一种简单但功能强大的语言,专为创建 HTML 内容而设计。本章介绍了 PHP 语言的基本背景,描述了 PHP 的性质和历史,它可以运行在哪些平台上,以及如何配置它。本章最后展示了 PHP 的实际应用,通过快速演示几个 PHP 程序,展示常见任务的处理,例如处理表单数据、与数据库交互和创建图形等。

PHP 能做什么?

PHP 可以以两种主要方式使用:

服务器端脚本

PHP 最初是为创建动态 Web 内容而设计的,至今仍然非常适合这项任务。要生成 HTML,您需要 PHP 解析器和一个 Web 服务器来发送编码的文档文件。PHP 也因其通过数据库连接生成动态内容而变得流行,还支持 XML 文档、图形、PDF 文件等更多内容。

命令行脚本

PHP 可以像 Perl、awk 或 Unix shell 一样从命令行运行脚本。您可以使用命令行脚本执行系统管理任务,例如备份和日志解析;甚至某些 CRON 作业类型的脚本也可以用这种方式完成(作为非可视化 PHP 任务)。

然而,在本书中,我们集中讨论第一项内容:使用 PHP 开发动态 Web 内容。

PHP 可在所有主要操作系统上运行,包括 Unix 变种(包括 Linux、FreeBSD、Ubuntu、Debian 和 Solaris)、Windows 和 macOS。它可以与所有主流的 Web 服务器一起使用,包括 Apache、Nginx 和 OpenBSD 等服务器;甚至像 Azure 和 Amazon 这样的云环境也在不断增长。

PHP 语言本身非常灵活。例如,您不仅限于输出 HTML 或其他文本文件 - 任何文档格式都可以生成。PHP 内置支持生成 PDF 文件以及 GIF、JPEG 和 PNG 图像。

PHP 最显著的特性之一是其广泛支持的数据库。PHP 支持所有主要的数据库(包括 MySQL、PostgreSQL、Oracle、Sybase、MS-SQL、DB2 和符合 ODBC 标准的数据库),甚至支持许多不太常见的数据库。甚至像 CouchDB 和 MongoDB 这样的新型 NoSQL 风格的数据库也得到了支持。通过 PHP,从数据库创建具有动态内容的网页非常简单。

最后,PHP 提供了一个 PHP 代码库,用于执行常见任务,如数据库抽象、错误处理等,通过 PHP 扩展和应用程序库(PEAR)。PEAR 是一个用于可重用 PHP 组件的框架和分发系统。

PHP 的简要历史

Rasmus Lerdorf 最初在 1994 年构思了 PHP,但今天人们使用的 PHP 与最初版本大不相同。要理解 PHP 是如何发展到现在的,了解语言的历史演变是很有用的。这里有详细的评论和来自 Rasmus 自己的电子邮件。

PHP 的演变

这是 PHP 1.0 的公告,发布于 1995 年 6 月的 Usenet 新闻组(comp.infosystems.www.authoring.cgi):

From: rasmus@io.org (Rasmus Lerdorf)
Subject: Announce: Personal Home Page Tools (PHP Tools)
Date: 1995/06/08
Message-ID: <3r7pgp$aa1@ionews.io.org>#1/1
organization: none
newsgroups: comp.infosystems.www.authoring.cgi

Announcing the Personal Home Page Tools (PHP Tools) version 1.0.

These tools are a set of small tight cgi binaries written in C.
They perform a number of functions including:

. Logging accesses to your pages in your own private log files
. Real-time viewing of log information
. Providing a nice interface to this log information
. Displaying last access information right on your pages
. Full daily and total access counters
. Banning access to users based on their domain
. Password protecting pages based on users' domains
. Tracking accesses ** based on users' e-mail addresses **
. Tracking referring URL's - HTTP_REFERER support
. Performing server-side includes without needing server support for it
. Ability to not log accesses from certain domains (ie. your own)
. Easily create and display forms
. Ability to use form information in following documents

Here is what you don't need to use these tools:

. You do not need root access - install in your ~/public_html dir
. You do not need server-side includes enabled in your server
. You do not need access to Perl or Tcl or any other script interpreter
. You do not need access to the httpd log files

The only requirement for these tools to work is that you have
the ability to execute your own cgi programs. Ask your system
administrator if you are not sure what this means.

The tools also allow you to implement a guestbook or any other
form that needs to write information and display it to users
later in about 2 minutes.

The tools are in the public domain distributed under the GNU
Public License. Yes, that means they are free!

For a complete demonstration of these tools, point your browser
at: http://www.io.org/~rasmus

--
Rasmus Lerdorf
rasmus@io.org
http://www.io.org/~rasmus

请注意,此消息中显示的 URL 和电子邮件地址早已消失。这份公告的语言反映了当时人们关注的问题,比如密码保护页面、轻松创建表单以及在后续页面访问表单数据。此外,该公告还说明了 PHP 最初作为一系列有用工具的框架的定位。

公告只谈到了随 PHP 一起提供的工具,但在幕后的目标是创建一个框架,使得扩展 PHP 并添加更多工具变得容易。这些插件的业务逻辑是用 C 编写的;一个简单的解析器从 HTML 中提取标签并调用各种 C 函数。真正的计划从来不是创建一个脚本语言。

那么发生了什么呢?

Rasmus 开始为多伦多大学进行一个相当大的项目,需要一个工具来整合来自各处的数据并呈现一个漂亮的基于 Web 的管理界面。当然,他使用了 PHP 来完成这项任务,但出于性能原因,需要更好地将 PHP 1.0 的各种小工具整合到一起,并集成到 Web 服务器中。

最初,对 NCSA Web 服务器进行了一些修改,以补丁形式支持 PHP 核心功能。这种方法的问题在于,作为用户,你必须用这种特殊的、经过修改的版本替换你的 Web 服务器软件。幸运的是,此时 Apache 也开始获得势头,Apache API 使得像 PHP 这样向服务器添加功能变得更加容易。

在接下来的一年左右,完成了大量工作,关注重点也有了相当大的变化。以下是 PHP 2.0(PHP/FI)的公告,发布于 1996 年 4 月:

 From: rasmus@madhaus.utcs.utoronto.ca (Rasmus Lerdorf)
 Subject: ANNOUNCE: PHP/FI Server-side HTML-Embedded Scripting Language
 Date: 1996/04/16
 Newsgroups: comp.infosystems.www.authoring.cgi

 PHP/FI is a server-side HTML embedded scripting language. It has built-in
 access logging and access restriction features and also support for
 embedded SQL queries to mSQL and/or Postgres95 backend databases.

 It is most likely the fastest and simplest tool available for creating
 database-enabled web sites.

 It will work with any UNIX-based web server on every UNIX flavour out
 there. The package is completely free of charge for all uses including
 commercial.

 Feature List:

 . Access Logging
 Log every hit to your pages in either a dbm or an mSQL database.
 Having hit information in a database format makes later analysis easier.
 . Access Restriction
 Password protect your pages, or restrict access based on the refering URL
 plus many other options.
 . mSQL Support
 Embed mSQL queries right in your HTML source files
 . Postgres95 Support
 Embed Postgres95 queries right in your HTML source files
 . DBM Support
 DB, DBM, NDBM and GDBM are all supported
 . RFC-1867 File Upload Support
 Create file upload forms
 . Variables, Arrays, Associative Arrays
 . User-Defined Functions with static variables + recursion
 . Conditionals and While loops
 Writing conditional dynamic web pages could not be easier than with
 the PHP/FI conditionals and looping support
 . Extended Regular Expressions
 Powerful string manipulation support through full regexp support
 . Raw HTTP Header Control
 Lets you send customized HTTP headers to the browser for advanced
 features such as cookies.
 . Dynamic GIF Image Creation
 Thomas Boutell's GD library is supported through an easy-to-use set of
 tags.

 It can be downloaded from the File Archive at: <URL:http://www.vex.net/php>

 --
 Rasmus Lerdorf
 rasmus@vex.net

这是第一次使用术语脚本语言。PHP 1.0 的简单标签替换代码被一个可以处理更复杂的嵌入式标签语言的解析器替换。按照今天的标准来看,这种标签语言并不特别复杂,但与 PHP 1.0 相比,它确实是。

这种变化的主要原因是,实际上使用 PHP 1.0 的人中很少有人真正有兴趣使用基于 C 的框架来创建插件。大多数用户更感兴趣的是能够直接在他们的网页中嵌入逻辑,以创建条件 HTML、自定义标签和其他类似功能。PHP 1.0 的用户不断要求能够添加点击跟踪页脚或有条件地发送不同的 HTML 块的功能。这导致了 if 标签的创建。一旦有了 if,你也需要 else,从那时起,你不管是否愿意,最终会编写出一个完整的脚本语言。

到 1997 年中期,PHP 2.0 已经有了很大的发展,并吸引了许多用户,但底层解析引擎仍然存在一些稳定性问题。该项目当时仍然主要是一个人的努力,偶尔会有一些贡献。在这一点上,以色列特拉维夫的 Zeev Suraski 和 Andi Gutmans 自愿重写了底层解析引擎,并且我们同意将他们的重写作为 PHP 版本 3.0 的基础。其他人也自愿参与 PHP 的其他部分的工作,这个项目从一个主要由少数贡献者组成的个人努力转变为一个真正的开源项目,在世界各地拥有许多开发人员。

这里是 PHP 3.0 的 1998 年 6 月公告:

 June 6, 1998 -- The PHP Development Team announced the release of PHP 3.0,
 the latest release of the server-side scripting solution already in use on
 over 70,000 World Wide Web sites.

 This all-new version of the popular scripting language includes support
 for all major operating systems (Windows 95/NT, most versions of Unix,
 and Macintosh) and web servers (including Apache, Netscape servers,
 WebSite Pro, and Microsoft Internet Information Server).

 PHP 3.0 also supports a wide range of databases, including Oracle,
 Sybase, Solid, MySQ, mSQL, and PostgreSQL, as well as ODBC data sources.

 New features include persistent database connections, support for the
 SNMP and IMAP protocols, and a revamped C API for extending the language
 with new features.

 "PHP is a very programmer-friendly scripting language suitable for
 people with little or no programming experience as well as the
 seasoned web developer who needs to get things done quickly. The
 best thing about PHP is that you get results quickly," said
 Rasmus Lerdorf, one of the developers of the language.

 "Version 3 provides a much more powerful, reliable, and efficient
 implementation of the language, while maintaining the ease of use and
 rapid development that were the key to PHP's success in the past,"
 added Andi Gutmans, one of the implementors of the new language core.

 "At Circle Net we have found PHP to be the most robust platform for
 rapid web-based application development available today," said Troy
 Cobb, Chief Technology Officer at Circle Net, Inc. "Our use of PHP
 has cut our development time in half, and more than doubled our client
 satisfaction. PHP has enabled us to provide database-driven dynamic
 solutions which perform at phenomenal speeds."

 PHP 3.0 is available for free download in source form and binaries for
 several platforms at http://www.php.net/.

 The PHP Development Team is an international group of programmers who
 lead the open development of PHP and related projects.

 For more information, the PHP Development Team can be contacted at
 core@php.net.

在发布 PHP 3.0 后,其使用量真正开始增长。版本 4.0 是由一些开发人员推动的,他们有兴趣对 PHP 的架构进行一些基本的更改。这些变化包括将语言与 Web 服务器之间的层抽象化,添加线程安全机制,以及添加更高级的两阶段解析/执行标记解析系统。这个由 Zeev 和 Andi 主要编写的新解析器被命名为 Zend 引擎。在许多开发人员的共同努力下,PHP 4.0 于 2000 年 5 月 22 日发布。

本书出版时,PHP 版本 7.3 已经发布了一段时间。已经发布了几个小的“点”版本,并且当前版本的稳定性非常高。正如您将在本书中看到的那样,这个 PHP 版本在服务器端的代码处理方面已经取得了一些重大进展。还包括许多其他小的更改、函数添加和功能增强。

PHP 的广泛使用

图 1-1 显示了根据 W3Techs 编译的 PHP 使用情况,截至 2019 年 3 月。这里最有趣的数据是,在所有调查的网站中,79% 使用了它,但仍然最广泛使用的是版本 5.0。如果您查看 W3Techs 调查 中使用的方法,您将看到他们选择了全球排名前 1000 万的网站(基于流量;网站流行度)。显然,PHP 的广泛采用确实令人印象深刻!

2019 年 3 月 PHP 使用情况

图 1-1 PHP 使用情况截至 2019 年 3 月

安装 PHP

如前所述,PHP 可用于许多操作系统和平台。因此,建议您查阅 PHP 文档,以找到最符合您将使用的环境,并遵循相应的设置说明。

有时,您可能还想更改 PHP 配置的方式。要做到这一点,您将不得不更改 PHP 配置文件并重新启动您的 Web(Apache)服务器。每次您更改 PHP 的环境时,都必须重新启动 Web(Apache)服务器,以使这些更改生效。

PHP 的配置设置通常保存在名为php.ini的文件中。该文件中的设置控制 PHP 特性的行为,比如会话处理和表单处理。本书后面的章节会提到一些php.ini选项,但通常情况下,本书中的代码不需要自定义配置。详细信息请参阅PHP 文档了解如何配置php.ini

PHP 简介

PHP 页面通常是带有嵌入 PHP 命令的 HTML 页面。这与许多其他动态网页解决方案形成对比,后者是生成 HTML 的脚本。Web 服务器处理 PHP 命令,并将其输出(以及来自文件的任何 HTML)发送到浏览器。示例 1-1 展示了一个完整的 PHP 页面。

示例 1-1. hello_world.php
<html>
 <head>
 <title>Look Out World</title>
 </head>

 <body>
 <?php echo "Hello, world!"; ?>
 </body>
</html>

将示例 1-1 的内容保存到文件hello_world.php中,并将浏览器指向它。结果显示在图 1-2 中。

hello_world.php 的输出

图 1-2. hello_world.php 的输出

PHP 的echo命令会输出内容(本例中是字符串“Hello, world!”),并插入到 HTML 文件中。在这个例子中,PHP 代码位于<?php?>标签之间。还有其他标记 PHP 代码的方法,请参阅第二章获取完整说明。

配置页面

PHP 函数phpinfo()创建一个 HTML 页面,包含有关 PHP 安装和当前配置的详细信息。您可以使用它查看是否安装了特定扩展,或者php.ini文件是否已定制。示例 1-2 是显示phpinfo()页面的完整页面。

示例 1-2. 使用 phpinfo()
<?php phpinfo();?>

图 1-3 展示了示例 1-2 输出的第一部分。

phpinfo()的部分输出

图 1-3. phpinfo()的部分输出

表单

示例 1-3 创建并处理表单。用户提交表单后,输入的名称字段信息通过$_SERVER['PHP_SELF']表单动作发送回该页面。PHP 代码检查名称字段,并在找到时显示问候语。

示例 1-3. 处理表单(form.php)
<html>
 <head>
 <title>Personalized Greeting Form</title>
 </head>

 <body>
 <?php if(!empty($_POST['name'])) {
 echo "Greetings, {$_POST['name']}, and welcome.";
 } ?>

 <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
 Enter your name: <input type="text" name="name" />
 <input type="submit" />
 </form>
 </body>
</html>

表单和消息显示在图 1-4 中。

表单和问候页面

图 1-4. 表单和问候页面

PHP 程序主要通过$_POST$_GET数组变量访问表单值。第八章详细讨论了表单和表单处理。

数据库

PHP 支持所有流行的数据库系统,包括 MySQL、PostgreSQL、Oracle、Sybase、SQLite 和兼容 ODBC 的数据库。图 1-5 显示了通过 PHP 脚本运行的 MySQL 数据库查询的部分结果,显示了在图书评论站点上进行书籍搜索的结果。它列出了书名、出版年份和书籍的 ISBN 号。

通过 PHP 脚本运行的 MySQL 书籍列表查询

图 1-5. 通过 PHP 脚本运行的 MySQL 书籍列表查询

示例 1-4 中的代码连接到数据库,发出查询以检索所有可用的书籍(带有WHERE子句),并通过while循环为所有返回的结果生成表格输出。

注意

此示例数据库的 SQL 代码在提供的文件library.sql中。在创建库数据库并准备好样本数据库后,您可以将此代码插入 MySQL 中,以测试以下代码示例及其在第九章中的相关示例。

示例 1-4. 查询图书数据库(booklist.php)
<?php

$db = new mysqli("localhost", "petermac", "password", "library");

// make sure the above credentials are correct for your environment
if ($db->connect_error) {
 die("Connect Error ({$db->connect_errno}) {$db->connect_error}");
}

$sql = "SELECT * FROM books WHERE available = 1 ORDER BY title";
$result = $db->query($sql);

?>
<html>
<body>

<table cellSpacing="2" cellPadding="6" align="center" border="1">
 <tr>
 <td colspan="4">
 <h3 align="center">These Books are currently available</h3>
 </td>
 </tr>

 <tr>
 <td align="center">Title</td>
 <td align="center">Year Published</td>
 <td align="center">ISBN</td>
 </tr>
 <?php while ($row = $result->fetch_assoc()) { ?>
 <tr>
 <td><?php echo stripslashes($row['title']); ?></td>
 <td align="center"><?php echo $row['pub_year']; ?></td>
 <td><?php echo $row['ISBN']; ?></td>
 </tr>
 <?php } ?>
</table>

</body>
</html>

数据库提供的动态内容推动了网络核心的新闻、博客和电子商务网站。关于如何从 PHP 访问数据库的更多详细信息,请参阅第九章。

图形

使用 PHP,您可以轻松创建和操作图像,使用 GD 扩展。示例 1-5 提供了一个文本输入字段,允许用户指定按钮的文本。它获取一个空按钮图像文件,并将传递为 GET 参数'message'的文本居中放置在上面。然后将结果作为 PNG 图像发送回浏览器。

示例 1-5. 动态按钮(graphic_example.php)
<?php
if (isset($_GET['message'])) {
 // load font and image, calculate width of text
 $font = dirname(__FILE__) . '/fonts/blazed.ttf';
 $size = 12;
 $image = imagecreatefrompng("button.png");
 $tsize = imagettfbbox($size, 0, $font, $_GET['message']);

 // center
 $dx = abs($tsize[2] - $tsize[0]);
 $dy = abs($tsize[5] - $tsize[3]);
 $x = (imagesx($image) - $dx) / 2;
 $y = (imagesy($image) - $dy) / 2 + $dy;

 // draw text
 $black = imagecolorallocate($im,0,0,0);
 imagettftext($image, $size, 0, $x, $y, $black, $font, $_GET['message']);

 // return image
 header("Content-type: image/png");
 imagepng($image);

 exit;
} ?>
<html>
 <head>
 <title>Button Form</title>
 </head>

 <body>
 <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="GET">
 Enter message to appear on button:
 <input type="text" name="message" /><br />
 <input type="submit" value="Create Button" />
 </form>
 </body>
</html>

由示例 1-5 生成的表单显示在图 1-6 中。创建的按钮显示在图 1-7 中。

按钮创建表单

图 1-6. 按钮创建表单

创建的按钮

图 1-7. 创建的按钮

您可以使用 GD 动态调整图像大小、生成图表等。PHP 还有几个扩展可以生成 Adobe 流行的 PDF 文档。在第十章中深入讨论了动态图像生成,而第十一章则介绍了如何创建 Adobe PDF 文件。

下一步

现在您已经体验了 PHP 的可能性,可以开始学习如何使用这种语言。我们从其基本结构开始,特别关注用户定义的函数、字符串操作和面向对象编程。然后我们转向特定的应用领域,如网络、数据库、图形、XML 和安全性。最后,我们提供内置函数和扩展的快速参考。掌握这些章节,您将掌握 PHP!

第二章:语言基础

本章快速浏览了核心 PHP 语言,涵盖了数据类型、变量、运算符和流控制语句等基本主题。PHP 受其他编程语言(如 Perl 和 C)的强烈影响,因此,如果您有这些语言的经验,学习 PHP 应该很容易。如果 PHP 是您的第一种编程语言之一,不要惊慌。我们从 PHP 程序的基本单元开始,并逐步构建您的知识。

词法结构

编程语言的词法结构是指控制您在该语言中编写程序的基本规则集。它是语言的最低级语法,指定变量名的外观、用于注释的字符以及如何将程序语句彼此分隔等内容。

大小写敏感性

用户定义的类和函数的名称,以及内置结构和关键字(如echowhileclass等)不区分大小写。因此,以下这三行是等效的:

echo("hello, world");
ECHO("hello, world");
EcHo("hello, world");

另一方面,变量是区分大小写的。也就是说,$name$NAME$NaME 是三个不同的变量。

语句和分号

语句是执行某些操作的 PHP 代码集合。它可以简单到变量赋值,也可以复杂到有多个退出点的循环。这里是 PHP 语句的一个小示例,包括函数调用、一些变量数据分配以及一个 if 语句:

echo "Hello, world";
myFunction(42, "O'Reilly");
$a = 1;
$name = "Elphaba";
$b = $a / 25.0;
if ($a == $b) {
 echo "Rhyme? And Reason?";
}

PHP 使用分号分隔简单语句。使用花括号标记代码块的复合语句,例如条件测试或循环,不需要在闭括号后加分号。与其他语言不同,在 PHP 中,在闭括号前的分号是不可选的:

if ($needed) {
 echo "We must have it!"; // semicolon required here
} // no semicolon required here after the brace

然而,在闭合 PHP 标签前分号是可选的:

<?php
if ($a == $b) {
 echo "Rhyme? And Reason?";
}
echo "Hello, world" // no semicolon required before closing tag
?>

最好在可选的地方包含分号,因为这样可以更轻松地添加代码。

空白和换行

一般情况下,PHP 程序中的空白不重要。您可以将语句扩展到任意行数,或将一堆语句放在一行上。例如,这条语句:

raisePrices($inventory, $inflation, $costOfLiving, $greed);

同样可以写得更多空白:

raisePrices (
 $inventory ,
 $inflation ,
 $costOfLiving ,
 $greed
) ;

或者更少的空白:

raisePrices($inventory,$inflation,$costOfLiving,$greed);

您可以利用这种灵活的格式使您的代码更易读(通过对齐赋值、缩进等)。有些懒惰的程序员利用这种自由格式创建完全不可读的代码——这是不推荐的。

注释

注释提供给阅读您代码的人的信息,但在 PHP 执行时会被忽略。即使您认为只有您会阅读您的代码,也最好在代码中包含注释——回顾几个月前编写的代码可能会觉得好像是陌生人写的。

一个良好的实践是让您的注释尽量少,不要妨碍代码本身,但足够丰富,以便您可以使用注释告诉发生了什么。不要注释显而易见的事情,以免淹没描述棘手事物的注释。例如,这是毫无价值的:

$x = 17; // store 17 into the variable $x

而在这个复杂的正则表达式上的注释将帮助维护代码的人:

// convert &#nnn; entities into characters

$text = preg_replace('/&#([0-9])+;/', "chr('\\1')", $text);

PHP 提供了几种在代码中包含注释的方法,所有这些方法都借鉴自现有的语言,如 C、C++和 Unix shell。一般来说,使用 C 风格的注释来注释代码,使用 C++风格的注释来注释代码。

Shell 风格的注释

当 PHP 在代码中遇到井号字符(#)时,从井号到行尾或 PHP 代码段的末尾(以先到者为准)的所有内容都被视为注释。这种注释方法在 Unix shell 脚本语言中很常见,可用于注释单行代码或做简短的备注。

由于井号在页面上是可见的,有时会使用 shell 风格的注释来标记代码块:

#######################
## Cookie functions
#######################

有时它们用于代码行之前,以标识该代码的功能,此时它们通常缩进到与所用于目标代码相同的级别:

if ($doubleCheck) {
 # create an HTML form requesting that the user confirm the action
 echo confirmationForm();
}

经常将单行代码的简短注释放在同一行上:

$value = $p * exp($r * $t); # calculate compounded interest

当您紧密混合 HTML 和 PHP 代码时,让闭合的 PHP 标记终止注释会很有用:

<?php $d = 4; # Set $d to 4\. ?> Then another <?php echo $d; ?>
Then another 4

C++注释

当 PHP 在代码中遇到两个斜杠(//)时,从斜杠到行尾或代码段末尾(以先到者为准),都将被视为注释。这种注释方法源于 C++。其结果与 shell 注释样式相同。

这里是使用 C++注释重写的 shell 风格注释示例:

////////////////////////
// Cookie functions
////////////////////////

if ($doubleCheck) {
 // create an HTML form requesting that the user confirm the action
 echo confirmationForm();
}

$value = $p * exp($r * $t); // calculate compounded interest

<?php $d = 4; // Set $d to 4\. ?> Then another <?php echo $d; ?>
Then another 4

C 注释

虽然 shell 风格和 C++风格的注释可用于注释代码或做简短的备注,但较长的注释需要不同的风格。因此,PHP 支持块注释,其语法源自 C 编程语言。当 PHP 遇到斜杠后跟一个星号(/*)时,从那之后直到遇到一个星号后跟一个斜杠(*/)之前的所有内容都被视为注释。这种类型的注释与前面展示的不同,可以跨多行。

这是 C 风格多行注释的示例:

/* In this section, we take a bunch of variables and
 assign numbers to them. There is no real reason to
 do this, we're just having fun.
*/
$a = 1;
$b = 2;
$c = 3;
$d = 4;

由于 C 风格的注释具有特定的起始和结束标记,您可以将它们与代码紧密集成。这倾向于使您的代码更难阅读,因此不建议使用:

/* These comments can be mixed with code too,
see? */ $e = 5; /* This works just fine. */

与其他类型不同,C 风格的注释可以延续到 PHP 结束标记后面。例如:

<?php
$l = 12;
$m = 13;
/* A comment begins here
?>
<p>Some stuff you want to be HTML.</p>
<?= $n = 14; ?>
*/
echo("l=$l m=$m n=$n\n");
?><p>Now <b>this</b> is regular HTML...</p>
`l=12 m=13 n=`
`<p``>``Now` `<b``>``this``</b>` `is regular HTML...``</p>`

您可以根据需要缩进注释:

/* There are no
 special indenting or spacing
 rules that have to be followed, either.

 */

C 风格的注释可用于禁用代码的部分。在以下示例中,我们通过将它们包含在块注释中禁用了第二和第三个语句,以及内联注释。要启用代码,我们只需删除注释标记:

$f = 6;
/*
$g = 7; # This is a different style of comment
$h = 8;
*/

但是,您必须小心不要尝试嵌套块注释:

$i = 9;
/*
$j = 10; /* This is a comment */
$k = 11;
Here is some comment text.
*/

在这种情况下,PHP 尝试(但失败)执行(非)语句 Here is some comment text 并返回错误。

字面量

字面量是程序中直接出现的数据值。以下是 PHP 中的所有字面量:

2001
0xFE
1.4142
"Hello World"
'Hi'
true
null

标识符

标识符简单来说就是一个名称。在 PHP 中,标识符用于命名变量、函数、常量和类。标识符的第一个字符必须是 ASCII 字母(大写或小写)、下划线字符 (_) 或者在 ASCII 0x7F 到 ASCII 0xFF 之间的任何字符。初始字符之后,这些字符和数字 0–9 都是有效的。

变量名

变量名总是以美元符号($)开头且区分大小写。以下是一些有效的变量名:

$bill
$head_count
$MaximumForce
$I_HEART_PHP
$_underscore
$_int

以下是一些非法的变量名:

$not valid
$|
$3wa

由于区分大小写,以下变量是不同的:

$hot_stuff $Hot_stuff $hot_Stuff $HOT_STUFF

函数名

函数名不区分大小写(函数详细讨论请参见 第三章)。以下是一些有效的函数名:

tally
list_all_users
deleteTclFiles
LOWERCASE_IS_FOR_WIMPS
_hide

这些函数名均指向同一个函数:

howdy HoWdY HOWDY HOWdy howdy

类名

类名遵循 PHP 标识符的标准规则,也不区分大小写。以下是一些有效的类名:

Person
account

类名 stdClass 是保留类名。

常量

常量是一个值的标识符,其值不会改变;标量值(布尔值、整数、浮点数和字符串)和数组可以是常量。一旦设置,常量的值就不能更改。通过 define() 函数设置常量:

define('PUBLISHER', "O'Reilly Media");
echo PUBLISHER;

关键字

关键字(或保留字)是语言为其核心功能保留的单词,您不能给函数、类或常量赋予与关键字相同的名称。表 2-1 列出了 PHP 中的关键字,这些关键字不区分大小写。

表 2-1. PHP 核心语言关键字

| __CLASS__ __DIR__

__FILE__

__FUNCTION__

__LINE__

__METHOD__

__NAMESPACE__

__TRAIT__

__halt_compiler()

abstract

and

array()

as

break

callable

case

catch

class

clone

const

continue

declare

default

die()

do | echo else

elseif

empty()

enddeclare

endfor

endforeach

endif

endswitch

endwhile

eval()

exit()

extends

final

finally

for

foreach

function

global

goto

if

implements

include

include_once

instanceof | insteadof interface

isset()

list()

namespace

new

or

print

private

protected

public

require

require_once

return

static

switch

throw

trait

try

unset()

use

var

while

xor

yield

yield from |

此外,您不能使用与内置 PHP 函数同名的标识符。完整列表请参见 附录。

数据类型

PHP 提供八种值或数据类型。其中四种是标量(单值)类型:整数、浮点数、字符串和布尔值。两种是复合(集合)类型:数组和对象。剩下的两种是特殊类型:资源和 NULL。数字、布尔值、资源和 NULL 在此详细讨论,而字符串、数组和对象是如此庞大的主题,它们有自己的章节(第 4、5 和 6 章)。

整数

整数是整数,如 1、12 和 256。可接受值的范围根据您平台的详细信息而异,但通常从 −2,147,483,648 到 +2,147,483,647。具体来说,范围相当于您的 C 编译器的长整型数据类型的范围。不幸的是,C 标准没有指定长整型应具有的范围,因此在某些系统上,整数范围可能会有所不同。

整数字面量可以用十进制、八进制、二进制或十六进制表示。十进制值由一系列数字组成,没有前导零。序列可以以加号(+)或减号()开头。如果没有符号,则假定为正数。十进制整数的示例包括以下内容:

1998641
+33

八进制数由前导 0 和由 0 到 7 的一系列数字组成。与十进制数类似,八进制数可以以加号或减号为前缀。以下是一些八进制值及其相应的十进制值示例:

0755 // decimal 493
+010 // decimal 8

十六进制值以 0x 开头,后跟一系列数字(0–9)或字母(A–F)。字母可以是大写或小写,但通常用大写字母表示。与十进制和八进制值一样,十六进制数可以包含符号:

0xFF // decimal 255
0x10 // decimal 160xDAD1 // decimal −56017

二进制数以 0b 开头,后跟一系列数字(0 和 1)。与其他值一样,二进制数可以包含符号:

0b01100000 // decimal 96
0b00000010 // decimal 2
-0b10 // decimal -2

如果您试图存储一个太大无法存储为整数或不是整数的变量,它将自动转换为浮点数。

使用 is_int() 函数(或其 is_integer() 别名)来测试一个值是否为整数:

if (is_int($x)) {
 // $x is an integer
}

浮点数

浮点数(通常称为“实数”)用十进制数字表示数值。与整数类似,其限制取决于您计算机的详细信息。PHP 浮点数相当于您的 C 编译器的双精度数据类型的范围。通常,这允许在 1.7E−308 到 1.7E+308 之间的数值,具有 15 位精度。如果您需要更高的精度或更广范围的整数值,可以使用 BC 或 GMP 扩展。

PHP 可识别两种不同格式的浮点数。有一种是我们日常使用的:

3.14
0.017
-7.1

但是 PHP 也能识别科学计数法表示的数字:

0.314E1 // 0.314*10¹, or 3.14
17.0E-3 // 17.0*10^(-3), or 0.017

浮点数值只是数字的近似表示。例如,在许多系统上,3.5 实际上表示为 3.4999999999. 这意味着您必须小心避免编写假定浮点数完全准确表示的代码,例如直接使用==比较两个浮点数值。通常的方法是比较几个小数位数:

if (intval($a * 1000) == intval($b * 1000)) {
 // numbers equal to three decimal places
}

使用is_float()函数(或其is_real()别名)来测试一个值是否为浮点数:

if (is_float($x)) {
 // $x is a floating-point number
}

字符串

由于字符串在 Web 应用程序中非常常见,PHP 包含了在核心级别支持创建和操作字符串的功能。字符串是任意长度的字符序列。字符串字面量由单引号或双引号括起来:

'big dog'
"fat hog"

变量在双引号内扩展(插值),而在单引号内不会:

$name = "Guido";
echo "Hi, $name <br/>";
echo 'Hi, $name';
`Hi``,` `Guido`
`Hi``,` `$name`

双引号还支持多种字符串转义,如表 2-2 中列出的:

表 2-2. 双引号字符串中的转义序列

转义序列表示的字符
\"双引号
\n换行符
\r回车符
\t制表符
\\反斜杠
\$美元符号
\{左大括号
\}右大括号
\[左括号
\]右括号
\0\777由八进制值表示的 ASCII 字符
\x0\xFF由十六进制值表示的 ASCII 字符

单引号字符串识别\\以获取文字反斜杠和\'以获取文字单引号:

$dosPath = 'C:\\WINDOWS\\SYSTEM';
$publisher = 'Tim O\'Reilly';
echo "$dosPath $publisher";
C:\WINDOWS\SYSTEM Tim O'Reilly

要测试两个字符串是否相等,请使用==(双等号)比较运算符:

if ($a == $b) {
 echo "a and b are equal";
}

使用is_string()函数来测试一个值是否为字符串:

if (is_string($x)) {
 // $x is a string
}

PHP 提供了运算符和函数来比较、拆解、组装、搜索、替换和修剪字符串,以及一系列专门用于处理 HTTP、HTML 和 SQL 编码的字符串函数。由于有许多字符串操作函数,我们已经专门撰写了一个完整的章节(第四章)来详细介绍所有细节。

布尔值

布尔值表示一个真值——它表示某事是真的还是不真的。像大多数编程语言一样,PHP 定义了一些值为真,其他为假。真实性和虚假性决定了条件代码的结果,如:

if ($alive) { ... }

在 PHP 中,以下值都被评估为false

  • 关键字false

  • 整数0

  • 浮点值0.0

  • 空字符串("")和字符串"0"

  • 零元素的数组

  • NULL

一个不为假的值就是真,包括所有资源值(稍后在“资源”部分描述)。

PHP 提供了truefalse关键字以确保清晰:

$x = 5; // $x has a true value
$x = true; // clearer way to write it
$y = ""; // $y has a false value
$y = false; // clearer way to write it

使用is_bool()函数来测试一个值是否为布尔值:

if (is_bool($x)) {
 // $x is a Boolean
}

数组

数组保存一组值,可以通过位置(数字,零为第一个位置)或某些标识名称(字符串),称为关联索引来标识:

$person[0] = "Edison";
$person[1] = "Wankel";
$person[2] = "Crapper";

$creator['Light bulb'] = "Edison";
$creator['Rotary Engine'] = "Wankel";
$creator['Toilet'] = "Crapper";

array()结构创建一个数组。以下是两个例子:

$person = array("Edison", "Wankel", "Crapper");
$creator = array('Light bulb' => "Edison",
 'Rotary Engine' => "Wankel",
 'Toilet' => "Crapper");

有几种方法可以遍历数组,但最常见的是foreach循环:

foreach ($person as $name) {
 echo "Hello, {$name}<br/>";
}

foreach ($creator as $invention => $inventor) {
 echo "{$inventor} invented the {$invention}<br/>";
}
`Hello``,` `Edison`
`Hello``,` `Wankel`
`Hello``,` `Crapper`
`Edison` `created` `the` `Light` `bulb`
`Wankel` `created` `the` `Rotary` `Engine`
`Crapper` `created` `the` `Toilet`

可以使用不同的排序函数对数组元素进行排序:

 sort($person);
// $person is now array("Crapper", "Edison", "Wankel") 
 asort($creator);
// $creator is now array('Toilet' => "Crapper", // 'Light bulb' => "Edison", // 'Rotary Engine' => "Wankel");

使用is_array()函数来测试一个值是否为数组:

if (is_array($x)) {
 // $x is an array
}

有返回数组中项数的函数、获取数组中每个值的函数等。数组在第五章中有详细介绍。

对象

PHP 也支持面向对象编程(OOP)。OOP 促进了清晰的模块化设计;简化了调试和维护;并有助于代码重用。类是面向对象设计的构建块。类是一个包含属性(变量)和方法(函数)定义的结构。类用class关键字定义:

class Person
{
 public $name = '';

 function name ($newname = NULL)
 {
 if (!is_null($newname)) {
 $this->name = $newname;
 }

 return $this->name;
 }
}

一旦类被定义,可以用new关键字从中创建任意数量的对象,并且可以通过->构造访问对象的属性和方法:

$ed = new Person;
$ed->name('Edison');
echo "Hello, {$ed->name} <br/>";
$tc = new Person;
$tc->name('Crapper');
echo "Look out below {$tc->name} <br/>";
`Hello``,` `Edison`
`Look` `out` `below` `Crapper`

使用is_object()函数来测试一个值是否为对象:

if (is_object($x)) {
 // $x is an object
}

第六章详细描述了类和对象,包括继承、封装和内省。

资源

许多模块提供多个处理外部世界的函数。例如,每个数据库扩展至少有一个连接数据库的函数、一个查询数据库的函数和一个关闭数据库连接的函数。因为你可以同时打开多个数据库连接,连接函数在你调用查询和关闭函数时提供了一个用于标识唯一连接的东西:一个资源(或一个句柄)。

每个活动资源都有一个唯一的标识符。每个标识符都是一个数值索引,指向内部 PHP 查找表中所有活动资源的信息。PHP 在这个表中维护关于每个资源的信息,包括代码中对该资源的引用次数。当对资源值的最后一个引用消失时,调用创建资源的扩展来执行释放内存或关闭连接等任务:

$res = database_connect(); // fictitious database connect function
database_query($res);

$res = "boo";
// database connection automatically closed because $res is redefined

这种自动清理的好处在于函数内部最为明显,当资源被分配给一个局部变量时。当函数结束时,变量的值会被 PHP 回收:

function search() {
 $res = database_connect();
 database_query($res);
}

当不再有指向资源的引用时,它会自动关闭。

尽管如此,大多数扩展提供了特定的关闭或清理函数,明确调用该函数被认为是良好的风格,而不是依赖变量作用域触发资源清理。

使用is_resource()函数来测试一个值是否为资源:

if (is_resource($x)) {
 // $x is a resource
}

回调

回调是一些函数或对象方法,由某些函数(如call_user_func())使用。回调也可以通过create_function()方法和闭包(在第三章中描述)创建:

$callback = function()
{
 echo "callback achieved";
};

call_user_func($callback);

NULL

NULL 数据类型只有一个值。通过大小写不敏感的关键字NULL可以访问这个值。NULL值表示一个没有值的变量(类似于 Perl 的undef或 Python 的None):

$aleph = "beta";
$aleph = null; // variable's value is gone
$aleph = Null; // same
$aleph = NULL; // same

使用is_null()函数来测试一个值是否为NULL,例如检查一个变量是否有值:

if (is_null($x)) {
 // $x is NULL
}

变量

PHP 中的变量是以美元符号($)为前缀的标识符。例如:

$name
$Age
$_debugging
$MAXIMUM_IMPACT

变量可以保存任何类型的值。在变量上没有编译时或运行时类型检查。可以用不同类型的值替换变量的值:

$what = "Fred";
$what = 35;
$what = array("Fred", 35, "Wilma");

PHP 中没有明确的语法用于声明变量。第一次设置变量的值时,变量就会在内存中创建。换句话说,给变量赋值也是声明变量的方式。例如,这是一个有效的完整 PHP 程序:

$day = 60 * 60 * 24;
echo "There are {$day} seconds in a day.";
`There` `are` `86400` `seconds` `in` `a` `day``.`

如果变量的值尚未设置,则变量的行为类似于NULL值:

if ($uninitializedVariable === NULL) {
 echo "Yes!";
}
`Yes``!`

变量变量

可以通过在变量引用前面加一个额外的美元符号($)来引用存储在另一个变量中的变量的值。例如:

$foo = "bar";
$$foo = "baz";

第二条语句执行后,变量$bar的值为"baz"

变量引用

在 PHP 中,引用是创建变量别名或指针的方式。要将$black设为变量$white的别名,请使用:

$black =& $white;

$black的旧值(如果有)会丢失。现在,$black是存储在$white中的值的另一个名称:

$bigLongVariableName = "PHP";
$short =& $bigLongVariableName;
$bigLongVariableName .= " rocks!";
print "\$short is $short <br/>";
print "Long is $bigLongVariableName";
`$short` `is` `PHP` `rocks``!`
`Long` `is` `PHP` `rocks``!`

$short = "Programming $short";
print "\$short is $short <br/>";
print "Long is $bigLongVariableName";
`$short` `is` `Programming` `PHP` `rocks``!`
`Long` `is` `Programming` `PHP` `rocks``!`

赋值后,两个变量是同一个值的替代名称。对别名变量取消设置不会影响该变量值的其他名称:

$white = "snow";
$black =& $white;
unset($white);
print $black;
`snow`

函数可以通过引用返回值(例如,避免复制大字符串或数组,如第三章中所述):

function &retRef() // note the &
{
 $var = "PHP";

 return $var;
}

$v =& retRef(); // note the &

变量作用域

变量的作用域由变量声明的位置控制,决定程序中可以访问它的部分。在 PHP 中,变量的作用域有四种类型:局部、全局、静态和函数参数。

局部作用域

在函数中声明的变量只在该函数内部可见。也就是说,它只能被函数内的代码访问(除了嵌套函数定义);它不能在函数外部访问。此外,默认情况下,在函数外部定义的变量(称为全局变量)在函数内部不可访问。例如,下面是一个更新局部变量而不是全局变量的函数示例:

function updateCounter()
{
 $counter++;
}

$counter = 10;
updateCounter();

echo $counter;
`10`

函数内部的$counter是局部变量,因为我们没有明确指定。函数递增其私有的$counter变量,在子程序结束时销毁。全局的$counter保持为 10。

只有函数能提供局部作用域。与其他语言不同,在 PHP 中,你不能创建其作用域为循环、条件分支或其他类型块的变量。

全局作用域

在函数外声明的变量是全局的。也就是说,它们可以从程序的任何部分访问。但是,默认情况下,它们在函数内部不可用。为了允许函数访问全局变量,你可以在函数内部使用 global 关键字来声明函数内部的变量。下面是我们如何重写 updateCounter() 函数,以允许它访问全局 $counter 变量的示例:

function updateCounter()
{
 global $counter;
 $counter++;
}

$counter = 10;
updateCounter();
echo $counter;
`11`

更新全局变量的一种更麻烦的方法是使用 PHP 的 $GLOBALS 数组,而不是直接访问变量:

function updateCounter()
{
 $GLOBALS[‘counter’]++;
}

$counter = 10;
updateCounter();
echo $counter;
`11`

静态变量

静态变量在函数调用之间保持其值,但仅在该函数内部可见。你可以使用 static 关键字声明变量为静态变量。例如:

function updateCounter()
{
 static $counter = 0;
 $counter++;

 echo "Static counter is now {$counter}<br/>";
}

$counter = 10;
updateCounter();
updateCounter();

echo "Global counter is {$counter}";
`Static` `counter` `is` `now` `1`
`Static` `counter` `is` `now` `2`
`Global` `counter` `is` `10`

函数参数

正如我们将在第三章中详细讨论的那样,函数定义可以有命名参数:

function greet($name)
{
 echo "Hello, {$name}";
}

greet("Janet");
`Hello``,` `Janet`

函数参数是局部的,意味着它们仅在其函数内部可用。在这种情况下,$name 无法从 greet() 外部访问。

垃圾回收

PHP 使用引用计数写时复制来管理内存。写时复制确保在变量之间复制值时不会浪费内存,并且引用计数确保在不再需要内存时将内存返回给操作系统。

要理解 PHP 中的内存管理,你必须首先理解符号表的概念。变量有两部分组成——它的名称(例如 $name)和它的值(例如 "Fred")。符号表是一个数组,将变量名映射到它们值在内存中位置的数组。

当你将一个值从一个变量复制到另一个变量时,PHP 不会为该值创建一个新的内存空间。相反,它会更新符号表,指示“这两个变量都是指向同一块内存的名称”。所以下面的代码实际上并不创建一个新的数组:

$worker = array("Fred", 35, "Wilma");
$other = $worker; // array isn't duplicated in memory

如果随后修改任一副本,PHP 会分配所需的内存并进行复制:

$worker[1] = 36; // array is copied in memory, value changed

通过延迟分配和复制,PHP 在许多情况下节省时间和内存。这就是写时复制。

符号表指向的每个值都有一个引用计数,这个数字表示到达该内存块的方式数量。在将数组分配给 $worker 和将 $worker 分配给 $other 后,符号表条目所指向的数组的引用计数为 2。¹ 换句话说,这块内存可以通过 $worker$other 两种方式访问。但是在修改 $worker[1] 后,PHP 会为 $worker 创建一个新的数组,并且每个数组的引用计数仅为 1。

当变量在函数结束时超出范围时,如函数参数和局部变量,其值的引用计数将减少一次。当变量在内存的不同区域被赋予一个新值时,旧值的引用计数将减少一次。当值的引用计数达到 0 时,其内存将被释放。这就是引用计数。

引用计数是管理内存的首选方法。保持变量局限于函数内部,传递函数需要处理的值,并让引用计数处理内存管理。如果您坚持想要获取有关释放变量值的更多信息或控制,请使用 isset()unset() 函数。

若要查看变量是否设置为某些内容(甚至是空字符串),请使用 isset()

$s1 = isset($name); // $s1 is false
$name = "Fred";
$s2 = isset($name); // $s2 is true

使用 unset() 来移除变量的值:

$name = "Fred";
unset($name); // $name is NULL

表达式和操作符

表达式 是一段 PHP 代码,可以求值为一个值。最简单的表达式是文字值和变量。文字值求值为它自身,而变量求值为存储在变量中的值。更复杂的表达式可以使用简单表达式和操作符组成。

操作符 接受一些值(操作数)并执行某些操作(例如,将它们加在一起)。操作符有时用标点符号表示,例如我们在数学中熟悉的 +- 。某些操作符会修改它们的操作数,而大多数则不会。

表格 2-3 总结了 PHP 中的操作符,其中许多操作符来自 C 和 Perl。标有“P”的列显示了操作符的优先级;操作符按照从高到低的优先级顺序列出。标有“A”的列显示了操作符的结合性,可以是 L(从左到右)、R(从右到左)或 N(非结合性)。

表格 2-3. PHP 操作符

PA操作符操作
24Nclone, new创建新对象
23L``数组下标
22R**指数运算
21R~按位非
 R++自增
 R−−自减
 R(int), (bool), (float), (string), (array), (object), (unset)强制类型转换
 R@抑制错误
20Ninstanceof类型检测
19R!逻辑非
18L*乘法
 L/除法
 L%取模
17L+加法
 L减法
 L.字符串连接
16L<<左移
 L>>右移
15N<, <=小于,小于等于
 N>, >=大于,大于等于
14N==值相等
 N!=, <>不等于
 N===类型和值相等
 N!==类型和值不等
 N<=>基于两个操作数的比较返回一个整数:当左右相等时为 0,当左小于右时为 -1,当左大于右时为 1
13L&按位与
12L^按位异或
11L&#124;按位或
10L&&逻辑与
9L&#124;&#124;逻辑或
8R??比较
7L?:条件运算符
6R=赋值
 R+=, −=, *=, /=, .=, %=, &=, &#124;=, ^=, ~=, <<=, >>=带操作的赋值
5 yield from从产出
4 yield产出
3Land逻辑与
2Lxor逻辑异或
1Lor逻辑或

操作数的数量

大多数 PHP 中的操作符是二元操作符;它们将两个操作数(或表达式)组合成一个更复杂的表达式。PHP 也支持几个一元操作符,将单个表达式转换为更复杂的表达式。最后,PHP 支持一些三元操作符,将多个表达式组合成单个表达式。

操作符优先级

表达式中操作符的求值顺序取决于它们的相对优先级。例如,您可能会写:

2 + 4 * 3

如您在[表 2-3 中所见,加法和乘法运算符具有不同的优先级,其中乘法高于加法。因此,乘法先于加法运算,得出 2 + 12,即 14。如果加法和乘法的优先级被反转,6 * 3,即 18,将成为答案。

要强制执行特定顺序,可以使用括号将操作数分组。在我们之前的例子中,要获取值 18,可以使用以下表达式:

(2 + 4) * 3

可以通过将操作数和操作符按照正确的顺序放置,使它们的相对优先级产生您想要的答案,来编写所有复杂的表达式(包含多个操作符的表达式)。然而,大多数程序员按照他们认为最合理的顺序编写操作符,并添加括号以确保 PHP 也能理解。如果优先级弄错了,会导致类似于以下的代码:

$x + 2 / $y >= 4 ? $z : $x << $z

这段代码很难阅读,几乎肯定不会达到程序员的预期。

许多程序员处理编程语言中复杂的优先级规则的一种方式是将优先级简化为两个规则:

  • 乘法和除法的优先级高于加法和减法。

  • 对于其他情况,请使用括号。

操作符的结合性

结合性定义了在具有相同优先级顺序的操作符中的求值顺序。例如,看一下:

2 / 2 * 2

除法和乘法运算符具有相同的优先级,但表达式的结果取决于我们首先执行哪个操作:

2 / (2 * 2) // 0.5
(2 / 2) * 2 // 2

除法和乘法运算符是左结合的;这意味着在有歧义的情况下,运算符从左到右进行计算。在这个例子中,正确的结果是 2。

隐式类型转换

许多运算符对其操作数有特定的要求,例如,二进制数学运算符通常要求两个操作数是相同的类型。PHP 的变量可以存储整数、浮点数、字符串等,为了尽可能地将类型细节远离程序员,PHP 会根据需要将值从一种类型转换为另一种类型。

将一个值从一种类型转换为另一种类型称为类型转换。这种隐式转换在 PHP 中称为类型强制转换。算术运算符进行的类型强制转换的规则见表 2-4。

表 2-4. 二元算术操作的隐式转换规则

第一个操作数的类型第二个操作数的类型执行的转换
整数浮点数将整数转换为浮点数。
整数字符串将字符串转换为数字;如果转换后的值为浮点数,则整数转换为浮点数。
浮点数字符串将字符串转换为浮点数。

其他一些运算符对其操作数有不同的期望,因此有不同的规则。例如,字符串连接运算符在连接之前将两个操作数转换为字符串:

3 . 2.74 // gives the string 32.74

在 PHP 期望数字的任何地方都可以使用字符串。假定字符串以整数或浮点数开头。如果在字符串开头找不到数字,则该字符串的数值为 0. 如果字符串包含句点(.)或大写或小写 e,则将其数值化后产生一个浮点数。例如:

"9 Lives" - 1; // 8 (int)
"3.14 Pies" * 2; // 6.28 (float)
"9\. Lives" - 1; // 8 (float / double)
"1E3 Points of Light" + 1; // 1001 (float)

算术运算符

算术运算符是你在日常使用中会认识到的运算符。大多数算术运算符都是二元的;然而,算术否定和算术断言运算符是一元的。这些运算符需要数值,非数值将按照“强制转换运算符”部分描述的规则转换为数值。算术运算符包括:

加法 (+)

加法运算符的结果是两个操作数的和。

减法 ()

减法运算符的结果是两个操作数之间的差值,即第二个操作数从第一个操作数中减去的值。

乘法 (*)

乘法运算符的结果是两个操作数的乘积。例如,3 * 412

除法 (/)

除法运算的结果是两个操作数的商。两个整数相除可以得到一个整数(例如,4 / 2)或者浮点数结果(例如,1 / 2)。

取模 (%)

模运算符将两个操作数转换为整数,并返回第一个操作数除以第二个操作数的余数。例如,10 % 6 的余数为 4

算术取反 ()

算术取反运算符返回操作数乘以−1,有效地改变其符号。例如,−(3 − 4) 的计算结果为 1。算术取反与减法运算符不同,尽管它们都写成减号。算术取反始终是一元的,并置于操作数之前。减法是二元的,并置于其操作数之间。

算术断言 (+)

算术断言运算符返回操作数乘以+1,这没有任何效果。它仅用作视觉提示,指示值的符号。例如,+(3 − 4) 的计算结果为 -1,就像 (3 − 4) 一样。

指数运算 (**)

指数运算符返回将 $var1 的值提高到 $var2 次幂的结果。

$var1 = 5;
$var2 = 3;
echo $var1 ** $var2; // outputs 125

字符串连接运算符

在 PHP 应用程序中,操作字符串是非常核心的部分,因此 PHP 单独有一个字符串连接运算符 (.)。连接运算符将右操作数附加到左操作数并返回结果字符串。必要时,操作数首先会被转换为字符串。例如:

$n = 5;
$s = 'There were ' . $n . ' ducks.';
// $s is 'There were 5 ducks'

字符串连接运算符非常高效,因为 PHP 大部分操作都可以归结为字符串连接。

自增和自减运算符

在编程中,增加或减少变量值是最常见的操作之一。一元自增 (++) 和自减 (−−) 运算符为这些常见操作提供了快捷方式。这些运算符独特之处在于它们仅适用于变量;运算符会改变它们的操作数的值并返回一个值。

在表达式中使用自增或自减有两种方法。如果将运算符放在操作数前面,它将返回操作数的新值(增加或减少后)。如果将运算符放在操作数后面,它将返回操作数的原始值(增加或减少前)。表 2-5 列出了不同的操作。

表 2-5. 自增和自减操作

运算符名称返回的值$var 的影响
$var++后自增$var自增后的值
++$var前自增$var + 1自增后的值
$var−−后自减$var自减后的值
−−$var前自减$var − 1自减后的值

这些运算符可以应用于字符串和数字。对字母进行自增操作会将其转换为字母表中的下一个字母。正如在 表 2-6 中所示,对 "z""Z" 进行自增操作会将其回环到 "a""A",并使前一个字符自增一次(或者在字符串的第一个字符处插入新的 "a""A"),就像字符处于一个 26 进制数系统中一样。

表 2-6. 字母的自增

递增此值得到此值
"a""b"
"z""aa"
"spaz""spba"
"K9""L0"
"42""43"

比较运算符

正如它们的名称所示,比较运算符用于比较操作数。结果要么为true(如果比较为真),要么为false

比较运算符的操作数可以是全数字、全字符串或一个数字和一个字符串。根据操作数的类型和值,这些运算符稍有不同地检查真实性,可以使用严格的数值比较或按字典序(文本)比较。表 2-7 概述了每种检查何时使用。

表 2-7. 比较运算符执行的比较类型

第一个操作数第二个操作数比较
NumberNumberNumeric
完全是数字的字符串完全是数字的字符串数字型
完全是数字的字符串数字数字型
完全是数字的字符串不完全是数字的字符串字典序
不完全是数字的字符串数字数字型
不完全是数字的字符串不完全是数字的字符串字典序

一个重要的注意事项是,两个数字字符串会被比较为数值。如果你有两个完全由数字字符组成的字符串,并且需要按字典顺序比较它们,请使用strcmp()函数。

比较运算符包括:

等于(==

如果两个操作数相等,则该运算符返回true;否则返回false

相同(===

如果两个操作数相等且类型相同,则该运算符返回true;否则返回false。请注意,该运算符不会进行隐式类型转换。当你不确定所比较的值是否为相同类型时,此运算符非常有用。简单比较可能涉及值转换。例如,字符串 "0.0""0" 不相等。== 运算符说它们相等,但 === 运算符说它们不相等。

不等于(!=<>

如果操作数不相等,则该运算符返回true;否则返回false

不相同(!==

如果操作数不相等或它们不是相同类型,则该运算符返回true;否则返回false

大于 (>)

如果左操作数大于右操作数,则该运算符返回true;否则返回false

大于或等于 (>=)

如果左操作数大于或等于右操作数,则该运算符返回true;否则返回false

小于 (<)

如果左操作数小于右操作数,则该运算符返回true;否则返回false

小于或等于 (<=)

如果左操作数小于或等于右操作数,则该运算符返回 true;否则,返回 false

太空船运算符(<=>),也称为“达斯·维达的 TIE 战斗机”

当左操作数等于右操作数时,该运算符返回 0;当左操作数小于右操作数时,返回 -1;当左操作数大于右操作数时,返回 1

$var1 = 5;
$var2 = 65;

`echo` $var1 <=> $var2 ; // outputs -1 `echo` $var2 <=> $var1 ; // outputs 1

空值合并运算符(??

如果左操作数为 NULL,则该运算符求值为右操作数;否则,求值为左操作数。

$var1 = null;
$var2 = 31;

echo $var1 ?? $var2 ; //outputs 31

位运算符

位运算符作用于其操作数的二进制表示。首先将每个操作数转换为其数值的二进制表示,如下面列表中位取反运算符条目所述。所有位运算符都可以作用于数字以及字符串,但它们在处理长度不同的字符串操作数时有所不同。位运算符包括:

位取反(~

位取反运算符将操作数的二进制表示中的 1 变为 0,0 变为 1。在执行操作之前,浮点数值会转换为整数。如果操作数是字符串,则结果值是与原始字符串长度相同的字符串,其中字符串中的每个字符都被取反。

位与(&

位与运算符比较操作数的二进制表示中的每个对应位。如果两个位都是 1,则结果中的对应位为 1;否则,对应位为 0。例如,0755 & 06710651。如果我们查看二进制表示,会更容易理解。八进制 0755 的二进制是 111101101,八进制 0671 的二进制是 110111001。然后我们可以轻松看出这两个数字中哪些位是相同的,并直观地得出答案:

 111101101
& 110111001
 ---------
 110101001

二进制数 110101001 是八进制 0651。² 在尝试理解二进制算术时,可以使用 PHP 函数 bindec()decbin()octdec()decoct() 进行数字的双向转换。

如果两个操作数都是字符串,则该运算符返回一个字符串,其中每个字符都是两个操作数中对应字符进行位与操作的结果。结果字符串的长度是两个操作数中较短的那个;在较长字符串的末尾多余字符将被忽略。例如,"wolf" & "cat""cad"

位或(|

位或运算符比较操作数的二进制表示中的每个对应位。如果两个位都是 0,则结果位为 0;否则,结果位为 1。例如,0755 | 0200775

如果两个操作数都是字符串,则运算符返回一个字符串,其中每个字符是操作数中对应字符进行位或操作的结果。结果字符串的长度为两个操作数中较长的那个,并且较短的字符串在末尾填充二进制 0。例如,"pussy" | "cat""suwsy"

位异或 (^)

位异或运算符比较操作数的二进制表示中的每个对应位。如果一对中的任一位(但不是两者都是)为 1,则结果位为 1;否则,结果位为 0。例如,0755 ^ 023776

如果两个操作数都是字符串,则此运算符返回一个字符串,其中每个字符是操作数中对应字符进行位异或操作的结果。如果两个字符串长度不同,则结果字符串的长度为较短操作数的长度,并且忽略较长字符串中多余的尾部字符。例如,"big drink" ^ "AA""#("

左移位 (<<)

左移位运算符将左操作数的二进制表示中的位向左移动右操作数指定的位数。如果它们尚未是整数,则两个操作数将被转换为整数。向左移动二进制数会在数字的最右边插入一个 0,并将所有其他位向左移动一个位置。例如,3 << 1(或二进制 11 向左移动一位)的结果是 6(二进制 110)。

注意,每次向左移动数字的位置都会导致数字加倍。左移的结果是将左操作数乘以右操作数的 2 的幂。

右移位 (>>)

右移位运算符将左操作数的二进制表示中的位向右移动右操作数指定的位数。如果它们尚未是整数,则两个操作数将被转换为整数。将正数二进制数向右移动会在数字的最左边插入一个 0,并将所有其他位向右移动一个位置。将负数二进制数向右移动会在数字的最左边插入一个 1,并将所有其他位向右移动一个位置。最右边的位被丢弃。例如,13 >> 1(或二进制 1101 向右移动一位)的结果是 6(二进制 110)。

逻辑运算符

逻辑运算符提供了构建复杂逻辑表达式的方法。逻辑运算符将其操作数视为布尔值并返回布尔值。运算符有标点和英文版本(||or 是相同的运算符)。逻辑运算符包括:

逻辑与 (&&, and)

逻辑与操作的结果如果且仅如果两个操作数都为 true,则结果为 true;否则为 false。如果第一个操作数的值为 false,逻辑与操作符知道结果值必须也为 false,因此右操作数不会被评估。这个过程称为 短路,一个常见的 PHP 习惯用法是确保只有在某些条件为真时才评估代码。例如,你可能只有在某些标志不为 false 时才连接到数据库:

$result = $flag and mysql_connect();

&&and 操作符在它们的优先级上是有所不同的:&&and 之前。

逻辑或 (||, or)

逻辑或操作的结果如果任一操作数为 true,则结果为 true;否则为 false。与逻辑与操作符类似,逻辑或操作符也是短路的。如果左操作数为 true,则操作符的结果必须为 true,因此右操作数永远不会被评估。一个常见的 PHP 习惯用法是在发生错误时触发错误条件。例如:

$result = fopen($filename) or exit();

||or 操作符在它们的优先级上是有所不同的。

逻辑异或 (xor)

逻辑异或操作的结果如果任一操作数为 true 但不是两个操作数都为 true,则结果为 true;否则为 false

逻辑非 (!)

逻辑非操作符如果操作数评估为 false,则返回布尔值 true,如果操作数评估为 true,则返回布尔值 false

强制转换操作符

虽然 PHP 是一种弱类型语言,但在某些情况下,将值视为特定类型是很有用的。强制转换操作符 (int), (float), (string), (bool), (array), (object), 和 (unset) 允许你将一个值强制转换为特定类型。要使用强制转换操作符,将操作符放在操作数的左侧。表 2-8 列出了强制转换操作符、同义操作符以及操作符将值转换为的类型。

表 2-8. PHP 强制转换操作符

运算符同义操作符转换为
(int)(integer)整数
(bool)(boolean)布尔值
(float)(double), (real)浮点数
(string) String
(array) Array
(object) Object
(unset) NULL

强制转换影响其他操作符解释值的方式,而不是改变变量中的值。例如,代码:

$a = "5";
$b = (int) $a;

$b 赋予 $a 的整数值;$a 保持字符串 "5"。要将变量本身的值强制转换,必须将强制转换的结果再赋回变量:

$a = "5";
$a = (int) $a; // now $a holds an integer

并非每种强制转换都是有用的。将数组强制转换为数值类型会得到 1(如果数组为空,则为 0),将数组强制转换为字符串会得到 "Array"(在输出中看到这个,表明你已经打印了一个包含数组的变量)。

将对象强制转换为数组会构建一个属性数组,从而将属性名映射到值:

class Person
{
 var $name = "Fred";
 var $age = 35;
}

$o = new Person;
$a = (array) $o;

print_r($a);
`Array` `(` `[``name``]` `=>` `Fred` `[``age``]` `=>` `35``)`

您可以将数组转换为对象,以构建一个对象,其属性对应于数组的键和值。例如:

$a = array('name' => "Fred", 'age' => 35, 'wife' => "Wilma");
$o = (object) $a;
echo $o->name;
`Fred`

不是有效标识符的键是无效的属性名称,在将数组转换为对象时不可访问,但在将对象转换回数组时恢复。

赋值运算符

赋值运算符用于存储或更新变量中的值。我们之前看到的自增和自减运算符是高度专业化的赋值运算符—在这里我们看到更一般的形式。基本赋值运算符是=,但我们还会看到赋值和二进制运算的组合,如+=&=

赋值

基本赋值运算符 (=) 将一个值分配给一个变量。左操作数始终是一个变量。右操作数可以是任何表达式—任何简单的字面量、变量或复杂的表达式。右操作数的值存储在由左操作数命名的变量中。

因为所有运算符都要返回一个值,所以赋值运算符返回分配给变量的值。例如,表达式$a = 5不仅将5赋给了$a,而且在较大的表达式中使用时,也会表现为值5。考虑以下表达式:

$a = 5;
$b = 10;
$c = ($a = $b);

表达式$a = $b首先被评估,因为有括号。现在,$a$b都有相同的值10。最后,$c被赋值为表达式$a = $b的结果,即分配给左操作数的值(在本例中为$a)。当完整表达式评估完成时,所有三个变量都包含相同的值10

带操作的赋值

除了基本赋值运算符外,还有几个方便的简写赋值运算符。这些运算符由一个二元运算符直接跟随一个等号组成,它们的效果等同于对完整操作数执行操作,然后将结果值分配给左操作数。这些赋值运算符包括:

加等号 (+=)

将右操作数加到左操作数的值上,然后将结果分配给左操作数。$a += 5$a = $a + 5相同。

减等号 (−=)

从左操作数的值减去右操作数,然后将结果分配给左操作数。

除等号 (/=)

将左操作数的值除以右操作数,然后将结果分配给左操作数。

乘等号 (*=)

将右操作数乘以左操作数的值,然后将结果分配给左操作数。

取模等号 (%=)

对左操作数和右操作数的值执行模运算,然后将结果分配给左操作数。

按位异或等号 (^=)

对左操作数和右操作数执行按位异或,然后将结果分配给左操作数。

按位与等号 (&=)

对左操作数的值和右操作数执行按位与操作,然后将结果分配给左操作数。

按位或等于(|=

对左操作数的值和右操作数执行按位或操作,然后将结果分配给左操作数。

连接等于(.=

将右操作数连接到左操作数的值,然后将结果分配给左操作数。

杂项运算符

剩余的 PHP 运算符用于错误抑制、执行外部命令和选择值:

错误抑制(@

一些运算符或函数可能会生成错误消息。完整讨论错误抑制运算符,请参阅第十七章。

执行(`...`

反引号运算符执行包含在反引号之间的字符串作为 shell 命令并返回输出。例如:

$listing = `ls -ls /tmp`;
echo $listing;

条件(? :

条件运算符是根据您查看的代码而定,可能是最常用或最不常用的运算符之一。它是唯一的三元(三操作数)运算符,因此有时只被称为三元运算符。

条件运算符在?之前评估表达式。如果表达式为true,则运算符返回?:之间的表达式的值;否则,运算符返回:之后的表达式的值。例如:

<a href="<? echo $url; ?>"><? echo $linktext ? $linktext : $url; ?></a>

如果变量$linktext中存在链接$url的文本,则将其用作链接的文本;否则,显示 URL 本身。

类型(instanceof

instanceof运算符测试变量是否是给定类的实例化对象或实现接口(有关对象和接口的更多信息,请参阅第六章):

$a = new Foo;
$isAFoo = $a instanceof Foo; // true
$isABar = $a instanceof Bar; // false

流程控制语句

PHP 支持许多传统的编程构造来控制程序的执行流程。

条件语句,例如if/elseswitch,允许程序根据某些条件执行不同的代码片段,或者根本不执行。循环,例如whilefor,支持对代码段的重复执行。

if

if语句检查表达式的真实性,如果表达式为真,则评估一个语句。一个if语句看起来像:

if (*`expression`*)*`statement`*

若要指定在表达式为假时执行的替代语句,请使用else关键字:

if (*`expression`*)
 *`statement`*
else *`statement`*

例如:

if ($user_validated)
 echo "Welcome!";
else
 echo "Access Forbidden!";

若要在if语句内包含多个语句,请使用—由花括号括起来的一组语句:

if ($user_validated) {
 echo "Welcome!";
 $greeted = 1;
}
else {
 echo "Access Forbidden!";
 exit;
}

PHP 为测试和循环中的块提供了另一种语法。不是用花括号将语句块括起来,而是在if行末尾使用冒号(:)并使用特定关键字来结束块(在这种情况下为endif)。例如:

if ($user_validated):
 echo "Welcome!";
 $greeted = 1;
else:
 echo "Access Forbidden!";
 exit;
endif;

本章中描述的其他语句也有类似的备选语法样式(和结束关键字);如果您的语句内部有大量的 HTML 块,它们可能非常有用。例如:

<?php if ($user_validated) : ?>
 <table>
 <tr>
 <td>First Name:</td><td>Sophia</td>
 </tr>
 <tr>
 <td>Last Name:</td><td>Lee</td>
 </tr>
 </table>
<?php else: ?>
 Please log in.
<?php endif ?>

因为if是一个语句,您可以链接(嵌套)多个。这也是如何使用块来帮助保持组织的一个很好的例子:

if ($good) {
 print("Dandy!");
}
else {
 if ($error) {
 print("Oh, no!");
 }
 else {
 print("I'm ambivalent...");
 }
}

PHP 提供了一个更简单的语法来处理这样的if语句链:elseif语句。例如,前面的代码可以重写为:

if ($good) {
 print("Dandy!");
}
elseif ($error) {
 print("Oh, no!");
}
else {
 print("I'm ambivalent...");
}

三元条件运算符(? :)可用于缩短简单的真/假测试。考虑一个常见的情况,例如检查给定变量是否为 true 并在其为 true 时打印某些内容。使用普通的if/else语句,看起来像这样:

<td><?php if($active) { echo "yes"; } else { echo "no"; } ?></td>

使用三元条件运算符时,看起来像这样:

<td><?php echo $active ? "yes" : "no"; ?></td>

比较这两者的语法:

if (*`expression`*) { *`true_statement`* } else { *`false_statement`* }
 (*`expression`*) ? *`true_expression`* : *`false_expression`*

这里的主要区别在于条件运算符根本不是一个语句。这意味着它用于表达式,完整的三元表达式的结果本身就是一个表达式。在上面的例子中,echo语句位于if条件内部,而使用三元运算符时,它位于表达式之前。

开关

单个变量的值可能决定多个不同的选择(例如,变量保存用户名,您希望针对每个用户执行不同的操作)。switch语句正是为这种情况设计的。

switch语句给定一个表达式并将其值与 switch 中的所有 case 进行比较;匹配 case 中的所有语句都会执行,直到找到第一个break关键字。如果没有匹配,且有default,则执行default关键字后的所有语句,直到遇到第一个break关键字。

例如,假设您有以下内容:

if ($name == 'ktatroe') {
 // do something
}
else if ($name == 'dawn') {
 // do something
}
else if ($name == 'petermac') {
 // do something
}
else if ($name == 'bobk') {
 // do something
}

您可以用以下switch语句替换该语句:

switch($name) {
 case 'ktatroe':
 // do something
 break;
 case 'dawn':
 // do something
 break;
 case 'petermac':
 // do something
 break;
 case 'bobk':
 // do something
 break;
}

这种的替代语法是:

switch($name):
 case 'ktatroe':
 // do something
 break;
 case 'dawn':
 // do something
 break;
 case 'petermac':
 // do something
 break;
 case 'bobk':
 // do something
 break;
endswitch;

因为语句从匹配的 case 标签到下一个break关键字被执行,您可以将多个 case 结合在一起进行穿透。在下面的例子中,当$name等于sylviebruno时,会打印出"yes":

switch ($name) {
 case 'sylvie': // fall-through
 case 'bruno':
 print("yes");
 break;
 default:
 print("no");
 break;
}

switch中注释您使用了穿透 case 是个好主意,这样别人就不会以为您忘记了加上break

您可以为break关键字指定可选的中断级别数。这样,break语句可以跳出多层嵌套的switch语句。下一节中展示了使用break的示例。

最简单的循环形式是while语句:

while (*`expression`*)*`statement`*

如果表达式计算结果为true,则执行语句,然后重新评估表达式(如果仍为true,则再次执行循环体,依此类推)。当表达式不再为真时(即计算结果为false时),循环退出。

作为示例,这里是一些将整数从 1 加到 10 的代码:

$total = 0;
$i = 1;

while ($i <= 10) {
 $total += $i;
 $i++;
}

while的替代语法具有以下结构:

while (*`expr`*):
 *`statement``;`*
 *`more` `statements`* ;
endwhile;

例如:

$total = 0;
$i = 1;

while ($i <= 10):
 $total += $i;
 $i++;
endwhile;

您可以使用break关键字提前退出循环。在以下代码中,一旦$i达到5,循环就会停止,因此$i永远不会达到6

$total = 0;
$i = 1;

while ($i <= 10) {
 if ($i == 5) {
 break; // breaks out of the loop
 }

 $total += $i;
 $i++;
}

可选地,在break关键字后面可以放一个数字,指示要跳出的循环结构级别数。通过这种方式,嵌套循环中深埋的语句可以跳出最外层循环。例如:

$i = 0;
$j = 0;

while ($i < 10) {
 while ($j < 10) {
 if ($j == 5) {
 break 2; // breaks out of two while loops
 }

 $j++;
 }

 $i++;
}

echo "{$i}, {$j}";
`0``,` `5`

continue语句跳到下一个循环条件测试。与break关键字一样,您可以跨可选的循环结构级别继续:

while ($i < 10) {
 $i++;

 while ($j < 10) {
 if ($j == 5) {
 continue 2; // continues through two levels
 }

 $j++;
 }
}

在此代码中,$j永远不会超过5,但$i会经历从09的所有值。

PHP 还支持do/while循环,其形式如下:

do
 *`statement`*
while (*`expression`*)

使用do/while循环确保至少执行一次循环体(第一次):

$total = 0;
$i = 1;

do {
 $total += $i++;
} while ($i <= 10);

您可以在do/while语句中像在普通while语句中一样使用breakcontinue语句。

当发生错误条件时,有时会使用do/while语句来跳出代码块。例如:

do {
 // do some stuff

 if ($errorCondition) {
 break;
 }

 // do some other stuff
} while (false);

因为循环的条件为false,所以无论循环内部发生什么,循环只执行一次。但是,如果发生错误,则break后面的代码不会被评估。

for

for语句类似于while语句,但它添加了计数器初始化和计数器操作表达式,通常比等效的while循环更短且更易读。

这里是一个while循环,从 0 到 9 进行计数,并打印每个数字:

$counter = 0;

while ($counter < 10) {
 echo "Counter is {$counter} <br/>";
 $counter++;
}

这是相应的更简洁的for循环:

for ($counter = 0; $counter < 10; $counter++) {
 echo "Counter is $counter <br/>";
}

for语句的结构是:

for (*`start`*; *`condition`*; *`increment`*) { *`statement``(``s``);`* }

表达式startfor语句开始时评估一次。每次循环时,表达式condition被测试。如果为true,则执行循环体;如果为false,则循环结束。表达式increment在运行循环体后评估。

for语句的替代语法是:

for (*`expr1`*; *`expr2`*; *`expr3`*):
 *`statement``;`*
 *`...``;`*
endfor;

此程序使用for循环将数字从 1 加到 10:

$total = 0;

for ($i= 1; $i <= 10; $i++) {
 $total += $i;
}

使用替代语法的相同循环如下所示:

$total = 0;

for ($i = 1; $i <= 10; $i++):
 $total += $i;
endfor;

您可以通过用逗号分隔表达式来为for语句中的任何表达式指定多个表达式。例如:

$total = 0;

for ($i = 0, $j = 1; $i <= 10; $i++, $j *= 2) {
 $total += $j;
}

您还可以将表达式留空,表示该阶段不执行任何操作。在最简单的情况下,for语句变成一个无限循环。您可能不希望运行此示例,因为它永远不会停止打印:

for (;;) {
 echo "Can't stop me!<br />";
}

for循环中,与while循环一样,您可以使用breakcontinue关键字来结束循环或当前迭代。

foreach

foreach语句允许您遍历数组中的元素。在第五章中进一步讨论了foreach语句的两种形式,更深入地讨论了数组。要循环访问数组并访问每个键处的值,请使用:

foreach (*`$array`* as *`$current`*) {
 // ... }

替代语法是:

foreach (*`$array`* as *`$current`*):
 // ... endforeach;

要循环访问数组并访问键和值,请使用:

foreach (*`$array`* as *`$key` `=>` `$value`*) {
 // ... }

替代语法是:

foreach (*`$array`* as *`$key` `=>` `$value`*):
 // ... endforeach;

try...catch

try...catch结构不仅仅是一个流程控制结构,它更像是一种更优雅的处理系统错误的方式。例如,如果要确保您的 Web 应用在继续之前有一个有效的连接到数据库,您可以编写如下代码:

try {
 $dbhandle = new PDO('mysql:host=localhost; dbname=library', $username, $pwd);
 doDB_Work($dbhandle); // call function on gaining a connection
 $dbhandle = null; // release handle when done
}
catch (PDOException $error) {
 print "Error!: " . $error->getMessage() . "<br/>";
 die();
}

在这里,连接尝试使用构造的try部分,并且如果有任何与之相关的错误,则代码流会自动跳转到catch部分,其中PDOException类被实例化到$error变量中。然后可以在屏幕上显示它,并且代码可以“优雅”地失败,而不是突然结束。甚至可以尝试连接到备用数据库,或者在catch部分内以其他任何方式响应错误。

注意

参见第九章,了解与 PDO(PHP 数据对象)和事务处理相关的更多try...catch示例。

declare

declare语句允许您为代码块指定执行指令。declare语句的结构如下:

declare (*`directive`*)*`statement`*

目前只有三种declare形式:ticksencodingstrict_types指令。您可以使用ticks指令指定在调用register_tick_function()时注册 tick 函数的频率(大致以代码语句数量计算)。例如:

register_tick_function("someFunction");

declare(ticks = 3) {
 for($i = 0; $i < 10; $i++) {
 // do something
 }
}

在此代码中,在执行块内每三条语句后都调用someFunction()

您可以使用encoding指令指定 PHP 脚本的输出编码。例如:

declare(encoding = "UTF-8");

除非使用--enable-zend-multibyte选项编译 PHP,否则会忽略此形式的declare语句。

最后,您可以使用strict_types指令在定义和使用变量时强制使用严格数据类型。

退出和返回

一旦到达,exit语句将结束脚本的执行。return语句从函数中返回,或者在程序顶层时从脚本返回。

exit语句接受一个可选值。如果是数字,则为进程的退出状态。如果是字符串,则在进程终止之前打印该值。函数die()是此形式exit语句的别名:

$db = mysql_connect("localhost", $USERNAME, $PASSWORD);

if (!$db) {
 die("Could not connect to database");
}

这通常被写成:

$db = mysql_connect("localhost", $USERNAME, $PASSWORD)
 or die("Could not connect to database");

参见第三章,了解在函数中使用return语句的更多信息。

转到

goto 语句允许执行“跳转”到程序的另一个位置。您通过添加标签来指定执行点,标签是由标识符后跟冒号(:)组成。然后,您可以通过 goto 语句从脚本的另一个位置跳转到该标签:

for ($i = 0; $i < $count; $i++) {
 // oops, found an error
 if ($error) {
 goto cleanup;
 }
}

cleanup:
// do some cleanup

您只能在与 goto 语句本身相同的作用域内跳转到标签,并且不能跳转到循环或开关中。通常,您可以重写代码以更清晰地处理 goto(或多级 break 语句)的任何地方。

包括代码

PHP 提供了两种结构来加载来自另一个模块的代码和 HTML:requireinclude。这两者在 PHP 脚本运行时加载文件,在条件和循环中工作,并在找不到要加载的文件时报错。文件可以通过使用函数中的包含文件路径作为指令的一部分来定位,或者基于 php.ini 文件中 include_path 的设置。include_path 可以通过 set_include_path() 函数进行覆盖。如果所有这些途径都失败了,PHP 的最后尝试是在调用脚本的同一目录中查找文件。主要区别在于尝试 require 一个不存在的文件会导致致命错误,而尝试 include 这样一个文件会产生警告但不会停止脚本执行。

include 的常见用途是将特定于页面的内容与通用站点设计分离。常见元素(如标题和页脚)放在单独的 HTML 文件中,然后每个页面看起来像:

<?php include "header.html"; ?>
*`content`*
<?php include "footer.html"; ?>

我们使用 include 是因为它允许 PHP 继续处理页面,即使在站点设计文件中存在错误。require 结构则不太宽容,更适合加载代码库,如果库未加载,则无法显示页面。例如:

require "codelib.php";
mysub(); // defined in codelib.php

处理标题和页脚的稍微更有效的方法是加载单个文件,然后调用函数生成标准化的站点元素:

<?php require "design.php";
header(); ?>
*`content`*
<?php footer();

如果 PHP 无法解析通过 includerequire 添加的文件的某些部分,则会打印警告并继续执行。您可以在调用前加上静默操作符(@)来消除警告 - 例如,@include

如果通过 PHP 的配置文件 php.ini 启用了 allow_url_fopen 选项,则可以通过提供 URL 而不是简单的本地路径来包含来自远程站点的文件:

include "http://www.example.com/codelib.php";

如果文件名以 http://https://ftp:// 开头,则从远程站点检索并加载文件。

使用 includerequire 包含的文件可以任意命名。常见的扩展名包括 .php.php5.html

注意

注意,从启用了 PHP 的 Web 服务器获取以 .php 结尾的文件将获取该 PHP 脚本的输出 - 它执行该文件中的 PHP 代码。

如果程序使用includerequire两次包含同一文件(例如在循环中错误地执行),则加载文件并运行代码,或者 HTML 打印两次。这可能导致关于函数重定义的错误,或者发送多个标题或 HTML 副本。为防止这些错误发生,使用include_oncerequire_once结构。它们在第一次加载文件时行为与includerequire相同,但会静默地忽略后续尝试加载同一文件。例如,许多页面元素,每个存储在单独文件中,需要知道当前用户的偏好设置。元素库应使用require_once加载用户偏好设置库。然后页面设计人员可以包含一个页面元素,而不必担心用户偏好代码是否已加载。

在包含文件中的代码被导入到include语句所在位置的作用域中,因此包含的代码可以查看和更改您的代码的变量。这可能是有用的——例如,用户跟踪库可能会将当前用户的名称存储在全局$user变量中:

// main page
include "userprefs.php";
echo "Hello, {$user}.";

库查看和更改您的变量的能力也可能是一个问题。您必须知道库使用的每个全局变量,以确保不会意外尝试将其中一个用于自己的目的,从而覆盖库的值并扰乱其工作方式。

如果includerequire结构位于函数中,则包含文件中的变量成为该函数的函数作用域变量。

因为includerequire是关键字,而不是真正的语句,所以在条件和循环语句中,您必须始终将它们括在花括号中:

for ($i = 0; $i < 10; $i++) {
 include "repeated_element.html";
}

使用get_included_files()函数来了解您的脚本包含或需要的文件。它返回一个包含每个已包含或需要的文件的完整系统路径文件名的数组。未解析的文件不包括在此数组中。

在 Web 页面中嵌入 PHP

虽然可以编写并运行独立的 PHP 程序,但大多数 PHP 代码都嵌入在 HTML 或 XML 文件中。毕竟,这正是它首次创建的原因。处理这些文档涉及将每个 PHP 源代码块替换为执行时产生的输出。

因为单个文件通常包含 PHP 和非 PHP 源代码,我们需要一种方法来识别要执行的 PHP 代码区域。PHP 提供了四种不同的方法来实现这一点。

正如您将看到的,第一种和首选的方法看起来像 XML。第二种方法看起来像 SGML。第三种方法基于 ASP 标签。第四种方法使用标准的 HTML <script> 标签;这使得可以使用常规 HTML 编辑器轻松编辑启用 PHP 的页面。

标准(XML)样式

由于可扩展标记语言(XML)的出现以及 HTML 向 XML 语言(XHTML)的迁移,目前首选的嵌入 PHP 的技术使用符合 XML 的标记来表示 PHP 指令。

在 XML 中定义 PHP 命令标签很容易,因为 XML 允许定义新标签。要使用此样式,请用<?php?>包围你的 PHP 代码。这些标记之间的所有内容都被解释为 PHP,标记外的内容则不是。虽然不必在标记和封闭文本之间包含空格,但这样做会增加可读性。例如,要让 PHP 打印“Hello, world”,可以在网页中插入以下行:

<?php echo "Hello, world"; ?>

语句的结尾分号是可选的,因为块的结尾也强制结束表达式。嵌入在完整的 HTML 文件中时,看起来像这样:

<!doctype html>
<html>
<head>
 <title>This is my first PHP program!</title>
</head>

<body>
<p>
 Look, ma! It's my first PHP program:<br />
 <?php echo "Hello, world"; ?><br />
 How cool is that?
</p>
</body>

</html>

当然,这并不是很令人兴奋——我们可以在没有 PHP 的情况下完成它。PHP 真正的价值在于我们将来会将来自数据库和表单值等来源的动态信息放入网页中。不过,这是后面的章节内容了。让我们回到我们的“Hello, world”示例。当用户访问此页面并查看其源代码时,看起来像这样:

<!doctype html>
<html>
<head>
 <title>This is my first PHP program!</title>
</head>

<body>
<p>
 Look, ma! It's my first PHP program:<br />
 Hello, world!<br />
 How cool is that?
</p>
</body>

</html>

请注意,原始文件中的 PHP 源代码已经消失了。用户只看到其输出。

还要注意,我们在一行内在 PHP 和非 PHP 之间切换。PHP 指令可以放在文件中的任何位置,甚至在有效的 HTML 标记内。例如:

<input type="text" name="first_name" value="<?php echo "Peter"; ?>" />

当 PHP 完成此文本时,将读取:

<input type="text" name="first_name" value="Peter" />

开始和结束标记内的 PHP 代码不必在同一行上。如果 PHP 指令的结束标记是最后一行的内容,那么跟在结束标记后的换行符也会被移除。因此,我们可以用以下方式替换“Hello, world”示例中的 PHP 指令:

<?php
echo "Hello, world"; ?>
<br />

结果 HTML 没有任何变化。

SGML 风格

另一种嵌入 PHP 的样式来自 SGML 指令处理标签。要使用此方法,只需在<??>中包围 PHP 即可。以下是“Hello, world”示例:

<? echo "Hello, world"; ?>

这种风格称为短标签,默认情况下是关闭的。可以通过使用--enable-short-tags选项构建 PHP 或在 PHP 配置文件中启用short_open_tag来支持短标签。这是不鼓励的,因为它依赖于此设置的状态;如果将代码导出到另一个平台,可能会工作也可能不会。

内联代码,<?= ... ?>,即使短标签不可用也可以使用。

直接回显内容

在 PHP 应用程序中最常见的操作之一可能是向用户显示数据。在 Web 应用程序的上下文中,这意味着将将在用户查看时变成 HTML 的信息插入到 HTML 文档中。

为了简化这个操作,PHP 提供了一种特殊版本的 SGML 标记,它自动获取标记内部的值并将其插入到 HTML 页面中。要使用此功能,请在开放标记中添加等号(=)。使用此技术,我们可以将我们的表单示例重写为:

<input type="text" name="first_name" value="<?= "Dawn"; ?>">

下一步

现在您已经掌握了语言的基础——变量的基本理解及其命名方式,数据类型的概念,以及代码流控制的工作原理——我们将继续讨论 PHP 语言的一些细节。接下来,我们将涵盖三个对 PHP 非常重要的主题,它们各自都有专门的章节:如何定义函数(第三章),操作字符串(第四章),以及管理数组(第五章)。

¹ 如果您从 C API 的角度看引用计数,实际上是 3,但为了本说明以及从用户空间的角度来看,将其视为 2 更容易理解。

² 这里有个小提示:将二进制数分成三组——6 是二进制 110,5 是二进制 101,1 是二进制 001;因此,0651 的二进制表示为 110101001。