PHP 8 return 静态和参数类型
PHP 8 return static and parameter type
Foo 是一个具有特定方法的基础 class。大多数这些方法使用相同的类型(例如:Foo::setNext(self $foo)
)。
我想创建扩展 Foo 的 classes,并且只允许使用与它们自身完全相同的类型(Extend1Foo 类型的对象不能与 Extend2Foo 类型的对象一起使用)。
在下面的代码中,由于 return 类型为静态,getBar()
会引发错误。这是我想要的。
但是,setBar()
允许接收 Foo 的任何实例作为参数,因为 self
参数类型。
可重现的例子:
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
不能在参数中使用,而不是 self
,例如 public function setBar(static $object)
.
您的用例在 PHP RFC which added the static
type annotation in return position 中被明确考虑并拒绝,因为允许它会破坏继承点:
The static
type is only allowed inside return types, where it may also appear as part of a complex type expression, such as ?static
or static|array
.
To understand why static
cannot be used as a parameter type (apart from the fact that this just makes little sense from a practical perspective), consider the following example:
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);
Under the Liskov substitution principle (LSP), we should be able to substitute class B
anywhere class A
is expected. However, in this example passing B
instead of A
will throw a TypeError
, because B::test()
does not accept a A
as a parameter.
More generally, static
is only sound in covariant contexts, which at present are only return types.
另一方面,可以将你想要的接口封装在一个特征中,这就是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;
}
这样做,public 方法将不会成为 Foo
class 接口的一部分,但这可能是最好的,因为上面没有办法为其赋予有意义的类型签名。
Foo 是一个具有特定方法的基础 class。大多数这些方法使用相同的类型(例如:Foo::setNext(self $foo)
)。
我想创建扩展 Foo 的 classes,并且只允许使用与它们自身完全相同的类型(Extend1Foo 类型的对象不能与 Extend2Foo 类型的对象一起使用)。
在下面的代码中,由于 return 类型为静态,getBar()
会引发错误。这是我想要的。
但是,setBar()
允许接收 Foo 的任何实例作为参数,因为 self
参数类型。
可重现的例子:
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
不能在参数中使用,而不是 self
,例如 public function setBar(static $object)
.
您的用例在 PHP RFC which added the static
type annotation in return position 中被明确考虑并拒绝,因为允许它会破坏继承点:
The
static
type is only allowed inside return types, where it may also appear as part of a complex type expression, such as?static
orstatic|array
.To understand why
static
cannot be used as a parameter type (apart from the fact that this just makes little sense from a practical perspective), consider the following example: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);
Under the Liskov substitution principle (LSP), we should be able to substitute class
B
anywhere classA
is expected. However, in this example passingB
instead ofA
will throw aTypeError
, becauseB::test()
does not accept aA
as a parameter.More generally,
static
is only sound in covariant contexts, which at present are only return types.
另一方面,可以将你想要的接口封装在一个特征中,这就是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;
}
这样做,public 方法将不会成为 Foo
class 接口的一部分,但这可能是最好的,因为上面没有办法为其赋予有意义的类型签名。