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 这些值的一个子集;我会让你自己想出一个例子)。