41、PHP 8.4 中弃用 - 已弃用隐式可为 null 的参数声明
在 PHP 8.4 中,隐式可为 null 的参数声明(Implicit nullable parameters)被正式弃用。这一变化主要影响的是函数或方法参数类型声明中隐式地允许 null
作为合法值的情况。
PHP 支持为函数参数、返回值、类属性、类常量和枚举声明类型。作为一门动态类型的语言,并且拥有几十年历史,PHP 多年来经历了多个版本的改进和功能增强。
随着标量类型(PHP 7.0)、可为 null
的类型(PHP 7.1)、类型化属性(PHP 7.4)、联合类型(PHP 8.0)、交集类型(PHP 8.1)、DNF 类型(PHP 8.2)以及类型化类属性(PHP 8.3)等特性的引入,PHP 一直在朝着使类型声明更加富有表现力的方向发展。
类型声明的历史改进
从 PHP 5.1 开始,PHP 就可以声明带有默认值为 null
的类型,即使声明了类型,它也会隐式地允许 null
作为有效值:
function test(string $test = null) {}
test('PHP'); // 允许
test(null); // 允许
PHP 7.1 引入了对可为 null
的类型的支持,使用 ?TYPE
语法来声明类型为可为 null
:
function test(?string $test = null) {}
test('PHP'); // 允许
test(null); // 允许
联合类型的引入(PHP 8.0)
PHP 8.0 引入了联合类型(Union Types),使得可以声明一个类型为多种类型中的一种(例如,string
或 int
),并且不仅限于 null
,还可以是其他类型:
function test_with_default(string|null $test = null) {}
其他限制和规定
尽管 PHP 引入了多个类型系统的改进,例如在 PHP 8.0 中弃用了可选参数之后不能再跟必需参数,但由于现有 PHP 应用程序和项目中可能存在大量向后兼容性问题,PHP 在早期版本中并未弃用对隐式可为 null
的参数类型的支持。
xxxxxxxxxx pecl install oci8pecl install pdo_ociphp
-
可为
null
的类型 仅允许在函数/方法的类型声明中使用。类型化属性(PHP 7.4 引入)不支持隐式的可为null
类型声明。 - 返回类型 不支持默认值。
-
枚举类型(PHP 8.1 引入)仅支持
string
和int
类型。 -
属性提升的构造函数参数 不支持隐式可为
null
的语法。
PHP 8.4 弃用了隐式可为 null
的类型。建议 PHP 应用程序显式声明类型为可为 null
。所有带有默认值为 null
但没有在类型声明中声明 null
的类型声明都会触发弃用通知:
function test(array $value = null) {}
隐式地将参数 $value
标记为可为 null
的做法已被弃用,必须显式使用可为 null
的类型。
弃用通知会在 PHP 遇到隐式可为 null
的类型声明时触发,而不是在调用这些函数时。
推荐的更改
将隐式可为 null
的类型声明改为显式的可为 null
的类型声明:
function test(string $test = null) {}
function test(?string $test = null) {}
test('PHP'); // 允许
test(null); // 允许
或者,在 PHP 8.0 及更高版本中,可以使用联合类型(Union Type)使这一点更加明显:
function test(string $test = null) {}
function test(string|null $test = null) {}
test('PHP'); // 允许
test(null); // 允许
这两种类型声明在功能上是等效的,即使在反射 API 中也是如此。第二种声明方式更为冗长,并且仅适用于 PHP 8.0 及更高版本。可以根据 PHP 版本要求、代码风格以及代码清晰度来选择适合的方式。
类方法的变化
对于类方法,将隐式可为 null
的参数改为显式声明并不会破坏向后兼容性。父类和子类可以独立地将隐式可为 null
的参数声明替换为显式声明。
考虑以下代码片段:
class ParentClass {
public function tester(string $value = null) {}
}
class SubClass extends ParentClass {
public function tester(?string $value = null) {}
}
在这个例子中,ParentClass::tester
方法会触发弃用通知。SubClass::tester
方法正确地显式声明了可为 null
,并且不会触发弃用通知。父类可以在不破坏方法兼容性的情况下进行更新:
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 8.4 之间所有版本上运行的应用程序和 PHP 库,由于可为 null
的类型仅支持 PHP 7.1 及更高版本,因此不能使用可为 null
的类型。
虽然强烈不推荐,但移除参数类型声明,然后在函数内部检查类型是避免触发弃用通知的最后手段,同时保持代码兼容 PHP 7.0 及更高版本。
移除类型安全性
当参数没有类型声明(即未提供类型)时,该参数可以接受任何类型(混合类型)。为了弥补缺少类型安全的情况,必须在函数内部检查类型。
例如,一个之前声明为 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;
}
// 其余函数内容
}
}
类型检查会因每种类型而异,如果类型可以被强制转换/变换为另一种类型。对于启用了 strict_types
的文件,这种类型转换检查是不必要的。
类型安全性的显著丧失
虽然移除参数类型可以让函数避免弃用通知,但这会显著丧失类型安全性。这一变化也可以通过反射 API 观察到,这意味着任何依赖于参数类型的代码也会观察到这种变化。最显著的用例包括依赖注入容器和对象填充器,这些组件检查参数的类型来决定填充值的正确类型。
添加 PHPDoc 注释
为了缓解缺少 IDE 支持的问题,可以通过添加 PHPDoc 注释来帮助补充类型信息。例如:
/**
* @param string|null $value
*/
function test_discouraged($value = null) {
// 具体实现
}
通过使用 PHPDoc 注释,IDE 和其他工具能够提供类型提示和代码补全,从而弥补缺失的类型声明带来的问题。