From ba30f736374bfec94a229d7de19ef9d7ec21a4f2 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 15 Jun 2026 13:39:58 +0200 Subject: [PATCH] Ensure unique association aliases for self-referencing keys A non-foreign-key column matching the table name (such as a system_id column on a systems table) produced the same alias for both the generated belongsTo and hasMany association. The previous deduplication only recomputed the identical conventional name, so applying the second association threw "Association alias `Systems` is already set" and aborted baking entirely. Append a numeric suffix until the alias is unique, so bake generates valid, editable association code instead of failing. --- src/Command/ModelCommand.php | 11 +++++++- tests/TestCase/Command/ModelCommandTest.php | 29 +++++++++++++++++++++ tests/schema.php | 11 ++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Command/ModelCommand.php b/src/Command/ModelCommand.php index 2a18fd73..8ab9ee56 100644 --- a/src/Command/ModelCommand.php +++ b/src/Command/ModelCommand.php @@ -1587,7 +1587,16 @@ protected function ensureAliasUniqueness(array $associations): array foreach ($associationsPerType as $k => $association) { $alias = $association['alias']; if (in_array($alias, $existing, true)) { - $alias = $this->createAssociationAlias($association); + // Derive a unique alias by appending a numeric suffix. + // Self-referencing keys (e.g. `system_id` on `systems`) + // would otherwise yield the same colliding alias again. + $base = $this->createAssociationAlias($association); + $alias = $base; + $i = 1; + while (in_array($alias, $existing, true)) { + $i++; + $alias = $base . $i; + } } $existing[] = $alias; if (empty($association['className'])) { diff --git a/tests/TestCase/Command/ModelCommandTest.php b/tests/TestCase/Command/ModelCommandTest.php index 8870892b..9e48ce65 100644 --- a/tests/TestCase/Command/ModelCommandTest.php +++ b/tests/TestCase/Command/ModelCommandTest.php @@ -282,6 +282,35 @@ public function testApplyAssociationsConcreteClass(): void $this->assertEquals($original, $new); } + /** + * A non-foreign-key column matching the table name (e.g. `system_id` on + * `systems`) must not produce duplicate aliases that break baking. + * + * @return void + */ + public function testGetAssociationsEnsuresUniqueAliasForSelfReferencingKey(): void + { + $systems = $this->getTableLocator()->get('Systems'); + + $command = new ModelCommand(); + $command->connection = 'test'; + $args = new Arguments([], [], []); + $io = $this->createStub(ConsoleIo::class); + $result = $command->getAssociations($systems, $args, $io); + + $belongsToAliases = array_column($result['belongsTo'], 'alias'); + $hasManyAliases = array_column($result['hasMany'], 'alias'); + $allAliases = array_merge($belongsToAliases, $hasManyAliases); + + $this->assertContains('Systems', $belongsToAliases); + $this->assertContains('Systems2', $hasManyAliases); + $this->assertSame($allAliases, array_unique($allAliases), 'Association aliases must be unique.'); + + // Applying the deduplicated associations must not throw. + $command->applyAssociations($systems, $result); + $this->assertSame(['Systems', 'Systems2'], $systems->associations()->keys()); + } + /** * Test getAssociations * diff --git a/tests/schema.php b/tests/schema.php index 336c7d84..b05430e8 100644 --- a/tests/schema.php +++ b/tests/schema.php @@ -587,6 +587,17 @@ 'unique_self_referencing_parent' => ['type' => 'unique', 'columns' => ['parent_id']], ], ], + // "systems" has a "system_id" column that is not a real foreign key. + // It would otherwise produce duplicate "Systems" belongsTo/hasMany aliases. + [ + 'table' => 'systems', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'system_id' => ['type' => 'string', 'length' => 255, 'null' => false], + 'name' => ['type' => 'string', 'length' => 255, 'null' => true], + ], + 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], + ], // "news" is both singular and plural - tests variable collision fix [ 'table' => 'news',