PHP 8.2 预计将于2022 年底发布。有哪些值得期待的新特性和改进呢?让我们逐个来看看。
null
和 false
作为独立类型
类型安全是一个复杂的问题,这里不作深入的讨论,但从技术上讲null
和false
它们本身可以被看作有效类型。常见的例子是 PHP 的内置函数,其中false
用作发生错误时的返回类型。例如下面的file_get_contents
:
file_get_contents(/* … */): string|false
注意,false早已经可以在联合类型中使用;但在 PHP 8.2 中,它也可以用作独立类型:
function alwaysFalse(): false
{
return false;
}
许多开发人员,包括我自己,都对这个 RFC 持谨慎态度。它不支持true
作为独立类型,并且类型不是应该代表类别而不是单个值吗?事实证明,类型系统中有一个称为单元类型的概念,它是只允许一个值的类型。但它真的有意义吗?它能否鼓励简洁的软件设计?嗯,可能吧。
一个独立的null
类型看起来更有意义:因为null
可以被视为其自身的类别,而不仅仅是类别中的值。想象一下nulll对象模式:
class Post
{
public function getAuthor(): ?string { /* … */ }
}
class NullPost extends Post
{
public function getAuthor(): null { /* … */ }
}
这里NullPost::getAuthor()
只能返回null
而不是以前的null
或string
是讲的通的,这个以前版本做不到。
仅仅是个人看法,我会尽量不使用false
作为传达错误状态的独立类型,有更好的解决方案来解决这些问题。而null
作为独立类型的确有一些适用场景,我可能会偶尔用一下。
弃用动态属性
这是一个更好的改变,但它也会有点坏处。动态属性在 PHP 8.2 中被弃用,并且在 PHP 9.0 以后会直接抛出 ErrorException
。什么是动态属性?这是对象上不存在的属性,但仍然可以动态设置或获取的属性:
class Post
{
public string $title;
}
// …
$post->name = 'Name';
当然,实现了 get
和 set
方法的类依然可以工作:
class Post
{
private array $properties = [];
public function __set(string $name, mixed $value): void
{
$this->properties[$name] = $value;
}
}
// …
$post->name = 'Name';
stdClass
的对象也是如此,它们将继续支持动态属性。
PHP 曾经是一种非常动态的语言,但现在已经远离这种思维方式一段时间了。我觉得接受更严格的规则并尽可能依赖静态分析是一件好事,它有助于开发人员编写更好的代码。
不过,我可以理解依赖动态属性的开发人员对这种变化并不满意。也许它有助于更深入地了解静态分析的好处?
如果你在升级到 PHP 8.2 时不想看到这些警告,你可以这样做。
你可以在仍允许这些属性的类上加上该属性:#[AllowDynamicProperties]
#[AllowDynamicProperties]
class Post
{
public string $title;
}
// …
$post->name = 'Name'; // 正常使用
另一个选项是简单地禁用弃用警告。
error_reporting(E_ALL ^ E_DEPRECATED);
我不建议这样做,因为PHP 9.0时你忘记了会遇到麻烦。
标记 back traces 中的参数
任何代码库中的常见做法是将生产错误发送到跟踪它们的服务,并在出现问题时通知开发人员。这种做法通常涉及通过线路将 stack traces 发送到第三方服务。然而,在某些情况下,这些 stack traces 可能包含敏感信息,例如环境变量、密码或用户名等。
PHP 8.2 允许你使用属性标记此类“敏感参数”,这样当出现问题时你无需担心它们会在 stack traces 中列出。这是 RFC 中的一个示例:
function test(
$foo,
#[SensitiveParameter] $bar,
$baz
) {
throw new Exception('Error');
}
test('foo', 'bar', 'baz');
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('foo', Object(SensitiveParameterValue), 'baz')
#1 {main}
thrown in test.php on line 8
弃用部分支持的 callables
另一个变化是部分支持的 callables 现在也被弃用了,不过这个变化影响较小。部分支持的 callables 是可以用 call_user_func($callable) 调用,但是不能直接调用 $callable() 的可调用对象。顺便说一下,这些可调用对象的列表相当短:
"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]
这样做的原因是什么?这是朝着能够将 callable
用于类型化属性的正确方向迈出的一步。Nikita 在 RFC 中做了很好的解释:
所有这些 callables 都是上下文相关的。“self::method”所指的方法取决于从哪个类执行调用或可调用性检查。在实践中,当以 [new Foo, “parent::method”] 的形式使用时,后面两种情况也适用。
减少 callables 的上下文相关性是本 RFC 的次要目标。在这个 RFC 之后,唯一剩下的范围依赖是方法可见性:“Foo::bar”可能在一个范围内可见,但在另一个范围内不可见。如果将来可调用对象仅限于公共方法(而私有方法必须使用 first-class 的callables 或 Closure::fromCallable() 以使其与范围无关),那么 callable 类型将变得明确定义并且可以用作属性类型。但是,对可见性处理的更改不建议作为本 RFC 的一部分。
独立于环境的大小写转换
最后一项已知的变化是对大小写转换的工作方式进行了一些更改,现在不再依赖于环境。