github rectorphp/rector 2.8.0
Released Rector 2.8

latest release: 2.8.1
5 hours ago

Faster runs, single-pass rules, and a leaner rule set 🚀

The Rector core engine is ~26 % faster (1.36x) with ~8 % less memory than 2.7.0.

If you run PHP 8.3+, it's 3x faster using PHPStan Turbo, the native parsing extension shipped with phpstan/phpstan:

Rev Core engine (time / peak RSS)
2.7.0 32.0s / 351 MB
2.8.0 23.6s / 324 MB
2.8.0 + PHPStan Turbo (PHP 8.3+) ~10.0s / ~317 MB

Turbo factor measured on this release via an A/B run on Rector's own rules/ (24-core host, PHP 8.4, cold, identical changed-file fingerprint): ~2.4x wall and ~2.6x worker CPU. Turbo speeds up PHPStan parsing in the workers, so the wall-time gain scales with the available core count.


This release is mostly about speed and reliability. A large performance campaign hoists cheap node guards in front of expensive reflection, scope and type lookups across hundreds of rules, replaces per-node config scans in configurable rules with keyed lookups, and resolves short names and unused imports in a single AST walk.

Rules now converge in a single pass: instead of a re-processing guard, the engine iterates rules to a fixed point and tests enforce idempotency, so a rule that changes a file produces the same result whether it runs once or many times.

We also trimmed the rule set: many niche, context-dependent rules that are not part of any set and rarely used are now deprecated. Deprecated rules still run and print a warning; they will be removed in a future major release. The goal is a smaller set of valuable, reliable rules for everyday upgrades.


New features 🥳

  • [CodeQuality] Add ExplicitNullableObjectInstanceRector, replacing 4 nullable-object instanceof rules (#8636)
  • [Console] Add --all option to list-rules to list every rule in core and rector-* packages (#8637)
  • [Console] Undeprecate the list-rules command (#8533)
  • Respect cgroup/CFS CPU quota when picking the parallel worker count (#8559)
  • Bump to PHPStan 2.3

Performance Improvements 🚀

  • Run the parallel workers with PHPStan Turbo, the native parsing extension shipped with phpstan/phpstan, on PHP 8.3+ - no main-process restart, the workers do the analysis (#8812)
  • Retarget class-level rules to a single Class_/ClassLike node and add getClassReflection() to AbstractRector: RemoveParentDelegatingClassMethodRector, ReturnTypeFromMockObjectRector, AddVoidReturnTypeWhereNoReturnRector and ChangeMethodVisibilityRector (#8824, #8827, #8828, #8830, #8831)
  • Hoist the repeated fully qualified name build in UseImportNameMatcher::resolveName() and resolve the parent class reflection by name in Php4ConstructorRector (#8841, #8838)
  • Drop scope attachment on pre/post increment expressions and remove an unneeded array_values() in BetterStandardPrinter::pArray() (#8814, #8821)
  • Hoist cheap name and structural guards before reflection, scope and type lookups across CodeQuality, DeadCode, CodingStyle, TypeDeclaration, Transform, EarlyReturn and legacy Php rules (#8664, #8666, #8667, #8668, #8669, #8670, #8671, #8672, #8661, #8663)
  • Replace per-node config scans with keyed lookups in configurable rules - method, property, rename, transform, arguments and removing rules (#8734, #8735, #8736, #8739, #8742, #8743, #8744, #8745)
  • Iterate rules to a fixed point with a content-cycle guard instead of a per-node re-processing guard, and enforce single-pass idempotency in tests (#8679, #8719, #8715, #8722)
  • Skip rules whose work depends on a docblock when the node has none (#8624, #8625, #8626, #8627, #8628, #8629, #8630, #8631, #8632)
  • Resolve short names and used names in a single AST walk, and cache resolved names per file version (#8697, #8700, #8701, #8702, #8704)
  • Reuse the rebuilt ClassReflection by class content hash to stop memory growth on class refresh (#8731, #8728, #8729)
  • Cache per-file import facts in ClassNameImportContext (#8687)
  • Cache realpath() of skip patterns in FileInfoMatcher (#8699)
  • Memoize StaticPHPUnitEnvironment::isPHPUnitRun and replace the isStringName wildcard loop with a single strpbrk scan (#8732, #8733)
  • Only process the catch statements PHPStan skipped and run the decorating visitors once (#8738)
  • Avoid nested traversals in ContextNodeVisitor by tracking loop/try/attribute depth (#8741)
  • Short-circuit NodeComparator::areNodesEqual before printing (#8752)
  • Move skipping from enterNode() to FileProcessor so it runs once per file (#8593)
  • Parallel LPT buckets for a more even worker load (#8496)
  • Add a single-process hotspot profiler and A/B benchmark script with a performance methodology (#8718, #8536)
  • Speed up ConvertStaticToSelfRector, ArrayToFirstClassCallableRector and SimplifyEmptyCheckOnEmptyArrayRector with cheap guards before scope fetching (#8801, #8796, #8795)
  • Hoist loop-invariant resolve+continue guards above foreach (#8761)
  • Stop attaching scope to class extends/implements names and drop the unused process property on the node scope (#8788, #8792)
  • Remove the infinite-loop guard from FileProcessor now that rules converge in a single pass (#8811)

Bugfixes 🐛

  • NewlineBeforeNewAssignSetRector: fix a false positive on a local var and $this property name collision (#8535)
  • SimplifyQuoteEscapeRector: handle strings with accented characters (#8532)
  • TernaryToNullsafeCoalesceRector: skip mixed and untyped calls (#8531)
  • TernaryToNullCoalescingRector: preserve fallback parentheses on the Identical/NotIdentical path (#8674)
  • AddArrowFunctionReturnTypeRector: fix a crash on NonexistentParentClassType (#8568)
  • Fix scope on an enum when combining DowngradeEnumToConstantListClassRector and RenameMethodRector (#8727)
  • Fix class scope refresh losing method scope when the class node is already in a class (#8725)
  • StrContainsRector / MbStrContainsRector: skip a non-zero strpos()/mb_strpos() offset (#8640, #8642)
  • SplitGroupedClassConstantsRector: preserve the attribute and type (#8650)
  • RemoveUselessUnionReturnDocblockRector: skip a static/$this union @return (#8649)
  • StrictArrayParamDimFetchRector: skip a private method param called with an ArrayAccess arg (#8648)
  • ReadOnlyAnonymousClassRector: skip an anonymous class with attributes (#8665)
  • NullToStrictStringFuncCallArgRector: skip an arg already cast to the target type (#8688)
  • AddNameToBooleanArgumentRector: skip session_set_save_handler (#8750)
  • RemoveDuplicatedCaseInSwitchRector: compare switch case statements node-by-node (#8754)
  • MergePhpstanDocTagIntoNativeRector: keep the native @param/@var/@return description (#8781)
  • Record in-place node mutations as changes, so a rule that mutates a node and returns null is still reported, across rector-doctrine, rector-symfony, rector-phpunit and rector-downgrade-php (doctrine #539, symfony #1087, phpunit #810, downgrade #418)
  • fix(parallel): report the error limit only when it is actually reached (#8844)
  • Lazy-load rule class aliases to fix the PHP 7.4 scoped-build lint (#8845)
  • Fix Windows CI error on ChangedFilesDetectorPortableCacheTest (#8537)

Deprecations 💀

Deprecated rules still run and print a warning; they will be removed in a future major release. These are niche, context-dependent or rarely used rules outside the main PHP upgrade path.

Transform / Arguments

  • FuncCallToNewRector (#8776), FuncCallToStaticCallRector (#8775), MethodCallToFuncCallRector (#8769), MethodCallToStaticCallRector (#8770), NewToStaticCallRector (#8768), StaticCallToMethodCallRector (#8759), ConstFetchToClassConstFetchRector (#8771), MergeInterfacesRector (#8772), ArrayDimFetchToMethodCallRector (#8762), StringToClassConstantRector (#8779), RemoveMethodCallParamRector (#8766)
  • ReplaceArgumentDefaultValueRector and FunctionArgumentDefaultValueReplacerRector in favor of dedicated rules (#8686, #8692)

Renaming

  • RenameAnnotationRector (#8780), RenameStaticMethodRector (#8777)

Naming (and the SetList::NAMING set itself)

  • RenameParamToMatchTypeRector (#8553), RenamePropertyToMatchTypeRector (#8547), RenameVariableToMatchNewTypeRector (#8554), RenameVariableToMatchMethodCallReturnTypeRector (#8549), RenameForeachValueVariableToMatchExprVariableRector (#8548), RenameForeachValueVariableToMatchMethodCallReturnTypeRector (#8550), and the naming prepared set warning (#8555)

TypeDeclaration

  • ParamTypeByMethodCallTypeRector and its Object/Scalar/Array splits (#8567), ParamTypeByParentCallTypeRector (#8813), AddPropertyTypeDeclarationRector (#8774), AddParamStringTypeFromSprintfUseRector (#8723), DeclareStrictTypesRector in favor of SafeDeclareStrictTypesRector (#8562), BinaryOpNullableToInstanceofRector folded into ExplicitNullableObjectInstanceRector (#8817)

CodeQuality / CodingStyle / DeadCode / EarlyReturn / Php

  • CompleteDynamicPropertiesRector (#8753), SwitchTrueToMatchRector (#8622), InnerFunctionToPrivateMethodRector (#8621), MoveInnerFunctionToTopLevelRector (#8616), ForRepeatedCountToOwnVariableRector (#8620), CallUserFuncWithArrowFunctionToInlineRector (#8615), RemoveUselessIsObjectCheckRector (#8614), SimplifyStrposLowerRector (#8611), SetTypeToCastRector (#8610), ForeachItemsAssignToEmptyArrayToAssignRector (#8577), PostIncDecToPreIncDecRector (#8583), TernaryFalseExpressionToIfRector (#8683), RemoveDeadReturnRector in favor of RemoveAlwaysTrueIfConditionRector (#8685), RemoveDuplicatedReturnSelfDocblockRector (#8634), RemoveParentDelegatingConstructorRector (#8755), ChangeConstantVisibilityRector (#8767), AddOverrideAttributeToOverriddenMethodsRector (#8773), RemoveTraitUseRector (#8778), ReturnEarlyIfVariableRector (#8818)

Internals ⚙️

  • Replace tomasvotruba/unused-public with shipmonk/dead-code-detector (#8530)
  • Decouple Rector rules from the PhpParser NodeVisitor and move enterNode() out of AbstractRector into RectorRunner (#8541, #8560)
  • Add a standalone RectorRegistry and merge the skipper services into a single Skipper (#8600, #8601, #8604)
  • Inline the deprecated symplify/rule-doc-generator-contracts under the Rector\RuleDoc namespace (#8586)
  • Add a PHPStan rule to flag Rector rules not registered in any set (#8760)
  • Remove dead and deprecated public API: @api annotations, unused collectors, converters and interfaces, the created_by_rule attribute and RectifiedAnalyzer, and the deprecated withPhp53Sets()..withPhp74Sets() and cache-meta methods (#8794, #8803, #8804, #8805, #8806, #8807, #8808, #8809, #8810, #8798, #8799)
  • Register MergePhpstanDocTagIntoNativeRector in the type-declaration docblock level (#8763)
  • Move VariableNaming to rector-downgrade-php (#8797, downgrade #417)
  • Deprecate PhpVersionFeature and use PhpVersion constants directly in rules (#8839)
  • Remove the deprecated custom-rule and setup-ci commands, and the unused doctrine/inflector dependency (#8815, #8816)
  • Add a NoGetClassReflectionOnScopeRule PHPStan rule and route class reflection through a safer fetchClassLikeReflection(), used in ReadonlyClassManipulator and ObjectTypeSpecifier (#8820, #8829, #8832, #8835, #8840)
  • Remove NullToStrictStringIntConverter, use an explicit null type in the null-to-strict rules (#8837)
  • Lock the fixture directory while a test uses its temp file (#8822)

rector-doctrine 🟠

  • Deprecate AddArrayResultColumnNamesRector (#533)
  • Retarget RemoveNullFromNullableCollectionTypeRector to a single Class_ node and make it idempotent (#535, #534)
  • Cap removed-API rules at an upper version bound (#532)
  • Check the method name before type inference in DBAL/ORM method call rules (#536, #537)

rector-symfony 🎵

  • [Symfony 8.2] Add rules for Schedule::with() to add() and Doctrine middleware renames (#1082)
  • [Symfony 8.0] Merge the eraseCredentials body into an existing __serialize() instead of a dead serialize() (#1085)
  • Reimplement ReplaceArgumentDefaultValueRector usages as dedicated rules (#1079)
  • Make LogoutHandlerToLogoutEventSubscriberRector idempotent (#1080)
  • Cap legacy removed-API rules and StringToArrayArgumentProcessRector at upper version bounds (#1076, #1075)
  • Move the nullable ServiceMap out of the ServiceMapProvider constructor (#1073)
  • Check the method name before type inference in Intl and WebLink rules, and bail before reflection when a node has no attributes (#1083, #1084)
  • Deprecate the JMS AccessType/Accessor annotation-to-attribute rules and drop the orphaned enum (#1086, #1088)

rector-phpunit 🟢

  • [PHPUnit 7.0] Add ScenarioToTestAnnotationRector to replace the scenario annotation with test (#809)
  • Match the rename map case-insensitively in IdentifierManipulator (#808)
  • WillReturnCallbackFallbackToReturnFalseRector: skip never-returning fall-through and a trailing comment after return (#801, #800)
  • AssertEmptyNullableObjectToAssertInstanceofRector: skip an anonymous class (#792)
  • Deprecate SuffixMockObjectPropertyRector as it can produce invalid variable names (#795)
  • Make AddProphecyTraitRector idempotent and skip a non-test class early (#806, #793)
  • Speed up AssertRegExpRector and the PHPUnit test-case call guard with a cheap method-name gate (#796, #807)
  • Inline the scenario-to-test doc tag replace in ScenarioToTestAnnotationRector and drop the deprecated findFirstNonAnonymousClass() call (#812, #811)

rector-downgrade-php ⬇️

  • Fix an infinite loop on DowngradeIteratorCountToArrayRector, and skip iterator_to_array()/iterator_count() already guarded by is_array() (#414, #415)
  • Match array_key_first/array_key_last in a single statement traversal, and convert nested arrow functions to anonymous functions in a single pass (#416, #413)
  • Drop JSON constants from a bitwise-or structurally in JsonConstCleaner (#410)
  • Make the exposed downgrade rules idempotent on repeated application (#409)
  • Deprecate DowngradeParameterTypeWideningRector (#411)
  • Add a local VariableNaming service moved from core, and fix the missing COVARIANT_RETURN constant with PhpVersion::PHP_74 (#417, #419)

Don't miss a new rector release

NewReleases is sending notifications on new releases.