rectorphp/rector · error · InvalidConfigurationException
Method " ()" can be called only once. It always includes…
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.
Solutions
- Keep a single call, ideally ->withPhpSets() with no arguments so the version follows composer.json
- If two versions were attempted, decide the real target and delete the other call
- 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
- Keep exactly one ->withPhpSets() call, ideally without arguments
- Shared/imported config files must not also call withPhpSets()
- During merges, grep for 'withPhpSets' to catch duplicated blocks
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
- Your config already enables
- Bootstrap file " " does not exist.
- Following rules are registered twice:
- Matching file paths by using glob-patterns is no longer…
- Pick only one PHP version target in "withDowngradeSets()"…
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/3211f8db6be41c1c.
Report an issue: GitHub.
Appendix: 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(): selfView on GitHub (pinned to 408fcb0ff1)