编程

PHP 8.2: 析取范式 (DNF) 类型

1547 2022-12-08 21:08:20

PHP 8.2 引入了对联合类型(PHP 8.0)和相交类型(PHP8.1)二者联合的类型支持。最通用的情景是声明一个可接受相交类型或 null 的类型(比如,nullable intersection type)。

function respond(): (JSONResponse&SuccessResponse)|HTMLResponse|string {
}

上面的代码中,respond 函数将其返回类型声明为 JSONResponse&SuccessResponse 的相交类型或 HTMLResponse 或字符串。

DNF 类型的一些范例如下:

  • A|B|C
  • A|B|(C&D)
  • (A&B&C)|null

PHP 会拒绝不是用 DNF 格式声明的结合的类型,并且显示 Parse error。下面的类型不是 DNF 范式,会产生解析错误(Parse Error)。

  • A&(B|C)
  • A|(B&(C|D))

这些类型可以用 DNF 范式重写,如下:

- A&(B|C)
+ (A&B)|(A&C)
- A|(B&(C|D))
+ A|(B&C)|B&D)

DNF 类型方差

当扩展 PHP 类或者实现接口时,DNF 也同样遵循里氏替换原则(LSP)。

  • 属性类型是恒定的:扩展类不允许改变属性类型。
  • 返回类型协变:子类及接口实现的返回类型可以缩小。在 DNF 类型中,这意味着,返回类型可以更加严格。比如父类返回类型为 A&B|C,其子类返回类型可以缩小到 A&B。
  • 参数类型逆变:参数类型可以变宽。在 DNF 类型中,这意味着子类中的参数声明可以有其他类型结合。比如,参数声明 A|(B&C) 可以在子类中放大到 A|(B&C)|D。

DNF 类型声明约束

声明一个 DNF 类型时,PHP 有一些强制约束。

支持冗余类型:此特性在解析时检查,如果碰到冗余类型,PHP 会显示致命错误:

interface X {}
interface Y {}

function foo(): (X&Y)|(Y&X) {}
Fatal error: Type X&A is redundant with type X&A in ... on line ...

不过,在运行时声明的类别名不会导致错误,因为此约束只在解析时强制。

限制较少的类型不允许作为 DNF 类型的一部分:声明 DNF 类似时,不允许存在其中一部分比另一部分限制更少。 

interface A {}
interface B {}

function foo(): (A&B)|A {}
Fatal error: Type A&B is redundant as it is more restrictive than type A in ... on line ...

向后兼容性影响

DNF 类型是 PHP 8.2 中引入的新特性,同时也带来了解析器的变化。DNF 类型无法通过补丁去兼容旧版本。