Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 099c5b4

Browse files
Crossjoincziegenberg
authored andcommitted
Fix for bug doctrine#8229 (id column from parent class renamed in child class)
This fixes problems with id columns defined in the parent class but renamed in the child class using an attribute override. Before this change always the child column name was used (which was not present in the parent class), now the correct column names are used for the parent table when creating inserts, joins and deletions for it.
1 parent 8230afc commit 099c5b4

6 files changed

Lines changed: 375 additions & 48 deletions

File tree

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use ReflectionClass;
3131
use ReflectionProperty;
3232
use RuntimeException;
33+
use function array_key_exists;
3334
use function explode;
3435

3536
/**
@@ -2262,6 +2263,12 @@ public function setAttributeOverride($fieldName, array $overrideMapping)
22622263
throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
22632264
}
22642265

2266+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
2267+
// The contained 'inherited' information was accidentally deleted by the unset() call below.
2268+
if (array_key_exists('inherited', $this->fieldMappings[$fieldName])) {
2269+
$overrideMapping['inherited'] = $this->fieldMappings[$fieldName]['inherited'];
2270+
}
2271+
22652272
unset($this->fieldMappings[$fieldName]);
22662273
unset($this->fieldNames[$mapping['columnName']]);
22672274
unset($this->columnNames[$mapping['fieldName']]);

lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use Doctrine\ORM\UnitOfWork;
3838
use Doctrine\ORM\Utility\IdentifierFlattener;
3939
use Doctrine\ORM\Utility\PersisterHelper;
40+
use function array_key_exists;
4041
use function array_map;
4142
use function array_merge;
4243
use function assert;
@@ -447,14 +448,26 @@ protected final function updateTable($entity, $quotedTableName, array $updateDat
447448
$types[] = $this->columnTypes[$columnName];
448449
}
449450

450-
$where = [];
451-
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
451+
$where = [];
452+
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
453+
$quotedClassTableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
452454

453455
foreach ($this->class->identifier as $idField) {
454456
if ( ! isset($this->class->associationMappings[$idField])) {
455-
$params[] = $identifier[$idField];
456-
$types[] = $this->class->fieldMappings[$idField]['type'];
457-
$where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform);
457+
$params[] = $identifier[$idField];
458+
$types[] = $this->class->fieldMappings[$idField]['type'];
459+
460+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
461+
// This method is called with the updated entity, but with different table names
462+
// (the entity table name or a table name of an inherited entity). In dependence
463+
// of the used table, the identifier name must be adjusted.
464+
$class = $this->class;
465+
if (isset($class->fieldMappings[$idField]['inherited']) && $quotedTableName !== $quotedClassTableName) {
466+
$className = $this->class->fieldMappings[$idField]['inherited'];
467+
$class = $this->em->getClassMetadata($className);
468+
}
469+
470+
$where[] = $this->quoteStrategy->getColumnName($idField, $class, $this->platform);
458471

459472
continue;
460473
}
@@ -632,7 +645,18 @@ protected function prepareUpdateData($entity)
632645
$newVal = $change[1];
633646

634647
if ( ! isset($this->class->associationMappings[$field])) {
635-
$fieldMapping = $this->class->fieldMappings[$field];
648+
$class = $this->class;
649+
650+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
651+
// Get the correct class metadata
652+
foreach ($class->parentClasses as $parentClassName) {
653+
$parentClass = $this->em->getClassMetadata($parentClassName);
654+
if (array_key_exists($field, $parentClass->fieldMappings)) {
655+
$class = $parentClass;
656+
}
657+
}
658+
659+
$fieldMapping = $class->fieldMappings[$field];
636660
$columnName = $fieldMapping['columnName'];
637661

638662
$this->columnTypes[$columnName] = $fieldMapping['type'];
@@ -1672,11 +1696,17 @@ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $c
16721696
private function getSelectConditionStatementColumnSQL($field, $assoc = null)
16731697
{
16741698
if (isset($this->class->fieldMappings[$field])) {
1675-
$className = (isset($this->class->fieldMappings[$field]['inherited']))
1676-
? $this->class->fieldMappings[$field]['inherited']
1677-
: $this->class->name;
1699+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
1700+
// Use the correct metadata and name for the id column
1701+
if (isset($this->class->fieldMappings[$field]['inherited'])) {
1702+
$className = $this->class->fieldMappings[$field]['inherited'];
1703+
$class = $this->em->getClassMetadata($className);
1704+
} else {
1705+
$className = $this->class->name;
1706+
$class = $this->class;
1707+
}
16781708

1679-
return [$this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform)];
1709+
return [$this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $class, $this->platform)];
16801710
}
16811711

16821712
if (isset($this->class->associationMappings[$field])) {

lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
use Doctrine\Common\Collections\Criteria;
2828
use Doctrine\ORM\Utility\PersisterHelper;
29+
use function array_combine;
30+
use function array_reverse;
2931

3032
/**
3133
* The joined subclass persister maps a single entity instance to several tables in the
@@ -271,35 +273,42 @@ public function update($entity)
271273
public function delete($entity)
272274
{
273275
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
274-
$id = array_combine($this->class->getIdentifierColumnNames(), $identifier);
275276

276277
$this->deleteJoinTableRecords($identifier);
277278

278-
// If the database platform supports FKs, just
279-
// delete the row from the root table. Cascades do the rest.
280-
if ($this->platform->supportsForeignKeyConstraints()) {
281-
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
282-
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
283-
$rootTypes = $this->getClassIdentifiersTypes($rootClass);
284-
285-
return (bool) $this->conn->delete($rootTable, $id, $rootTypes);
286-
}
287-
288-
// Delete from all tables individually, starting from this class' table up to the root table.
289-
$rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform);
290-
$rootTypes = $this->getClassIdentifiersTypes($this->class);
291-
292-
$affectedRows = $this->conn->delete($rootTable, $id, $rootTypes);
293-
294-
foreach ($this->class->parentClasses as $parentClass) {
279+
// Delete parent entries in reverse order (root first, until the direct parent of the current class).
280+
//
281+
// If foreign key are supported (and set as well as active), the first deletion will cascade and the
282+
// following queries won't delete anything, but it's better not to rely on a possible foreign key
283+
// functionality which cannot be checked here (foreign keys can be missing or disabled although
284+
// supported in general).
285+
//
286+
// On the other hand the supportsForeignKeyConstraints() method of the platform is also not reliable
287+
// when it returns false, because it only means that Doctrine\DBAL doesn't support foreign keys for
288+
// this platform, but they may be set in an existing database structure or added manually, so the
289+
// previous order of deletion (current class to root) would have failed - this could i.e. happen with
290+
// SQLite where foreign keys are theoretically possible but not supported by DBAL.
291+
$deleted = false;
292+
foreach (array_reverse($this->class->parentClasses) as $parentClass) {
295293
$parentMetadata = $this->em->getClassMetadata($parentClass);
296294
$parentTable = $this->quoteStrategy->getTableName($parentMetadata, $this->platform);
297295
$parentTypes = $this->getClassIdentifiersTypes($parentMetadata);
296+
$parentId = array_combine($parentMetadata->getIdentifierColumnNames(), $identifier);
298297

299-
$this->conn->delete($parentTable, $id, $parentTypes);
298+
$affectedRows = $this->conn->delete($parentTable, $parentId, $parentTypes);
299+
$deleted = $deleted ?: ($affectedRows > 0);
300300
}
301301

302-
return (bool) $affectedRows;
302+
// Now delete the entries from the current class (if not deleted automatically
303+
// because of a foreign key).
304+
$currentTable = $this->quoteStrategy->getTableName($this->class, $this->platform);
305+
$currentTypes = $this->getClassIdentifiersTypes($this->class);
306+
$currentId = array_combine($this->class->getIdentifierColumnNames(), $identifier);
307+
308+
$affectedRows = $this->conn->delete($currentTable, $currentId, $currentTypes);
309+
$deleted = $deleted ?: ($affectedRows > 0);
310+
311+
return $deleted;
303312
}
304313

305314
/**
@@ -412,8 +421,11 @@ protected function getLockTablesSql($lockMode)
412421
$parentClass = $this->em->getClassMetadata($parentClassName);
413422
$joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
414423

415-
foreach ($identifierColumns as $idColumn) {
416-
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
424+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
425+
// Use the correct name for the id column as named in the parent class.
426+
$parentIdentifierColumns = $parentClass->getIdentifierColumnNames();
427+
foreach ($identifierColumns as $index => $idColumn) {
428+
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $parentIdentifierColumns[$index];
417429
}
418430

419431
$joinSql .= implode(' AND ', $conditions);
@@ -589,9 +601,11 @@ private function getJoinSql($baseTableAlias)
589601
$tableAlias = $this->getSQLTableAlias($parentClassName);
590602
$joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
591603

592-
593-
foreach ($identifierColumn as $idColumn) {
594-
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
604+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
605+
// Use the correct name for the id column as named in the parent class.
606+
$parentIdentifierColumn = $parentClass->getIdentifierColumnNames();
607+
foreach ($identifierColumn as $index => $idColumn) {
608+
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $parentIdentifierColumn[$index];
595609
}
596610

597611
$joinSql .= implode(' AND ', $conditions);
@@ -604,8 +618,11 @@ private function getJoinSql($baseTableAlias)
604618
$tableAlias = $this->getSQLTableAlias($subClassName);
605619
$joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
606620

607-
foreach ($identifierColumn as $idColumn) {
608-
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
621+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
622+
// Use the correct name for the id column as named in the parent class.
623+
$subClassIdentifierColumn = $subClass->getIdentifierColumnNames();
624+
foreach ($identifierColumn as $index => $idColumn) {
625+
$conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $subClassIdentifierColumn[$index];
609626
}
610627

611628
$joinSql .= implode(' AND ', $conditions);

lib/Doctrine/ORM/Query/SqlWalker.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,12 @@ private function _generateClassTableInheritanceJoins($class, $dqlAlias)
366366

367367
$sqlParts = [];
368368

369-
foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
370-
$sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
369+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
370+
// Use the correct name for the id column as named in the parent class.
371+
$identifierColumn = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform);
372+
$parentIdentifierColumn = $this->quoteStrategy->getIdentifierColumnNames($parentClass, $this->platform);
373+
foreach ($identifierColumn as $index => $idColumn) {
374+
$sqlParts[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $parentIdentifierColumn[$index];
371375
}
372376

373377
// Add filters on the root class
@@ -661,11 +665,21 @@ public function walkPathExpression($pathExpr)
661665
$dqlAlias = $pathExpr->identificationVariable;
662666
$class = $this->queryComponents[$dqlAlias]['metadata'];
663667

668+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
669+
// Use the correct name for the id column as named in the inherited class.
670+
$mapping = $class->fieldMappings[$fieldName];
671+
if (isset($mapping['inherited'])) {
672+
$inheritedClass = $this->em->getClassMetadata($mapping['inherited']);
673+
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $inheritedClass, $this->platform);
674+
} else {
675+
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
676+
}
677+
664678
if ($this->useSqlTableAliases) {
665679
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
666680
}
667681

668-
$sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
682+
$sql .= $quotedColumnName;
669683
break;
670684

671685
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
@@ -1405,13 +1419,19 @@ public function walkSelectExpression($selectExpression)
14051419
continue;
14061420
}
14071421

1408-
$tableName = (isset($mapping['inherited']))
1409-
? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1410-
: $class->getTableName();
1422+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
1423+
// Use the correct name for the id column as named in the inherited class.
1424+
if (isset($mapping['inherited'])) {
1425+
$inheritedClass = $this->em->getClassMetadata($mapping['inherited']);
1426+
$tableName = $inheritedClass->getTableName();
1427+
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $inheritedClass, $this->platform);
1428+
} else {
1429+
$tableName = $class->getTableName();
1430+
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1431+
}
14111432

1412-
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1413-
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
1414-
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1433+
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1434+
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
14151435

14161436
$col = $sqlTableAlias . '.' . $quotedColumnName;
14171437

lib/Doctrine/ORM/Tools/SchemaTool.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Doctrine\ORM\ORMException;
3232
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
3333
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
34+
use function array_intersect_key;
3435

3536
/**
3637
* The SchemaTool is a tool to create/drop/update database schemas based on
@@ -248,14 +249,20 @@ function (ClassMetadata $class) use ($idMapping) : bool {
248249
}
249250

250251
if ( ! empty($inheritedKeyColumns)) {
252+
// Fix for bug GH-8229 (id column from parent class renamed in child class):
253+
// Use the correct name for the id columns as named in the parent class.
254+
$parentClass = $this->em->getClassMetadata($class->rootEntityName);
255+
$parentKeyColumnNames = $parentClass->getIdentifierColumnNames();
256+
$parentKeyColumns = array_intersect_key($parentKeyColumnNames, $inheritedKeyColumns);
257+
251258
// Add a FK constraint on the ID column
252259
$table->addForeignKeyConstraint(
253260
$this->quoteStrategy->getTableName(
254-
$this->em->getClassMetadata($class->rootEntityName),
261+
$parentClass,
255262
$this->platform
256263
),
257264
$inheritedKeyColumns,
258-
$inheritedKeyColumns,
265+
$parentKeyColumns,
259266
['onDelete' => 'CASCADE']
260267
);
261268
}

0 commit comments

Comments
 (0)