rectorphp/rector · error · InvalidConfigurationException

Your config uses "withPhp*()" and "withPhpLevel()" methods a

Error message

Your config uses "withPhp*()" and "withPhpLevel()" methods at the same time.%sPick one of them to avoid rule conflicts.

What it means

RectorConfigBuilder applies your config lazily and cross-checks flags: if both withPhpLevel() and any withPhp*() sets method (withPhpSets/withPhpSets() variants) were called, it throws InvalidConfigurationException, because each independently builds the PHP upgrade rule list and combining them causes conflicting, duplicated rules. Pick exactly one strategy for PHP-version upgrades.

Source

Thrown at src/Configuration/RectorConfigBuilder.php:179

    {
        if ($this->setGroups !== [] || $this->setProviders !== []) {
            $setProviderCollector = new SetProviderCollector(array_map(\Closure::fromCallable([$rectorConfig, 'make']), \array_keys($this->setProviders)));
            $setManager = new SetManager($setProviderCollector, new InstalledPackageResolver(getcwd()));
            $this->groupLoadedSets = $setManager->matchBySetGroups($this->setGroups);
            SimpleParameterProvider::addParameter(\Rector\Configuration\Option::COMPOSER_BASED_SETS, $this->groupLoadedSets);
        }
        // not to miss it by accident
        if ($this->isWithPhpSetsUsed === \true) {
            $this->sets[] = SetList::PHP_POLYFILLS;
        }
        if ($this->pickedPhpSetsVersion !== null) {
            SimpleParameterProvider::setParameter(\Rector\Configuration\Option::POLYFILL_CEILING_PHP_VERSION, $this->pickedPhpSetsVersion);
        }
        // merge sets together
        $this->sets = array_merge($this->sets, $this->groupLoadedSets);
        $uniqueSets = array_unique($this->sets);
        if ($this->isWithPhpLevelUsed && $this->isWithPhpSetsUsed) {
            throw new InvalidConfigurationException(sprintf('Your config uses "withPhp*()" and "withPhpLevel()" methods at the same time.%sPick one of them to avoid rule conflicts.', \PHP_EOL));
        }
        foreach (self::LEVEL_METHOD_TO_SET as $levelMethod => [$setFilePath, $setTitle]) {
            if (!isset($this->usedLevelMethods[$levelMethod])) {
                continue;
            }
            if (!in_array($setFilePath, $uniqueSets, \true)) {
                continue;
            }
            throw new InvalidConfigurationException(sprintf('Your config already enables %s set.%sRemove "->%s()" as it only duplicates it, or remove %s set.', $setTitle, \PHP_EOL, $levelMethod, $setTitle));
        }
        if ($uniqueSets !== []) {
            $rectorConfig->sets($uniqueSets);
        }
        // log rules from sets and compare them with explicit rules
        $setRegisteredRectorClasses = $rectorConfig->getMainRectorClasses();
        SimpleParameterProvider::addParameter(\Rector\Configuration\Option::SET_REGISTERED_RULES, $setRegisteredRectorClasses);
        if ($this->paths !== []) {
            $rectorConfig->paths($this->paths);

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Keep ->withPhpSets() (no args picks the version from composer.json) and delete the withPhpLevel() line
  2. Or keep ->withPhpLevel(n) and remove every withPhp*() call
  3. Re-generate a baseline config: vendor/bin/rector init and merge your custom rules into it

Example fix

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

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

Strategy: validation

Validate before calling

// static guard in a config lint step: forbid mixing the two APIs
$config = file_get_contents('rector.php');
if (preg_match('/withPhpLevel\s*\(/', $config) && preg_match('/withPhpSets\s*\(/', $config)) {
    fwrite(STDERR, "rector.php mixes withPhpLevel() and withPhpSets(); pick one\n");
    exit(1);
}

Try / catch

catch \Rector\Exception\Configuration\InvalidConfigurationException in config-loading tests; assert your checked-in rector.php never triggers it.

Prevention

When it happens

Trigger: A rector.php that contains both ->withPhpLevel(82) and ->withPhpSets() (or withPhpSets(php82: true)), often left over after migrating from the old level API to the newer sets API.

Common situations: Merging an old config snippet (withPhpLevel) with a freshly generated one (withPhpSets) during upgrades; copy-pasting examples from different Rector eras into one rector.php.

Related errors


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