Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/Type/Php/ArrayColumnHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,15 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $
continue;
}

$returnTypes[] = $type->getInstanceProperty($propertyName, $scope)->getReadableType();
$property = $type->getInstanceProperty($propertyName, $scope);
if (!$property->isPublic()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does getInstanceProperty return non-public property? I though the point of the scope parameter was to filter the property by visibility.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has never worked like that. The main reason why Type::getInstanceProperty() asks for Scope is to correctly transform static type in property type based on the place it's accessed from.

If a class has private property, this method will still return that. It's useful to provide an error like "Access to private property of class X" instead of "Access to undefined property".

Also Scope is used in ClassReflection::getProperty() to decide if it should continute iterating over class reflection extensions, or return the reflection object:

if ($this->phpClassReflectionExtension->hasProperty($this, $propertyName)) {
$property = $this->phpClassReflectionExtension->getProperty($this, $propertyName, $scope);
if ($scope->canReadProperty($property)) {
return $this->properties[$key] = $property;
}
$this->properties[$key] = $property;
}
if ($this->allowsDynamicProperties()) {
foreach ($this->propertiesClassReflectionExtensions as $extension) {
if (!$extension->hasProperty($this, $propertyName)) {
continue;
}
$property = $this->wrapExtendedProperty($propertyName, $extension->getProperty($this, $propertyName));
if ($scope->canReadProperty($property)) {
return $this->properties[$key] = $property;
}
$this->properties[$key] = $property;
}
}

if (!$type->hasMethod('__isset')->no() && !$type->hasMethod('__get')->no()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it would make sense to work with ClassReflection->allowsDynamicProperties(). this would also include support for the #[AllowDynamicProperties] attribute etc.

$returnTypes[] = new MixedType();
}
continue;
}

$returnTypes[] = $property->getReadableType();
}
}

Expand Down
58 changes: 58 additions & 0 deletions tests/PHPStan/Analyser/nsrt/array-column.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,69 @@ public function testObjects2(array $array): void
final class Foo
{

public int $publicProperty;
protected int $protectedProperty;
private int $privateProperty;

/** @param array<int, self> $a */
public function doFoo(array $a): void
{
assertType('array{}', array_column($a, 'nodeName'));
assertType('array{}', array_column($a, 'nodeName', 'tagName'));
assertType('list<int>', array_column($a, 'publicProperty'));
assertType('array{}', array_column($a, 'protectedProperty'));
assertType('array{}', array_column($a, 'privateProperty'));
}

}


class FooNotFinal
{

public int $publicProperty;
protected int $protectedProperty;
private int $privateProperty;

/** @param array<int, self> $a */
public function doFoo(array $a): void
{
assertType('list', array_column($a, 'nodeName'));
assertType('array', array_column($a, 'nodeName', 'tagName'));
assertType('list<int>', array_column($a, 'publicProperty'));
assertType('list', array_column($a, 'protectedProperty'));
assertType('list', array_column($a, 'privateProperty'));
}

}


final class Magic
{

public int $publicProperty;
protected int $protectedProperty;
private int $privateProperty;

public function __get($prop)
{
return $this->$prop;
}

public function __isset($prop) : bool
{
return isset($this->$prop);
}

/** @param array<int, self> $a */
public function doFoo(array $a): void
{
assertType('list', array_column($a, 'nodeName'));
assertType('array', array_column($a, 'nodeName', 'tagName'));
assertType('list<int>', array_column($a, 'publicProperty'));
assertType('list', array_column($a, 'protectedProperty'));
assertType('list', array_column($a, 'privateProperty'));
}

}

Loading