Skip to content

Commit 3ad2fd1

Browse files
committed
Test WritingToReadOnlyPropertiesRule for hooked properties
1 parent 2a6c5c5 commit 3ad2fd1

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

src/Reflection/Php/PhpPropertyReflection.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,15 @@ public function isReadable(): bool
179179

180180
public function isWritable(): bool
181181
{
182-
return true;
182+
if ($this->isStatic()) {
183+
return true;
184+
}
185+
186+
if (!$this->isVirtual()->yes()) {
187+
return true;
188+
}
189+
190+
return $this->hasHook('set');
183191
}
184192

185193
public function getDeprecatedDescription(): ?string

tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php

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

910
/**
1011
* @extends RuleTestCase<WritingToReadOnlyPropertiesRule>
@@ -87,4 +88,23 @@ public function testConflictingAnnotationProperty(): void
8788
]);
8889
}
8990

91+
public function testPropertyHooks(): void
92+
{
93+
if (PHP_VERSION_ID < 80400) {
94+
$this->markTestSkipped('Test requires PHP 8.4.');
95+
}
96+
97+
$this->checkThisOnly = false;
98+
$this->analyse([__DIR__ . '/data/writing-to-read-only-hooked-properties.php'], [
99+
[
100+
'Property WritingToReadOnlyHookedProperties\Foo::$i is not writable.',
101+
16,
102+
],
103+
[
104+
'Property WritingToReadOnlyHookedProperties\Bar::$i is not writable.',
105+
32,
106+
],
107+
]);
108+
}
109+
90110
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php // lint >= 8.4
2+
3+
namespace WritingToReadOnlyHookedProperties;
4+
5+
interface Foo
6+
{
7+
8+
public int $i {
9+
// virtual, not writable
10+
get;
11+
}
12+
13+
}
14+
15+
function (Foo $f): void {
16+
$f->i = 1;
17+
};
18+
19+
class Bar
20+
{
21+
22+
public int $i {
23+
// virtual, not writable
24+
get {
25+
return 1;
26+
}
27+
}
28+
29+
}
30+
31+
function (Bar $b): void {
32+
$b->i = 1;
33+
};
34+
35+
class Baz
36+
{
37+
38+
public int $i {
39+
// backed, writable
40+
get {
41+
return $this->i + 1;
42+
}
43+
}
44+
45+
}
46+
47+
function (Baz $b): void {
48+
$b->i = 1;
49+
};

0 commit comments

Comments
 (0)