How to define stricter typing in child class inherited methods in PHP?

落爺英雄遲暮 提交于 2020-12-26 08:10:21

问题


Assume I have three classes like so.

abstract class A {
   abstract protected function doSomething($object): array;
}

class B extends A {
   protected function doSomething(SomeObject $object): array
   {
       // something
   }
}

class C extends A {
   protected function doSomething(OtherObject $object): array
   {
       // something
   }
}

According to principles of inheritance as PHP does it, the structure described above is not legal PHP code, as the method definitions are not compatible with the base class. I can, of course, do something like

class B extends A {
    protected function doSomething($object): array
    {
        if (!is_a($object, 'SomeObject')) {
            throw new Exception('Object passed to method is not of the correct class for this method.');
        }

        // something
    }
}

But I was wondering if there is another solution to implementing a method in an abstract class that can be redefined to only accept objects of specific type in it.


回答1:


You can't without implementing it yourself in similar way than in your example.

PHP support for covariance and contravariance is clearly defined:

Covariance allows a child's method to return a more specific type than the return type of its parent's method. Whereas, contravariance allows a parameter type to be less specific in a child method, than that of its parent.

If a child class were capable of having stricter parameter requirements than its parent or interface, this would lead to broken expectations.

For example: let's say that you have the following:

abstract class AbstractParam{}

class ParamOne extends AbstractParam{}
class ParamTwo extends AbstractParam{}
class ParamThree extends AbstractParam{}

abstract class AbstractService {

    abstract public function foo(AbstractParam $bar);
}

Any time you would need to use an implementation of this service, you would do so with the assurance that passing any valid instance of AbstractParam to foo() would be legal. The method signature is clearly communicating this.

If I could change this on inheritance/implementation, and suddenly I could change foo() signature to:

public function foo(ParamOne $bar)

The contract would be broken, and you couldn't depend on AbstractService safely, since there would be no longer possible to pass any AbstractParam instance to foo(), thus violating the LSP in the process: You could no longer substitute objects of the base class with objects that belong to a subtype of that base object while maintaining correctness.

The approach in your question would work, but I don't think it's a great design for the above reasons. If you really must go that way, simply remove the type hint from method definition:

abstract class AbstractService {

    abstract public function foo($bar);
}

class ConcreteService extends AbstractService {
    public function foo($bar) {}
}

But it's probably better to rethink why you actually need this, and try to retain type-safety.



来源:https://stackoverflow.com/questions/62930305/how-to-define-stricter-typing-in-child-class-inherited-methods-in-php

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!