← Back to writing
5 min read

Rector Beyond the Upgrades

Laravel Php Learning

Dave Liddament (Director, Lamp Bristol) is a conference speaker and software engineer based in Bristol, UK. He organises PHP South West and has given workshops on custom PHPStan and Rector rules at SymfonyCon Amsterdam 2025 and SymfonyLive Berlin 2026. He has also given a talk titled "Rector Beyond Upgrades: Transforming Your Workflow" at WebDevCon 2026, so this is the same talk or a close variant.

Installing Rector

composer require rector/rector --dev
composer require driftingly/rector-laravel --dev

Generate a starting config:

vendor/bin/rector setup-ci

Or create rector.php manually. Always run with --dry-run first to preview changes before applying them.

vendor/bin/rector --dry-run
vendor/bin/rector

References:

Automatic code refactors based on rules

Rector works by parsing PHP source into an abstract syntax tree, applying transformation rules to the nodes, and writing the modified code back to disk. Each rule is a PHP class that implements the RectorInterface. Rules inspect nodes in the tree, identify patterns, and return modified nodes.

This is not string replacement; Rector understands the structure and context of the code. It knows the difference between a variable named $deprecated and an actual deprecated function call.

A rector.php config file specifies which paths to process and which rulesets to apply:

RectorConfig::configure()
    ->withPaths([__DIR__ . '/app', __DIR__ . '/database', __DIR__ . '/routes'])
    ->withPhpSets()
    ->withPreparedSets(
        deadCode: true,
        codeQuality: true,
        typeDeclarations: true,
        privatization: true,
        earlyReturn: true,
        strictBooleans: true,
    );

The safest approach on an existing project is to enable a small ruleset, run --dry-run, review the diff, apply it, run tests, and commit. Then enable the next set. Enabling everything at once produces unmanageable diffs.

References:

Laravel-specific rulesets

The driftingly/rector-laravel package provides rulesets that apply Laravel-specific transformations.

use RectorLaravel\Set\LaravelSetProvider;
use RectorLaravel\Set\LaravelSetList;

RectorConfig::configure()
    ->withSetProviders(LaravelSetProvider::class) // auto-detects version
    ->withSets([
        LaravelSetList::UP_TO_LARAVEL_120,         // or specific version
        LaravelSetList::LARAVEL_IF_HELPERS,        // abort_if() etc.
        LaravelSetList::LARAVEL_TYPE_DECLARATIONS, // typed properties
    ]);

UP_TO_LARAVEL_120 applies all upgrade rules up to Laravel 12. Individual sets can be enabled for more granular control. Dave has also given talks specifically on upgrading legacy Laravel codebases, including a documented journey from Laravel 4 to Laravel 9.

References:

Abstract Syntax Tree (AST) for PHP

Understanding the AST is necessary to write custom Rector or PHPStan rules. When PHP parses source code, it converts it into a tree of node objects, where each node represents a structural element: a class, a method, an expression, a variable, etc.

Rector uses nikic/PHP-Parser, a PHP library that produces an AST you can work with in PHP. Each node type has its own class, with typed properties for its children.

# Inspect the AST of any PHP file
vendor/bin/php-parse src/MyClass.php

The Rector playground at getrector.com lets you write and test rules interactively against sample code. It shows the AST alongside the rule output, which is the fastest way to understand which node types to target.

Writing a custom rule means implementing a getNodeTypes() method (returns the classes of nodes this rule handles) and a refactor() method (receives a matching node, modifies it, and returns it or returns null to skip).

References:

Rector book

"Rector - The Power of Automated Refactoring" by Matthias Noback and Tomas Votruba (creator of Rector). Available on Leanpub. The 2024 edition was updated alongside the Rector 1.0 release. Covers:

  • Making Rector part of a daily development workflow
  • Creating custom rules for project-specific refactoring
  • Adding Rector to CI
  • Understanding the AST and node traversal

References:

Tombstone triggers for unused code detection

Static analysis tools like PHPStan can tell you if a piece of code is referenced, but they cannot tell you if it is actually called at runtime. In dynamic languages like PHP, a function might be reachable statically but never triggered in practice.

Tombstones solve this. A tombstone is a function call placed in code suspected to be dead. When the code runs and the tombstone is invoked, it logs the fact. After enough time in production, you can check which tombstones were never triggered: those sections are genuinely unused.

tombstone('2024-01-01', 'payment-v1-callback');
// ... suspected dead code ...

The scheb/tombstone library provides this for PHP:

composer require scheb/tombstone
composer require scheb/tombstone-analyzer --dev

The analyser reads the log files and produces a report showing which tombstones are "dead" (never invoked) and which are "vampires" (still being called). This is particularly useful for large legacy codebases where static analysis cannot resolve all dynamic call paths.

An alternative PHPStan-based approach is shipmonk/dead-code-detector, which detects unused methods, constants, properties, and enum cases across the full codebase without needing to run the code.

References:


Notes from Laravel Live UK 2026.