在开始之前,让我们创建一个minimal, reproducible example:
class User {}
class Example {
public User $user;
public function __construct(User $user) {
$this->user = $user;
}
}
class ExampleTest extends PHPUnit\Framework\TestCase {
public function testExample() {
$args = [];
$args['User'] = $this->getMockBuilder('User')->disableOriginalConstructor()->getMock();
$mock = $this->getMockBuilder(Example::class)
->setConstructorArgs($args)
->getMock();
$this->assertTrue(true);
}
}
在PHP7.4和PHPUnit 9.5.14下运行,这就通过了;对于PHP 8.0和同一个库,它会给出您报告的错误:
错误:未知的命名参数$User
实际上,我们可以进一步简化:我们可以说$args['User'] = new User;
,而不是$args['User'] = $this->getMockBuilder('User')->disableOriginalConstructor()->getMock();
,得到同样的错误.
现在,让我们看看我们在做什么:
- 我们创建一个关联数组,将类名映射到(mock)对象
- 我们将该关联数组传递给模拟生成器的
setConstructorArgs
方法
- 奇迹发生了...
那么,会发生什么?也许the source of PHPUnit会提供一些线索.
setConstructorArgs
just sets a property,也就是used in getMock
,然后通过一系列不同的方法;最终,它会传递到MockObject\Generator::getObject
,如果我们go 掉一些错误处理,它会执行以下操作:
$class = new ReflectionClass($className);
$object = $class->newInstanceArgs($arguments);
那么,让我们看看是否可以用它来解决我们的问题:
class User {}
class Example {
public User $user;
public function __construct(User $user) {
$this->user = $user;
}
}
$class = new ReflectionClass(Example::class);
$object = $class->newInstanceArgs(['User' => new User]);
由于这是自包含的代码,我们可以使用方便的在线工具at https://3v4l.org来比较不同PHP版本的输出:https://3v4l.org/QU4jS
正如所料,PHP7.4对此表示满意,PHP8.0及以上版本给出了一个错误:
Fatal error: Uncaught 错误:未知的命名参数$User in /in/QU4jS:14
Stack trace:
#0 /in/QU4jS(14): ReflectionClass->newInstanceArgs(Array)
#1 {main}
thrown in /in/QU4jS on line 14
那么,这是怎么回事?the manual page for ReflectionClass::newInstanceArgs
(目前)并没有说明所提供的数组应该是什么样子,也没有说明命名参数支持,但我们可以进行一个有根据的猜测:它试图将我们的关联数组作为命名参数匹配到构造函数.以前的版本由于没有命名参数,所以只会忽略键并按顺序应用参数.
我们可以很容易地测试这个理论,方法是将一个类的构造函数设置为两个参数:
class Example2 {
public function __construct($first, $second) {
echo "$first then $second\n";
}
}
$class = new ReflectionClass(Example2::class);
$object = $class->newInstanceArgs(['second' => 'two', 'first' => 'one']);
当值为run on multiple versions时,我们可以看到旧版本的PHP根据数组的order输出"二加一";而较新的版本会根据数组的keys输出"一到二".
那么,长话短说,解决办法是什么?很简单,不要在构造函数参数数组中使用键:
class ExampleTest extends PHPUnit\Framework\TestCase {
public function testExample() {
$args = [];
$args[] = new User;
$mock = $this->getMockBuilder(Example::class)
->setConstructorArgs($args)
->getMock();
$this->assertTrue(true);
}
}
如果您需要在设置逻辑期间使用它们来跟踪事物,只需在传递它们时使用array_values
即可:
class ExampleTest extends PHPUnit\Framework\TestCase {
public function testExample() {
$args = [];
$args['User'] = new User;
$mock = $this->getMockBuilder(Example::class)
->setConstructorArgs(array_values($args))
->getMock();
$this->assertTrue(true);
}
}