使用联合类型时,你必须了解协变和逆变这两个概念。它们在文档中有解释,不过下面的例子可能会有所帮助。
假设我们有一个 Calculator 类,它使用一个类似上面示例的函数:
class Calculator
{
public function add(int|float $x, int|float $y): int|float
{
return $x + $y;
}
}
如果要继承该类,你可以更改形参类型和返回类型,但必须遵循以下规则。
首先,形参类型可以“更宽”,也就是能接受比基类更多的类型。例如,下面这样是允许的:
class StringCalc extends Foo
{
public function add(float|int|string $x, float|int|string $y): int|float
{
if (!is_numeric($x) || !is_numeric($y)) {
throw new \InvalidArgumentException('$x and $y must be numeric');
}
return $x + $y;
}
}
我们可以用StringCalc替换原来的Calculator,一切仍然能正常工作。调用方仍然可以传入它原本就能传入的float或int,返回类型也仍然是预期的int或float。
同样,你也可以缩小返回类型,也就是从联合类型中去掉一些类型,让它更具体。下面这个例子就利用了这条规则:
class IntCalc extends Calculator
{
public function add(float|int $x, float|int $y) : int
{
return (int)($x + $y);
}
}
由于IntCalc仍然接受int和float,它依然可以毫无问题地替换原来的Calculator。
想进一步了解哪些允许、哪些不允许,可以参考里氏替换原则(LSP)。
至于不允许的例子,可以看看下面的InvalidCalc:
class InvalidCalc extends Calculator
{
public function add(int $x, int $y) : int|float|string
{
// Declaration must be compatible with Calculator->add(x: float|int, y: float|int)
// Return type declaration must be compatible with Calculator->add(x: float|int, y: float|int) : float|int
}
}
InvalidCalc类将无法编译,并且存在两个问题。由于缩小了形参允许的类型,InvalidCalc无法替代原来的Calculator,因为它不再接受float。此外,它现在可能返回string,而这也不在原来类允许的返回类型范围内。