PHP 8.2: 析取范式 (DNF) 类型
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 类型无法通过补丁去兼容旧版本。