The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Using Rector to Automate PHP Version Upgrades


In 1890, the US Census faced a crisis. The 1880 census had taken eight years to complete—by the time it was done, the data was already stale. Processing millions of hand-written records with mechanical adding machines was simply too slow and error-prone. The solution came in the form of Herman Hollerith’s tabulation machine, which automated what had been a grueling manual process. The machine didn’t replace human judgment—it freed people from repetitive, error-prone work so they could focus on higher-value tasks.

You face a similar, if less monumental, challenge today. Your PHP application has evolved over years—perhaps a decade. It works, it serves its purpose, but it’s built on older patterns: deprecated functions, inconsistent styles, constructs that make you nervous to touch because they might break something subtle. Now a security requirement mandates PHP 8.1, but your codebase is still on 7.4. What do you do?

Historically, PHP version upgrades meant manual, painstaking work. Developers would comb through files hunting for mysql_* functions, replacing each() loops, updating constructor calls—all while crossing their fingers that nothing broke. For a medium application, this could take weeks. And human error? Practically guaranteed.

What if you could automate most of that work? Of course, manual upgrades are possible—Rector doesn’t eliminate the need for review and testing—but it frees you from the most tedious, error-prone tasks.

What is Rector?

Rector is a reconstructor tool that analyzes your PHP code and applies automated refactorings. Unlike simple search-and-replace tools, Rector understands the code’s structure—it uses an Abstract Syntax Tree (AST) to parse and transform PHP code safely. This means it doesn’t just find text patterns; it understands what the code does and can make intelligent changes that preserve behavior while modernizing syntax.

Strictly speaking, Rector is more than just an upgrade tool—it’s a general-purpose PHP refactoring engine with hundreds of pre-built rules. You can use it for everything from upgrading PHP versions to adopting new framework patterns, improving code quality, and enforcing coding standards. But for our purposes, we’ll focus on version migrations.

Why (and When) to Use Rector

Let’s be clear, though: Rector isn’t magic. It won’t fix every problem, and it won’t understand your business logic. But for the mechanical aspects of version upgrades—syntax changes, deprecated function replacements, and pattern migrations—it’s remarkably effective.

Here’s what Rector typically handles well:

  • Consistent Syntax Updates: Converting old constructor syntax to property promotion, replacing create_function() with closures, updating ternary operator associativity.
  • Deprecated API Replacements: Finding uses of ereg() and replacing with preg_match(), updating mcrypt to openssl, converting split() to explode() or preg_split().
  • Type System Improvements: Adding scalar type hints, return types, and converting docblocks to native types where possible.
  • Framework-Specific Patterns: Modernizing Symfony, Laravel, or WordPress code to use newer patterns.

That said, you should also be aware of Rector’s limitations:

  • No Business Logic Understanding: Rector won’t know if your custom strpos wrapper function should be updated differently.
  • Configuration Complexity: For large codebases, you’ll likely need custom rule configurations and possibly even custom Rector rules.
  • Not 100% Safe: While Rector is generally reliable, you should always review changes and run your test suite. Edge cases exist.
  • Learning Curve: Understanding what rules do and how to combine them takes time.

For many teams, though, the time savings are substantial. What might take a developer weeks can happen in minutes—with the confidence that the changes are applied consistently across the entire codebase.

Alternatives to Rector

Of course, Rector isn’t the only way to upgrade PHP versions. Before we dive into using it, let’s acknowledge what else is out there:

Manual Refactoring: You could update everything by hand. This gives you complete control, but it’s error-prone and terribly inefficient for large codebases. It also doesn’t scale—if you have 100 files with deprecated patterns, you’ll miss some.

PHP_CodeSniffer with Custom Sniffs: PHP_CodeSniffer can detect deprecated usage, but it doesn’t automatically fix code. You’d still need to manually apply fixes or write custom fixers. It’s more of a linter than a refactoring tool.

PHP-CS-Fixer: This tool focuses on coding style (like PSR-12) and some simple refactorings. It’s excellent for formatting but doesn’t have the depth of PHP version migration rules that Rector provides.

Custom Scripts: You could write your own scripts using PHP-Parser (the library Rector is built on). This gives ultimate flexibility but requires significant expertise and maintenance overhead.

In practice, Rector has become the de facto standard for automated PHP upgrades because it combines breadth of rules with practical safety. For most teams, it’s the most efficient path forward.

Installation and Basic Configuration

Let’s walk through setting up Rector for a basic PHP version upgrade. We’ll start with the simplest possible scenario and build from there.

First, add Rector to your project as a development dependency:

composer require rector/rector --dev

That’s it, of course—Rector is now installed. Next, we need to tell it what to do. Create a file named rector.php in your project root:

<?php

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;

return static function (RectorConfig $rectorConfig): void {
    // Directories Rector should analyze
    $rectorConfig->paths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ]);

    // Apply upgrades for PHP 8.1
    $rectorConfig->sets([
        SetList::PHP_81,
    ]);
};

This configuration tells Rector to analyze your src and tests directories and apply all rules from the PHP 8.1 set. You can adjust the paths and version as needed.

Running Rector Safely

Before applying any changes, you should see what Rector plans to do. Always run a dry run first:

vendor/bin/rector process --dry-run

Rector will output a diff showing all proposed changes but won’t modify any files. This is your chance to review the changes. You might see hundreds of modifications—that’s normal.

Tip: Rector can also output changes in other formats. For a summary without seeing the full diff:

vendor/bin/rector process --dry-run --output-format=json > rector-changes.json

That command saves a structured report of all planned changes to a JSON file, which you can inspect programmatically if needed.

Applying Changes

Once you’re satisfied with the dry run output, apply the changes:

vendor/bin/rector process

Rector will now modify your files. Let’s look at a concrete example of what you might see.

Before Rector

<?php

class UserService
{
    private $name;
    private $email;

    public function __construct($name, $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

After Rector (with PHP 8.0+)

<?php

class UserService
{
    public function __construct(
        private $name,
        private $email
    ) {
    }

    public function getName(): string
    {
        return $this->name;
    }
}

Rector automatically converts traditional constructor property assignments to constructor property promotion—a PHP 8.0 feature that reduces boilerplate. This is just one of hundreds of transformations Rector can perform.

Verifying the Upgrade

After Rector finishes, you should verify that your application still works. At minimum:

  1. Run your test suite: vendor/bin/phpunit or vendor/bin/pest
  2. Check for syntax errors: php -l src/ or use php -d display_errors=1 -r "require 'src/your-file.php';"
  3. Start your development server and manually test critical paths

Rector is reliable, but it’s not infallible. Complex code with reflection, variable variables, or dynamic method calls might confuse Rector’s transformations. That’s why your test suite is essential—it’s your safety net.

Going Deeper: Custom Rules and Configuration

For many projects, the basic setup above is sufficient. But complex codebases often need more granular control. Let’s look at a few advanced scenarios.

Excluding Specific Rules

Suppose you want most PHP 8.1 upgrades but need to skip the isset to count rule because it breaks some of your code. You can disable specific rules:

<?php

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->paths([__DIR__ . '/src']);

    // Import the PHP 8.1 set...
    $rectorConfig->sets([SetList::PHP_81]);

    // ...but skip this specific rule
    $rectorConfig->skip([
        \Rector\Php81\Rector\FuncCall\NonNullableToStringRector::class,
    ]);
};

Using Multiple Sets Together

You can combine different rule sets. For example, to upgrade to PHP 8.1 AND adopt some code quality improvements:

$rectorConfig->sets([
    SetList::PHP_81,
    SetList::CODE_QUALITY,
    SetList::TYPE_DECLARATION,
]);

Of course, combining sets means more changes to review. Start conservative—you can always add more rules later.

Custom Rule Sets

If you find yourself repeatedly configuring the same rules, you can create your own reusable set. Create rector-custom.php:

<?php

use Rector\Config\RectorConfig;
use Rector\Set\Contract\SetInterface;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->sets([
        MyProjectSetList::UPGRADE_TO_81_WITH_QUALITY,
    ]);
};

class MyProjectSetList implements SetInterface
{
    public const UPGRADE_TO_81_WITH_QUALITY = 'MyProjectSetList/upgrade-to-81-with-quality';
    
    public function getSetList(): array
    {
        return [
            SetList::PHP_81,
            SetList::CODE_QUALITY,
            // Plus your custom rules...
        ];
    }
}

This is more advanced, but useful for large organizations with consistent coding standards.

Real-World Considerations

Let’s talk about what happens when you run Rector on a non-trivial codebase.

Incremental Upgrades vs. Big Bang

You can upgrade directly from PHP 7.4 to 8.2 if you want—Rector handles multi-version jumps. Many teams, though, prefer incremental upgrades: 7.4 → 8.0, test, then 8.0 → 8.1, test again. The advantage is easier troubleshooting if something breaks. If you jump directly and hit an issue, you have to untangle which version change caused it.

My recommendation: If your test coverage is good, a direct upgrade is fine. If tests are sparse, consider incremental steps.

Handling Third-Party Code

Rector analyzes everything in your configured paths. That includes vendor code if you accidentally include it. You almost certainly don’t want to modify vendor dependencies—those are managed by Composer and will be updated separately.

Make sure your paths configuration excludes vendor/:

$rectorConfig->paths([
    __DIR__ . '/src',
    __DIR__ . '/tests',
    // Don't include vendor!
]);

Dealing with False Positives

Rector isn’t perfect. Occasionally, it might suggest changes that aren’t appropriate for your specific code. The skip configuration mentioned earlier handles this. You can also write custom rules to handle project-specific patterns—but that’s beyond our scope.

Troubleshooting Common Issues

Tests Fail After Rector

This is the most common problem. Here’s how to debug:

  1. Look at the specific test failures. Are they related to Rector’s changes?
  2. Run vendor/bin/rector process --dry-run again and review the diff. Did Rector change something unexpected?
  3. Use git diff to see exactly what changed. Sometimes the cumulative effect of many small changes can interact in surprising ways.
  4. Consider excluding problematic files or rules temporarily and addressing those manually.

Rector Throws Errors

If Rector itself crashes during processing:

  1. Ensure you’re using a compatible Rector version for your PHP version.
  2. Check that your rector.php configuration is valid PHP.
  3. Try running with --verbose to see more details: vendor/bin/rector process --dry-run --verbose
  4. Search the Rector GitHub issues—someone may have encountered the same problem.

Performance on Large Codebases

Rector processes files sequentially by default. For large projects, you can enable parallel processing by adding:

$rectorConfig->parallelProcesses(4); // Adjust based on CPU cores

This can significantly speed up processing, but uses more memory.

Conclusion: A Pragmatic Approach

Rector is a powerful tool that can transform how your team approaches PHP upgrades. But it’s not a silver bullet. Here’s what we’ve covered:

  • Rector automates mechanical refactorings by understanding PHP code structure
  • It’s generally reliable but requires review and testing
  • Alternatives exist, but Rector is the most comprehensive solution for PHP version upgrades
  • Start with a basic configuration and a dry run
  • Always verify changes with your test suite
  • Use skip rules for edge cases Rector doesn’t handle correctly

The goal isn’t to eliminate manual work entirely—it’s to free your team from tedious, error-prone tasks so they can focus on the complex logic that actually requires human intelligence. Used thoughtfully, Rector makes PHP upgrades less daunting and more routine. That means your application stays secure, performs better, and can use modern language features without the usual pain.

Next steps: Install Rector in a development branch, run a dry run on your codebase, and see what changes it proposes. The results might surprise you.


Integrate Rector into your CI/CD pipeline to catch deprecations early. Run it regularly to keep your codebase incrementally modernized. And remember: the best upgrade is the one that happens before your PHP version becomes end-of-life.

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming