PHP 7中的抽象函数参数类型提示覆盖

10

在PHP 7中,是否可以通过子类中的函数覆盖抽象函数,并缩小接受参数类型?

详细说明一下 - 假设我有一个抽象方法:

abstract public function setTarget( AbstractDeliveryTarget $deliveryTarget ): Delivery;

现在,在一个子类中,我想重写这个方法的签名,以便它只接受特定的参数类型(假设EmailDeliveryTarget扩展自AbstractDeliveryTarget):

public function setTarget( EmailDeliveryTarget $emailDeliveryTarget ): Delivery;

PHP解释器会抱怨无法更改抽象方法的签名。那么,在除了在方法体内使用类型保护之外,是否有其他方法可以在子类中实现类型安全?


1
你可以使用接口。 - Vahan
据我所知,AbstractDeliveryTarget和EmailDeliveryTarget可以实现相同的接口。 - Vahan
2个回答

11

这不是技术上的限制;你要求的内容与面向对象编程原则不符。

你的抽象类是一个合约;让我们定义基类:

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合约中告诉我们这是正确的值要传递!出了什么问题?

规则是子类必须接受父类接受的所有输入,但如果它想要,它可以接受附加输入,这被称为“逆变性”。(相反,返回类型是“协变的”:子类绝不能返回父类无法返回的值,但可以做出更强的承诺,只返回这些值的子集;我会让您自己想出一个例子。)


0

看起来没有太好的解决方案,所以我们可以尝试使用接口。

<?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){}
}

这取决于你想做什么。


抱歉,我不明白如何使用接口可以在派生类中实现更好的类型安全。在这方面,它与仅使用抽象类作为参数类型是等效的。正如问题中提到的那样,覆盖的目的是为了在子类中增加类型安全性(请参见在子类中接受EmailDeliveryTarget)。 - luqo33

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接