rectorphp/rector · error · InvalidConfigurationException
Pick only one PHP version target in "withDowngradeSets()"…
Error message
Pick only one PHP version target in "withDowngradeSets()". All rules down to this version will be used.
What it means
RectorConfigBuilder::withDowngradeSets() accepts nine boolean flags (php84 down to php71) and resolves exactly one target level via array_filter/array_keys. Downgrade rule sets are cumulative, so one target automatically includes every set above it; passing zero flags or more than one true flag is ambiguous and throws InvalidConfigurationException before any set is registered.
Solutions
- Enable only the LOWEST PHP version you must support, e.g. withDowngradeSets(php80: true); every rule down to that version is included automatically
- Remove the extra true flags one by one until exactly one remains
- If you genuinely need several independent levels, use withSets([...]) with explicit DowngradeLevelSetList constants instead
- Run vendor/bin/rector and confirm the InvalidConfigurationException no longer triggers before committing
Example fix
// before return $rectorConfigBuilder->withDowngradeSets(php82: true, php80: true); // after: one target; sets above PHP 8.0 are included automatically return $rectorConfigBuilder->withDowngradeSets(php80: true);
Defensive patterns
Strategy: validation
Validate before calling
// before building config: exactly one downgrade flag may be true
$flags = ['php84' => $php84, 'php83' => $php83, 'php82' => $php82, 'php81' => $php81,
'php80' => $php80, 'php74' => $php74, 'php73' => $php73, 'php72' => $php72, 'php71' => $php71];
if (count(array_filter($flags)) !== 1) {
throw new InvalidArgumentException('withDowngradeSets(): enable exactly one (the lowest) PHP target.');
}
$builder->withDowngradeSets(...array_filter($flags)); Try / catch
try {
$builder->withDowngradeSets(php80: true);
} catch (InvalidConfigurationException $e) {
// surface to the user editing rector.php with the offending flags
$logger->error($e->getMessage());
} Prevention
- Treat the lowest supported PHP version as the single source of truth in one env/parameter, then set only that flag
- Add a config smoke test that instantiates the builder and runs withDowngradeSets() with your production flags
- Remember downgrade sets are cumulative — enabling more than one target is always redundant, never additive
When it happens
Trigger: Calling withDowngradeSets() with no arguments, or with two or more flags set to true, e.g. withDowngradeSets(php82: true, php80: true). The count($pickedDowngradeSets) !== 1 check fires on both 0 and >= 2 selections.
Common situations: Migrating a config from withPhpSets() (where named arguments imply one version) to the downgrade API and enabling several levels 'to be safe'; or an editor/rector-upgrade tool commenting flags in and leaving none enabled.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Pick only one version target in "withPhpSets()". All rules…
- The "->withPhpSets()" method uses named arguments. Its…
- Your config uses "withPhp*()" and "withPhpLevel()" methods…
- Bootstrap file " " does not exist.
- Following rules are registered twice:
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/efc802aaeac89664.
Report an issue: GitHub.
Appendix: source
Thrown at src/Configuration/RectorConfigBuilder.php:773
$this->registerServices[] = new RegisteredService($className, $alias, $tag);
return $this;
}
/**
* DX helper
* @see https://getrector.com/documentation/creating-a-node-visitor
* @param class-string $decoratingNodeVisitorClass
*/
public function registerDecoratingNodeVisitor(string $decoratingNodeVisitorClass): self
{
Assert::isAOf($decoratingNodeVisitorClass, NodeVisitor::class);
$this->registerServices[] = new RegisteredService($decoratingNodeVisitorClass, null, DecoratingNodeVisitorInterface::class);
return $this;
}
public function withDowngradeSets(bool $php84 = \false, bool $php83 = \false, bool $php82 = \false, bool $php81 = \false, bool $php80 = \false, bool $php74 = \false, bool $php73 = \false, bool $php72 = \false, bool $php71 = \false): self
{
$pickedDowngradeSets = array_keys(array_filter([DowngradeLevelSetList::DOWN_TO_PHP_84 => $php84, DowngradeLevelSetList::DOWN_TO_PHP_83 => $php83, DowngradeLevelSetList::DOWN_TO_PHP_82 => $php82, DowngradeLevelSetList::DOWN_TO_PHP_81 => $php81, DowngradeLevelSetList::DOWN_TO_PHP_80 => $php80, DowngradeLevelSetList::DOWN_TO_PHP_74 => $php74, DowngradeLevelSetList::DOWN_TO_PHP_73 => $php73, DowngradeLevelSetList::DOWN_TO_PHP_72 => $php72, DowngradeLevelSetList::DOWN_TO_PHP_71 => $php71]));
if (count($pickedDowngradeSets) !== 1) {
throw new InvalidConfigurationException('Pick only one PHP version target in "withDowngradeSets()". All rules down to this version will be used.');
}
$this->sets[] = $pickedDowngradeSets[0];
return $this;
}
public function withRealPathReporting(bool $absolutePath = \true): self
{
$this->reportingRealPath = $absolutePath;
return $this;
}
/**
* Report skips configured via withSkip() that never matched anything during the run,
* so they can be safely removed.
*/
public function reportUnusedSkips(bool $report = \true): self
{
$this->reportUnusedSkips = $report;
return $this;
}View on GitHub (pinned to 408fcb0ff1)