Abstract function parameter type hint overriding in PHP 7

后端 未结 2 772
有刺的猬
有刺的猬 2020-12-20 17:01

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

相关标签:
2条回答
  • 2020-12-20 17:10

    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).

    0 讨论(0)
  • 2020-12-20 17:16

    Looks like no pretty solutions so we can try use interfaces.

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

    OR

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

    Depends on what you want to do.

    0 讨论(0)
提交回复
热议问题