本文属于专题文章《深入浅出 PHP 8》。
根据w3tech的数据,PHP 仍是互联网上使用最为广泛的脚本语言之一,77.3%的网站在服务器端均使用该编程语言。PHP 8 为我们带来了许多新功能与优化,具体将在本系列文章中分析。
PHP 8 是PHP的一次重大更新,它引入了一些新特性和性能优化,包括注解(attributes)、 match
表达式、 instanceof
运算符、 new
运算符、全新的 JIT 编译器等等。
注解(attributes)提供了一种向类、方法、函数、参数、属性和类常量中添加元数据的方法。Attributes 注解类似于其他一些语言所支持的 annotations 注解。new
运算符仅从 PHP 8.1 开始可用,可用于默认参数值、静态变量的初始化设置和属性参数。 match
表达式是一种新的控制结构,它使用身份运算符(identity operator)对带有多分支条件表达式的主题表达式进行匹配,并执行匹配的分支。 instanceof
运算符以前只能用于类对象,现在也可以用于任意表达式了。 JIT(Just-In-Time)编译器带来了性能和可用性提升。
其中一些示例所基于的数据结构需要从此处下载并安装 php_ds
扩展。在 Windows 上,需要从此处下载 8.1 非线程安全(Non-Thread-Safe,NTS)x64 的 DLL,并将 php_ds-1.4.0-8.1-NTS-vs16-x64.zip 文件解压缩到某个目录下。解压后,将目录中的 php_ds.dll 复制到 PHP 8.x 安装根目录中的 .\ext
目录中,例如 C:\PHP-8.1.9-nts-Win32-vs16-x64\ext
。并在 php.ini 配置文件中添加如下行:
extension=php_ds
如果 PHP 内置服务器已经在运行了,那么请重新启动它。
注解(Attributes)
注解(Attributes)是配置指令,用于使用结构化的和机器可读的元数据注释或修饰类、方法、函数、参数、属性和类常量。“结构化”意味着可以读取和解析元数据的信息,这使得注解不同于非结构化的文档注释,后者只是普通字符串。注解可用于提供仅在某些时候相关的配置和其他信息,因此它可以是嵌入式的,而不是硬编码到 PHP 脚本中。以下是使用注解的典型顺序:
声明注解类。注解类是一个常规的 PHP 类,其前面是一行单独使用的
#[Attribute]
。该类可以选择性地声明一个构造函数。注解由其类标识;当使用该注解时,类名就是注解名。在类、方法、函数、参数、属性和类常量声明上应用或使用该注解。例如,如果注解类名为
Validate
,则该注解用作#[Validate]
。同一注解可以在不同的声明中多次使用。并且多个注解也可以应用于同一个声明。如果需要,可以在运行时中使用反射API获取或读取注解。注解有多种用途,例如:
提供接口的替代方案,好处是实现接口的类需要实现接口中的所有方法,而注解可以仅在需要时使用,从而避免不必要的方法实现。
更改编译、诊断、代码生成和运行时行为。
分离 PHP 引擎和扩展。PHP 核心和扩展可以在某些声明上具有某些注解。
在声明中嵌入特定的配置信息。例如,方法上的注解可以指示该方法监听哪些事件。
从文档块迁移到注解中。默认情况下,可以在任何或所有受支持的声明类型上使用注解。但是,注解的使用可能仅限于使用选定的位掩码标志
Attribute::TARGET_CLASS
、Attribute::TARGET_FUNCTION
、Attribute::TARGET_METHOD
、Attribute::TARGET_PROPERTY
、Attribute::TARGET_CLASS_CONSTANT
、Attribute::TARGET_PARAMETER
、Attribute::TARGET_ALL
的一种或多种类型的声明上。Attribute::TARGET_ALL
标志是默认值。使用位掩码标志Attribute::IS_REPEATABLE
,可以在单个声明中多次使用同一注解。
接下来,我们将通过一个示例来探讨注解的使用。假设你想使用一个或多个排序函数对数组进行排序,例如 sort()
用于升序排序, rsort()
用于降序排序, shuffle()
用于随机排序。你可能还需要验证一下输入的数组,以验证它不是空数组,或者它至少有两个元素以使排序相关。排序类型信息和验证信息可以以注解的形式提供。
首先,声明用于验证的注解类。
然后,为排序类型声明另一个注解类。位掩码标志 Attribute::TARGET_CLASS
和 Attribute::IS_REPEATABLE
表示该注解仅用于类声明,并且该注解是可重复的。
现在声明带有 sortArray()
方法的接口。
最后,声明 Sort 接口的实现类。
在类中,声明两个类属性,一个用于排序类型,另一个用于要排序的数组。
声明一个方法来验证要排序的数组不是空数组。用 #[Validate]
注解来注解该方法。将注解应用于方法,可以使用反射 API 来发现该方法。
声明第二个方法来验证数组是否至少有两个元素,并对其应用 #[Validate]
注解。
实现 sortArray
函数。根据 SortType
的值,进行升序/降序/随机排序。
添加一个名为 performSort(Sort $sort)
的函数来执行排序。在该函数中,在执行排序之前会先应用验证。
声明一个类并对其应用 #[SortType]
注解。由于该注解是可重复的,因此多次应用该注解以执行不同类型的排序。
最后,创建一个 SortImpl
类的实例。
使用反射 API 获取类注解。
遍历注解数组,并调用 performSort()
函数,对每种排序类型执行排序,并输出排序结果。
GitHub上提供了演示注解使用的完整 sort.php
脚本。将 sort.php
脚本复制到 scripts
文件夹,并在http://localhost:8000上运行并监听 PHP 内置服务器,使用 url http://localhost:8000/scripts/sort.php在浏览器中调用 sort.php
脚本。使用不同排序类型的排序结果将显示在浏览器中,如图 1 所示。
图 1 使用注解排序的结果
因为 shuffle 排序是一种随机排序,所以每次运行脚本时,shuffle 的结果可能不同,如图 2 所示。
图 2 Shuffle 排序产生不同的结果
因为要排序的示例数组有 4 个元素,所以没有一个验证失败。如果示例数组为空,作为对比,使用相同脚本,仅更改示例数组,将会生成运行时异常消息: Uncaught RuntimeException: Array is empty; please provide a non-empty array
。类似地,如果示例数组只有 1 个元素,也会生成运行时异常,并显示消息: Uncaught RuntimeException: Please provide an array of size 2 or more
。
增强的 new
运算符
new
运算符用于创建类的实例。从 PHP 8.1 开始,new
运算符可以用于具有如下语法的任意表达式中,其中表达式必须用圆括号括起来。
new
运算符可用于参数默认值的初始化设置、静态变量的初始化设置、全局常量的初始化设置和注解参数,接下来将通过示例进行探讨。
在函数的参数默认值中使用 new
运算符
为了演示在函数的参数默认值中使用 new
运算符,我们将使用数组排序示例的变体。考虑一个名为 SortArray
的类,它声明了一个方法 sortArray($arrayToSort)
来按升序对数组进行排序。
第二个类,名为 ReverseSortArray
,它声明了一个按降序排列数组的方法。
现在声明一个可接受任意数组的函数以及一个数组排序器来对数组进行排序。可以使用 new
运算符来定义默认的数组排序器,以创建 SortArray
的实例。
完整的脚本可以在 GitHub上找到。使用 url http://localhost:8000/scripts/sample.php在内置服务器上运行示例脚本。
输出如下所示:
在变量和常量初始化中使用 new
运算符
new
运算符可用于静态变量的初始化设置和全局常量的初始化设置。但是,在静态和非静态类属性的初始化设置中,以及在类常量的初始化设置中,都不支持 new
运算符。下面的脚本演示了哪些变量和常量的初始化设置是受支持的,哪些是不受支持的。
在注解参数中使用 new
运算符
正如你所料, new
运算符也可以用于注解参数。我们将使用与演示注解功能相同的示例,但有一点不同。请记住,我们使用 #[SortType]
注解来注解 QSort
类,并将注解参数作为字符串传递给它。
针对这种情况,声明一个名为 Str
的类,它接受一个字符串型的参数作为构造函数的参数。
在注解参数中使用 new
运算符,脚本如下所示。
在内置服务器中运行脚本,输出与之前相同,如图 3 所示。
图 3 在注解参数中使用 new 的结果
在某些上下文中不允许使用 new
运算符
前面我们提到了一些不支持 new
运算符的上下文,即静态和非静态类属性的初始化设置,以及类常量的初始化设置。 此外,在以下的上下文中也不支持 new
运算符:
非字符串动态类名
常量表达式中的参数解包(Argument unpacking)
常量表达式中的匿名类
不受支持的常量表达式下面脚本中所注释掉的语句如果运行都会生成错误:
新的 match
表达式
PHP 8 引入了一个新的 match
表达式作为控制流结构,它使用身份比较(identity comparison)将给定的主题表达式与一个或多个可选分支相匹配,并返回匹配分支的结果值。match
表达式类似于 switch
语句,但区别如下:
match
表达式使用身份运算符(===
)进行比较,而switch
则使用相等运算符(==
)。match
表达式返回一个值,而switch
不返回。返回值可以赋值给某个变量。在匹配其中某个分支后,
match
表达式会自动终止match
。switch
的终止则需要使用break;
声明。match
表达式不会像switch
那样在没有声明break;
的情况下失败。match
表达式支持在同一个分支中用逗号 (,)分隔多个条件,而switch
不支持。match
表达式必须是穷尽的,这意味着它必须要处理主题表达式的所有值。接下来,我们通过一些示例来演示match
表达式的使用。
输出返回值
先从一个简单的示例开始,下面脚本中的match
表达式将作为主题表达式的整数 1
与包含默认模式的多个条件表达式进行匹配。
在内置引擎中运行脚本,通过使用 url http://localhost:8000/scripts/match.php在浏览器中调用脚本,输出为:
B
在下面的脚本中,主题表达式与任何非默认条件表达式都不匹配。
输出为:
Default
default
条件不能与脚本中所示的其他条件组合:
当脚本运行时,它会生成如下错误:
一个相对复杂的示例,下面的脚本有一个通过 \Ds\Vector
类初始化的变量。主题表达式以数组表示法访问 vector
变量。
使用 url http://localhost:8000/scripts/match.php 在浏览器中调用脚本。
结果是:
b
将返回值赋给变量
返回值也可以赋值给某个变量,匹配条件可以是任意表达式。在下面的脚本中, match
表达式将布尔值 true
作为主题表达式与调用其他内置字符串函数的条件表达式进行匹配。
输出是:
没有强制类型转换
与 switch
语句进行松散比较不同, match
表达式进行严格比较,并且不进行类型强制转换。首先,看一个与 switch
语句进行松散比较的示例。下面的脚本会匹配第一个条件并输出值 a
。
相比之下,下面脚本中的match
进行了严格比较,并匹配默认条件输出 Default
。
即使是 NULL
值也可使用身份比较进行匹配
match
表达式使用身份运算符( ===
)进行身份比较,这意味着即使是 NULL
值也会匹配。由于 Ds\Vector::push()
方法返回 NULL
,所以,下面的脚本中的match
表达式实际上与Ds\Vector::push()
方法所返回的NULL
值相匹配。
匹配的是第一个条件,输出为:
身份比较( ===
)可以应用于任意值,即使没有返回值也是如此。 match
返回的 NULL
值可以赋值给某个变量。 下面的脚本匹配 NULL
值,并且匹配的是第一个条件。
使用 url http://localhost:8000/scripts/sample.php 调用脚本,输出为:
NULL
可以用逗号分隔多个条件
多个条件表达式可以用逗号分隔。首先看一个具有单个条件的示例,脚本如下所示。
输出是:
以相同的示例为例,添加多个用逗号分隔的条件。
第一个条件表达式列表中所列出的第二个可选选项将与拼写错误的“puush”相匹配,输出为:
穷举
match
表达式必须是可穷举的。在下面的脚本中,主题表达式与任何条件都不匹配。
结果是输出一个错误:
instanceof
运算符支持任意表达式
另一个新特性是 instanceof
运算符可接受任意表达式。仅有的两个要求是:
表达式必须用圆括号括起来,并且
表达式的计算结果必须为字符串。为了演示示新的
instanceof
运算符,请创建一个 PHP 脚本 sample.php。声明三个不同类型的集合变量,类型如\Ds\Vector
和\Ds\Set
。
添加一个返回类的任意函数,如返回字符串形式的 \Ds\Vector::class
。
输出对不同集合变量使用 instanceof
运算符后的结果。instanceof
运算符接受一个表达式,该表达式在每次调用中会被计算为字符串。sample.php 的脚本如下所示。
该脚本在 PHP 8 中能正常运行,并生成如下的输出:
JIT(Just-In-Time)编译器
PHP 8 引入了即时(Just-in-time ,JIT)编译,其目的如下:
提高性能和可用性。
使 PHP 适用于非 Web 的 CPU 密集型用例,在这些用例中可以获得显著的性能优势。
创造使用 PHP 而不是 C 开发内置函数的潜力。PHP 可能是一个更好的选择,因为它没有 C 语言那样的内存管理和溢出问题。引入了两个 JIT 编译引擎:跟踪 JIT(Tracing JIT)和函数 JIT(Function JIT)。函数 JIT 只优化单个函数范围内的代码,而跟踪 JIT 则能优化整个堆栈跟踪。在综合基准测试中,跟踪 JIT 可以提高 3 倍的性能,在长时间运行的查询上可提高 1.5 到 2 倍。在 Mandelbrot 基准测试中,跟踪 JIT 可以将性能提高 4 倍以上。
在本文中,我们介绍了一些与 PHP 8 语法最相关的改进,包括注解(attributes)、 new
运算符、 match
表达式等。在下一篇文章中,我们将探讨类和构造函数的改进。
作者介绍:
Deepak Vohra 是 Oracle 认证的 Java 程序员和 Oracle 认证的 Web Component 开发人员。Deepak 已经出版了 20 多本书。
原文链接:
https://www.infoq.com/articles/php8-attributes-match-new-operator/
相关阅读:
为什么在 20 多年后,我仍然爱着 PHP 和 JavaScript
评论