如何在 PHP 中对方法参数使用"静态"类型注释(或实现等效效果)?



Foo是一个具有特定方法的基类。这些方法大多使用相同的类型(例如:Foo::setNext(self $foo)(。

我想创建扩展Foo的类,并且只允许使用与自身严格相同的类型(类型为Extende1Foo的对象不能与类型为Extende2Foo的对象一起使用(。

在下面的代码中,由于返回类型为static,getBar()抛出一个错误。这就是我想要的。但是,由于self的参数类型,setBar()允许接收Foo的任何实例作为参数。

可复制示例:


class Foo 
{
private ?self $bar = null;

public function getBar(): static {
return $this->bar;
}

public function setBar(self $object): void {
$this->bar = $object;
}
}
class Foo1 extends Foo { /* specific methods */ }
class Foo2 extends Foo { /* specific methods */ }
$foo1 = new Foo1;
$foo1->setBar(new Foo2); // <<< No TypeError, but I want it.
$foo2 = $foo1->getBar(); // <<< Got error, I'm OK.

我已强制输入TypeError:

public function setBar(self $object): void
{
if (get_class($object) != static::class) {
throw new TypeError(
sprintf('%s::%s(): Parameter value must be of type %s, %s given',
__class__, __function__,
static::class, get_class($object)
)
);
}
$this->child = $object;
}

和用途:

$foo1 = new Foo1;
$foo1->setBar(new Foo2); // TypeError : Foo::setBar(): Parameter value must be of type Foo1, Foo2 given

这是预期的行为。

我的问题是:

有没有办法避免对类型进行这种动态测试?我认为不能在参数中使用static,而不能像public function setBar(static $object)那样使用self

您的用例在PHP RFC中被明确考虑并拒绝,该RFC在返回位置添加了static类型注释,因为允许它会破坏继承点:

static类型只允许在返回类型内部出现,在返回类型中,它也可以作为复杂类型表达式的一部分出现,如?staticstatic|array

为了理解为什么static不能用作参数类型(除了从实际角度来看这几乎没有意义之外(,请考虑以下示例:

class A {
public function test(static $a) {}
}
class B extends A {}

function call_with_new_a(A $a) {
$a->test(new A);
}
call_with_new_a(new B);

在Liskov替换原理(LSP(下,我们应该能够在期望A类的任何地方替换B类。然而,在本例中,传递B而不是A将抛出TypeError,因为B::test()不接受A作为参数。

更普遍地说,static只在协变上下文中是声音,而协变上下文目前只是返回类型。

另一方面,可以将您想要的接口封装在一个特性中,这就是PHP所称的mixin:

trait Bar {
private ?self $bar = null;

public function getBar(): static {
return $this->bar;
}

public function setBar(self $object) {
$this->bar = $object;
}
}
class Foo {}
final class Foo1 extends Foo { use Bar; }
final class Foo2 extends Foo { use Bar; }
try {
$foo1 = new Foo1;
$foo1->setBar(new Foo2); // TypeError
}
catch (Throwable $error) 
{
echo $error->getMessage(), PHP_EOL;
}

这样做,公共方法将不属于Foo类接口的一部分,但这可能是最好的,因为根据上面的内容,没有办法将有意义的类型签名赋予它

相关内容

  • 没有找到相关文章

最新更新