From ce801c71d131efc3fc1bc8fa703fcc7853ab294b Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Wed, 6 Aug 2025 18:03:36 +0200 Subject: [PATCH 1/7] [Php85] Remove calls to deprecated no-op functions --- config/set/php85.php | 14 +++ .../Fixture/different_function.php.inc | 8 ++ .../skip_func_call_in_assignment.php.inc | 7 ++ .../skip_func_call_in_condition.php.inc | 9 ++ .../Fixture/wrapped_function.php.inc | 21 ++++ ...cCallWithPhpVersionIdCheckerRectorTest.php | 26 +++++ .../config/configured_rule.php | 13 +++ ...pFuncCallWithPhpVersionIdCheckerRector.php | 98 +++++++++++++++++++ .../WrapFuncCallWithPhpVersionIdChecker.php | 22 +++++ 9 files changed, 218 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc create mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc create mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc create mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc create mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php create mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php create mode 100644 rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php create mode 100644 rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php diff --git a/config/set/php85.php b/config/set/php85.php index 7536382f3b3..35dc170b23f 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -8,6 +8,8 @@ use PhpParser\Node\Expr\Cast\String_; use Rector\Config\RectorConfig; use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; +use Rector\DeadCode\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector; +use Rector\DeadCode\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Rector\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector; use Rector\Php85\Rector\ClassMethod\NullDebugInfoReturnRector; use Rector\Php85\Rector\FuncCall\RemoveFinfoBufferContextArgRector; @@ -180,4 +182,16 @@ new RenameCast(String_::class, String_::KIND_BINARY, String_::KIND_STRING), ] ); + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_no-op_functions_from_the_resource_to_object_conversion + $rectorConfig->ruleWithConfiguration( + WrapFuncCallWithPhpVersionIdCheckerRector::class, + [ + new WrapFuncCallWithPhpVersionIdChecker('curl_close', 80500), + new WrapFuncCallWithPhpVersionIdChecker('curl_share_close', 80500), + new WrapFuncCallWithPhpVersionIdChecker('finfo_close', 80500), + new WrapFuncCallWithPhpVersionIdChecker('imagedestroy', 80500), + new WrapFuncCallWithPhpVersionIdChecker('xml_parser_free', 80500), + ] + ); }; diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc new file mode 100644 index 00000000000..72b7a10c02f --- /dev/null +++ b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc @@ -0,0 +1,8 @@ + diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc new file mode 100644 index 00000000000..c5dfa4479ed --- /dev/null +++ b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc @@ -0,0 +1,7 @@ + diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc new file mode 100644 index 00000000000..833e398d5d1 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc @@ -0,0 +1,9 @@ + diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc new file mode 100644 index 00000000000..1376ed90d2c --- /dev/null +++ b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc @@ -0,0 +1,21 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php new file mode 100644 index 00000000000..192f3c79767 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php @@ -0,0 +1,26 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php new file mode 100644 index 00000000000..da94881a613 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php @@ -0,0 +1,13 @@ +withConfiguredRule( + WrapFuncCallWithPhpVersionIdCheckerRector::class, + [new WrapFuncCallWithPhpVersionIdChecker('no_op_function', 80500)] + ); diff --git a/rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php new file mode 100644 index 00000000000..ac888af7d78 --- /dev/null +++ b/rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -0,0 +1,98 @@ +> + */ + public function getNodeTypes(): array + { + return [Expression::class]; + } + + /** + * @param Expression $node + */ + public function refactor(Node $node): ?Node + { + if (! $node->expr instanceof FuncCall) { + return null; + } + + $funcCall = $node->expr; + + foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { + if ($this->getName($funcCall) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName()) { + continue; + } + + $phpVersionIdConst = new ConstFetch(new Name('PHP_VERSION_ID')); + $if = new If_(new Smaller($phpVersionIdConst, new Int_( + $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() + ))); + $if->stmts = [$node]; + + return $if; + } + + return null; + } + + public function configure(array $configuration): void + { + Assert::allIsInstanceOf($configuration, WrapFuncCallWithPhpVersionIdChecker::class); + + $this->wrapFuncCallWithPhpVersionIdCheckers = $configuration; + } +} diff --git a/rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php b/rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php new file mode 100644 index 00000000000..1ac07a0660a --- /dev/null +++ b/rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php @@ -0,0 +1,22 @@ +functionName; + } + + public function getPhpVersionId(): int + { + return $this->phpVersionId; + } +} From 7e9aa6fd77962ccb702077116c034e46483ad80e Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Tue, 12 Aug 2025 12:18:19 +0200 Subject: [PATCH 2/7] Change namespace from DeadCode to Transform --- config/set/php85.php | 2 +- .../Fixture/different_function.php.inc | 8 ------- .../skip_func_call_in_assignment.php.inc | 7 ------- .../skip_func_call_in_condition.php.inc | 9 -------- .../Fixture/wrapped_function.php.inc | 21 ------------------- .../Fixture/different_function.php.inc | 8 +++++++ .../skip_func_call_in_assignment.php.inc | 7 +++++++ .../skip_func_call_in_condition.php.inc | 9 ++++++++ .../Fixture/wrapped_function.php.inc | 21 +++++++++++++++++++ ...cCallWithPhpVersionIdCheckerRectorTest.php | 2 +- .../config/configured_rule.php | 2 +- ...pFuncCallWithPhpVersionIdCheckerRector.php | 5 ++--- 12 files changed, 50 insertions(+), 51 deletions(-) delete mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc delete mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc delete mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc delete mode 100644 rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc create mode 100644 rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc create mode 100644 rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc create mode 100644 rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc create mode 100644 rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc rename rules-tests/{DeadCode => Transform}/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php (86%) rename rules-tests/{DeadCode => Transform}/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php (80%) rename rules/{DeadCode => Transform}/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php (92%) diff --git a/config/set/php85.php b/config/set/php85.php index 35dc170b23f..084d1c9b479 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -8,7 +8,6 @@ use PhpParser\Node\Expr\Cast\String_; use Rector\Config\RectorConfig; use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; -use Rector\DeadCode\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector; use Rector\DeadCode\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Rector\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector; use Rector\Php85\Rector\ClassMethod\NullDebugInfoReturnRector; @@ -22,6 +21,7 @@ use Rector\Renaming\ValueObject\MethodCallRename; use Rector\Renaming\ValueObject\RenameCast; use Rector\Renaming\ValueObject\RenameClassAndConstFetch; +use Rector\Transform\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector; return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules( diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc deleted file mode 100644 index 72b7a10c02f..00000000000 --- a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc deleted file mode 100644 index c5dfa4479ed..00000000000 --- a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc deleted file mode 100644 index 833e398d5d1..00000000000 --- a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc b/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc deleted file mode 100644 index 1376ed90d2c..00000000000 --- a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc +++ /dev/null @@ -1,21 +0,0 @@ - ------ - diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc new file mode 100644 index 00000000000..e8ad4d4693d --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/different_function.php.inc @@ -0,0 +1,8 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc new file mode 100644 index 00000000000..a09f15bdd31 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_assignment.php.inc @@ -0,0 +1,7 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc new file mode 100644 index 00000000000..241bcbe23bb --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_func_call_in_condition.php.inc @@ -0,0 +1,9 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc new file mode 100644 index 00000000000..91c9db0b58d --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc @@ -0,0 +1,21 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php similarity index 86% rename from rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php rename to rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php index 192f3c79767..6a3fbf7ad80 100644 --- a/rules-tests/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php @@ -1,6 +1,6 @@ withConfiguredRule( diff --git a/rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php similarity index 92% rename from rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php rename to rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index ac888af7d78..64a250a74d1 100644 --- a/rules/DeadCode/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -2,14 +2,13 @@ declare(strict_types=1); -namespace Rector\DeadCode\Rector\FuncCall; +namespace Rector\Transform\Rector\FuncCall; use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\Smaller; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; -use PhpParser\Node\Param; use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; @@ -21,7 +20,7 @@ use Webmozart\Assert\Assert; /** - * @see \Rector\Tests\DeadCode\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector\WrapFuncCallWithPhpVersionIdCheckerRectorTest + * @see \Rector\Tests\Transform\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector\WrapFuncCallWithPhpVersionIdCheckerRectorTest */ final class WrapFuncCallWithPhpVersionIdCheckerRector extends AbstractRector implements ConfigurableRectorInterface { From 7ec3674456668ab16dc204251fe0638802043901 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Tue, 19 Aug 2025 20:45:52 +0200 Subject: [PATCH 3/7] Check if wrapped func call is already wrapped --- .../skip_already_if_version_check.php.inc | 12 +++ ...pFuncCallWithPhpVersionIdCheckerRector.php | 87 ++++++++++++++++--- 2 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc new file mode 100644 index 00000000000..0e9f813fd62 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check.php.inc @@ -0,0 +1,12 @@ + diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 64a250a74d1..5a5b90a5f1d 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -12,6 +12,8 @@ use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; +use PhpParser\NodeVisitor; +use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\DeadCode\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Rector\Rector\AbstractRector; @@ -57,32 +59,49 @@ public function getRuleDefinition(): RuleDefinition */ public function getNodeTypes(): array { - return [Expression::class]; + return [StmtsAwareInterface::class]; } /** - * @param Expression $node + * @param StmtsAwareInterface $node */ - public function refactor(Node $node): ?Node + public function refactor(Node $node): null|Node|int { - if (! $node->expr instanceof FuncCall) { + if ($node->stmts === null) { return null; } - $funcCall = $node->expr; + if ($this->isWrappedFuncCall($node)) { + return NodeVisitor::DONT_TRAVERSE_CHILDREN; + } - foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { - if ($this->getName($funcCall) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName()) { + $hasChanged = false; + foreach ($node->stmts as $key => $stmt) { + if (! $stmt instanceof Expression || ! $stmt->expr instanceof FuncCall) { continue; } - $phpVersionIdConst = new ConstFetch(new Name('PHP_VERSION_ID')); - $if = new If_(new Smaller($phpVersionIdConst, new Int_( - $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() - ))); - $if->stmts = [$node]; + $funcCall = $stmt->expr; + + foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { + if ($this->getName($funcCall) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName()) { + continue; + } + + $phpVersionIdConst = new ConstFetch(new Name('PHP_VERSION_ID')); + $if = new If_(new Smaller($phpVersionIdConst, new Int_( + $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() + ))); + $if->stmts = [$stmt]; + + $node->stmts[$key] = $if; + + $hasChanged = true; + } + } - return $if; + if ($hasChanged) { + return $node; } return null; @@ -94,4 +113,46 @@ public function configure(array $configuration): void $this->wrapFuncCallWithPhpVersionIdCheckers = $configuration; } + + private function isWrappedFuncCall(StmtsAwareInterface $node): bool + { + if (! $node instanceof If_) { + return false; + } + + if (! $node->cond instanceof Smaller) { + return false; + } + + if (! $node->cond->left instanceof ConstFetch || ! $this->isName($node->cond->left->name, 'PHP_VERSION_ID')) { + return false; + } + + if (! $node->cond->right instanceof Int_) { + return false; + } + + if (count($node->stmts) !== 1) { + return false; + } + + $childStmt = $node->stmts[0]; + + if (! $childStmt instanceof Expression || ! $childStmt->expr instanceof FuncCall) { + return false; + } + + foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { + if ( + $this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() + || $node->cond->right->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() + ) { + continue; + } + + return true; + } + + return false; + } } From cb82c81f258ad21c0607352ef0230b2df0fb9864 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Wed, 20 Aug 2025 17:01:07 +0200 Subject: [PATCH 4/7] Add additional function_exists condition --- ...version_check_with_function_exists.php.inc | 12 +++++ .../Fixture/wrapped_function.php.inc | 4 +- ...pFuncCallWithPhpVersionIdCheckerRector.php | 49 +++++++++++++------ 3 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc new file mode 100644 index 00000000000..6bad9d59da8 --- /dev/null +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/skip_already_if_version_check_with_function_exists.php.inc @@ -0,0 +1,12 @@ + diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc index 91c9db0b58d..52d509c0a10 100644 --- a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/Fixture/wrapped_function.php.inc @@ -11,10 +11,10 @@ no_op_function(1, 2); namespace Rector\Tests\Transform\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector\Fixture; -if (PHP_VERSION_ID < 80500) { +if (function_exists('no_op_function') && PHP_VERSION_ID < 80500) { no_op_function(); } -if (PHP_VERSION_ID < 80500) { +if (function_exists('no_op_function') && PHP_VERSION_ID < 80500) { no_op_function(1, 2); } diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 5a5b90a5f1d..2e1866a6f93 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -5,11 +5,16 @@ namespace Rector\Transform\Rector\FuncCall; use PhpParser\Node; +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\BinaryOp\BooleanAnd; use PhpParser\Node\Expr\BinaryOp\Smaller; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Scalar\Int_; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; use PhpParser\NodeVisitor; @@ -89,9 +94,12 @@ public function refactor(Node $node): null|Node|int } $phpVersionIdConst = new ConstFetch(new Name('PHP_VERSION_ID')); - $if = new If_(new Smaller($phpVersionIdConst, new Int_( - $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() - ))); + $if = new If_(new BooleanAnd( + new FuncCall(new Name('function_exists'), [new Arg(new String_( + $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() + ))]), + new Smaller($phpVersionIdConst, new Int_($wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId())), + )); $if->stmts = [$stmt]; $node->stmts[$key] = $if; @@ -120,17 +128,7 @@ private function isWrappedFuncCall(StmtsAwareInterface $node): bool return false; } - if (! $node->cond instanceof Smaller) { - return false; - } - - if (! $node->cond->left instanceof ConstFetch || ! $this->isName($node->cond->left->name, 'PHP_VERSION_ID')) { - return false; - } - - if (! $node->cond->right instanceof Int_) { - return false; - } + $phpVersionIdComparison = $this->getPhpVersionIdComparison($node->cond); if (count($node->stmts) !== 1) { return false; @@ -145,7 +143,7 @@ private function isWrappedFuncCall(StmtsAwareInterface $node): bool foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { if ( $this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() - || $node->cond->right->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() + || $phpVersionIdComparison->right->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() ) { continue; } @@ -155,4 +153,25 @@ private function isWrappedFuncCall(StmtsAwareInterface $node): bool return false; } + + private function getPhpVersionIdComparison(Expr $expr): ?Smaller + { + if ($expr instanceof BooleanAnd) { + return $this->getPhpVersionIdComparison($expr->left) ?? $this->getPhpVersionIdComparison($expr->right); + } + + if (! $expr instanceof Smaller) { + return null; + } + + if (! $expr->left instanceof ConstFetch || ! $this->isName($expr->left->name, 'PHP_VERSION_ID')) { + return null; + } + + if (! $expr->right instanceof Int_) { + return null; + } + + return $expr; + } } From 881b7a52e0f856d24144beb6ead78d0be5640dce Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Wed, 20 Aug 2025 17:05:44 +0200 Subject: [PATCH 5/7] Move value object into correct namespace --- config/set/php85.php | 4 ++-- .../config/configured_rule.php | 2 +- .../FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php | 3 +-- .../ValueObject/WrapFuncCallWithPhpVersionIdChecker.php | 4 +++- 4 files changed, 7 insertions(+), 6 deletions(-) rename rules/{DeadCode => Transform}/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php (85%) diff --git a/config/set/php85.php b/config/set/php85.php index 084d1c9b479..a81f317de0a 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -7,10 +7,9 @@ use PhpParser\Node\Expr\Cast\Int_; use PhpParser\Node\Expr\Cast\String_; use Rector\Config\RectorConfig; -use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; -use Rector\DeadCode\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Rector\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector; use Rector\Php85\Rector\ClassMethod\NullDebugInfoReturnRector; +use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; use Rector\Php85\Rector\FuncCall\RemoveFinfoBufferContextArgRector; use Rector\Removing\Rector\FuncCall\RemoveFuncCallArgRector; use Rector\Removing\ValueObject\RemoveFuncCallArg; @@ -22,6 +21,7 @@ use Rector\Renaming\ValueObject\RenameCast; use Rector\Renaming\ValueObject\RenameClassAndConstFetch; use Rector\Transform\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector; +use Rector\Transform\ValueObject\WrapFuncCallWithPhpVersionIdChecker; return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules( diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php index edfd437f5bb..0a49f668967 100644 --- a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/config/configured_rule.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Rector\DeadCode\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Rector\Transform\Rector\FuncCall\WrapFuncCallWithPhpVersionIdCheckerRector; +use Rector\Transform\ValueObject\WrapFuncCallWithPhpVersionIdChecker; return RectorConfig::configure() ->withConfiguredRule( diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 2e1866a6f93..02e5b5f94e3 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -11,7 +11,6 @@ use PhpParser\Node\Expr\BinaryOp\Smaller; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Scalar\String_; @@ -20,8 +19,8 @@ use PhpParser\NodeVisitor; use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\Contract\Rector\ConfigurableRectorInterface; -use Rector\DeadCode\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Rector\Rector\AbstractRector; +use Rector\Transform\ValueObject\WrapFuncCallWithPhpVersionIdChecker; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; diff --git a/rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php b/rules/Transform/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php similarity index 85% rename from rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php rename to rules/Transform/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php index 1ac07a0660a..cc100a6ccec 100644 --- a/rules/DeadCode/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php +++ b/rules/Transform/ValueObject/WrapFuncCallWithPhpVersionIdChecker.php @@ -1,6 +1,8 @@ Date: Wed, 20 Aug 2025 17:06:43 +0200 Subject: [PATCH 6/7] Update code sample --- .../FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 02e5b5f94e3..200e0e19111 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -47,7 +47,7 @@ public function getRuleDefinition(): RuleDefinition , <<<'CODE_SAMPLE' - if (PHP_VERSION_ID < 80500) { + if (function_exists('no_op_function') && PHP_VERSION_ID < 80500) { no_op_function(); } CODE_SAMPLE From 8b653cdaa24065a20f8d145b73c78a9b69630e2f Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Wed, 20 Aug 2025 17:14:20 +0200 Subject: [PATCH 7/7] Fix PHPStan issue --- .../WrapFuncCallWithPhpVersionIdCheckerRector.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 200e0e19111..850646f0321 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -127,7 +127,10 @@ private function isWrappedFuncCall(StmtsAwareInterface $node): bool return false; } - $phpVersionIdComparison = $this->getPhpVersionIdComparison($node->cond); + $phpVersionId = $this->getPhpVersionId($node->cond); + if ($phpVersionId === null) { + return false; + } if (count($node->stmts) !== 1) { return false; @@ -142,7 +145,7 @@ private function isWrappedFuncCall(StmtsAwareInterface $node): bool foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { if ( $this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() - || $phpVersionIdComparison->right->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() + || $phpVersionId->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() ) { continue; } @@ -153,10 +156,10 @@ private function isWrappedFuncCall(StmtsAwareInterface $node): bool return false; } - private function getPhpVersionIdComparison(Expr $expr): ?Smaller + private function getPhpVersionId(Expr $expr): ?Int_ { if ($expr instanceof BooleanAnd) { - return $this->getPhpVersionIdComparison($expr->left) ?? $this->getPhpVersionIdComparison($expr->right); + return $this->getPhpVersionId($expr->left) ?? $this->getPhpVersionId($expr->right); } if (! $expr instanceof Smaller) { @@ -171,6 +174,6 @@ private function getPhpVersionIdComparison(Expr $expr): ?Smaller return null; } - return $expr; + return $expr->right; } }