Skip to content

Commit cf7f25d

Browse files
committed
Hooked properties cannot be both final and private
1 parent f2bf43c commit cf7f25d

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

src/Node/ClassPropertyNode.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public function isPrivate(): bool
8686
return (bool) ($this->flags & Modifiers::PRIVATE);
8787
}
8888

89+
public function isFinal(): bool
90+
{
91+
return (bool) ($this->flags & Modifiers::FINAL);
92+
}
93+
8994
public function isStatic(): bool
9095
{
9196
return (bool) ($this->flags & Modifiers::STATIC);

src/Rules/Properties/PropertyInClassRule.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,32 @@ public function processNode(Node $node, Scope $scope): array
8181
];
8282
}
8383

84+
if ($node->isPrivate()) {
85+
if ($node->hasHooks()) {
86+
if ($node->isFinal()) {
87+
return [
88+
RuleErrorBuilder::message('Hooked properties cannot be both final and private.')
89+
->nonIgnorable()
90+
->identifier('property.abstractPrivate')
91+
->build(),
92+
];
93+
}
94+
95+
foreach ($node->getHooks() as $hook) {
96+
if (!$hook->isFinal()) {
97+
continue;
98+
}
99+
100+
return [
101+
RuleErrorBuilder::message('Hooked properties cannot be both final and private.')
102+
->nonIgnorable()
103+
->identifier('property.abstractPrivate')
104+
->build(),
105+
];
106+
}
107+
}
108+
}
109+
84110
if ($node->isReadOnly()) {
85111
if ($node->hasHooks()) {
86112
return [

tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,22 @@ public function testPhp84AndStaticHookedProperties(): void
213213
]);
214214
}
215215

216+
public function testPhp84AndPrivateFinalHookedProperties(): void
217+
{
218+
if (PHP_VERSION_ID < 80400) {
219+
$this->markTestSkipped('Test requires PHP 8.4 or later.');
220+
}
221+
222+
$this->analyse([__DIR__ . '/data/private-final-property-hooks.php'], [
223+
[
224+
'Hooked properties cannot be both final and private.',
225+
7,
226+
],
227+
[
228+
'Hooked properties cannot be both final and private.',
229+
11,
230+
],
231+
]);
232+
}
233+
216234
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php // lint >= 8.4
2+
3+
namespace PrivateFinalHook;
4+
5+
final class User
6+
{
7+
final private string $privatePropGet = 'mailto: example.org' {
8+
get => 'private:' . $this->privatePropGet;
9+
}
10+
11+
private string $privateSet = 'mailto: example.org' {
12+
final set => 'private:' . $this->privateSet;
13+
}
14+
15+
protected string $protected = 'mailto: example.org' {
16+
final get => 'protected:' . $this->protected;
17+
}
18+
19+
public string $public = 'mailto: example.org' {
20+
final get => 'public:' . $this->public;
21+
}
22+
23+
private string $email = 'mailto: example.org' {
24+
get => 'mailto:' . $this->email;
25+
}
26+
27+
function doFoo(): void
28+
{
29+
$u = new User;
30+
var_dump($u->private);
31+
var_dump($u->protected);
32+
var_dump($u->public);
33+
var_dump($u->email);
34+
}
35+
}

0 commit comments

Comments
 (0)