Skip to content

Commit 6aa0b4d

Browse files
committed
Respect calling scope visibility and __isset for non-public properties
- Use scope->canReadProperty() instead of isPublic() to match PHP's actual behavior (array_column respects calling context) - When __isset is defined on the class, treat non-accessible private properties as maybe-accessible (returns generic array) - Add tests for child class accessing protected parent properties and __isset interaction
1 parent 6d522a9 commit 6aa0b4d

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $
172172
}
173173

174174
$property = $type->getProperty($propertyName, $scope);
175-
if (!$property->isPublic()) {
175+
if (!$scope->canReadProperty($property)) {
176+
foreach ($type->getObjectClassReflections() as $classReflection) {
177+
if ($classReflection->hasMethod('__isset')) {
178+
return $allowMaybe ? new MixedType() : null;
179+
}
180+
}
176181
continue;
177182
}
178183

tests/PHPStan/Analyser/nsrt/array-column-php82.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,55 @@ class ArrayColumnVisibilityFromInsideTest
264264
public function testFromInside(array $objects): void
265265
{
266266
assertType('list<int>', array_column($objects, 'pub'));
267+
assertType('list<int>', array_column($objects, 'priv'));
268+
}
269+
270+
}
271+
272+
class ObjectWithIsset
273+
{
274+
public int $pub = 1;
275+
private int $priv = 2;
276+
277+
public function __isset(string $name): bool
278+
{
279+
return true;
280+
}
281+
282+
public function __get(string $name): mixed
283+
{
284+
return $this->$name;
285+
}
286+
}
287+
288+
class ArrayColumnVisibilityFromChildTest extends ObjectWithVisibility
289+
{
290+
291+
/** @param list<ObjectWithVisibility> $objects */
292+
public function testFromChild(array $objects): void
293+
{
294+
assertType('list<int>', array_column($objects, 'pub'));
295+
assertType('list<int>', array_column($objects, 'prot'));
267296
assertType('array{}', array_column($objects, 'priv'));
268297
}
269298

270299
}
300+
301+
class ArrayColumnVisibilityWithIssetTest
302+
{
303+
304+
/** @param array<int, ObjectWithIsset> $objects */
305+
public function testWithIsset(array $objects): void
306+
{
307+
assertType('list<int>', array_column($objects, 'pub'));
308+
assertType('list', array_column($objects, 'priv'));
309+
}
310+
311+
/** @param array{ObjectWithIsset} $objects */
312+
public function testWithIssetConstant(array $objects): void
313+
{
314+
assertType('array{int}', array_column($objects, 'pub'));
315+
assertType('list', array_column($objects, 'priv'));
316+
}
317+
318+
}

tests/PHPStan/Analyser/nsrt/array-column.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,55 @@ class ArrayColumnVisibilityFromInsideTest
278278
public function testFromInside(array $objects): void
279279
{
280280
assertType('list<int>', array_column($objects, 'pub'));
281+
assertType('list<int>', array_column($objects, 'priv'));
282+
}
283+
284+
}
285+
286+
class ObjectWithIsset
287+
{
288+
public int $pub = 1;
289+
private int $priv = 2;
290+
291+
public function __isset(string $name): bool
292+
{
293+
return true;
294+
}
295+
296+
public function __get(string $name): mixed
297+
{
298+
return $this->$name;
299+
}
300+
}
301+
302+
class ArrayColumnVisibilityFromChildTest extends ObjectWithVisibility
303+
{
304+
305+
/** @param list<ObjectWithVisibility> $objects */
306+
public function testFromChild(array $objects): void
307+
{
308+
assertType('list<int>', array_column($objects, 'pub'));
309+
assertType('list<int>', array_column($objects, 'prot'));
281310
assertType('array{}', array_column($objects, 'priv'));
282311
}
283312

284313
}
314+
315+
class ArrayColumnVisibilityWithIssetTest
316+
{
317+
318+
/** @param array<int, ObjectWithIsset> $objects */
319+
public function testWithIsset(array $objects): void
320+
{
321+
assertType('list<int>', array_column($objects, 'pub'));
322+
assertType('list', array_column($objects, 'priv'));
323+
}
324+
325+
/** @param array{ObjectWithIsset} $objects */
326+
public function testWithIssetConstant(array $objects): void
327+
{
328+
assertType('array{int}', array_column($objects, 'pub'));
329+
assertType('list', array_column($objects, 'priv'));
330+
}
331+
332+
}

0 commit comments

Comments
 (0)