PHP 7 中覆盖抽象函数参数类型提示
Abstract function parameter type hint overriding in PHP 7
是否可以用 child class 中的函数覆盖 PHP 7 中的抽象函数,从而缩小可接受的参数类型?
详细说明 - 假设我有一个抽象方法:
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
现在在 child class 中,我想覆盖此方法的签名,以便它只接受特定的参数类型(假设 EmailDeliveryTarget
扩展了 AbstractDeliveryTarget
):
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery;
PHP 解释器然后会抱怨不能在 child class 中更改抽象方法的签名。那么,除了某些 in-method-body 类型守卫之外,有没有办法在 child class 中实现 type-safety?
看起来没有很好的解决方案,所以我们可以尝试使用接口。
<?php
interface DeliveryTarget {}
class AbstractDeliveryTarget implements DeliveryTarget {
}
class EmailDeliveryTarget extends AbstractDeliveryTarget{
}
abstract class Q {
abstract protected function action(DeliveryTarget $class);
}
class Y extends Q {
protected function action(DeliveryTarget $class){}
}
或
interface DeliveryTargetAction {}
class AbstractDeliveryTarget {
}
class EmailDeliveryTarget extends AbstractDeliveryTarget implements DeliveryTargetAction {
}
abstract class Q {
abstract protected function action(DeliveryTargetAction $class);
}
class Y extends Q {
protected function action(DeliveryTargetAction $class){}
}
取决于你想做什么。
这不是技术限制;您要求的内容与 OOP 的原则毫无意义。
您的摘要class是一份合同;让我们定义基数 class:
class AbstractDeliverer {
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
}
以及一些投放目标
class AbstractDeliveryTarget {}
class EmailDeliveryTarget extends AbstractDeliveryTarget {}
class SMSDeliveryTarget extends AbstractDeliveryTarget {}
那你可以写在别的地方:
function deliverAll(AbstractDeliverer $d) {
$e = new EmailDeliveryTarget;
$d->setTarget($e);
$s = new SMSDeliveryTarget;
$d->setTarget($s);
}
因为我们知道 $d
是一个 AbstractDeliverer
,所以我们知道将 $e
和 $s
传递给它应该都有效。当我们确定我们的输入是那种类型时,这是向我们保证的合同。
现在让我们看看如果按照您想要的方式扩展它会发生什么:
class EmailOnlyDeliverer extends AbstractDeliverer {
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery {
/* ... */
}
}
$emailonly = new EmailOnlyDeliverer;
我们知道 $e instanceOf AbstractDeliverer
将是 true
,因为我们继承了它,所以我们知道我们可以安全地调用我们的 deliverAll
方法:
deliverAll($emailonly);
函数的第一部分很好,并且会有效地 运行 这个:
$e = new EmailDeliveryTarget;
$emailonly->setTarget($e);
但后来我们遇到了这部分:
$s = new SMSDeliveryTarget;
$emailonly->setTarget($s);
糟糕!致命错误!但是 AbstractDeliverer
上的合约告诉我们这是传递的正确值!出了什么问题?
规则是 子class 必须接受父 class 会接受的所有输入,但如果需要,它可以接受额外的输入,称为 "contravariance"。 (Return 类型改为 "covariant":子 class 绝不能 return 不能从父 return 编辑的值 class,但可以做出更有力的承诺,它只会 return 这些值的一个子集;我会让你自己想出一个例子)。
是否可以用 child class 中的函数覆盖 PHP 7 中的抽象函数,从而缩小可接受的参数类型?
详细说明 - 假设我有一个抽象方法:
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
现在在 child class 中,我想覆盖此方法的签名,以便它只接受特定的参数类型(假设 EmailDeliveryTarget
扩展了 AbstractDeliveryTarget
):
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery;
PHP 解释器然后会抱怨不能在 child class 中更改抽象方法的签名。那么,除了某些 in-method-body 类型守卫之外,有没有办法在 child class 中实现 type-safety?
看起来没有很好的解决方案,所以我们可以尝试使用接口。
<?php
interface DeliveryTarget {}
class AbstractDeliveryTarget implements DeliveryTarget {
}
class EmailDeliveryTarget extends AbstractDeliveryTarget{
}
abstract class Q {
abstract protected function action(DeliveryTarget $class);
}
class Y extends Q {
protected function action(DeliveryTarget $class){}
}
或
interface DeliveryTargetAction {}
class AbstractDeliveryTarget {
}
class EmailDeliveryTarget extends AbstractDeliveryTarget implements DeliveryTargetAction {
}
abstract class Q {
abstract protected function action(DeliveryTargetAction $class);
}
class Y extends Q {
protected function action(DeliveryTargetAction $class){}
}
取决于你想做什么。
这不是技术限制;您要求的内容与 OOP 的原则毫无意义。
您的摘要class是一份合同;让我们定义基数 class:
class AbstractDeliverer {
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
}
以及一些投放目标
class AbstractDeliveryTarget {}
class EmailDeliveryTarget extends AbstractDeliveryTarget {}
class SMSDeliveryTarget extends AbstractDeliveryTarget {}
那你可以写在别的地方:
function deliverAll(AbstractDeliverer $d) {
$e = new EmailDeliveryTarget;
$d->setTarget($e);
$s = new SMSDeliveryTarget;
$d->setTarget($s);
}
因为我们知道 $d
是一个 AbstractDeliverer
,所以我们知道将 $e
和 $s
传递给它应该都有效。当我们确定我们的输入是那种类型时,这是向我们保证的合同。
现在让我们看看如果按照您想要的方式扩展它会发生什么:
class EmailOnlyDeliverer extends AbstractDeliverer {
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery {
/* ... */
}
}
$emailonly = new EmailOnlyDeliverer;
我们知道 $e instanceOf AbstractDeliverer
将是 true
,因为我们继承了它,所以我们知道我们可以安全地调用我们的 deliverAll
方法:
deliverAll($emailonly);
函数的第一部分很好,并且会有效地 运行 这个:
$e = new EmailDeliveryTarget;
$emailonly->setTarget($e);
但后来我们遇到了这部分:
$s = new SMSDeliveryTarget;
$emailonly->setTarget($s);
糟糕!致命错误!但是 AbstractDeliverer
上的合约告诉我们这是传递的正确值!出了什么问题?
规则是 子class 必须接受父 class 会接受的所有输入,但如果需要,它可以接受额外的输入,称为 "contravariance"。 (Return 类型改为 "covariant":子 class 绝不能 return 不能从父 return 编辑的值 class,但可以做出更有力的承诺,它只会 return 这些值的一个子集;我会让你自己想出一个例子)。