rectorphp/rector · error · ShouldNotHappenException

"%s" is deprecated as noisy change with little value. Use ma

Error message

"%s" is deprecated as noisy change with little value. Use manually or custom rule where needed instead

What it means

StaticClosureRector used to prepend static to closures (function () {...} -> static function () {...}) to prevent implicit $this binding. It is deprecated as a noisy change with little value — it rewrites every closure in a codebase for a micro-optimisation most projects never need. refactor() now throws ShouldNotHappenException on every Closure node; the rule is a tombstone that only exists to break configs that still enable it.

Source

Thrown at rules/CodingStyle/Rector/Closure/StaticClosureRector.php:52

    return 2;
}
CODE_SAMPLE
)]);
    }
    /**
     * @return array<class-string<Node>>
     */
    public function getNodeTypes(): array
    {
        return [Closure::class];
    }
    /**
     * @param Closure $node
     */
    public function refactor(Node $node): ?Node
    {
        throw new ShouldNotHappenException(sprintf('"%s" is deprecated as noisy change with little value. Use manually or custom rule where needed instead', self::class));
    }
}

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Remove StaticClosureRector::class from rector.php and all imported set files, then re-run vendor/bin/rector dry-run
  2. Mark only the closures that actually leak or hold references (long-lived listeners, queued callbacks) as static, manually
  3. For blanket enforcement use PHP-CS-Fixer's static_lambda fixer, which is the appropriate tool for this style rule
  4. grep -r StaticClosureRector across the repository to find every remaining registration

Example fix

// before (rector.php)
->withRules([
    \Rector\CodingStyle\Rector\Closure\StaticClosureRector::class,
])

// after
// rule removed; annotate hot/leak-prone closures by hand:
$ids = array_map(static fn (Item $i) => $i->id, $items);
Defensive patterns

Strategy: validation

Validate before calling

$deprecated = ['StaticClosureRector'];
foreach (glob(__DIR__ . '/rector*.php') as $config) {
    $src = file_get_contents($config);
    foreach ($deprecated as $rule) {
        if (str_contains($src, $rule)) {
            fwrite(STDERR, "Remove deprecated rule {$rule} from {$config}\n");
            exit(1);
        }
    }
}

Prevention

When it happens

Trigger: Registering rules/CodingStyle/Rector/Closure/StaticClosureRector.php in config and running process/dry-run over any file containing a closure. The throw comes from rules/CodingStyle/Rector/Closure/StaticClosureRector.php:52 on the first closure visited.

Common situations: This rule was part of widely-copied CodingStyle setups, so many rector.php files and tutorial-derived set bundles still list it. After upgrading rector/rector-prefixed, the first dry-run over callback-heavy code (Collections, event dispatchers, array_map) crashes with this exception.

Related errors


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/ae804755ab7a9abd. Report an issue: GitHub.