-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Summary
Comparator::detectRenamedIndexes() crashes with Warning: Undefined array key 0 at line 399 of src/Schema/Comparator.php when generating migrations via doctrine:migrations:diff or make:migration.
DBAL Version
- doctrine/dbal: 4.4.1 and 4.4.2
- doctrine/orm: 3.6.1
- doctrine/migrations: 3.7.0
- PHP: 8.4
- Database: PostgreSQL 16
How to reproduce
The issue occurs when the database has manually named indexes (e.g. idx_argus_failed_user) that Doctrine wants to rename to its auto-generated format (e.g. IDX_A1CF8725A76ED395). When one removed index is semantically equal to multiple added indexes, the bug triggers.
- Have an entity with a manually named index in the database
- Doctrine schema diff detects the index should be renamed to the auto-generated name
- Run
php bin/console make:migrationorphp bin/console doctrine:migrations:diff - Crash:
Warning: Undefined array key 0inComparator.phpline 399
Root Cause
In detectRenamedIndexes() (line 371-416), the method iterates over $candidatesByName and for each group with exactly one candidate, it accesses $removedIndexes[$removedIndexKey] and $addedIndexes[$addedIndexKey]. However, these arrays are modified by reference via unset() at line 409-412 during iteration.
When the same removed index (e.g. key 0) is semantically equal to two different added indexes (different names), it creates two separate entries in $candidatesByName, each with count === 1. The first iteration successfully processes the match and calls unset($removedIndexes[0]). The second iteration then tries to access $removedIndexes[0] which no longer exists, causing the crash.
Relevant code (src/Schema/Comparator.php:388-412):
foreach ($candidatesByName as $candidates) {
if (count($candidates) !== 1) {
continue;
}
[$removedIndexKey, $addedIndexKey] = $candidates[0];
// CRASH HERE: $removedIndexes[$removedIndexKey] may have been unset
// by a previous iteration of this loop
$removedIndex = $removedIndexes[$removedIndexKey];
$removedIndexName = strtolower($removedIndex->getName());
// ...
unset(
$addedIndexes[$addedIndexKey],
$removedIndexes[$removedIndexKey], // <-- This unset causes the crash in next iteration
);
}Suggested Fix
Add an isset() guard before accessing the arrays:
[$removedIndexKey, $addedIndexKey] = $candidates[0];
if (! isset($removedIndexes[$removedIndexKey]) || ! isset($addedIndexes[$addedIndexKey])) {
continue;
}
$removedIndex = $removedIndexes[$removedIndexKey];This is safe because if the key was already consumed by a previous rename, skipping it is the correct behavior — the index has already been handled.
Stack Trace
In Comparator.php line 399:
Warning: Undefined array key 0
Exception trace:
at vendor/doctrine/dbal/src/Schema/Comparator.php:399
Doctrine\DBAL\Schema\Comparator->detectRenamedIndexes()
at vendor/doctrine/dbal/src/Schema/Comparator.php:272
Doctrine\DBAL\Schema\Comparator->compareTables()
at vendor/doctrine/dbal/src/Schema/Comparator.php:62
Doctrine\DBAL\Schema\Comparator->compareSchemas()
at vendor/doctrine/migrations/src/Generator/DiffGenerator.php:96
Doctrine\Migrations\Generator\DiffGenerator->generate()