Late Static Binding Mocking Problem
In a recent attempt to upgrade Disco to the latest version of ProxyManager I ran into this FATAL ERROR: "Declaration of Mock_ClassGenerator_f7ebad25::setExtendedClass($extendedClass): ProxyManager\Generator\ClassGenerator must be compatible with ProxyManager\Generator\ClassGenerator::setExtendedClass($extendedClass): Zend\Code\Generator\ClassGenerator" when I tried to execute the unit tests. It took me a while to understand the source of the problem, thus I think sharing the problem might help my future me and probably you as well. Let's assume you have two classes The_Parent and The_Child:
class The_Parent
{
public function method()
{
return $this;
}
}
class The_Child extends The_Parent
{
public function method(): parent
{
// additional custom code here...
return $this;
}
}
The child class overwrites one of the parent's methods and adds a "parent" type hint to indicate the return type. In your unit test environment a mock object of The_Child is created, based on the following code:
class The_Mock extends The_Child
{
public function method(): parent
{
// Mocking logic here...
return $this;
}
}
Now when trying to create an instance of The_Mock class in your unit test, you will get a PHP FATAL error, stating "Declaration of The_Mock::method(): The_Mock must be compatible with The_Child::method()". It is crystal-clear why this happens: The parent of The_Mock class is The_Child, the parent of the The_Child class is The_Parent, thus the "parent" type hint has different meanings in both cases. And since PHP is still not able to allow to substitute sub types in neither method argument type hints nor return type hints, the fatal error gets thrown.
The problem would not occur if you replace the "parent" type hint with the concrete class name. Thus you should be very careful when to use the parent or self aliases and when not to use them. A good idea might be to consider making classes final when using the parent or the self typehint as this would not allow subclassing.