问题
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