Integration To New Project

Every new project is different. It depends on the PHP version, framework, libraries, coding style, level of PHPStan without baseline, type coverage or dead code coverage.

Rector runs natively on PHP 7.2 and higher, so you can install it easily.

1. Coding Standard and PHPStan First

Rector works best in company of two other tools. Set them up before the first Rector run.

Coding standard. Rector uses php-parser to print the code, so it might add extra space here and there. A coding standard tool fixes that in the same CI run. We recommend Easy Coding Standard: fast, reliable and its configuration is very similar to Rector.

composer require --dev symplify/easy-coding-standard

Install it first and get it to the highest level possible, then move on to Rector.

Static analysis. Rector uses PHPStan to understand types. What PHPStan can see, Rector can see too and vice versa. If you use a PHPStan baseline and ignore thousands of errors, you're making Rector blind: it can't rename a method call on a mixed type.

composer require --dev phpstan/phpstan

Add PHPStan, remove the baseline and increase the level one by one. To get the best out of Rector, reach PHPStan level 3-4 without baseline before using it.

2. Take it Slow

The most crucial step is to integrate into a new project slowly. We apply Rector rulesets in our clients' projects carefully and knowingly. The goal is not to make a huge change fast but to get you and your teammates comfortable, trust changes in pull requests, and be able to review them.

What does it mean?

  • don't apply all rules at once at first
  • start with 1-3 rules that are easy to integrate and are safe
  • make sure the 1st PR is merged; only then start adding new rules

3. Upgrade PHP First

Before diving into any prepared sets, we start with the crucial part - the PHP upgrade sets. Let's say this is a composer file of our project:

{
    "require": {
        "php": "^7.4"
    }
}

This means our code base can use features from PHP 7.4, but it doesn't mean it actually uses them. So at first, we check what is the lowest version our codebase uses:

<?php

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
    ->withPhpSets();

This configuration tells Rector, "Upgrade my code to PHP 7.4, based on the composer.json version." Does that sound about right?

No, because this would invoke sets from PHP 5.3, 5.4, 5.5, 5.6, 7.0, 7.1, 7.2, 7.3 and 7.4 simultaneously. That's over 100 rules that will be applied. That sounds dangerous to do and even more tedious to review.

4. One PHP Level at a Time

Instead, we raise the PHP level one rule at a time with withPhpLevel():

 <?php

 use Rector\Config\RectorConfig;

 return RectorConfig::configure()
     ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
-    ->withPhpSets();
+    ->withPhpLevel(1);

Now, we told Rector, "Instead of applying all PHP sets up to 7.4, apply only the first rule from them."


We run Rector to see the changes it proposes:

vendor/bin/rector --dry-run

If all looks good in the diff, we apply Rector:

vendor/bin/rector

Instead of 100s of rules, we only run one. We can create a small pull request, get a review the same day, and merge.


Once merged, we raise the level:

 <?php

 use Rector\Config\RectorConfig;

 return RectorConfig::configure()
     ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
-    ->withPhpLevel(1);
+    ->withPhpLevel(2);

Again, we run Rector, apply changes, create pull-request, and go for merge. Slowly but surely, we are making our codebase better at a stable and safe pace. Once the level covers all rules for your PHP version, switch to withPhpSets() and keep it there.


Skip what you don't need

Every codebase is different, and sometimes, we come across a rule that is not safe to apply, or files we want to skip. Use skipping rules or paths for that.

Prepared sets, one level at a time

Rector provides dozens of prepared sets. But the same way we don't read every book in library we visit for first time, we don't enable all prepared sets at once.

Instead, use level methods and take it step by step. It more relaxing path to reach the goal.


More tools that help you out: