PHP 8.2 预计将于2022 年底发布。有哪些值得期待的新特性和改进呢?让我们逐个来看看。

null false作为独立类型

类型安全是一个复杂的问题,这里不作深入的讨论,但从技术上讲nullfalse它们本身可以被看作有效类型。常见的例子是 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而不是以前的nullstring是讲的通的,这个以前版本做不到。

仅仅是个人看法,我会尽量不使用false作为传达错误状态的独立类型,有更好的解决方案来解决这些问题。而null作为独立类型的确有一些适用场景,我可能会偶尔用一下。

弃用动态属性

这是一个更好的改变,但它也会有点坏处。动态属性在 PHP 8.2 中被弃用,并且在 PHP 9.0 以后会直接抛出 ErrorException。什么是动态属性?这是对象上不存在的属性,但仍然可以动态设置或获取的属性:

class Post
{
    public string $title;
}

// …

$post->name = 'Name';

当然,实现了 getset 方法的类依然可以工作:

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 的一部分。

独立于环境的大小写转换

最后一项已知的变化是对大小写转换的工作方式进行了一些更改,现在不再依赖于环境。