{"record":{"id":"1022c5510161e83f","repo":"rectorphp/rector","slug":"following-rules-are-registered-twice","errorCode":null,"errorMessage":"Following rules are registered twice: ","messagePattern":"Following rules are registered twice: ","errorType":"validation","errorClass":"ShouldNotHappenException","httpStatus":null,"severity":"error","filePath":"src/Validation/RectorConfigValidator.php","lineNumber":22,"sourceCode":"namespace Rector\\Validation;\n\nuse Rector\\Configuration\\Option;\nuse Rector\\Configuration\\Parameter\\SimpleParameterProvider;\nuse Rector\\Contract\\Rector\\RectorInterface;\nuse Rector\\Exception\\ShouldNotHappenException;\nuse Rector\\PostRector\\Contract\\Rector\\PostRectorInterface;\nfinal class RectorConfigValidator\n{\n    /**\n     * @param string[] $rectorClasses\n     */\n    public static function ensureNoDuplicatedClasses(array $rectorClasses): void\n    {\n        $duplicatedRectorClasses = self::resolveDuplicatedValues($rectorClasses);\n        if ($duplicatedRectorClasses === []) {\n            return;\n        }\n        throw new ShouldNotHappenException('Following rules are registered twice: ' . implode(', ', $duplicatedRectorClasses));\n    }\n    /**\n     * @param mixed[] $skip\n     */\n    public static function ensureRectorRulesExist(array $skip): void\n    {\n        $nonExistingRules = [];\n        $skippedRectorRules = [];\n        $skippedNonRectorClasses = [];\n        foreach ($skip as $key => $value) {\n            if (is_string($key) && self::isNonRectorClass($key)) {\n                $skippedNonRectorClasses[] = $key;\n                continue;\n            }\n            if (self::isRectorClassValue($key)) {\n                if (class_exists($key)) {\n                    $skippedRectorRules[] = $key;\n                } else {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/rectorphp/rector/blob/408fcb0ff1833e3d26cbc9b04c23a77565814a16/src/Validation/RectorConfigValidator.php#L4-L40","documentation":"Rector validates the rule list passed to RectorConfig::rules() (called from rector.php via $rectorConfig->rules([...]) or the fluent ->withRules([...]) builder) and throws a ShouldNotHappenException when the same rule class string appears more than once. Duplicates are always a config mistake: registering a rule twice does not make it run twice, it only hides that the config contains copy-paste or merge artifacts. The exception is thrown at config load time (src/Config/RectorConfig.php:254), before any file is analysed, so the whole run aborts immediately.","triggerScenarios":"Calling $rectorConfig->rules([...]) or ->withRules([...]) with the same class string listed twice in one array (e.g. CodeQualityRectorClass::class appearing at two positions); merging several rule arrays with array_merge/array concatenation where a rule was already present in both; copy-pasting a rule line when extending a shared base config; upgrading configs by hand where an imported set already includes a rule you also add explicitly in the same rules() call.","commonSituations":"Teams maintaining a large rector.php that grew over years and a rule got added twice by different PRs; configs assembled dynamically (e.g. rules from multiple packages merged in a loop); migrating from the oldLevel()/sets() config style where the same rule ends up registered via both the old and new entry; resolving git merge conflicts in rector.php by keeping both duplicated lines.","solutions":["Open rector.php and search the rules([...]/withRules([...]) array for the exact class name printed in the message; remove the duplicate line so the class appears exactly once.","If the rule list is assembled dynamically, de-duplicate before passing it: wrap the array with array_unique($rules) (or array_values(array_unique($rules))) at the call site.","If you merge rule arrays from multiple sources (e.g. $rulesA + $rulesB, array_merge), switch to union ($a += $b) or array_unique on the merged result so keys/classes cannot repeat.","Make sure a rule added explicitly is not also pulled in twice through a duplicated ->withRules()/->withPreparedSets() builder call in the same fluent chain."],"exampleFix":"// before\n$rectorConfig->rules([\n    \\Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyAssignmentRector::class,\n    \\Rector\\CodeQuality\\Rector\\If_\\CombinedIfRector::class,\n    \\Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyAssignmentRector::class, // duplicate\n]);\n\n// after\n$rectorConfig->rules([\n    \\Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyAssignmentRector::class,\n    \\Rector\\CodeQuality\\Rector\\If_\\CombinedIfRector::class,\n]);","handlingStrategy":"validation","validationCode":"// before calling $rectorConfig->rules() in rector.php\n$rules = [\n    RectorA::class,\n    RectorB::class,\n    RectorA::class, // would throw\n];\n$duplicates = array_keys(array_filter(array_count_values($rules), fn (int $count): bool => $count > 1));\nif ($duplicates !== []) {\n    throw new InvalidArgumentException('Duplicate rules, fix config first: ' . implode(', ', $duplicates));\n}\n$rectorConfig->rules($rules); // guaranteed duplicate-free","typeGuard":"/** @param list<class-string> $rules */\nfunction hasUniqueRules(array $rules): bool\n{\n    return count($rules) === count(array_unique($rules));\n}","tryCatchPattern":null,"preventionTips":["Build rule arrays with array_unique() (or array keys as class names) so duplicates collapse automatically.","In code review, treat any rector.php diff that only adds a rule as worth a duplicate scan: `php -r '$r = require 'rector.php';'` or a simple grep for the class name before adding it.","When merging config arrays from multiple files/packages, use union (+) keyed by class-string instead of array_merge with numeric keys."],"tags":["rector","config","duplicate-rules","should-not-happen-exception","php"],"backgroundTag":"duplicate-config-entries","analyzedSha":"408fcb0ff1833e3d26cbc9b04c23a77565814a16","analyzedAt":"2026-08-21T05:11:02.643Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}