Skip to content

Bug: Undefined array key in Comparator::detectRenamedIndexes() when same removed index matches multiple added indexes #7323

@MakFly

Description

@MakFly

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.

  1. Have an entity with a manually named index in the database
  2. Doctrine schema diff detects the index should be renamed to the auto-generated name
  3. Run php bin/console make:migration or php bin/console doctrine:migrations:diff
  4. Crash: Warning: Undefined array key 0 in Comparator.php line 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()

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions