本文属于专题文章《深入浅出 PHP 8》。
根据w3tech的数据,PHP 仍然是 Web 上使用最广泛的脚本语言之一,77.3%的网站使用 PHP 进行服务器端编程。PHP 8 带来了许多新特性和其他改进,我们将在本系列文章中进行探讨。PHP 8.0 添加了对多个函数和方法相关特性的支持,其中一些是对现有特性的改进,而另一些则是全新的特性。PHP 8.1 中增强的可调用语法可用于通过可调用对象创建匿名函数。命名函数参数可以与位置参数一起使用,另外还有一个好处,即命名参数没有顺序,可以通过它们的名称来传达含义。纤程(Fiber)是可中断的函数,增加了对多任务的支持。
在本文中,我们将讨论 PHP 8、8.1 和 8.2 对 PHP 类型系统的扩展,其中包括联合类型、交集类型和mixed
类型,以及返回类型static
和never
。
此外,PHP 8 还支持独立类型true
、null
和false
。
一些定义
在 PHP 中,类型声明与类属性、函数参数和函数返回类型一起使用。我们经常使用各种定义从类型系统方面描述一种语言:强/弱,动态/静态。
PHP 是一种动态类型语言。所谓动态类型是指类型检查是在运行时进行的,与之相对的是在静态编译时进行类型检查。PHP 默认是弱类型的,这意味着它在运行时支持的隐式类型转换规则比较少。不过,在 PHP 中可以启用强类型。
PHP 会在不同的上下文中使用类型:
独立类型:可以在类型声明中使用的类型,如
int
、string
、array
;字面量类型:除了值的类型之外,还对值本身进行检查的类型。PHP 支持两种字面量类型:
true
和false
;单元类型:保存单个值的类型,如
null
。除了简单类型之外,PHP 8 还引入了复合类型,如联合类型和交集类型。联合类型是多个简单类型的并集。其值只需匹配联合类型中的一种类型。联合类型可用于指定类属性、函数形参的类型或函数返回类型。新增类型mixed
是联合类型的一种特殊类型。
PHP 8.1 还增加了交集类型,用于说明那种是多个类类型的交集的类类型。它还增加了两个新的返回类型。如果函数无返回值,则使用返回类型never
。例如,当函数抛出异常或调用exit()
时,可能出现这种情况。返回类型static
意味着返回值必须是调用该方法的类的实例。
联合类型
如果你熟悉文氏图,那么你可能还记得集合的并集和交集。为了支持简单类型的并集,PHP 8 引入了联合类型。用于声明联合类型的语法如下:
我们首先看个例子。在下面的脚本中,$var1
属于联合类型int|string|array
。它被初始化为一个整数值,然后它被设置为联合类型声明中包含的其他类型的值。
上述脚本输出如下:
由于 PHP 是弱类型语言,如果将$var1
的值设置为float
值1.0
,就会执行隐式转换。下面的脚本将输出1
。
然而,如果在声明时启用了强类型declare(strict_types = 1)
,那么就不能将$var1
设置为1.0
,否则会报下面这个错:
有时候,在弱类型的情况下,可以将值转换为密切相关的类型,但这种转换并非总能执行。例如,我们不能像下面的脚本那样,给联合类型(int|array
)的变量赋字符串值:
上述脚本会报如下错误:
我们看一个稍微复杂一点的例子。下面的脚本在类属性声明、函数参数和函数返回类型中使用了联合类型。
输出如下:
联合类型中的 null
联合类型可以为空,在这种情况下,null
是联合类型声明中的类型之一。在下面的脚本中,类属性、函数参数和函数返回类型都声明为可空的联合类型。
联合类型中的 false
伪类型false
可用于联合类型。在下面的示例中,false
类型用于类属性声明、函数参数和函数返回类型,它们全都声明为联合类型。
输出如下:
如果在联合类型中使用了bool
,则不能再使用false
,那会被认为是重复声明。考虑下面的脚本,其中一个函数在声明参数时使用了包含false
和bool
的联合类型。
执行上述脚本会显示如下错误信息:
联合类型中的 class 类型
Class 类型可以用于联合类型。如下所示,在联合类型中使用 class 类型A
:
输出如下:
但是,如果联合类型中使用了object
类型,就不能再使用 class 类型了。下面的脚本在联合类型中同时使用了 class 类型和object
。
执行上述脚本会显示如下错误信息:
如果联合类型中使用了iterable
,则不能再使用array
和Traversable
。下面的脚本在联合类型中同时使用了array
和iterable
:
执行上述脚本会显示如下错误信息:
联合类型与类继承
如果一个类继承了另一个类,那么联合类型可以单独声明两个类,或者只声明超类。例如,在下面的脚本中,类C
继承了类B
,类B
又继承了类A
。然后,在声明联合类型的函数参数时把类A
、B
和C
都包含了进去。
输出如下:
或者,在声明fn1
的参数时可以只使用A
类型,输出不变:
联合类型中的 void
不能在联合类型的返回类型中使用void
。执行如下脚本:
将显示如下错误信息:
联合类型的隐式类型转换
前面我们提到过,如果不启用强类型,那么当一个值与联合类型中的任何类型都无法匹配时,它将转换为与之密切相关的类型。但是,哪些是密切相关的类型呢?隐式转换会按以下优先顺序进行:
整型
浮点型
字符串型
布尔型例如,在下面的脚本中,字符串值
"1"
将被转换成一个浮点数:
输出如下:
但是,如果联合类型中包含int
,则输出为int(1)
。在下面的脚本中,联合类型(int|float
)变量被赋值为字符串"1.0"
。
输出如下:
在下面的脚本中,字符串值"true"
会被解释成string
值,因为联合类型中包含string
。
输出如下:
但是,在下面的脚本中,字符串值"true"
会被转换成bool
值,因为联合类型中不包含string
。
输出如下:
下面这个例子的输出难以预测。这个脚本将一个字符串值赋给联合类型为int|bool|float
的变量。
输出如下:
string
被转换为bool
值,因为它无法转换为int
或float
。
新增的 mixed 类型
PHP 8 引入了一个名为mixed
的新类型,它相当于联合类型object |resource|array|string|int|float|bool|null
。例如,在下面的脚本中,mixed
被用作类属性类型、函数参数类型和函数返回类型。启用强类型是为了证明mixed
不受强类型所影响。
显然,mixed
非常灵活,可以输出不同类型:
在联合类型中将其他标量类型与混合类型一起使用是多余的,因为mixed
类型是所有其他标量类型的联合类型。请看下面的脚本,在一个联合类型中同时使用int
和mixed
类型。
执行脚本会显示如下错误信息:
类似地,mixed
也不能和任何类类型一起使用。下面的脚本会显示同样的错误信息:
子类的方法可以缩小返回类型mixed
。例如,子类中的函数fn1
将返回类型mixed
缩小为array
。
新增的独立类型 null、false 和 true
在 PHP 8.2 之前,null
类型是 PHP 的单元类型,即保存单个值的类型。类似地,false
类型是bool
类型的字面量类型。不过,null
和false
类型只能在联合类型中使用,而不能作为独立类型使用。要证明这一点,可在 PHP 8.1 及更早的版本中运行如下脚本:
在 PHP 8.1 中,该脚本会输出以下错误信息:
类似地,为了证明在 PHP 8.1 或更早的版本中,false
类型不能作为独立类型使用,可运行以下脚本:
在 PHP 8.1 中,该脚本会生成如下错误信息:
PHP 8.2 支持将null
和false
作为独立类型来使用。下面的脚本使用null
作为方法参数类型和方法返回类型。
null
不能使用?null
显式标记为可空。要证明这一点,可运行以下脚本:
该脚本会生成以下错误信息:
以下脚本将false
作为独立类型来使用:
null
和false
可以用于联合类型,如下所示:
此外,PHP 8.2 还新增了一个类型true
,它可以用作独立类型使用。下面的脚本使用true
作为类属性类型、方法参数类型和方法返回类型。
true
类型不能和false
一起用于联合类型,如下所示:
该脚本会生成以下错误信息:
类似地,true
也不能和bool
一起用于联合类型,如下所示:
该脚本会生成以下错误信息:
交集类型
PHP 8.1 将交集类型作为复合类型引入。交集类型可以与类和接口类型一起使用。交集类型用于表示多个类和接口类型的类型,而不是单个类或接口类型的类型。交集类型的语法如下:
何时使用交集类型,何时使用联合类型?如果一个类型表示多个类型中的一个,则使用联合类型。如果一个类型要同时表示多个类型,则使用交集类型。下面这个例子很好地说明了这种差异。A
、B
和C
是 3 个相互之间没有关系的类。如果一个类型要表示这些类型中的任何一个,则要使用联合类型,如下所示:
输出如下:
这里如果使用交集类型,就会产生错误。修改这个函数,使用交集类型:
执行上述脚本会产生以下错误信息:
如果C
继承了B
,B
继承了A
,就可以使用交集类型了,如下所示:
输出如下:
标量类型与交集类型
交集类型只能与类和接口类型一起使用,但不能与标量类型一起使用。为了证明这一点,修改前述脚本中的fn1
函数,如下所示:
执行上述脚本会产生以下错误信息:
交集类型与联合类型
交集类型不能与联合类型组合使用。具体来说,在同一类型声明中,交集类型表示法不能与联合类型表示法组合使用。为了证明这一点,修改fn1
函数如下:
上述脚本会导致如下解析错误信息:
在同一个函数声明中,交集类型可以与联合类型一起使用,如下所示:
返回类型 static 与 never
PHP 8.0 引入了一个新的返回类型static
,PHP 8.1 引入了一个新的返回类型 never
。
返回类型 static
如果返回类型指定为static
,则返回值的类型必须是定义方法的类的类型。例如,类A
中定义的fn1
方法返回类型为static
,因此,该方法必须返回类型为A
的值,即声明该函数的类。
输出如下:
返回类型声明为static
的函数必须属于某个类。为了证明这一点,声明一个返回类型为static
的全局函数:
上述脚本会导致如下错误:
返回的类对象必须是外围类。下面的脚本会生成一个错误,因为返回值是类类型B
,而返回类型static
要求返回类型为类型A
。
上述脚本会产生以下错误:
如果类B
继承了类A
,那么上述脚本就不会有什么问题,将输出1
。
返回类型static
可用于联合类型。如果在联合类型中使用static
,则返回值不一定是类类型。例如,以下脚本在联合类型中使用了static
:
输出如下:
类型static
不能用于交集类型。为了证明这一点,请看下面的脚本:
该脚本会导致以下错误:
返回类型 never
如果返回类型为never
,则函数必须不返回值,或者根本不返回,即函数不终止。返回类型never
是其他所有返回类型的子类型。也就是说,在继承一个类时,never
可以在重写方法中替换任何其他返回类型。返回never
的函数必须执行以下操作之一:
抛出一个异常
调用
exit()
启动一个无限循环如果返回
never
的函数永远不会被调用,那么这个函数可以为空,如下所示:
类A
中的函数fn1()
不能被调用,因为该函数隐式返回NULL
。为了证明这一点,我们将上述脚本修改为:
执行该脚本将生成以下错误信息:
下面的脚本将生成同样的错误消息,因为if
条件永远无法满足,而函数隐式返回NULL
:
与static
返回类型不同,never
可以用作不属于类作用域的函数的返回类型,例如:
返回类型为never
的函数一定不能返回值。为了证明这一点,下面的脚本声明了一个函数,该函数试图返回值,尽管它的返回类型为never
。
执行上述脚本会生成以下错误信息:
如果返回类型为never
,则函数即使是隐式返回也不行。例如,下述脚本中的fn1
函数不返回值,而是在其作用域结束时隐式返回。
上述脚本会导致以下错误:
声明返回类型为never
且不会终止的函数有什么用?返回类型never
可以在开发、测试和调试期间使用。返回never
类型的函数可以通过调用exit()
退出。这样的函数甚至可以被调用,如下面的脚本所示:
返回never
类型的函数可以抛出异常,如下所示:
包含无限循环的函数可以将返回类型声明为never
,如下所示:
返回类型never
可以覆盖派生类中的任何类型,如下所示:
返回类型never
不能用于联合类型。为了证明这一点,下面的脚本在联合类型中使用了never
:
该脚本将导致以下错误:
never
类型不能用于交集类型。为了证明这一点,请运行以下将never
和类类型B
一起使用的脚本。
该脚本将导致以下错误:
标量类型不支持别名
从 PHP 8 开始,如果使用标量类型别名,就会生成警告信息。例如,如果使用boolean
代替bool
,则生成一条消息,说明boolean
将被解释成类名。为了证明这一点,考虑下面的脚本,函数声明将integer
作为参数类型。
如下所示,该脚本的输出中将包含一条警告信息:
不再支持从 void 函数通过引用返回
从 PHP 8.1 开始,不再支持从void
函数通过引用返回,因为只有变量引用可以通过引用返回,而void
返回类型不返回值。为了证明这一点,可运行下面的脚本:
该脚本将输出一条弃用提示信息:
小结
在本文中,我们讨论了 PHP 8 中引入的与类型相关的新特性,包括联合类型、交集类型和mixed
类型,以及返回类型static
和never
。在下一篇文章中,我们将介绍与 PHP 数组、变量、运算符和异常处理相关的新特性。
原文链接:
https://www.infoq.com/articles/php-8-type-system-improvements/
相关阅读:
评论 1 条评论