Skip to content

Commit ec39fe6

Browse files
Merge branch '11.5' into 12.5
* 11.5: Update ChangeLog Rename test class Makes HookedProperty parameter setterType nullable. Removes the require_once statement in testExpectationCanBeConfiguredForCovariantSetHookForPropertyOfExtendableClass. Fixes #6470: Mocking a class with a covariant property hook setter leads to an unexpected fatal error
2 parents ad97031 + 8e2b1eb commit ec39fe6

7 files changed

Lines changed: 70 additions & 16 deletions

File tree

ChangeLog-12.5.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes of the PHPUnit 12.5 release series are documented in this fi
88

99
* [#6461](https://github.com/sebastianbergmann/phpunit/issues/6461): `any()` matcher (soft deprecation)
1010

11+
### Fixed
12+
13+
* [#6470](https://github.com/sebastianbergmann/phpunit/issues/6470): Mocking a class with a property hook setter accepting more types than the property results in a fatal error
14+
1115
## [12.5.4] - 2025-12-15
1216

1317
### Changed

src/Framework/MockObject/Generator/Generator.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -863,8 +863,9 @@ private function properties(?ReflectionClass $class): array
863863
continue;
864864
}
865865

866-
$hasGetHook = false;
867-
$hasSetHook = false;
866+
$hasGetHook = false;
867+
$hasSetHook = false;
868+
$setHookMethodParameterType = null;
868869

869870
if ($property->hasHook(PropertyHookType::Get) &&
870871
!$property->getHook(PropertyHookType::Get)->isFinal()) {
@@ -873,7 +874,8 @@ private function properties(?ReflectionClass $class): array
873874

874875
if ($property->hasHook(PropertyHookType::Set) &&
875876
!$property->getHook(PropertyHookType::Set)->isFinal()) {
876-
$hasSetHook = true;
877+
$hasSetHook = true;
878+
$setHookMethodParameterType = $mapper->fromParameterTypes($property->getHook(PropertyHookType::Set))[0]->type();
877879
}
878880

879881
if (!$hasGetHook && !$hasSetHook) {
@@ -885,6 +887,7 @@ private function properties(?ReflectionClass $class): array
885887
$mapper->fromPropertyType($property),
886888
$hasGetHook,
887889
$hasSetHook,
890+
$setHookMethodParameterType,
888891
);
889892
}
890893

src/Framework/MockObject/Generator/HookedProperty.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@
2525
private Type $type;
2626
private bool $getHook;
2727
private bool $setHook;
28+
private ?Type $setterType;
2829

2930
/**
3031
* @param non-empty-string $name
3132
*/
32-
public function __construct(string $name, Type $type, bool $getHook, bool $setHook)
33+
public function __construct(string $name, Type $type, bool $getHook, bool $setHook, ?Type $setterType)
3334
{
34-
$this->name = $name;
35-
$this->type = $type;
36-
$this->getHook = $getHook;
37-
$this->setHook = $setHook;
35+
$this->name = $name;
36+
$this->type = $type;
37+
$this->getHook = $getHook;
38+
$this->setHook = $setHook;
39+
$this->setterType = $setterType;
3840
}
3941

4042
public function name(): string
@@ -56,4 +58,9 @@ public function hasSetHook(): bool
5658
{
5759
return $this->setHook;
5860
}
61+
62+
public function setterType(): Type
63+
{
64+
return $this->setterType;
65+
}
5966
}

src/Framework/MockObject/Generator/HookedPropertyGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function generate(string $className, array $properties): string
6868
}
6969

7070
EOT,
71-
$property->type()->asString(),
71+
$property->setterType()->asString(),
7272
$className,
7373
$property->name(),
7474
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <sebastian@phpunit.de>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\TestFixture\MockObject;
11+
12+
class ExtendableClassWithPropertyWithCovariantSetHook
13+
{
14+
public string $property {
15+
set(string|int|float|bool $value) {
16+
$this->property = (int) $value;
17+
}
18+
}
19+
}

tests/unit/Framework/MockObject/Generator/PropertyTest.php renamed to tests/unit/Framework/MockObject/Generator/HookedPropertyTest.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
#[CoversClass(HookedProperty::class)]
1919
#[Group('test-doubles')]
2020
#[Small]
21-
final class PropertyTest extends TestCase
21+
final class HookedPropertyTest extends TestCase
2222
{
2323
public function testHasName(): void
2424
{
2525
$name = 'property-name';
2626

27-
$property = new HookedProperty($name, Type::fromName('string', false), false, false);
27+
$property = new HookedProperty($name, Type::fromName('string', false), false, false, Type::fromName('string', false));
2828

2929
$this->assertSame($name, $property->name());
3030
}
@@ -33,36 +33,46 @@ public function testHasType(): void
3333
{
3434
$type = Type::fromName('string', false);
3535

36-
$property = new HookedProperty('property-name', $type, false, false);
36+
$property = new HookedProperty('property-name', $type, false, false, $type);
3737

3838
$this->assertSame($type, $property->type());
3939
}
4040

4141
public function testMayHaveGetHook(): void
4242
{
43-
$property = new HookedProperty('property-name', Type::fromName('string', false), true, false);
43+
$property = new HookedProperty('property-name', Type::fromName('string', false), true, false, Type::fromName('string', false));
4444

4545
$this->assertTrue($property->hasGetHook());
4646
}
4747

4848
public function testMayNotHaveGetHook(): void
4949
{
50-
$property = new HookedProperty('property-name', Type::fromName('string', false), false, false);
50+
$property = new HookedProperty('property-name', Type::fromName('string', false), false, false, Type::fromName('string', false));
5151

5252
$this->assertFalse($property->hasGetHook());
5353
}
5454

5555
public function testMayHaveSetHook(): void
5656
{
57-
$property = new HookedProperty('property-name', Type::fromName('string', false), false, true);
57+
$property = new HookedProperty('property-name', Type::fromName('string', false), false, true, Type::fromName('string', false));
5858

5959
$this->assertTrue($property->hasSetHook());
6060
}
6161

6262
public function testMayNotHaveSetHook(): void
6363
{
64-
$property = new HookedProperty('property-name', Type::fromName('string', false), false, false);
64+
$property = new HookedProperty('property-name', Type::fromName('string', false), false, false, Type::fromName('string', false));
6565

6666
$this->assertFalse($property->hasSetHook());
6767
}
68+
69+
public function testHasSetterType(): void
70+
{
71+
$type = Type::fromName('string', false);
72+
$setterType = Type::fromName('string', true);
73+
74+
$property = new HookedProperty('property-name', $type, false, false, $setterType);
75+
76+
$this->assertSame($setterType, $property->setterType());
77+
}
6878
}

tests/unit/Framework/MockObject/MockObjectTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use PHPUnit\Framework\TestCase;
2222
use PHPUnit\TestFixture\MockObject\AnInterface;
2323
use PHPUnit\TestFixture\MockObject\ExtendableClassWithCloneMethod;
24+
use PHPUnit\TestFixture\MockObject\ExtendableClassWithPropertyWithCovariantSetHook;
2425
use PHPUnit\TestFixture\MockObject\ExtendableClassWithPropertyWithSetHook;
2526
use PHPUnit\TestFixture\MockObject\ExtendableReadonlyClassWithCloneMethod;
2627
use PHPUnit\TestFixture\MockObject\InterfaceWithImplicitProtocol;
@@ -476,6 +477,16 @@ public function testExpectationCanBeConfiguredForSetHookForPropertyOfExtendableC
476477
$double->property = 'value';
477478
}
478479

480+
#[RequiresMethod(ReflectionProperty::class, 'isFinal')]
481+
public function testExpectationCanBeConfiguredForCovariantSetHookForPropertyOfExtendableClass(): void
482+
{
483+
$double = $this->createTestDouble(ExtendableClassWithPropertyWithCovariantSetHook::class);
484+
485+
$double->expects($this->once())->method(PropertyHook::set('property'))->with('0');
486+
487+
$double->property = '0';
488+
}
489+
479490
#[TestDox('__toString() method returns empty string when return value generation is disabled and no return value is configured')]
480491
public function testToStringMethodReturnsEmptyStringWhenReturnValueGenerationIsDisabledAndNoReturnValueIsConfigured(): void
481492
{

0 commit comments

Comments
 (0)