Skip to content

Commit bb7171b

Browse files
committed
Hooked properties cannot be unset()
1 parent 4efa22b commit bb7171b

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

src/Rules/Variables/UnsetRule.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@ private function canBeUnset(Node $node, Scope $scope): ?IdentifierRuleError
103103
->identifier($propertyReflection->isReadOnly() ? 'unset.readOnlyProperty' : 'unset.readOnlyPropertyByPhpDoc')
104104
->build();
105105
}
106+
107+
if ($propertyReflection->getNativeReflection()->getHooks() !== []) {
108+
return RuleErrorBuilder::message(
109+
sprintf(
110+
'Cannot unset hooked property %s::$%s.',
111+
$propertyReflection->getDeclaringClass()->getDisplayName(),
112+
$foundPropertyReflection->getName(),
113+
),
114+
)
115+
->line($node->getStartLine())
116+
->identifier('unset.hookedProperty')
117+
->build();
118+
}
106119
}
107120

108121
return null;

tests/PHPStan/Rules/Variables/UnsetRuleTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\Rules\Properties\PropertyReflectionFinder;
66
use PHPStan\Rules\Rule;
77
use PHPStan\Testing\RuleTestCase;
8+
use const PHP_VERSION_ID;
89

910
/**
1011
* @extends RuleTestCase<UnsetRule>
@@ -122,4 +123,30 @@ public function testBug12421(): void
122123
]);
123124
}
124125

126+
public function testUnsetHookedProperty(): void
127+
{
128+
if (PHP_VERSION_ID < 80400) {
129+
$this->markTestSkipped('Test requires PHP 8.4 or later.');
130+
}
131+
132+
$this->analyse([__DIR__ . '/data/unset-hooked-property.php'], [
133+
[
134+
'Cannot unset hooked property UnsetHookedProperty\User::$name.',
135+
6,
136+
],
137+
[
138+
'Cannot unset hooked property UnsetHookedProperty\User::$fullName.',
139+
7,
140+
],
141+
[
142+
'Cannot unset hooked property UnsetHookedProperty\Foo::$ii.',
143+
9,
144+
],
145+
[
146+
'Cannot unset hooked property UnsetHookedProperty\Foo::$iii.',
147+
10,
148+
],
149+
]);
150+
}
151+
125152
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php // lint >= 8.4
2+
3+
namespace UnsetHookedProperty;
4+
5+
function doUnset(Foo $foo, User $user): void {
6+
unset($user->name);
7+
unset($user->fullName);
8+
9+
unset($foo->ii);
10+
unset($foo->iii);
11+
}
12+
13+
class User
14+
{
15+
public string $name {
16+
set {
17+
if (strlen($value) === 0) {
18+
throw new \ValueError("Name must be non-empty");
19+
}
20+
$this->name = $value;
21+
}
22+
}
23+
24+
public string $fullName {
25+
get {
26+
return "Yennefer of Vengerberg";
27+
}
28+
}
29+
30+
public function __construct(string $name) {
31+
$this->name = $name;
32+
}
33+
}
34+
35+
abstract class Foo
36+
{
37+
abstract protected int $ii { get; }
38+
39+
abstract public int $iii { get; }
40+
}
41+

0 commit comments

Comments
 (0)