dongyou6768 2017-12-13 20:59
浏览 75
已采纳

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

Is it possible to override an abstract function in PHP 7 with a function in a child class that would narrow down on the accepted argument type?

A word of elaboration - let's assume that I have an abstract method:

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

Now in one child class, I'd like to override this method's signature in order for it to accept only a specific parameter type (let's assume that EmailDeliveryTarget extends AbstractDeliveryTarget):

public function setTarget( EmailDeliveryTarget $emailDeliveryTarget ): Delivery;

PHP interpreter would then complain that the signature of the abstract method cannot be altered in the child class. Is there, then, a way to achieve that type-safety in a child class other than some in-method-body type guards?

  • 写回答

2条回答 默认 最新

  • douxing1850 2017-12-13 21:56
    关注

    This is not a technical limitation; what you're asking for doesn't make sense with the principles of OOP.

    Your abstract class is a contract; let's define the base class:

    class AbstractDeliverer {
        abstract public function setTarget(
            AbstractDeliveryTarget $deliveryTarget
        ): Delivery;
    }
    

    And some delivery targets

    class AbstractDeliveryTarget {}
    class EmailDeliveryTarget extends AbstractDeliveryTarget {}
    class SMSDeliveryTarget extends AbstractDeliveryTarget {}
    

    Then you can write this somewhere else:

    function deliverAll(AbstractDeliverer $d) {
        $e = new EmailDeliveryTarget;
        $d->setTarget($e);
    
        $s = new SMSDeliveryTarget;
        $d->setTarget($s);
    }
    

    Because we know that $d is an AbstractDeliverer, we know that passing $e and $s to it should both work. That is the contract guaranteed to us when we made sure our input was of that type.

    Now lets see what happens if you extend it the way you wanted:

    class EmailOnlyDeliverer extends AbstractDeliverer {
        public function setTarget(
            EmailDeliveryTarget $emailDeliveryTarget
        ): Delivery { 
            /* ... */
        }
    }
    $emailonly = new EmailOnlyDeliverer;
    

    We know that $e instanceOf AbstractDeliverer will be true, because we've inherited, so we know we can safely call our deliverAll method:

    deliverAll($emailonly);
    

    The first part of the function is fine, and will effectively run this:

    $e = new EmailDeliveryTarget;
    $emailonly->setTarget($e);
    

    But then we hit this part:

    $s = new SMSDeliveryTarget;
    $emailonly->setTarget($s);
    

    Oops! Fatal error! But the contract on AbstractDeliverer told us this was the correct value to pass! What went wrong?

    The rule is that a sub-class must accept all inputs that the parent class would accept, but it can accept additional inputs if it wants, something known as "contravariance". (Return types are instead "covariant": the sub-class must never return a value which couldn't be returned from the parent class, but can make a stronger promise that it will only return a subset of those values; I'll leave you to come up with an example of that one yourself).

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 求差集那个函数有问题,有无佬可以解决
  • ¥15 【提问】基于Invest的水源涵养
  • ¥20 微信网友居然可以通过vx号找到我绑的手机号
  • ¥15 寻一个支付宝扫码远程授权登录的软件助手app
  • ¥15 解riccati方程组
  • ¥15 display:none;样式在嵌套结构中的已设置了display样式的元素上不起作用?
  • ¥15 使用rabbitMQ 消息队列作为url源进行多线程爬取时,总有几个url没有处理的问题。
  • ¥15 Ubuntu在安装序列比对软件STAR时出现报错如何解决
  • ¥50 树莓派安卓APK系统签名
  • ¥65 汇编语言除法溢出问题