编程

PHP 8.4: 隐式 nullable 参数声明弃用

553 2024-05-08 03:56:00

PHP 8.4 中的重要弃用
这是 PHP 8.4 中重要的弃用,由于 PHP 8.4 中的这一更新,旧版 PHP 应用可能会出现弃用通知。

PHP 支持为函数参数、返回值、类属性、类常量和枚举声明类型。PHP 是一种动态类型语言,有着几十年的历史,多年来得到了一些改进和特性。

随着标量类型(PHP 7.0)、 nullable 的类型(7.1)、类型化属性(7.4)、联合类型(8.0)、交集类型(8.1)、DNF 类型(8.2)和类型化类属性(8.3)等特性的引入,PHP 一直致力于使类型声明具有表达性。

自 PHP 5.1 以来,可以声明一个默认值为 null 的类型,即使声明了一个类型,它也使该类型隐式有效地允许 null

function test(string $test = null) {}

test('PHP'); // Allowed
test(null); // Allowed

PHP 7.1 添加了 nullable 类型支持,引入了 ?TYPE 语法将类型声明位 nullable:

- function test(string $test = null) {}
+ function test(?string $test = null) {}

  test('PHP'); // Allowed
  test(null); // Allowed

随着 PHP 8.0 中联合类型的引入,你可以将类型声明位某种类型(比如 string)另一种类型(不限于 null, 也可以是 int 等)。

Nullable 类型也可以使用 null 将其声明位联合类型,比如:

function test_with_default(string|null $test = null) {}

尽管PHP得到了一些类型改进,例如在 PHP 8.0 中,启用了可选参数之后的必需参数,但考虑到现有 PHP 应用和项目中存在大量潜在的向后兼容性问题,在早期的 PHP 版本中,对隐式 nullable 参数类型的支持并没有被弃用。

Nullable 类型只在函数/方法类型中被允许。类型属性(PHP 7.4 中添加)不允许隐式 nullable 类型的声明。返回类型不支持默认值。枚举(PHP 8.1 中添加) 只支持 stringint 作为类型。此外,属性提升构造器参数不支持隐式 nullable 语法。

PHP 8.4 弃用了隐式 nullable 类型。建议 PHP 应用将类型显式声明为 nullable。所有默认值为 null 但在类型声明中未声明 null 的类型声明都会发出弃用通知:

function test(array $value = null) {}
Implicitly marking parameter $value as nullable is deprecated, the explicit nullable type must be used instead

当 PHP 遇到具有隐式 nullable 类型的声明时,会发出弃用。而不是调用此类函数时。

修改建议

将隐式 nullable 类型声明修改为显示声明:

- function test(string $test = null) {}
+ function test(?string $test = null) {}

  test('PHP'); // Allowed
  test(null); // Allowed

此外,运行在 PHP 8.0 及其之后版本上的应用可以使用联合类型使之更为明显:

- function test(string $test = null) {}
+ function test(string|null $test = null) {}

  test('PHP'); // Allowed
  test(null); // Allowed

两种类型声明方式都是等效的,即使在反射 API 中。第二个示例更详细,仅适用于 PHP 8.0 及更高版本。根据 PHP 版本要求、代码风格并考虑代码清晰度,选择其中一种方式。

在类方法上,将隐式 nullable 的参数更改为显式参数并不会破坏向后兼容性。父类和子类可以独立地用显式声明替换隐式 nullable 的参数声明。
参考如下代码片段:

class ParentClass {
    public function tester(string $value = null) {}
}

class SubClass extends ParentClass {
    public function tester(?string $value = null) {}
}

ParentClass::tester 方法触发了弃用通知。子类 SubClass::tester 显式声明了 nullable 类型,且不会触发弃用通知。更新父类而不出现方法兼容性问题是可能的:

 class ParentClass {
-   public function tester(string $value = null) {}
+   public function tester(?string $value = null) {}
 }

 class SubClass extends ParentClass {
     public function tester(?string $value = null) {}
 }

弃用参数类型

必须在从 PHP 7.0 或 PHP 5 到 PHP 8.4 的所有 PHP 版本上运行的应用和 PHP 库不能使用 nulllable 的类型,因为只有在 PHP 7.1 及更高版本上才支持 nullable 类型。
尽管十分不鼓励,但避免弃用通知的终极手段,同时保持代码与 PHP 7.0 及更高版本兼容是:删除参数中的类型声明,然后在函数内部检查类型。

安全地移除类型
如果参数未提供类型,则参数可以接受任何类型(即 mixed 类型)。请确保在函数中进行类型检查,以应对类型安全性的不足。

比如,如果参数值在函数内进行了检查,那么此前有 string $value = null 声明的函数参数可以将声明删除:

- function test_discouraged(string $value = null) {
+ /**
+  * @param string|null $value
+  */
+ function test_discouraged($value = null) {
+     if (is_array($value) || is_object($value)) {
+         throw new TypeError(__FUNCTION__ . ': Argument #1 ($value) must be of type string, '. gettype($value) .' given');
+     }
+ 
+     if ($value !== null) {
+         $value = (string) $value;
+     }
+ 
     // Rest of the function here.
 }

每种类型的类型检查将有所不同,如果可以将这些类型强制/篡改为另一种类型。在启用了 strict_types 的文件上,不需要进行这种类型切换检查

尽管删除参数类型可以使函数避免弃用通知,但这会严重顺还类型安全。这种变化也可以从反射 API 中观察到,这意味着任何依赖于参数类型的代码也会观察到这种变化。最值得注意的用例包括依赖注入容器和对象合成器,它们检查参数的类型以决定要 hydrated 的值的正确类型。

添加 PHPDoc 注释有助于缓解IDE支持的不足。

向后兼容性影响

这义弃用使类型声明的可见型和表达力得到提升。建议的替换为兼容 PHP 7.1 及更高版本。

自动修复

以下工具可以标准并自动修复隐式 nullable 类型声明。