rectorphp/rector · error · InvalidConfigurationException

Method "%s()" can be called only once. It always includes al

Error message

Method "%s()" can be called only once. It always includes all previous sets UP TO the defined version.%sThe best practise is to call it once with no argument. That way it will pick up PHP version from composer.json and your project will always stay up to date.

What it means

withPhpSets() is designed to be called once: without arguments it picks the target PHP version from composer.json and includes all sets up to it, so a second call would either duplicate or contradict the first. RectorConfigBuilder tracks isWithPhpSetsUsed and throws InvalidConfigurationException (naming RectorConfigBuilder::withPhpSets) on the second invocation. The message recommends the zero-argument form so the project tracks composer.json automatically.

Source

Thrown at src/Configuration/RectorConfigBuilder.php:409

        bool $php81 = \false,
        bool $php80 = \false,
        bool $php74 = \false,
        bool $php73 = \false,
        bool $php72 = \false,
        bool $php71 = \false,
        bool $php70 = \false,
        bool $php56 = \false,
        bool $php55 = \false,
        bool $php54 = \false,
        bool $php53 = \false,
        // place on later as BC break when used in php 7.x without named arg
        bool $php84 = \false,
        bool $php85 = \false,
        bool $php86 = \false
    ): self
    {
        if ($this->isWithPhpSetsUsed === \true) {
            throw new InvalidConfigurationException(sprintf('Method "%s()" can be called only once. It always includes all previous sets UP TO the defined version.%sThe best practise is to call it once with no argument. That way it will pick up PHP version from composer.json and your project will always stay up to date.', __METHOD__, \PHP_EOL));
        }
        $this->isWithPhpSetsUsed = \true;
        $pickedPhpVersions = array_keys(array_filter([PhpVersion::PHP_53 => $php53, PhpVersion::PHP_54 => $php54, PhpVersion::PHP_55 => $php55, PhpVersion::PHP_56 => $php56, PhpVersion::PHP_70 => $php70, PhpVersion::PHP_71 => $php71, PhpVersion::PHP_72 => $php72, PhpVersion::PHP_73 => $php73, PhpVersion::PHP_74 => $php74, PhpVersion::PHP_80 => $php80, PhpVersion::PHP_81 => $php81, PhpVersion::PHP_82 => $php82, PhpVersion::PHP_83 => $php83, PhpVersion::PHP_84 => $php84, PhpVersion::PHP_85 => $php85, PhpVersion::PHP_86 => $php86]));
        if ($pickedPhpVersions !== []) {
            Notifier::errorWithPhpSetsNotSuitableForPHP74AndLower();
        }
        if (count($pickedPhpVersions) > 1) {
            throw new InvalidConfigurationException(sprintf('Pick only one version target in "withPhpSets()". All rules up to this version will be used.%sTo use your composer.json PHP version, keep arguments empty.', \PHP_EOL));
        }
        // no version picked, resolve it from the project composer.json
        if ($pickedPhpVersions === []) {
            return $this->addPhpLevelSets(ComposerJsonPhpVersionResolver::resolveFromCwdOrFail());
        }
        // explicitly picked version is a ceiling, even for polyfilled rules
        $this->pickedPhpSetsVersion = $pickedPhpVersions[0];
        return $this->addPhpLevelSets($pickedPhpVersions[0]);
    }
    public function withPhp53Sets(): self

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Keep a single call, ideally ->withPhpSets() with no arguments so the version follows composer.json
  2. If two versions were attempted, decide the real target and delete the other call
  3. If a shared imported config calls withPhpSets(), remove the call from the main file (or vice versa)

Example fix

// before
return RectorConfig::configure()
    ->withPhpSets(php74: true)
    ->withPhpSets(php82: true);

// after
return RectorConfig::configure()
    ->withPhpSets();  // single call, version from composer.json
Defensive patterns

Strategy: validation

Validate before calling

// lint: withPhpSets must appear at most once (imports included)
$occurrences = substr_count(file_get_contents('rector.php'), 'withPhpSets(');
$imported = glob(__DIR__ . '/config/*.php') ?: []; // if you re-export sub-configs, count those too
foreach ($imported as $file) {
    $occurrences += substr_count((string) file_get_contents($file), 'withPhpSets(');
}
if ($occurrences > 1) {
    fwrite(STDERR, "withPhpSets() called {$occurrences} times; call it once\n");
}

Try / catch

catch \Rector\Exception\Configuration\InvalidConfigurationException naming withPhpSets; deduplicate to a single call (prefer zero-argument) and reload.

Prevention

When it happens

Trigger: Two ->withPhpSets(...) calls in one rector.php, e.g. ->withPhpSets(php82: true) earlier and ->withPhpSets(php74: true) added later, or an imported sub-config that also calls withPhpSets().

Common situations: Merging configs during refactors; importing a shared config file that itself calls withPhpSets() while the main rector.php also does.

Related errors


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