data codes through eyeglasses

Refactoring PHP Code: A Comprehensive Guide Using VSCode and Rector

Updated October 2026: refreshed the Rector setup for the current RectorConfig::configure() style, fixed the code samples, and added a safe dry-run workflow.

Refactoring PHP code means improving its structure without changing what it does. The quickest way to do it: use VS Code for small, careful edits (rename, extract, tidy up) and Rector for repetitive changes across a whole codebase. Install Rector with composer require rector/rector --dev, preview changes with vendor/bin/rector process --dry-run, then apply them once the diff looks right.

Why refactor PHP code?

  1. Readability: clearer code is easier for your future self and your team to follow.
  2. Maintainability: small, focused functions are easier to change and test.
  3. Less technical debt: regular clean-ups stop messy code piling up into bugs and slow delivery.
  4. Performance and upgrades: refactoring often exposes inefficiencies and makes PHP version upgrades much less painful.

Manual refactoring in VS Code

VS Code is lightweight but works well for PHP once you add a PHP language extension. Which refactorings you get (rename, extract, and so on) depends on that extension, so check what your setup offers from the lightbulb menu (Ctrl+. or Cmd+.) or the right-click Refactor entry.

1. Extract functions

Pulling a block of code into its own function removes repetition and keeps each piece doing one job.

Before:

function processOrder($order) {
    // Calculate the total
    $total = 0;
    foreach ($order->items as $item) {
        $total += $item->price * $item->quantity;
    }

    // Apply discount
    if ($order->hasDiscount()) {
        $total -= $order->getDiscountAmount();
    }

    // Log the order processing
    file_put_contents('order.log', "Processed order with total: $total\n", FILE_APPEND);

    return $total;
}

After:

function calculateTotal($order) {
    $total = 0;
    foreach ($order->items as $item) {
        $total += $item->price * $item->quantity;
    }
    return $total;
}

function applyDiscount($total, $order) {
    if ($order->hasDiscount()) {
        $total -= $order->getDiscountAmount();
    }
    return $total;
}

function logOrder($total) {
    file_put_contents('order.log', "Processed order with total: $total\n", FILE_APPEND);
}

function processOrder($order) {
    $total = calculateTotal($order);
    $total = applyDiscount($total, $order);
    logOrder($total);
    return $total;
}

VS Code tip: select the block, open the Refactor menu and look for an extract function or method action. If your PHP extension does not offer one, cut and paste into a new function, then let your tests confirm nothing changed.

2. Rename variables and methods

Meaningful names are the cheapest readability win. Put the cursor on a symbol and press F2 (Windows/Linux, or Fn+F2 on some Macs) to rename it everywhere it is used.

// Before
function calc($x, $y) {
    return $x * $y;
}

// After
function calculateProduct($firstNumber, $secondNumber) {
    return $firstNumber * $secondNumber;
}

3. Extract constants

Magic numbers and strings hide intent. Give them a name.

// Before
function calculateShippingCost($weight) {
    return $weight * 0.5;
}

// After
const SHIPPING_RATE_PER_KG = 0.5;

function calculateShippingCost($weight) {
    return $weight * SHIPPING_RATE_PER_KG;
}

Automated refactoring with Rector

Manual refactoring does not scale. Rector parses your PHP into an abstract syntax tree and applies rules that rewrite it, so the same change lands consistently in hundreds of files. It also handles PHP version upgrades and framework migrations.

Install Rector

composer require rector/rector --dev

Create a rector.php config

Current Rector releases use a fluent RectorConfig::configure() builder. Start small: point it at your code and switch on a couple of prepared sets.

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

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

withPreparedSets() enables whole groups of rules (dead code removal, code quality, coding style, type declarations and more). withPhpSets() upgrades syntax to the PHP version declared in your composer.json. On an existing project, enable one set at a time so each diff stays reviewable.

Older tutorials, including the first version of this one, use the callback style return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules([...]); };. That still works for registering individual rules, but the builder style above is what the Rector docs now show.

Run it safely: dry run first

# Preview the changes without touching any files
vendor/bin/rector process --dry-run

# Apply them once the diff looks right
vendor/bin/rector process

Commit your work before the real run, then review the result with git diff and run your test suite. Rector is reliable, but you are still responsible for what ships.

Examples of Rector refactoring

Simplify an if that returns a boolean (SimplifyIfReturnBoolRector, part of the code quality set):

// Before
if ($value) {
    return true;
} else {
    return false;
}

// After
return (bool) $value;

Fix argument order in implode(). The old implode($array, ',') form was removed in PHP 8, and Rector can rewrite it:

// Before
$result = implode($array, ',');

// After
$result = implode(',', $array);

Turn a simple foreach into array_map:

// Before
$results = [];
foreach ($items as $item) {
    $results[] = $item->process();
}

// After
$results = array_map(fn($item) => $item->process(), $items);

Whether a loop should become array_map is partly a style choice. Check which rules a set enables before you run it, and treat the diff as a suggestion you review rather than a guarantee.

Write a custom Rector rule

When you have a pattern unique to your codebase, you can write your own rule. This one renames calls to a deprecated function:

<?php

declare(strict_types=1);

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class ReplaceDeprecatedFunctionRector extends AbstractRector
{
    public function getRuleDefinition(): RuleDefinition
    {
        // Required by AbstractRector: describe the rule with before/after examples.
        // See the Rector docs for the full definition.
    }

    public function getNodeTypes(): array
    {
        return [FuncCall::class];
    }

    public function refactor(Node $node): ?Node
    {
        if (! $this->isName($node, 'old_function')) {
            return null;
        }

        $node->name = new Node\Name('new_function');

        return $node;
    }
}

Register it in rector.php with ->withRules([ReplaceDeprecatedFunctionRector::class]) and run Rector as usual. The Rector documentation covers writing a full rule definition and testing your rule.

Which tool when?

  • VS Code: one-off renames, extractions and judgement calls where you want to see each change.
  • Rector: the same change repeated across many files, PHP upgrades, deprecated API clean-ups and enforcing team conventions in CI.

Conclusion

Use manual refactoring for fine-grained control and Rector for large, repetitive changes. Start with a dry run, one set of rules at a time, and a clean git state, and your PHP codebase will stay readable without any heroic rewrites.