From d9484951b21d51422f010e14b7da1b571359e0b9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 21 May 2026 11:54:44 +0200 Subject: [PATCH 1/4] NoDynamicNameRule: Skip named lookup on known object --- .../ExplicitClassPrefixSuffixRule.php | 2 +- src/Rules/NoDynamicNameRule.php | 10 +++++++- .../Fixture/SkipConstantLookupOnKnownType.php | 23 +++++++++++++++++++ .../NoDynamicNameRuleTest.php | 6 ++++- .../Source/SomeClassConstantA.php | 10 ++++++++ .../Source/SomeClassConstantB.php | 10 ++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php create mode 100644 tests/Rules/NoDynamicNameRule/Source/SomeClassConstantA.php create mode 100644 tests/Rules/NoDynamicNameRule/Source/SomeClassConstantB.php diff --git a/src/Rules/Explicit/ExplicitClassPrefixSuffixRule.php b/src/Rules/Explicit/ExplicitClassPrefixSuffixRule.php index c021d526..c2b5574d 100644 --- a/src/Rules/Explicit/ExplicitClassPrefixSuffixRule.php +++ b/src/Rules/Explicit/ExplicitClassPrefixSuffixRule.php @@ -14,8 +14,8 @@ use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use function str_ends_with; use Symplify\PHPStanRules\Enum\RuleIdentifier; +use function str_ends_with; /** * @implements Rule diff --git a/src/Rules/NoDynamicNameRule.php b/src/Rules/NoDynamicNameRule.php index 910d521b..279596ad 100644 --- a/src/Rules/NoDynamicNameRule.php +++ b/src/Rules/NoDynamicNameRule.php @@ -16,6 +16,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\UnionType; use Symplify\PHPStanRules\Enum\RuleIdentifier; use Symplify\PHPStanRules\TypeAnalyzer\CallableTypeAnalyzer; @@ -36,7 +37,7 @@ public function __construct( public function getNodeType(): string { // trick to allow multiple node types - return Node::class; + return Expr::class; } public function processNode(Node $node, Scope $scope): array @@ -54,6 +55,13 @@ public function processNode(Node $node, Scope $scope): array return []; } + $classType = $scope->getType($node->class); + if ($classType instanceof UnionType) { + if ($classType->getObjectClassReflections() !== []) { + return []; + } + } + $ruleError = RuleErrorBuilder::message(self::ERROR_MESSAGE) ->identifier(RuleIdentifier::NO_DYNAMIC_NAME) ->build(); diff --git a/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php b/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php new file mode 100644 index 00000000..c5096daf --- /dev/null +++ b/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php @@ -0,0 +1,23 @@ +getObject(); + if ($object::MY_CONSTANT === 3) { + echo "a"; + } + } + + private function getObject():SomeClassConstantA|SomeClassConstantB { + return rand(0,1) ? new SomeClassConstantA() : new SomeClassConstantB(); + } +} diff --git a/tests/Rules/NoDynamicNameRule/NoDynamicNameRuleTest.php b/tests/Rules/NoDynamicNameRule/NoDynamicNameRuleTest.php index b5d5fbf3..061e2f23 100644 --- a/tests/Rules/NoDynamicNameRule/NoDynamicNameRuleTest.php +++ b/tests/Rules/NoDynamicNameRule/NoDynamicNameRuleTest.php @@ -11,10 +11,13 @@ use PHPUnit\Framework\Attributes\DataProvider; use Symplify\PHPStanRules\Rules\NoDynamicNameRule; +/** + * @extends RuleTestCase + */ final class NoDynamicNameRuleTest extends RuleTestCase { /** - * @param array> $expectedErrorMessagesWithLines + * @param list $expectedErrorMessagesWithLines */ #[DataProvider('provideData')] public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void @@ -42,6 +45,7 @@ public static function provideData(): Iterator yield [__DIR__ . '/Fixture/SkipMagicGet.php', []]; yield [__DIR__ . '/Fixture/SkipCallableUnion.php', []]; yield [__DIR__ . '/Fixture/SkipNullableClosure.php', []]; + yield [__DIR__ . '/Fixture/SkipConstantLookupOnKnownType.php', []]; yield [__DIR__ . '/Fixture/SkipImmediatelyInvokedFunctionExpression.php', []]; } diff --git a/tests/Rules/NoDynamicNameRule/Source/SomeClassConstantA.php b/tests/Rules/NoDynamicNameRule/Source/SomeClassConstantA.php new file mode 100644 index 00000000..6f6a5e55 --- /dev/null +++ b/tests/Rules/NoDynamicNameRule/Source/SomeClassConstantA.php @@ -0,0 +1,10 @@ + Date: Thu, 21 May 2026 11:56:23 +0200 Subject: [PATCH 2/4] Update SkipConstantLookupOnKnownType.php --- .../Fixture/SkipConstantLookupOnKnownType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php b/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php index c5096daf..692f24aa 100644 --- a/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php +++ b/tests/Rules/NoDynamicNameRule/Fixture/SkipConstantLookupOnKnownType.php @@ -11,8 +11,8 @@ final class SkipConstantLookupOnKnownType { public function run() { - $object = $this->getObject(); - if ($object::MY_CONSTANT === 3) { + $objectOfKnownType = $this->getObject(); + if ($objectOfKnownType::MY_CONSTANT === 3) { echo "a"; } } From 8354d0cb51a7b3f5fb17ff5f87cf39d40db56024 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 21 May 2026 12:01:12 +0200 Subject: [PATCH 3/4] Update NoDynamicNameRule.php --- src/Rules/NoDynamicNameRule.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Rules/NoDynamicNameRule.php b/src/Rules/NoDynamicNameRule.php index 279596ad..04ce1329 100644 --- a/src/Rules/NoDynamicNameRule.php +++ b/src/Rules/NoDynamicNameRule.php @@ -56,10 +56,8 @@ public function processNode(Node $node, Scope $scope): array } $classType = $scope->getType($node->class); - if ($classType instanceof UnionType) { - if ($classType->getObjectClassReflections() !== []) { - return []; - } + if ($classType instanceof UnionType && $classType->getObjectClassReflections() !== []) { + return []; } $ruleError = RuleErrorBuilder::message(self::ERROR_MESSAGE) From 2394ac049442dd987b1123060cc0252937fbe6a5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 21 May 2026 12:32:31 +0200 Subject: [PATCH 4/4] run rector on unrelated files --- .../Doctrine/NoRepositoryCallInDataFixtureRule.php | 2 +- src/Rules/Explicit/NoProtectedClassStmtRule.php | 2 +- src/Rules/Rector/NoIntegerRefactorReturnRule.php | 2 +- src/Rules/StringFileAbsolutePathExistsRule.php | 2 ++ .../NoDuplicateArgAutowireByTypeRule.php | 2 +- .../SymfonyServiceReferenceFunctionAnalyzer.php | 12 ++++++++---- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Rules/Doctrine/NoRepositoryCallInDataFixtureRule.php b/src/Rules/Doctrine/NoRepositoryCallInDataFixtureRule.php index c7a4e5bc..4857765e 100644 --- a/src/Rules/Doctrine/NoRepositoryCallInDataFixtureRule.php +++ b/src/Rules/Doctrine/NoRepositoryCallInDataFixtureRule.php @@ -48,7 +48,7 @@ public function processNode(Node $node, Scope $scope): array } $methodName = $node->name->toString(); - if (! in_array($methodName, ['getRepository', 'find', 'findAll', 'findBy', 'findOneBy'])) { + if (! in_array($methodName, ['getRepository', 'find', 'findAll', 'findBy', 'findOneBy'], true)) { return []; } diff --git a/src/Rules/Explicit/NoProtectedClassStmtRule.php b/src/Rules/Explicit/NoProtectedClassStmtRule.php index d094e784..3c3ac062 100644 --- a/src/Rules/Explicit/NoProtectedClassStmtRule.php +++ b/src/Rules/Explicit/NoProtectedClassStmtRule.php @@ -80,7 +80,7 @@ private function shouldSkipClassMethod(ClassMethod|ClassConst|Property $classStm } // PHPUnit test methods - if (in_array($classStmt->name->toString(), [MethodName::SET_UP, MethodName::TEAR_DOWN])) { + if (in_array($classStmt->name->toString(), [MethodName::SET_UP, MethodName::TEAR_DOWN], true)) { return true; } diff --git a/src/Rules/Rector/NoIntegerRefactorReturnRule.php b/src/Rules/Rector/NoIntegerRefactorReturnRule.php index 9b44d88c..90c4f346 100644 --- a/src/Rules/Rector/NoIntegerRefactorReturnRule.php +++ b/src/Rules/Rector/NoIntegerRefactorReturnRule.php @@ -98,7 +98,7 @@ private function findUsedNodeVisitorConstantNames(ClassMethod $classMethod): arr return null; } - if (! in_array($subNode->class->toString(), [NodeVisitor::class, NodeTraverser::class])) { + if (! in_array($subNode->class->toString(), [NodeVisitor::class, NodeTraverser::class], true)) { return null; } diff --git a/src/Rules/StringFileAbsolutePathExistsRule.php b/src/Rules/StringFileAbsolutePathExistsRule.php index 3e90c747..be0f5911 100644 --- a/src/Rules/StringFileAbsolutePathExistsRule.php +++ b/src/Rules/StringFileAbsolutePathExistsRule.php @@ -1,5 +1,7 @@ value, self::NAMED_AUTOWIRED_TYPES)) { + if ($referenceExpr instanceof String_ && in_array($referenceExpr->value, self::NAMED_AUTOWIRED_TYPES, true)) { $ruleError = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $referenceExpr->value)) ->identifier(SymfonyRuleIdentifier::NO_DUPLICATE_ARG_AUTOWIRE_BY_TYPE) ->line($referenceFuncCall->getStartLine()) diff --git a/src/Symfony/ConfigClosure/SymfonyServiceReferenceFunctionAnalyzer.php b/src/Symfony/ConfigClosure/SymfonyServiceReferenceFunctionAnalyzer.php index 9004a6e2..1b438ce3 100644 --- a/src/Symfony/ConfigClosure/SymfonyServiceReferenceFunctionAnalyzer.php +++ b/src/Symfony/ConfigClosure/SymfonyServiceReferenceFunctionAnalyzer.php @@ -23,9 +23,13 @@ public static function isReferenceCall(Expr $expr): bool $functionName = $expr->name->toString(); - return in_array($functionName, [ - SymfonyFunctionName::REF, - SymfonyFunctionName::SERVICE, - ]); + return in_array( + $functionName, + [ + SymfonyFunctionName::REF, + SymfonyFunctionName::SERVICE, + ], + true + ); } }