编程

PHP 8.0 新特性: Stringable 接口

1215 2021-12-29 02:52:37

PHP 8.0 添加了一个新接口 Stringable,也就是说任何实现 Stringable 接口的类应该实现 __toString(): string 魔术方法。

为提供前向兼容性,PHP 引擎自动将 Stringable 接口添加到所有实现 __toString() 方法的类中。

interface Stringable {
    public function __toString(): string;
}

最常见的用例是在尝试使用字符串函数之前,在函数/方法内部的严格类型。

class Foo {
    public function __toString(): string {
        return 'FooBar';
    }
}

function safePrint(string|Stringable $input): void {
    echo htmlspecialchars((string) $input);
}

safePrint 函数可以使用联合类型指示或强制它支持标量 string 类型或者一个实现 Stringable 接口的类对象。

可选的声明 implements \Stringable

如果一个类实现了 __toString 方法,PHP会自动认为它实现了 Stringable 接口。它不是必需的,但可以显式声明它。

class Foo implements \Stringable{
    public function __toString(): string {
        return 'FooBar';
    }
}

检测字符串功能

有了标量 string 类型和 Stringable 接口,现在可以在强制执行类型的应用程序中安全地使用字符串函数。

$foo instanceof \Stringable

实现 __toString 方法的类,无论是否带有 implements \Stringable 显式声明,instanceof 检测都会返回 true

class Foo {
    public function __toString(): string {
        return 'FooBar';
    }
}
$foo = new Foo();
var_dump($foo instanceof Stringable); // true

请注意 instanceof 当前要求对象,暂且还不支持标量变量。

class_implements($foo)

class_implements 函数将正确返回具有 __toString() 方法的类中的给定对象,无论该方法带有或不带有显式 implements\Stringable 声明,该对象将返回 Stringable 作为该类实现的接口之一。

class Foo {
    public function __toString(): string {
        return 'FooBar';
    }
}
var_dump(class_implements(new Foo())); 
// array("Stringable" => "Stringable")

is_string($foo)

is_string 函数检测变量类型,只有提供的参数是 string 类型时,返回 true 。因为实现 __toString 的类对象是对象,is_string 会返回 false ,即使其符合 Stringable 接口。

class Foo {
    public function __toString(): string {
        return 'FooBar';
    }
}
var_dump(is_string(new Foo())); // false

strict_types = 1 表现

当未强制执行严格类型时,可字符串对象将被计算为具有 __toString 的字符串。如果强制执行严格的类型(在文件顶部使用 declare(strict_types=1)),则需要显式字符串强制转换。

declare(strict_types=1);
class Foo {
    public function __toString(): string {
        return 'FooBar';
    }
}

function safePrint(string|Stringable $input): void {
    echo htmlspecialchars($input);
                        //^^^ No string cast 
}
safePrint(new Foo());
// Fatal error: Uncaught TypeError: htmlspecialchars(): Argument #1 ($string) must be of type string, Foo given in ...:...

在上面的代码段中,强制执行了严格的类型。htmlspecialchars 函数只接受字符串参数。如果传递了一个 Stringable 接口的对象,PHP 将拒绝接受它,即使该对象实现了 Stringable 接口。

严格类型启用时,确保将 string|Stringable 值使用 (string)$input 进行类型转换。

向后兼容性影响

将新的 Stringable 接口引入 8.0 之前的 PHP 版本并不重要。但是,请注意,除非类明确声明 implements\Stringable,否则8.0之前的PHP版本将不会提供如上所述的字符串功能检查。

interface Stringable {
    public function __toString(): string;
}

class MyStringCapableClass implements Stringable {
    public function __toString(): string {
        return 'Hello World';
    }
}

为了与以前的PHP版本兼容,必须添加 Stringable 类,并且所有提供字符串功能的类都必须明确声明它们 implements \Stringable

 

扩展阅读:PHP RFC: 添加 Stringable 接口

介绍

PHP 8 引入了 Stringable 接口,自动为实现 __toString() 方法的类添加该接口

有两个目标:

允许用 string|Stringable 类型来表达 string|object-with-__toString()[带有__toString()方法的对象]

为PHP7 升级到8 提供一个升级路径

目标1:允许在php8中使用 string|Stringable 联合类型, 用来接收字符串或者能够实现 __toString() 方法的对象。这是当前的一个重要缺失: 使用 Stringable的对象编码时,没办法实现类型安全的。 

实现 __toString() 方法的类,也可以 地声明接口。没有显式声明接口,也仍然隐式声明了。这让其可以同时向前兼容以及向后兼容: 使用polyfill , 类可以在 php7 中声明接口; 在 PHP 8 中, 类无需如此仍然可以匹配 string|Stringable 联合类型。

一旦 polyfill代码块被广泛使用(比如,成为 symfony/polyfill-php80 中的一部分 ),  当__toString() 被显式声明的时候,代码规范检查器可以强制声明接口,使之成为大家的习惯。

该接口声明的装代码如下:

interface Stringable
{
   public function __toString(): string;
}

由于添加了返回类型,该接口会迫使现有想使用它的库拥有向后兼容的潜力。

为了让向前和向后兼容更容易,这个 RFC 同时建议如果 __toString() 方法没有显式返回类型,在编译时自动添加返回类型。如果在引擎上强制返回字符串,就不用在语法上做改动。

按照这种方式, 迁移到PHP8的代码,不必强制显式添加返回类型(向后兼容),PHP<8 的代码可以采用 pollfill 接口(为了兼容而不需要声明返回类型的接口)

提供简便的升级路径是本RFC的第二个目标。

原文查看https://wiki.php.net/rfc/stringable

 

PHP