Stop Manual Refactoring: Automate Your PHP Upgrades with Rector
One of the more challenging parts of coding is making sure we’re constantly refactoring our code to make it easier to maintain. It’s also a pain to try and keep up with all of the deprecated features in PHP, and major upgrades can be a major headache. But what if there was a tool that could help us with all of these challenges and more? Rector will do just that.
In this video, we’ll discuss what Rector is, how to add it to your project, and how to set it up to continually perform refactorings of your code as it changes and the underlying projects it uses change.
Hello developers, and welcome to the PHP Architect Channel! If this is your first time here, my name is Scott Keck-Warren, and on this channel, we discuss a wide variety of topics related to the PHP ecosystem. Make sure you subscribe to get our latest videos when they’re published.
What is Rector?
Rector is an open-source PHP tool designed to automate code transformations. It assists in upgrading codebases to newer versions, refactoring your code to fit new standards, and enforcing coding standards, making it invaluable for projects of all sizes.
Why Use Rector?
Rector provides a single solution in our code base that allows you to:
- Automate Refactorings – of your code so you can automatically remove deprecated features and functions from the core language and replace them with the suggested replacements. You can also define custom refactorings so they can be enforced across branches.
- Upgrade Faster – to newer PHP versions by automatically applying changes while ensuring compatibility and leveraging new language features.
- Enforce Consistent Code Quality – Rector will enforce coding standards (as it sees them) and automatically apply changes to keep your code in line with those standards.
All of these items will save you time, which is generally one of the most important resources when it comes to software engineering, so it’s well worth the initial investment of time to learn Rector and implement it in your CI/CD pipeline.
Getting Started with Rector
Rector, like most of our modern PHP tools, is installed via Composer (please note the --dev so we don’t install it in production):
composer require rector/rector --dev
When we run Rector for the first time, it will create a new “rector.php” file that is going to house the configuration for our setup.
% ./vendor/bin/rector
No "rector.php" config found. Should we generate it for you? [yes]:
> yes
[OK] The config is added now. Re-run the command to make Rector do the work!
```
When we look at the "rector.php" file, it's going to look something like what's below. It may have changed between the time this was recorded and when you're watching it, and the format change from Rector 1 to 2 drastically.
```php
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
])
// uncomment to reach your current PHP version
->withPhpSets()
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0);
For our first example, we’re going to uncomment “//->withPhpSets()” so it’s active:
->withPhpSets()
What this does is really cool. It causes Rector to look at the “composer.json” of the project it’s being run on and determine which “upgrades” are possible based on the minimum version of PHP listed in the “requires” section (you’re keeping this up to date, right?).
So, for example, if we have the following code:
<?php
class Scott
{
private const THE_ANSWER = 42;
private $theQuestion;
public function __construct(string $theQuestion)
{
$this->theQuestion = $theQuestion;
}
}
It’s perfectly functional code, but it was clearly written before PHP 8.0 was released because it’s not using constructor property promotion. However, if our “composer.json” requires PHP “^8.0” and we run Rector using its --dry-run mode it will show us how it can “upgrade” our code:
./vendor/bin/rector --dry-run
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
1 file with changes
===================
1) src/old-php.php:3
---------- begin diff ----------
@@ @@
{
private const THE_ANSWER = 42;
- private $theQuestion;
-
- public function __construct(string $theQuestion)
- {
- $this->theQuestion = $theQuestion;
+ public function __construct(private string $theQuestion)
+ {
}
}
----------- end diff -----------
Applied rules:
* ClassPropertyAssignToConstructorPromotionRector
We can run it without the --dry-run option to perform the refactoring automatically.
However the cool part is that when we bump our minimal version requirements to “^8.3” it will make additional refactorings available and actually clean our code even more:
./vendor/bin/rector --dry-run
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
1 file with changes
===================
1) src/old-php.php:1
---------- begin diff ----------
@@ @@
<?php
class Scott
{
- private const THE_ANSWER = 42;
+ private const int THE_ANSWER = 42;
- public function __construct(private string $theQuestion)
+ public function __construct(private readonly string $theQuestion)
{
}
}
----------- end diff -----------
Applied rules:
* ReadOnlyPropertyRector
* AddTypeToConstRector
And we can keep going, but the cool part about this is that Rector will find deprecated features in our codebase and replace them with non-deprecated versions. For example, if we have the following code:
<?php
class Scott
{
public function setTheQuestion(string $theQuestion = null)
{
$this->theQuestion = $theQuestion;
}
}
Again, this is syntactically correct, but the string $theQuestion = null is what is called an implicitly nullable type in that $theQuestion could be null or it could be a string. PHP 8.4 deprecated this, so we MUST declare the parameter like ?string $theQuestion = null after the 9.0 release. If you’re like me, you have a ton of these in your code, but Rector can automatically fix them for us:
% ./vendor/bin/rector --dry-run src/deprecated.php
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
1 file with changes
===================
1) src/deprecated.php:1
---------- begin diff ----------
@@ @@
<?php
class Scott
{
- public function setTheQuestion(string $theQuestion = null)
+ public function setTheQuestion(?string $theQuestion = null)
{
$this->theQuestion = $theQuestion;
}
}
----------- end diff -----------
And this will work for a number of deprecations, so we’re ready for PHP 9.0 when it comes out. Or at least more prepared.
Code Quality Options
The other options that were added allow us to add additional dead code, code quality, and missing type checks.
RectorConfig::configure()
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0);
These start at zero, which is essentially off, but if we can slowly work each of them up so they can provide these extra protections on our code over the course of days, if need be.
For example, if we increase our type coverage level to 50 (the highest support at the time this was written).
RectorConfig::configure()
->withTypeCoverageLevel(50)
And then feed Rector the following:
<?php
class Scott
{
private readonly string $theQuestion;
public function getTheQuestion()
{
return $this->theQuestion;
}
}
It will automatically convert it to public function getTheQuestion(): string. It’s not a major improvement, but it will make a huge improvement in your code quality and make other changes easier especially if you’re using additional static code analysis tools like phpstan.
Automated Refactoring
Suppose you have a class constant SECONDS_IN_A_MNUTE in your application’s DateTimeImmutate class that you want to rename to SECONDS_IN_A_MINUTE across your codebase to fix the annoying spelling error.
In modern IDEs, it’s simple enough to refactor this using a keyboard shortcut; however, that doesn’t mean that in another branch someone won’t use the old name, and then, regardless of the merge order, it will cause errors.
To fix this, we can use Rector to move from a “point in time” refactoring to an “all the time” refactoring.
Update your rector.php configuration:
<?php
use App\DateTimeImmutable;
use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\ClassConstFetch\RenameClassConstFetchRector;
use Rector\Renaming\ValueObject\RenameClassConstFetch;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
])
->withRules([
RenameClassConstFetchRector::class,
])
->withConfiguredRule(RenameClassConstFetchRector::class, [
new RenameClassConstFetch(DateTimeImmutable::class, "SECONDS_IN_A_MNUTE", "SECONDS_IN_A_MINUTE"),
]);
This configuration tells Rector to replace all occurrences of our old constant name with our new constant name.
Then we can run Rector locally to actually apply the refactoring, and if someone else added the old constant name in their private branch, Rector can easily fix it for them as well.
There are a ton of these rules built into Rector, and you can even define you’re own.
What You Need To Know
- Rector is a powerful tool that allows us to clean up and refactor our code
- Built-in options to remove deprecated language features
- Can do renaming across our codebase automatically across branches



Leave a comment
Use the form below to leave a comment: