Skip to content

Commit a1810ec

Browse files
committed
Support dynamic class constant fetch available in PHP 8.3
https://wiki.php.net/rfc/dynamic_class_constant_fetch Fix #241
1 parent 3d18e0f commit a1810ec

File tree

7 files changed

+79
-8
lines changed

7 files changed

+79
-8
lines changed

.github/workflows/php.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
run: composer lint
3232
- php-version: "8.1"
3333
run: composer lint
34+
- php-version: "8.2"
35+
run: composer lint
3436
include:
3537
- php-version: "7.2"
3638
run: composer lint-7.x
@@ -42,6 +44,8 @@ jobs:
4244
run: composer lint-8.0
4345
- php-version: "8.1"
4446
run: composer lint-8.1
47+
- php-version: "8.2"
48+
run: composer lint-8.2
4549

4650
steps:
4751
- uses: actions/checkout@v4

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
},
4141
"scripts": {
4242
"lint": "vendor/bin/parallel-lint --colors src/ tests/",
43-
"lint-7.x": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/functionCallsNamedParams.php --exclude tests/src/disallowed-allow/functionCallsNamedParams.php --exclude tests/src/disallowed/attributeUsages.php --exclude tests/src/disallowed-allow/attributeUsages.php",
44-
"lint-8.0": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php",
45-
"lint-8.1": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/AttributesEverywhere.php",
43+
"lint-7.x": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/functionCallsNamedParams.php --exclude tests/src/disallowed-allow/functionCallsNamedParams.php --exclude tests/src/disallowed/attributeUsages.php --exclude tests/src/disallowed-allow/attributeUsages.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",
44+
"lint-8.0": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",
45+
"lint-8.1": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",
46+
"lint-8.2": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",
4647
"lint-neon": "vendor/bin/neon-lint .",
4748
"phpcs": "vendor/bin/phpcs src/ tests/",
4849
"cs-fix": "vendor/bin/phpcbf src/ tests/",

src/Usages/ClassConstantUsages.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55

66
use PhpParser\Node;
77
use PhpParser\Node\Expr\ClassConstFetch;
8+
use PhpParser\Node\Expr\Variable;
89
use PhpParser\Node\Identifier;
910
use PHPStan\Analyser\Scope;
1011
use PHPStan\Rules\Rule;
1112
use PHPStan\Rules\RuleError;
1213
use PHPStan\Rules\RuleErrorBuilder;
1314
use PHPStan\ShouldNotHappenException;
1415
use PHPStan\Type\Constant\ConstantStringType;
16+
use PHPStan\Type\Type;
1517
use PHPStan\Type\VerbosityLevel;
1618
use Spaze\PHPStan\Rules\Disallowed\DisallowedConstant;
1719
use Spaze\PHPStan\Rules\Disallowed\DisallowedConstantFactory;
@@ -80,17 +82,38 @@ public function processNode(Node $node, Scope $scope): array
8082
if (!($node instanceof ClassConstFetch)) {
8183
throw new ShouldNotHappenException(sprintf('$node should be %s but is %s', ClassConstFetch::class, get_class($node)));
8284
}
83-
if (!($node->name instanceof Identifier)) {
84-
throw new ShouldNotHappenException(sprintf('$node->name should be %s but is %s', Identifier::class, get_class($node->name)));
85+
if ($node->name instanceof Identifier) {
86+
return $this->getConstantRuleErrors($scope, (string)$node->name, $this->typeResolver->getType($node->class, $scope));
8587
}
86-
$constant = (string)$node->name;
87-
$type = $this->typeResolver->getType($node->class, $scope);
88-
$usedOnType = $type->getObjectTypeOrClassStringObjectType();
88+
if ($node->name instanceof Variable) {
89+
$type = $scope->getType($node->name);
90+
$errors = [];
91+
foreach ($type->getConstantStrings() as $constantString) {
92+
$errors = array_merge(
93+
$errors,
94+
$this->getConstantRuleErrors($scope, $constantString->getValue(), $this->typeResolver->getType($node->class, $scope))
95+
);
96+
}
97+
return $errors;
98+
}
99+
throw new ShouldNotHappenException(sprintf('$node->name should be %s but is %s', Identifier::class, get_class($node->name)));
100+
}
89101

102+
103+
/**
104+
* @param Scope $scope
105+
* @param string $constant
106+
* @param Type $type
107+
* @return list<RuleError>
108+
* @throws ShouldNotHappenException
109+
*/
110+
private function getConstantRuleErrors(Scope $scope, string $constant, Type $type): array
111+
{
90112
if (strtolower($constant) === 'class') {
91113
return [];
92114
}
93115

116+
$usedOnType = $type->getObjectTypeOrClassStringObjectType();
94117
$displayName = $usedOnType->getObjectClassNames() ? $this->getFullyQualified($usedOnType->getObjectClassNames(), $constant) : null;
95118
if ($usedOnType->getConstantStrings()) {
96119
$classNames = array_map(

tests/Usages/ClassConstantUsagesTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,27 @@ public function testRule(): void
141141
}
142142

143143

144+
public function testRuleDynamicClassConstantFetch(): void
145+
{
146+
// Based on the configuration above, in this file:
147+
$this->analyse([__DIR__ . '/../src/disallowed/constantDynamicUsages.php'], [
148+
[
149+
'Using Waldo\Quux\Blade::RUNNER is forbidden, not a replicant.',
150+
8,
151+
],
152+
[
153+
'Using Waldo\Quux\Blade::DECKARD is forbidden, maybe a replicant.',
154+
10,
155+
],
156+
[
157+
'Using Waldo\Quux\Blade::RUNNER is forbidden, not a replicant.',
158+
10,
159+
],
160+
]);
161+
$this->analyse([__DIR__ . '/../src/disallowed-allow/constantDynamicUsages.php'], []);
162+
}
163+
164+
144165
public static function getAdditionalConfigFiles(): array
145166
{
146167
return [

tests/src/Blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class Blade
1212

1313
public const WESLEY = 'Snipes';
1414

15+
public const MOVIE = Blade::WESLEY;
16+
1517

1618
public function runner(int $everything = 0, bool $yes = false, string $roland = '303'): void
1719
{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
use Waldo\Quux\Blade;
5+
6+
// allowed by path
7+
$kind = 'RUNNER';
8+
echo Blade::{$kind};
9+
/** @var 'DECKARD'|'MOVIE'|'RUNNER' $kind2 */
10+
echo Blade::{$kind2};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
use Waldo\Quux\Blade;
5+
6+
// disallowed
7+
$kind = 'RUNNER';
8+
echo Blade::{$kind};
9+
/** @var 'DECKARD'|'MOVIE'|'RUNNER' $kind2 */
10+
echo Blade::{$kind2};

0 commit comments

Comments
 (0)