Skip to content

Commit 10655fc

Browse files
committed
Add stringable access check to AccessPropertiesCheck
1 parent 7417321 commit 10655fc

File tree

3 files changed

+78
-2
lines changed

3 files changed

+78
-2
lines changed

src/Rules/Properties/AccessPropertiesCheck.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public function __construct(
4040
private bool $reportMagicProperties,
4141
#[AutowiredParameter]
4242
private bool $checkDynamicProperties,
43+
#[AutowiredParameter(ref: '%featureToggles.checkNonStringableDynamicAccess%')]
44+
private bool $checkNonStringableDynamicAccess,
4345
)
4446
{
4547
}
@@ -49,13 +51,30 @@ public function __construct(
4951
*/
5052
public function check(PropertyFetch $node, Scope $scope, bool $write): array
5153
{
54+
$errors = [];
5255
if ($node->name instanceof Identifier) {
5356
$names = [$node->name->name];
5457
} else {
5558
$names = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $scope->getType($node->name)->getConstantStrings());
59+
60+
if ($this->checkNonStringableDynamicAccess) {
61+
$nameTypeResult = $this->ruleLevelHelper->findTypeToCheck(
62+
$scope,
63+
$node->name,
64+
'',
65+
static fn (Type $type) => $type->toString()->isString()->yes(),
66+
);
67+
$nameType = $nameTypeResult->getType();
68+
if ($nameType instanceof ErrorType || $nameType->toString() instanceof ErrorType || !$nameType->toString()->isString()->yes()) {
69+
$originalNameType = $scope->getType($node->name);
70+
$className = $scope->getType($node->var)->describe(VerbosityLevel::typeOnly());
71+
$errors[] = RuleErrorBuilder::message(sprintf('Property name for %s must be a string, but %s was given.', $className, $originalNameType->describe(VerbosityLevel::precise())))
72+
->identifier('property.nameNotString')
73+
->build();
74+
}
75+
}
5676
}
5777

58-
$errors = [];
5978
foreach ($names as $name) {
6079
$errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name, $write));
6180
}

tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AccessPropertiesRuleTest extends RuleTestCase
2626
protected function getRule(): Rule
2727
{
2828
$reflectionProvider = self::createReflectionProvider();
29-
return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false, true), new PhpVersion(PHP_VERSION_ID), true, $this->checkDynamicProperties));
29+
return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false, true), new PhpVersion(PHP_VERSION_ID), true, $this->checkDynamicProperties, true));
3030
}
3131

3232
public function testAccessProperties(): void
@@ -973,6 +973,31 @@ public function testBug8629(): void
973973
$this->analyse([__DIR__ . '/data/bug-8629.php'], []);
974974
}
975975

976+
public function testBug9475(): void
977+
{
978+
$this->checkThisOnly = false;
979+
$this->checkUnionTypes = false;
980+
$this->checkDynamicProperties = false;
981+
$this->analyse([__DIR__ . '/data/bug-9475.php'], [
982+
[
983+
'Property name for $this(Bug9475\Properties) must be a string, but $this(Bug9475\Properties) was given.',
984+
12,
985+
],
986+
[
987+
'Property name for $this(Bug9475\Properties) must be a string, but $this(Bug9475\Properties) was given.',
988+
13,
989+
],
990+
[
991+
'Property name for $this(Bug9475\Properties) must be a string, but object was given.',
992+
14,
993+
],
994+
[
995+
'Property name for $this(Bug9475\Properties) must be a string, but array was given.',
996+
15,
997+
],
998+
]);
999+
}
1000+
9761001
#[RequiresPhp('>= 8.0')]
9771002
public function testBug9694(): void
9781003
{
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9475;
4+
5+
use Stringable;
6+
7+
final class Properties
8+
{
9+
10+
public function testProperties(string $name, Stringable $stringable, object $object, array $array): void
11+
{
12+
echo 'Hello, ' . $this->{$this}->name;
13+
echo 'Hello, ' . $this->$this->name;
14+
echo 'Hello, ' . $this->$object;
15+
echo 'Hello, ' . $this->$array;
16+
17+
echo 'Hello, ' . $this->$name; // valid
18+
echo 'Hello, ' . $this->$stringable; // valid
19+
}
20+
21+
public function testStaticProperties(string $name, Stringable $stringable, object $object, array $array): void
22+
{
23+
echo 'Hello, ' . self::${$this}->name;
24+
echo 'Hello, ' . self::$$this->name;
25+
echo 'Hello, ' . self::$$object;
26+
echo 'Hello, ' . self::$$array;
27+
28+
echo 'Hello, ' . self::$$name; // valid
29+
echo 'Hello, ' . self::$$stringable; // valid
30+
}
31+
32+
}

0 commit comments

Comments
 (0)