-
Notifications
You must be signed in to change notification settings - Fork 464
Description
Mockery Version
1.6.x
PHP Version
PHP 8.3
Issue Description
When I try to mock an interface that extends an other interface with more restrictive return type, the less restrictive return type from the parent interface is used to generate the method.
The generated method has the wrong return type, which is not permitted by PHP.
Steps to Reproduce
Create an interface that extends the standard Iterator and override the current or key method with a more restrictive return type:
/** @implements \Iterator<string, bool> */
interface RestrictReturnType extends \Iterator
{
public function current(): ?bool; // Parent returns "mixed"
public function key(): ?string; // Parent returns "mixed"
}Try to mock it:
\mock(RestrictReturnType::class);Expected Behavior
This interface should be mockable.
Actual Behavior
Fatal Error, the generated code has the return type of Iterator::current() instead of RestrictReturnType::current().
public function current(): mixed{
$argc = func_num_args();
$argv = func_get_args();
$ret = $this->_mockery_handleMethodCall(__FUNCTION__, $argv);
return $ret;
}
public function key(): mixed{
$argc = func_num_args();
$argv = func_get_args();
$ret = $this->_mockery_handleMethodCall(__FUNCTION__, $argv);
return $ret;
}Exception or Error
PHP Fatal Error:
Declaration of Mockery_0_Iterator_RestrictReturnType::current(): mixed must be compatible with RestrictReturnType::current(): bool
Additional Information
In MockConfiguration::getAllMethods(), if I call $classes = array_reverse($classes); before iterating, that fixes the bug. But this is a poor fix that only acts on which method overrides the other. We need more intelligence to find which return type wins.
mockery/library/Mockery/Generator/MockConfiguration.php
Lines 656 to 671 in 73a9714
| protected function getAllMethods() | |
| { | |
| if ($this->allMethods) { | |
| return $this->allMethods; | |
| } | |
| $classes = $this->getTargetInterfaces(); | |
| if ($this->getTargetClass()) { | |
| $classes[] = $this->getTargetClass(); | |
| } | |
| $methods = []; | |
| foreach ($classes as $class) { | |
| $methods = array_merge($methods, $class->getMethods()); | |
| } |