Skip to content

Commit 5772b1a

Browse files
committed
PHPDoc tag @property should only be taken into account for protected properties and outside access when the declaring class allows dynamic properties
1 parent 827aa77 commit 5772b1a

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,10 @@ private function createProperty(
425425
$includingAnnotations
426426
&& !$declaringClassReflection->isEnum()
427427
&& !$propertyReflection->isStatic()
428-
&& ($classReflection->allowsDynamicProperties() || !$propertyReflection->isPrivate())
428+
&& ($classReflection->allowsDynamicProperties() || $scope->canReadProperty($nativeProperty))
429429
&& $this->annotationsPropertiesClassReflectionExtension->hasProperty($classReflection, $propertyName)
430430
&& (
431-
!$scope->canReadProperty($nativeProperty)
432-
|| $nativeProperty->isPublic()
431+
$nativeProperty->isPublic()
433432
|| ($scope->isInClass() && $scope->getClassReflection()->getName() !== $declaringClassReflection->getName())
434433
)
435434
) {

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ private static function findTestFiles(): iterable
187187
yield __DIR__ . '/../Rules/Classes/data/bug-5333.php';
188188
yield __DIR__ . '/../Rules/Methods/data/bug-8174.php';
189189

190+
yield __DIR__ . '/../Rules/Properties/data/bug-13537.php';
191+
190192
if (PHP_VERSION_ID >= 80000) {
191193
yield __DIR__ . '/../Rules/Comparison/data/bug-8169.php';
192194
}

tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,4 +1216,21 @@ public function testPrivatePropertyWithAllowedPropertyTagIsPublic(): void
12161216
$this->analyse([__DIR__ . '/data/private-property-with-allowed-property-tag-is-public.php'], []);
12171217
}
12181218

1219+
public function testBug13537(): void
1220+
{
1221+
$this->checkThisOnly = false;
1222+
$this->checkUnionTypes = true;
1223+
$this->checkDynamicProperties = true;
1224+
$this->analyse([__DIR__ . '/data/bug-13537.php'], [
1225+
[
1226+
'Cannot access property $bob on array<string, mixed>.',
1227+
25,
1228+
],
1229+
[
1230+
'Access to protected property Bug13537\Bar::$test.',
1231+
25,
1232+
],
1233+
]);
1234+
}
1235+
12191236
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Bug13537;
4+
5+
use stdClass;
6+
use function PHPStan\Testing\assertType;
7+
8+
class Foo
9+
{
10+
/**
11+
* @var array<non-empty-string, mixed>
12+
*/
13+
protected array $test = [];
14+
}
15+
16+
/**
17+
* @property stdClass $test
18+
*/
19+
class Bar extends Foo
20+
{
21+
22+
}
23+
24+
function (): void {
25+
$bob = new Bar()->test->bob;
26+
};
27+
28+
class BaseModel
29+
{
30+
/**
31+
* @var array<non-empty-string, mixed>
32+
*/
33+
protected array $attributes = [];
34+
35+
public function __get(string $key): mixed {
36+
return $this->attributes[$key] ?? null;
37+
}
38+
39+
public function __isset(string $key): bool {
40+
return isset($this->attributes[$key]);
41+
}
42+
}
43+
44+
/**
45+
* @property stdClass $attributes
46+
* @property bool $other
47+
*/
48+
class Bar2 extends BaseModel
49+
{
50+
public function __constructor(): void {
51+
$this->attributes = [
52+
'attributes' => (object) array('foo' => 'bar'),
53+
'other' => true
54+
];
55+
}
56+
}
57+
58+
function (): void {
59+
$bar = new Bar2();
60+
echo $bar->attributes->foo;
61+
62+
assertType(stdClass::class, $bar->attributes);
63+
};

0 commit comments

Comments
 (0)