Rector: Automated PHP Code Refactoring and Modernization
Rector is an automated refactoring tool for PHP that modernises legacy code, improves code quality, and maintains consistency across your codebase. It can automatically upgrade PHP versions, apply coding standards, and perform complex refactoring operations that would take hours to carry out manually.
Table of Contents
- What is Rector?
- Why Use Rector?
- Installation
- Configuration Explained
- Common Use Cases
- Running Rector
- Best Practices
- Advanced Configuration
What is Rector?
Rector is an instant-upgrade and automated refactoring tool for PHP. It analyses your codebase and applies transformations to:
- Upgrade PHP versions - Automatically modernise code for newer PHP versions
- Improve code quality - Apply best practices and remove code smells
- Refactor legacy code - Transform old patterns into modern equivalents
- Maintain consistency - Ensure uniform coding patterns across the project
Unlike traditional linters that only identify issues, Rector actually fixes them automatically.
Why Use Rector?
1. Automated PHP Version Upgrades
Upgrading PHP versions manually is time-consuming and error-prone. Rector handles this automatically:
- Replace deprecated functions with modern alternatives
- Update syntax for new PHP features
- Fix compatibility issues between PHP versions
2. Code Quality Improvements
Rector applies established best practices automatically:
- Remove dead code and unused variables
- Improve type declarations
- Optimise performance patterns
- Enforce strict typing
3. Consistency Across Large Codebases
For teams working on large projects, Rector helps to:
- Ensure uniform coding patterns
- Apply architectural decisions automatically
- Maintain code standards without relying on manual review alone
4. Time and Cost Savings
- Reduces the amount of manual refactoring work significantly
- Minimises human error in code transformations
- Frees the team to focus on business logic rather than boilerplate improvements
Installation
Via Composer (Recommended)
# Install as dev dependency
composer require rector/rector --dev
# Or install globally
composer global require rector/rector
Verify Installation
# Check Rector version
vendor/bin/rector --version
# View available commands
vendor/bin/rector list
Configuration Explained
Below is an example configuration file (rector.php) with detailed explanations:
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withSkip([
AddOverrideAttributeToOverriddenMethodsRector::class,
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
privatization: true,
earlyReturn: true,
strictBooleans: true,
)
->withPhpSets();
Configuration Breakdown
1. Paths Configuration
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
- Purpose: Defines which directories Rector should analyse
- Common paths:
src/,tests/,app/(for Laravel) - Benefits: Focuses analysis on relevant code, ignoring vendor dependencies
2. Skip Configuration
->withSkip([
AddOverrideAttributeToOverriddenMethodsRector::class,
])
- Purpose: Excludes specific rules from being applied
- When to use: When a rule does not fit your project's needs
- Example: Skipping PHP 8.3's
#[Override]attribute if your team isn't ready to adopt it yet
3. Prepared Sets
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
privatization: true,
earlyReturn: true,
strictBooleans: true,
)
Dead Code Removal (deadCode: true):
- Removes unused variables, methods, and classes
- Eliminates unreachable code
- Cleans up commented-out code
Code Quality (codeQuality: true):
- Simplifies complex expressions
- Removes unnecessary code constructs
- Applies performance optimisations
Type Declarations (typeDeclarations: true):
- Adds missing type hints to method parameters
- Adds return type declarations
- Improves type safety
Privatization (privatization: true):
- Changes public/protected methods to private when possible
- Improves encapsulation
- Reduces API surface area
Early Return (earlyReturn: true):
- Converts nested if-else to early returns
- Improves code readability
- Reduces nesting levels
Strict Booleans (strictBooleans: true):
- Converts loose boolean comparisons to strict ones
- Converts
if ($var)toif ($var !== null)where appropriate - Improves type safety
4. PHP Sets
->withPhpSets()
- Purpose: Applies all PHP version upgrade rules
- Effect: Automatically detects and applies rules for your PHP version
- Alternative: Use
->withPhpSets(php83: true)to target PHP 8.3, or->withPhpSets(php84: true)for PHP 8.4
Common Use Cases
1. PHP Version Upgrade
One of the most practical uses for Rector is upgrading code to take advantage of newer PHP syntax. Here's an example of moving from PHP 8.1 to 8.3, where Rector can apply typed class constants automatically:
// Before (PHP 8.1)
class Status
{
const ACTIVE = 'active';
const INACTIVE = 'inactive';
const PENDING = 'pending';
}
// After (PHP 8.3) - Rector adds typed class constants
class Status
{
const string ACTIVE = 'active';
const string INACTIVE = 'inactive';
const string PENDING = 'pending';
}
Rector can also apply readonly properties introduced in PHP 8.1, replacing verbose constructor assignments:
// Before (PHP 8.0 style)
class UserDto
{
public string $name;
public string $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
}
// After (PHP 8.1+) - Rector applies constructor promotion with readonly
class UserDto
{
public function __construct(
public readonly string $name,
public readonly string $email,
) {}
}
2. Type Declaration Improvements
// Before
class UserService
{
public function findUser($id)
{
return User::find($id);
}
}
// After
class UserService
{
public function findUser(int $id): ?User
{
return User::find($id);
}
}
3. Dead Code Removal
// Before
class Calculator
{
private $unusedProperty;
public function add($a, $b)
{
$unusedVariable = 'test';
return $a + $b;
}
private function neverUsedMethod()
{
return 'unused';
}
}
// After
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
}
4. Early Return Pattern
// Before
public function processOrder($order)
{
if ($order->isValid()) {
if ($order->isPaid()) {
return $this->fulfillOrder($order);
} else {
return $this->requestPayment($order);
}
} else {
return $this->rejectOrder($order);
}
}
// After
public function processOrder($order)
{
if (!$order->isValid()) {
return $this->rejectOrder($order);
}
if (!$order->isPaid()) {
return $this->requestPayment($order);
}
return $this->fulfillOrder($order);
}
Running Rector
1. Dry Run (Preview Changes)
# See what would be changed without applying
vendor/bin/rector process --dry-run
# Process specific directory
vendor/bin/rector process src --dry-run
2. Apply Changes
# Apply all changes
vendor/bin/rector process
# Process specific file
vendor/bin/rector process src/UserService.php
3. Generate Configuration
# Generate basic rector.php configuration
vendor/bin/rector init
4. List Available Rules
# Show all available rules
vendor/bin/rector list-rules
# Show rules for specific set
vendor/bin/rector list-rules --set=php83
Best Practices
1. Start with Dry Runs
Always preview changes before applying them:
vendor/bin/rector process --dry-run
2. Use Version Control
Commit your code before running Rector:
git add .
git commit -m "Before Rector refactoring"
vendor/bin/rector process
3. Run Tests After Rector
Ensure your test suite passes after refactoring:
vendor/bin/rector process
./vendor/bin/phpunit
4. Incremental Adoption
Start with the safe rule sets:
// Start conservative
->withPreparedSets(
deadCode: true,
codeQuality: true,
)
// Gradually add more
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
privatization: true,
)
5. CI/CD Integration
Add Rector to your CI pipeline:
# .github/workflows/rector.yml
name: Rector
on: [push, pull_request]
jobs:
rector:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
- run: composer install
- run: vendor/bin/rector process --dry-run
Advanced Configuration
Custom Rules and Skips
<?php
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withSkip([
// Skip specific rules
ClassPropertyAssignToConstructorPromotionRector::class,
// Skip rule for specific files
FinalizePublicClassConstantRector::class => [
__DIR__ . '/src/Legacy/OldClass.php',
],
// Skip entire directories
__DIR__ . '/src/External',
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
)
->withPhpSets(php83: true);
Laravel-specific Configuration
<?php
use Rector\Config\RectorConfig;
use RectorLaravel\Set\LaravelSetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/database',
__DIR__ . '/resources',
__DIR__ . '/routes',
__DIR__ . '/tests',
])
->withSets([
LaravelSetList::LARAVEL_120,
LaravelSetList::LARAVEL_CODE_QUALITY,
LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES,
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
);
Troubleshooting
Common Issues
- Memory Limit Exceeded
php -d memory_limit=2G vendor/bin/rector process
- Skip Problematic Files
->withSkip([
__DIR__ . '/src/ProblematicFile.php',
])
- Debugging Rule Application
vendor/bin/rector process --debug
Integration with Other Tools
With Laravel Pint
# First run Rector for refactoring
vendor/bin/rector process
# Then run Pint for code style
./vendor/bin/pint
With PHPStan
# Run Rector first
vendor/bin/rector process
# Then check with PHPStan
./vendor/bin/phpstan analyse
Conclusion
Rector is a practical tool for keeping PHP codebases current. It handles the mechanical parts of version upgrades and code quality improvements automatically, which means fewer hours spent on tedious rewrites and fewer chances for human error to sneak in.
The workflow is straightforward: install it, configure it conservatively, run a dry run to see what it would change, then apply. Run your tests. Commit. Add it to CI so the codebase stays consistent without anyone having to think about it.
If you're running PHP 8.1 or 8.2 and haven't updated your codebase to use features like readonly properties, typed class constants, or intersection types, Rector is the fastest way to get there without doing it by hand.
Start with deadCode and codeQuality, verify everything still passes, then layer in typeDeclarations and privatization. It takes an afternoon to set up properly and saves far more than that over the life of a project.