From 725d91403d6077d9d117f01c6944b78527cbcd8e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 8 Oct 2025 08:31:12 +0200 Subject: [PATCH] [fix] Skip symfony config closures on FirstClassCallableRector as they do not support first class callables --- .../Fixture/make_other_closure_pass.php.inc | 27 +++++++++++++++++++ .../Fixture/skip_symfony_config.php.inc | 11 ++++++++ .../Array_/FirstClassCallableRector.php | 17 +++++++++--- 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/make_other_closure_pass.php.inc create mode 100644 rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/skip_symfony_config.php.inc diff --git a/rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/make_other_closure_pass.php.inc b/rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/make_other_closure_pass.php.inc new file mode 100644 index 00000000000..63e5d13a313 --- /dev/null +++ b/rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/make_other_closure_pass.php.inc @@ -0,0 +1,27 @@ +services() + ->factory([SomeExternalObject::class, 'sleepStatic']); +}; + +?> +----- +services() + ->factory(SomeExternalObject::sleepStatic(...)); +}; + +?> diff --git a/rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/skip_symfony_config.php.inc b/rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/skip_symfony_config.php.inc new file mode 100644 index 00000000000..8035847efb8 --- /dev/null +++ b/rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/skip_symfony_config.php.inc @@ -0,0 +1,11 @@ +services() + ->factory([SomeExternalObject::class, 'sleepStatic']); +}; diff --git a/rules/Php81/Rector/Array_/FirstClassCallableRector.php b/rules/Php81/Rector/Array_/FirstClassCallableRector.php index 73f099de784..a870664c6c4 100644 --- a/rules/Php81/Rector/Array_/FirstClassCallableRector.php +++ b/rules/Php81/Rector/Array_/FirstClassCallableRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; @@ -24,6 +25,7 @@ use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; +use Rector\Symfony\NodeAnalyzer\SymfonyPhpClosureDetector; use Rector\ValueObject\PhpVersion; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -37,7 +39,8 @@ final class FirstClassCallableRector extends AbstractRector implements MinPhpVer public function __construct( private readonly ArrayCallableMethodMatcher $arrayCallableMethodMatcher, private readonly ReflectionProvider $reflectionProvider, - private readonly ReflectionResolver $reflectionResolver + private readonly ReflectionResolver $reflectionResolver, + private readonly SymfonyPhpClosureDetector $symfonyPhpClosureDetector ) { } @@ -83,15 +86,23 @@ public function name() */ public function getNodeTypes(): array { - return [Property::class, ClassConst::class, Array_::class]; + return [Property::class, ClassConst::class, Array_::class, Closure::class]; } /** - * @param Property|ClassConst|Array_ $node + * @param Property|ClassConst|Array_|Closure $node * @return StaticCall|MethodCall|null|NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN */ public function refactor(Node $node): int|null|StaticCall|MethodCall { + if ($node instanceof Closure) { + if ($this->symfonyPhpClosureDetector->detect($node)) { + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + return null; + } + if ($node instanceof Property || $node instanceof ClassConst) { return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; }