Automated Refactoring With Rector

Feature image: Automated Refactoring With Rector

Imagine that you are instructing an apprentice or junior programmer to go through your entire codebase, upgrade it to the latest version of PHP, and refactor the code to follow some pretty well laid out standards.

Now, imagine something even better: this programmer is instead a tool in your application that could do the same thing: conform your application code to meet your standards, upgrade it to support newer versions of PHP, and even identify and fix other potential issues that you may not even be aware of. Sounds pretty magical, right?

Introducing Rector

Well, that tool exists, and it's called Rector. Rector is a powerful tool that helps developers maintain consistency and improve the quality of their codebase. It does this by automatically refactoring code based on predefined rules. Developers can ensure that their code adheres to a specific set of coding standards and conventions, which makes it easier to understand, maintain, and evolve over time.

Rector can also help upgrade your codebase to a new framework, language, or library version. It can handle the most time-consuming and error-prone tasks and simplify the upgrade process.

Overall, Rector is a valuable tool for any developer looking to improve the quality and consistency of their codebase. With its ability to analyze code statically and make changes without breaking anything, Rector can help developers save time and effort while improving the overall quality of their code.

Let's dive in.

Installing Rector

You can add Rector to your project using Composer:

composer require rector/rector --dev

There's also a community-created Rector extension for Laravel, which I maintain:

composer require driftingly/rector-laravel --dev

Configuring Rector

Once you've installed Rector, you'll want to create a configuration file:

vendor/bin/rector init

The init command creates a file called rector.php in your project's root directory. This file is where we'll add and configure the rules that we want Rector to follow. A rule is a PHP class used to find and transform code.

Let's start with a simple single-rule configuration for a Laravel application. Laravel 9 introduced a new to_route helper. We can tell Rector we want to use to_route instead of redirect()->route() or Redirect::route() using RedirectRouteToToRouteHelperRector.

Replace the contents of your rector.php file with:

<?php
 
declare(strict_types=1);
 
use Rector\Config\RectorConfig;
use RectorLaravel\Rector\MethodCall\RedirectRouteToToRouteHelperRector;
 
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/app',
__DIR__ . '/config',
__DIR__ . '/database',
__DIR__ . '/public',
__DIR__ . '/resources',
__DIR__ . '/routes',
__DIR__ . '/tests',
]);
 
$rectorConfig->rule(RedirectRouteToToRouteHelperRector::class);
};

Running Rector

Now that we've told Rector what to look for, we can run it on our codebase:

# Preview changes
vendor/bin/rector --dry-run
 
# Run
vendor/bin/rector

If your codebase uses either redirect()->route() or Redirect::route() you will see changes like this:

- return redirect()->route('home')->with('error', 'Incorrect Details.')
+ return to_route('home')->with('error', 'Incorrect Details.')
- return Redirect::route('home')->with('error', 'Incorrect Details.')
+ return to_route('home')->with('error', 'Incorrect Details.')

Levelling Up

Let's add some more rules to our rector.php config file to see what else Rector can do.

Sets bundle rules together and can be added using $rectorConfig->sets(). If we wanted to upgrade our code to be compatible with PHP 8.1 we would use the following:

$rectorConfig->sets([
LevelSetList::UP_TO_PHP_81,
]);

Two other common sets are SetList::DEAD_CODE (which removes code that doesn't have any effect) and SetList::CODE_QUALITY (which fixes some common code quality issues). After adding these, our rector.php file now looks like this:

<?php
 
declare(strict_types=1);
 
use Rector\Config\RectorConfig;
use RectorLaravel\Rector\MethodCall\RedirectRouteToToRouteHelperRector;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
 
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/app',
__DIR__ . '/config',
__DIR__ . '/database',
__DIR__ . '/public',
__DIR__ . '/resources',
__DIR__ . '/routes',
__DIR__ . '/tests',
]);
 
$rectorConfig->sets([
SetList::DEAD_CODE,
SetList::CODE_QUALITY,
LevelSetList::UP_TO_PHP_81,
]);
 
$rectorConfig->rule(RedirectRouteToToRouteHelperRector::class);
};

By adding only a few lines to our rector.php file, we've improved code consistency an incredible amount. These three sets include over 150 individual rules. If you don't like a particular rule or need a rule skipped for a specific file or directory, you can update your config with a call to $rectorConfig->skip():

$rectorConfig->skip([
RecastingRemovalRector::class,
]);

To view all the rules and sets, you can check out the Rector Documentation. Rector even includes a bunch of Laravel-specific rules.

Automating

So far, we've run Rector manually from the command line, but we can set it and forget it by adding it to our CI pipeline. My preferred way is through GitHub Actions.

Create a file in ./github/workflows called rector.yaml with the following:

# Inspiration https://github.com/symplify/symplify/blob/main/.github/workflows/rector.yaml
name: Rector
 
on:
pull_request: null
 
jobs:
rector:
runs-on: ubuntu-latest
 
steps:
- uses: actions/checkout@v2
with:
token: "${{ secrets.ACCESS_TOKEN || secrets.GITHUB_TOKEN }}"
 
- uses: shivammathur/setup-php@v2
with:
php-version: 8.1
 
- uses: "ramsey/composer-install@v2"
 
- run: vendor/bin/rector --ansi
 
- uses: EndBug/add-and-commit@v5.1.0
with:
add: .
message: "[ci-review] Rector Rectify"
author_name: "GitHub Action"
author_email: "action@github.com"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

When a pull request is created or updated, this action will run and commit any changes.

Rector commit

This automation is especially useful when a new developer joins the project. Their pull requests will be updated automatically before the first code review.

Another common way to automate these tasks is with a Husky pre-commit hook.

Next Steps

Hopefully, I've demonstrated how Rector can help maintain consistency in your projects.

Here are a few other ways you might use Rector.

  • Instant upgrades. Upgrade your project's PHP version from 5.3 to 8.2.
  • Instant downgrades. If you work on a project locked to an older version of PHP you can still write the PHP you would like, then downgrade it to the required version. You can even fork and downgrade a package if needed.
  • Migrating frameworks. I've heard people writing Rector rules to simplify the process of migrating an entire application from one framework to another while continuing development on the original.

For more information check out:

Get our latest insights in your inbox:

By submitting this form, you acknowledge our Privacy Notice.

Hey, let’s talk.
©2024 Tighten Co.
· Privacy Policy