我们有实现
__toString()
的对象:
class Foo {
public function __toString()
{
return 'bar';
}
}
然后我们有返回
string
的函数或实现 __toString()
的对象,如上面的示例。
当使用返回类型
string
表示字符串时当然可以:
function getString():string {
return 'Works!';
}
返回
Foo
类型的对象不起作用,因为它不是 string
:
function getString(Foo $foo):string {
return $foo; // Fatal TypeError!
}
是否有任何 PHP 接口可以用来输入提示
string
以及实现该未知接口的对象,例如Printable
还是Stringable
(笑)?
目标是能够返回
string
或实现特定接口的对象(这将强制实现 __toString
)。
从 PHP 8.0 开始,有一个 magic
Stringable
接口,任何具有 __toString
方法的类都会自动实现它(尽管 PHP 在该语言的其他任何地方都没有自动/结构接口的概念)。
请注意,它仅匹配此类对象,因此为了也匹配实际字符串,您需要使用union类型(也在PHP 8.0中添加),并将您的类型声明为
string|Stringable
。
这个答案的其余部分最初是在添加
Stringable
之前写的,并且是我坚持的观点,因为它不是一个好的设计。
您需要问的设计问题是,您实际上在这里代表什么合约?从调用代码的角度来看,不能保证返回值实际上可以直接用作字符串 - 有许多操作对于字符串和对象会有不同的行为,或者接受字符串但不接受自动投射对象。因此,这样的提示实际上保证的唯一事情是
(string)$foo
将给出一个字符串。
所以你有几个选择:
Stringable
,提出一个更有意义的接口来返回;例如,也许您实际返回的是 Loggable
的东西。这样,包装字符串可以给您带来更具体的好处:您可以向接口添加其他方法,并且调用代码在调用这些方法之前不需要进行“对象或字符串”检查。string
,并在返回任何对象之前将其转换为字符串。从 PHP 8 开始,您将拥有 Union Types 和新接口 Stringable。您可以结合使用这两个概念来获得您想要的结果。
function getString(Stringable|string $foo) : string {
// Fatal TypeError if $foo is not a string or a class that implements Stringable
return $foo instanceof Stringable ? $foo->__toString() : $foo;
}
在 PHP 8 中,任何具有
__toString()
方法的类都会在底层自动实现 Stringable 接口。
您可以将字符串装箱在一个包装类中,该类只接受
c-tor
中的字符串,并有一个 toString()
方法返回它。
这个包装类将实现您的 Stringable 接口,就像您的其他类一样。
然后你可以提供一个返回类型提示方法,它返回 Stringable 类型的对象。
此方法可以决定它是返回一个对象还是只是字符串的包装器,因为它们都实现了 Stringable。
但是,这是为了与仅返回混合并检查调用方法中返回的值类型进行平衡。
此外,如果您的对象实现了魔法方法
__toString
,那么所有调用方法都可以始终在返回值上应用 cast (string)
。
如果返回值是一个字符串,那么它就一直是一个字符串。
如果它是实现魔术方法的对象之一,那么您将获得该字符串。
然而在这种情况下你不会有返回类型提示。
实现
__toString
的字符串和对象不可互换,例如 $s[1]
将在对象上失败(除非它实现了其他一些魔术方法)。在参数被强制为字符串的上下文中,对象“可能”是有效的替代品,但这并不适用于您可以对字符串执行的所有可能的操作。所以,不,没有原生接口,因此您也不应该混合这些类型。