Skip to content

Commit 3d52930

Browse files
committed
Fixed false positive about undefined property guarded with property_exists
1 parent a2834bc commit 3d52930

File tree

4 files changed

+59
-1
lines changed

4 files changed

+59
-1
lines changed

src/Rules/Properties/AccessPropertiesCheck.php

+14
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace PHPStan\Rules\Properties;
44

5+
use PhpParser\Node\Arg;
56
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\FuncCall;
68
use PhpParser\Node\Expr\PropertyFetch;
79
use PhpParser\Node\Identifier;
10+
use PhpParser\Node\Name\FullyQualified;
811
use PHPStan\Analyser\NullsafeOperatorHelper;
912
use PHPStan\Analyser\Scope;
1013
use PHPStan\Internal\SprintfHelper;
@@ -143,6 +146,17 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string
143146
}
144147
}
145148

149+
if ($node->name instanceof Expr) {
150+
$propertyExistsExpr = new FuncCall(new FullyQualified('property_exists'), [
151+
new Arg($node->var),
152+
new Arg($node->name),
153+
]);
154+
155+
if ($scope->getType($propertyExistsExpr)->isTrue()->yes()) {
156+
return [];
157+
}
158+
}
159+
146160
$ruleErrorBuilder = RuleErrorBuilder::message(sprintf(
147161
'Access to an undefined property %s::$%s.',
148162
$typeForDescribe->describe(VerbosityLevel::typeOnly()),

src/Type/Php/PropertyExistsTypeSpecifyingExtension.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node\Expr\FuncCall;
66
use PhpParser\Node\Expr\PropertyFetch;
77
use PhpParser\Node\Identifier;
8+
use PhpParser\Node\Name\FullyQualified;
89
use PHPStan\Analyser\Scope;
910
use PHPStan\Analyser\SpecifiedTypes;
1011
use PHPStan\Analyser\TypeSpecifier;
@@ -13,6 +14,7 @@
1314
use PHPStan\Reflection\FunctionReflection;
1415
use PHPStan\Rules\Properties\PropertyReflectionFinder;
1516
use PHPStan\Type\Accessory\HasPropertyType;
17+
use PHPStan\Type\Constant\ConstantBooleanType;
1618
use PHPStan\Type\Constant\ConstantStringType;
1719
use PHPStan\Type\FunctionTypeSpecifyingExtension;
1820
use PHPStan\Type\IntersectionType;
@@ -53,7 +55,12 @@ public function specifyTypes(
5355
{
5456
$propertyNameType = $scope->getType($node->getArgs()[1]->value);
5557
if (!$propertyNameType instanceof ConstantStringType) {
56-
return new SpecifiedTypes([], []);
58+
return $this->typeSpecifier->create(
59+
new FuncCall(new FullyQualified('property_exists'), $node->getRawArgs()),
60+
new ConstantBooleanType(true),
61+
$context,
62+
$scope,
63+
);
5764
}
5865

5966
if ($propertyNameType->getValue() === '') {

tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -1035,4 +1035,12 @@ public function testNewIsAlwaysFinalClass(): void
10351035
]);
10361036
}
10371037

1038+
public function testPropertyExists(): void
1039+
{
1040+
$this->checkThisOnly = false;
1041+
$this->checkUnionTypes = true;
1042+
$this->checkDynamicProperties = true;
1043+
$this->analyse([__DIR__ . '/data/property-exists.php'], []);
1044+
}
1045+
10381046
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace PropertyExists;
4+
5+
class Model
6+
{
7+
8+
}
9+
10+
class Defaults
11+
{
12+
public function defaults(Model $model): void
13+
{
14+
$columns = [
15+
'getCreatedByColumn',
16+
'getUpdatedByColumn',
17+
'getDeletedByColumn',
18+
'getCreatedAtColumn',
19+
'getUpdatedAtColumn',
20+
'getDeletedAtColumn',
21+
];
22+
23+
foreach ($columns as $column) {
24+
if (property_exists($model, $column)) {
25+
echo $model->{$column};
26+
}
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)