-
-
Save doctrinebot/b683ca5f10bf1aad232c to your computer and use it in GitHub Desktop.
Attachments to Doctrine Jira Issue DDC-117 - https://github.com/doctrine/doctrine2/issues/1772
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php | |
index f4bd3d6..e1981fe 100644 | |
--- a/lib/Doctrine/ORM/Id/AssignedGenerator.php | |
+++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php | |
@@ -49,7 +49,12 @@ class AssignedGenerator extends AbstractIdGenerator | |
foreach ($idFields as $idField) { | |
$value = $class->getReflectionProperty($idField)->getValue($entity); | |
if (isset($value)) { | |
- $identifier[$idField] = $value; | |
+ if (is_object($value)) { | |
+ // TODO: Single Id only, i enforce that. Compoite Key as Foreign Keys Primary Key part sounds ugly | |
+ $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); | |
+ } else { | |
+ $identifier[$idField] = $value; | |
+ } | |
} else { | |
throw ORMException::entityMissingAssignedId($entity); | |
} | |
@@ -58,7 +63,12 @@ class AssignedGenerator extends AbstractIdGenerator | |
$idField = $class->identifier[0]; | |
$value = $class->reflFields[$idField]->getValue($entity); | |
if (isset($value)) { | |
- $identifier[$idField] = $value; | |
+ if (is_object($value)) { | |
+ // TODO: Single Id only, i enforce that. Compoite Key as Foreign Keys Primary Key part sounds ugly | |
+ $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); | |
+ } else { | |
+ $identifier[$idField] = $value; | |
+ } | |
} else { | |
throw ORMException::entityMissingAssignedId($entity); | |
} | |
diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php | |
index 5ce4621..ad8c084 100644 | |
--- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php | |
+++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php | |
@@ -190,9 +190,11 @@ abstract class AbstractHydrator | |
continue; | |
} else { | |
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). | |
+ $fieldName = $this->_rsm->metaMappings[$key]; | |
$cache[$key]['isMetaColumn'] = true; | |
- $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; | |
+ $cache[$key]['fieldName'] = $fieldName; | |
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; | |
+ $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); | |
} | |
} | |
@@ -203,15 +205,15 @@ abstract class AbstractHydrator | |
$dqlAlias = $cache[$key]['dqlAlias']; | |
+ if ($cache[$key]['isIdentifier']) { | |
+ $id[$dqlAlias] .= '|' . $value; | |
+ } | |
+ | |
if (isset($cache[$key]['isMetaColumn'])) { | |
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; | |
continue; | |
} | |
- if ($cache[$key]['isIdentifier']) { | |
- $id[$dqlAlias] .= '|' . $value; | |
- } | |
- | |
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); | |
if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { | |
diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | |
index 4e2a075..fbf6ebd 100644 | |
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | |
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | |
@@ -700,6 +700,17 @@ class ClassMetadataInfo | |
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; | |
} | |
+ // Complete id mapping | |
+ if (isset($mapping['id']) && $mapping['id'] === true) { | |
+ if ( ! in_array($mapping['fieldName'], $this->identifier)) { | |
+ $this->identifier[] = $mapping['fieldName']; | |
+ } | |
+ // Check for composite key | |
+ if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { | |
+ $this->isIdentifierComposite = true; | |
+ } | |
+ } | |
+ | |
// Mandatory attributes for both sides | |
if ( ! isset($mapping['fieldName'])) { | |
throw MappingException::missingFieldName(); | |
@@ -979,7 +990,11 @@ class ClassMetadataInfo | |
if ($this->isIdentifierComposite) { | |
$columnNames = array(); | |
foreach ($this->identifier as $idField) { | |
- $columnNames[] = $this->fieldMappings[$idField]['columnName']; | |
+ if (isset($this->associationMappings[$idField])) { | |
+ $columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name']; | |
+ } else { | |
+ $columnNames[] = $this->fieldMappings[$idField]['columnName']; | |
+ } | |
} | |
return $columnNames; | |
} else { | |
@@ -1237,6 +1252,34 @@ class ClassMetadataInfo | |
} | |
/** | |
+ * Makes some automatic additions to the association mapping to make the life | |
+ * easier for the user, and store join columns in the metadata. | |
+ * | |
+ * @param array $mapping | |
+ * @todo Pass param by ref? | |
+ */ | |
+ private function _completeAssociationMapping(array $mapping) | |
+ { | |
+ $mapping['sourceEntity'] = $this->name; | |
+ if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false && strlen($this->namespace) > 0) { | |
+ $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; | |
+ } | |
+ | |
+ // Complete id mapping | |
+ if (isset($mapping['id']) && $mapping['id'] === true) { | |
+ if ( ! in_array($mapping['fieldName'], $this->identifier)) { | |
+ $this->identifier[] = $mapping['fieldName']; | |
+ } | |
+ // Check for composite key | |
+ if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { | |
+ $this->isIdentifierComposite = true; | |
+ } | |
+ } | |
+ | |
+ return $mapping; | |
+ } | |
+ | |
+ /** | |
* Adds a mapped field to the class. | |
* | |
* @param array $mapping The field mapping. | |
@@ -1572,4 +1615,4 @@ class ClassMetadataInfo | |
{ | |
$this->versionField = $versionField; | |
} | |
-} | |
\ No newline at end of file | |
+} | |
diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | |
index f6faeb8..0f3db55 100644 | |
--- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | |
+++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | |
@@ -290,6 +290,10 @@ class AnnotationDriver implements Driver | |
throw MappingException::tableIdGeneratorNotImplemented($className); | |
} | |
} else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) { | |
+ if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { | |
+ $mapping['id'] = true; | |
+ } | |
+ | |
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity; | |
$mapping['joinColumns'] = $joinColumns; | |
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy; | |
@@ -311,6 +315,10 @@ class AnnotationDriver implements Driver | |
$metadata->mapOneToMany($mapping); | |
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) { | |
+ if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { | |
+ $mapping['id'] = true; | |
+ } | |
+ | |
$mapping['joinColumns'] = $joinColumns; | |
$mapping['cascade'] = $manyToOneAnnot->cascade; | |
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy; | |
diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | |
index 93f6efa..6ca95c0 100644 | |
--- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | |
+++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | |
@@ -303,9 +303,16 @@ class BasicEntityPersister | |
$where = array(); | |
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); | |
foreach ($this->_class->identifier as $idField) { | |
- $where[] = $this->_class->getQuotedColumnName($idField, $this->_platform); | |
- $params[] = $id[$idField]; | |
- $types[] = $this->_class->fieldMappings[$idField]['type']; | |
+ if (isset($this->_class->associationMappings[$idField])) { | |
+ $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); | |
+ $where[] = $this->_class->associationMappings[$idField]['joinColumns'][0]['name']; | |
+ $params[] = $id[$idField]; | |
+ $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; | |
+ } else { | |
+ $where[] = $this->_class->getQuotedColumnName($idField, $this->_platform); | |
+ $params[] = $id[$idField]; | |
+ $types[] = $this->_class->fieldMappings[$idField]['type']; | |
+ } | |
} | |
if ($versioned) { | |
@@ -1120,6 +1127,12 @@ class BasicEntityPersister | |
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; | |
} | |
$conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); | |
+ } else if (isset($this->_class->associationMappings[$field])) { | |
+ // TODO: Inherited? | |
+ // TODO: Composite Keys as Foreign Key PK? That would be super ugly! And should probably be disallowed ;) | |
+ $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; | |
+ | |
+ $conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name']; | |
} else if ($assoc !== null) { | |
if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) { | |
$owningAssoc = $assoc['isOwningSide'] ? $assoc : $this->_em->getClassMetadata($assoc['targetEntity']) | |
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php | |
index e43965e..77fe7bc 100644 | |
--- a/lib/Doctrine/ORM/Query/SqlWalker.php | |
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php | |
@@ -970,6 +970,27 @@ class SqlWalker implements TreeWalker | |
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); | |
} | |
+ // Add double entry for association identifier columns to simplify hydrator code | |
+ foreach ($class->identifier AS $idField) { | |
+ if (isset($class->associationMappings[$idField])) { | |
+ if (isset($mapping['inherited'])) { | |
+ $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name']; | |
+ } else { | |
+ $tableName = $class->table['name']; | |
+ } | |
+ | |
+ if ($beginning) $beginning = false; else $sql .= ', '; | |
+ | |
+ $joinColumnName = $class->associationMappings[$idField]['joinColumns'][0]['name']; | |
+ $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias); | |
+ $columnAlias = $this->getSqlColumnAlias($joinColumnName); | |
+ $sql .= $sqlTableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; | |
+ | |
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); | |
+ $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $idField); | |
+ } | |
+ } | |
+ | |
// Add any additional fields of subclasses (excluding inherited fields) | |
// 1) on Single Table Inheritance: always, since its marginal overhead | |
// 2) on Class Table Inheritance only if partial objects are disallowed, | |
diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php | |
index fba93cd..f7851ee 100644 | |
--- a/lib/Doctrine/ORM/Tools/SchemaTool.php | |
+++ b/lib/Doctrine/ORM/Tools/SchemaTool.php | |
@@ -194,6 +194,22 @@ class SchemaTool | |
$this->_gatherRelationsSql($class, $table, $schema); | |
} | |
+ $pkColumns = array(); | |
+ foreach ($class->identifier AS $identifierField) { | |
+ if (isset($class->fieldMappings[$identifierField])) { | |
+ $pkColumns[] = $class->getQuotedColumnName($identifierField, $this->_platform); | |
+ } else if (isset($class->associationMappings[$identifierField])) { | |
+ /* @var $assoc \Doctrine\ORM\Mapping\OneToOne */ | |
+ $assoc = $class->associationMappings[$identifierField]; | |
+ foreach ($assoc['joinColumns'] AS $joinColumn) { | |
+ $pkColumns[] = $joinColumn['name']; | |
+ } | |
+ } | |
+ } | |
+ if (!$table->hasIndex('primary')) { | |
+ $table->setPrimaryKey($pkColumns); | |
+ } | |
+ | |
if (isset($class->table['indexes'])) { | |
foreach ($class->table['indexes'] AS $indexName => $indexData) { | |
$table->addIndex($indexData['columns'], $indexName); | |
@@ -276,10 +292,11 @@ class SchemaTool | |
$pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform); | |
} | |
} | |
+ | |
// For now, this is a hack required for single table inheritence, since this method is called | |
// twice by single table inheritence relations | |
if(!$table->hasIndex('primary')) { | |
- $table->setPrimaryKey($pkColumns); | |
+ //$table->setPrimaryKey($pkColumns); | |
} | |
return $columns; | |
@@ -605,7 +622,7 @@ class SchemaTool | |
$calc->addClass($class); | |
foreach ($class->associationMappings as $assoc) { | |
- if ($assoc->isOwningSide) { | |
+ if ($assoc['isOwningSide']) { | |
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); | |
if ( ! $calc->hasClass($targetClass->name)) { | |
@@ -627,7 +644,7 @@ class SchemaTool | |
foreach ($classes as $class) { | |
foreach ($class->associationMappings as $assoc) { | |
- if ($assoc->isOwningSide && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { | |
+ if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { | |
$associationTables[] = $assoc->joinTable['name']; | |
} | |
} | |
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php | |
index 242d84b..2ecc7f1 100644 | |
--- a/lib/Doctrine/ORM/UnitOfWork.php | |
+++ b/lib/Doctrine/ORM/UnitOfWork.php | |
@@ -1826,11 +1826,19 @@ class UnitOfWork implements PropertyChangedListener | |
if ($class->isIdentifierComposite) { | |
$id = array(); | |
foreach ($class->identifier as $fieldName) { | |
- $id[$fieldName] = $data[$fieldName]; | |
+ if (isset($class->associationMappings[$fieldName])) { | |
+ $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; | |
+ } else { | |
+ $id[$fieldName] = $data[$fieldName]; | |
+ } | |
} | |
$idHash = implode(' ', $id); | |
} else { | |
- $idHash = $data[$class->identifier[0]]; | |
+ if (isset($class->associationMappings[$class->identifier[0]])) { | |
+ $idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]; | |
+ } else { | |
+ $idHash = $data[$class->identifier[0]]; | |
+ } | |
$id = array($class->identifier[0] => $idHash); | |
} | |
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
new file mode 100644 | |
index 0000000..e7e9947 | |
--- /dev/null | |
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
@@ -0,0 +1,427 @@ | |
+<?php | |
+ | |
+namespace Doctrine\Tests\ORM\Functional\Ticket; | |
+ | |
+require_once __DIR__ . '/../../../TestInit.php'; | |
+ | |
+class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase | |
+{ | |
+ private $article1; | |
+ private $article2; | |
+ private $reference; | |
+ private $translation; | |
+ private $articleDetails; | |
+ | |
+ protected function setUp() { | |
+ parent::setUp(); | |
+ | |
+ try { | |
+ $this->_schemaTool->createSchema(array( | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117Article'), | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117Reference'), | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117Translation'), | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117ArticleDetails'), | |
+ )); | |
+ } catch(\Exception $e) { | |
+ | |
+ } | |
+ | |
+ $this->article1 = new DDC117Article("Foo"); | |
+ $this->article2 = new DDC117Article("Bar"); | |
+ | |
+ $this->_em->persist($this->article1); | |
+ $this->_em->persist($this->article2); | |
+ $this->_em->flush(); | |
+ | |
+ $this->reference = new DDC117Reference($this->article1, $this->article2, "Test-Description"); | |
+ $this->_em->persist($this->reference); | |
+ | |
+ $this->translation = new DDC117Translation($this->article1, "en", "Bar"); | |
+ $this->_em->persist($this->translation); | |
+ | |
+ $this->articleDetails = new DDC117ArticleDetails($this->article1, "Very long text"); | |
+ $this->_em->persist($this->articleDetails); | |
+ $this->_em->flush(); | |
+ | |
+ $this->_em->clear(); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testAssociationOnlyCompositeKey() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $mapRef); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->target()); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->source()); | |
+ $this->assertSame($mapRef, $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE r.source = ?1"; | |
+ $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 1)->getSingleResult(); | |
+ | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $mapRef); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->target()); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->source()); | |
+ $this->assertSame($dqlRef, $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; | |
+ $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getSingleResult(); | |
+ | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $dqlRef); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $dqlRef->target()); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $dqlRef->source()); | |
+ $this->assertSame($dqlRef, $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; | |
+ $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getSingleResult(); | |
+ | |
+ $this->_em->contains($dqlRef); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testUpdateAssocationEntity() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ $this->assertNotNull($mapRef); | |
+ $mapRef->setDescription("New Description!!"); | |
+ $this->_em->flush(); | |
+ $this->_em->clear(); | |
+ | |
+ $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ | |
+ $this->assertEquals('New Description!!', $mapRef->getDescription()); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testFetchDql() | |
+ { | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; | |
+ $refs = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getResult(); | |
+ | |
+ $this->assertTrue(count($refs) > 0, "Has to contain at least one Reference."); | |
+ foreach ($refs AS $ref) { | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $ref, "Contains only Reference instances."); | |
+ $this->assertTrue($this->_em->contains($ref), "Contains Reference in the IdentityMap."); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testRemoveCompositeElement() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $refRep = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ | |
+ $this->_em->remove($refRep); | |
+ $this->_em->flush(); | |
+ $this->_em->clear(); | |
+ | |
+ $this->assertNull($this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testDqlRemoveCompositeElement() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $dql = "DELETE ".__NAMESPACE__."\DDC117Reference r WHERE r.source = ?1 AND r.target = ?2"; | |
+ $this->_em->createQuery($dql) | |
+ ->setParameter(1, $this->article1->id()) | |
+ ->setParameter(2, $this->article2->id()) | |
+ ->execute(); | |
+ | |
+ $this->assertNull($this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testInverseSideAccess() | |
+ { | |
+ $this->article1 = $this->_em->find(__NAMESPACE__."\DDC117Article", $this->article1->id()); | |
+ | |
+ $this->assertEquals(1, count($this->article1->references())); | |
+ foreach ($this->article1->references() AS $this->reference) { | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $this->reference); | |
+ $this->assertSame($this->article1, $this->reference->source()); | |
+ } | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = 'SELECT a, r FROM '. __NAMESPACE__ . '\DDC117Article a INNER JOIN a.references r WHERE a.id = ?1'; | |
+ $articleDql = $this->_em->createQuery($dql) | |
+ ->setParameter(1, $this->article1->id()) | |
+ ->getSingleResult(); | |
+ | |
+ $this->assertEquals(1, count($this->article1->references())); | |
+ foreach ($this->article1->references() AS $this->reference) { | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $this->reference); | |
+ $this->assertSame($this->article1, $this->reference->source()); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testMixedCompositeKey() | |
+ { | |
+ $idCriteria = array('article' => $this->article1->id(), 'language' => 'en'); | |
+ | |
+ $this->translation = $this->_em->find(__NAMESPACE__ . '\DDC117Translation', $idCriteria); | |
+ $this->assertType(__NAMESPACE__ . '\DDC117Translation', $this->translation); | |
+ | |
+ $this->assertSame($this->translation, $this->_em->find(__NAMESPACE__ . '\DDC117Translation', $idCriteria)); | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = 'SELECT t, a FROM ' . __NAMESPACE__ . '\DDC117Translation t JOIN t.article a WHERE t.article = ?1 AND t.language = ?2'; | |
+ $dqlTrans = $this->_em->createQuery($dql) | |
+ ->setParameter(1, $this->article1->id()) | |
+ ->setParameter(2, 'en') | |
+ ->getSingleResult(); | |
+ | |
+ $this->assertType(__NAMESPACE__ . '\DDC117Translation', $this->translation); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testMixedCompositeKeyViolateUniqueness() | |
+ { | |
+ $this->article1 = $this->_em->find(__NAMESPACE__ . '\DDC117Article', $this->article1->id()); | |
+ $this->article1->addTranslation('en', 'Bar'); | |
+ $this->article1->addTranslation('en', 'Baz'); | |
+ | |
+ $this->setExpectedException('Exception'); | |
+ $this->_em->flush(); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testOneToOneForeignObjectId() | |
+ { | |
+ $this->article1 = new DDC117Article("Foo"); | |
+ $this->_em->persist($this->article1); | |
+ $this->_em->flush(); | |
+ | |
+ $this->articleDetails = new DDC117ArticleDetails($this->article1, "Very long text"); | |
+ $this->_em->persist($this->articleDetails); | |
+ $this->_em->flush(); | |
+ | |
+ $this->articleDetails->update("not so very long text!"); | |
+ $this->_em->flush(); | |
+ $this->_em->clear(); | |
+ | |
+ /* @var $article DDC117Article */ | |
+ $article = $this->_em->find(get_class($this->article1), $this->article1->id()); | |
+ $this->assertEquals('not so very long text!', $article->getText()); | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117Article | |
+{ | |
+ /** @Id @Column(type="integer", name="article_id") @GeneratedValue */ | |
+ private $id; | |
+ /** @Column */ | |
+ private $title; | |
+ | |
+ /** | |
+ * @OneToMany(targetEntity="DDC117Reference", mappedBy="source") | |
+ */ | |
+ private $references; | |
+ | |
+ /** | |
+ * @OneToOne(targetEntity="DDC117ArticleDetails", mappedBy="article") | |
+ */ | |
+ private $details; | |
+ | |
+ /** | |
+ * @OneToMany(targetEntity="DDC117Translation", mappedBy="article", cascade={"persist"}) | |
+ */ | |
+ private $translations; | |
+ | |
+ public function __construct($title) | |
+ { | |
+ $this->title = $title; | |
+ $this->references = new \Doctrine\Common\Collections\ArrayCollection(); | |
+ $this->translations = new \Doctrine\Common\Collections\ArrayCollection(); | |
+ } | |
+ | |
+ public function setDetails($details) | |
+ { | |
+ $this->details = $details; | |
+ } | |
+ | |
+ public function id() | |
+ { | |
+ return $this->id; | |
+ } | |
+ | |
+ public function addReference($reference) | |
+ { | |
+ $this->references[] = $reference; | |
+ } | |
+ | |
+ public function references() | |
+ { | |
+ return $this->references; | |
+ } | |
+ | |
+ public function addTranslation($language, $title) | |
+ { | |
+ $this->translations[] = new DDC117Translation($this, $language, $title); | |
+ } | |
+ | |
+ public function getText() | |
+ { | |
+ return $this->details->getText(); | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117ArticleDetails | |
+{ | |
+ /** | |
+ * @Id | |
+ * @OneToOne(targetEntity="DDC117Article", inversedBy="details") | |
+ * @JoinColumn(name="article_id", referencedColumnName="article_id") | |
+ */ | |
+ private $article; | |
+ | |
+ /** | |
+ * @Column(type="text") | |
+ */ | |
+ private $text; | |
+ | |
+ public function __construct($article, $text) | |
+ { | |
+ $this->article = $article; | |
+ $article->setDetails($this); | |
+ | |
+ $this->update($text); | |
+ } | |
+ | |
+ public function update($text) | |
+ { | |
+ $this->text = $text; | |
+ } | |
+ | |
+ public function getText() | |
+ { | |
+ return $this->text; | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117Reference | |
+{ | |
+ /** | |
+ * @Id | |
+ * @ManyToOne(targetEntity="DDC117Article", inversedBy="references") | |
+ * @JoinColumn(name="source_id", referencedColumnName="article_id") | |
+ */ | |
+ private $source; | |
+ | |
+ /** | |
+ * @Id | |
+ * @ManyToOne(targetEntity="DDC117Article", inversedBy="references") | |
+ * @JoinColumn(name="target_id", referencedColumnName="article_id") | |
+ */ | |
+ private $target; | |
+ | |
+ /** | |
+ * @column(type="string") | |
+ */ | |
+ private $description; | |
+ | |
+ /** | |
+ * @column(type="datetime") | |
+ */ | |
+ private $created; | |
+ | |
+ public function __construct($source, $target, $description) | |
+ { | |
+ $source->addReference($this); | |
+ $target->addReference($this); | |
+ | |
+ $this->source = $source; | |
+ $this->target = $target; | |
+ $this->description = $description; | |
+ $this->created = new \DateTime("now"); | |
+ } | |
+ | |
+ public function source() | |
+ { | |
+ return $this->source; | |
+ } | |
+ | |
+ public function target() | |
+ { | |
+ return $this->target; | |
+ } | |
+ | |
+ public function setDescription($desc) | |
+ { | |
+ $this->description = $desc; | |
+ } | |
+ | |
+ public function getDescription() | |
+ { | |
+ return $this->description; | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117Translation | |
+{ | |
+ /** | |
+ * @Id | |
+ * @ManyToOne(targetEntity="DDC117Article") | |
+ * @JoinColumn(name="article_id", referencedColumnName="article_id") | |
+ */ | |
+ private $article; | |
+ | |
+ /** | |
+ * @Id @column(type="string") | |
+ */ | |
+ private $language; | |
+ | |
+ /** | |
+ * @column(type="string") | |
+ */ | |
+ private $title; | |
+ | |
+ public function __construct($article, $language, $title) | |
+ { | |
+ $this->article = $article; | |
+ $this->language = $language; | |
+ $this->title = $title; | |
+ } | |
+} | |
\ No newline at end of file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
index e7e9947..0cad800 100644 | |
--- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
@@ -234,6 +234,19 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase | |
$article = $this->_em->find(get_class($this->article1), $this->article1->id()); | |
$this->assertEquals('not so very long text!', $article->getText()); | |
} | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testOneToOneCascadePersist() | |
+ { | |
+ $this->article1 = new DDC117Article("Foo"); | |
+ | |
+ $this->articleDetails = new DDC117ArticleDetails($this->article1, "Very long text"); | |
+ | |
+ $this->_em->persist($this->article1); | |
+ $this->_em->flush(); | |
+ } | |
} | |
/** | |
@@ -252,7 +265,7 @@ class DDC117Article | |
private $references; | |
/** | |
- * @OneToOne(targetEntity="DDC117ArticleDetails", mappedBy="article") | |
+ * @OneToOne(targetEntity="DDC117ArticleDetails", mappedBy="article", cascade={"persist"}) | |
*/ | |
private $details; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php | |
index f4bd3d6..e1981fe 100644 | |
--- a/lib/Doctrine/ORM/Id/AssignedGenerator.php | |
+++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php | |
@@ -49,7 +49,12 @@ class AssignedGenerator extends AbstractIdGenerator | |
foreach ($idFields as $idField) { | |
$value = $class->getReflectionProperty($idField)->getValue($entity); | |
if (isset($value)) { | |
- $identifier[$idField] = $value; | |
+ if (is_object($value)) { | |
+ // TODO: Single Id only, i enforce that. Compoite Key as Foreign Keys Primary Key part sounds ugly | |
+ $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); | |
+ } else { | |
+ $identifier[$idField] = $value; | |
+ } | |
} else { | |
throw ORMException::entityMissingAssignedId($entity); | |
} | |
@@ -58,7 +63,12 @@ class AssignedGenerator extends AbstractIdGenerator | |
$idField = $class->identifier[0]; | |
$value = $class->reflFields[$idField]->getValue($entity); | |
if (isset($value)) { | |
- $identifier[$idField] = $value; | |
+ if (is_object($value)) { | |
+ // TODO: Single Id only, i enforce that. Compoite Key as Foreign Keys Primary Key part sounds ugly | |
+ $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); | |
+ } else { | |
+ $identifier[$idField] = $value; | |
+ } | |
} else { | |
throw ORMException::entityMissingAssignedId($entity); | |
} | |
diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php | |
index 5ce4621..ad8c084 100644 | |
--- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php | |
+++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php | |
@@ -190,9 +190,11 @@ abstract class AbstractHydrator | |
continue; | |
} else { | |
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). | |
+ $fieldName = $this->_rsm->metaMappings[$key]; | |
$cache[$key]['isMetaColumn'] = true; | |
- $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; | |
+ $cache[$key]['fieldName'] = $fieldName; | |
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; | |
+ $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); | |
} | |
} | |
@@ -203,15 +205,15 @@ abstract class AbstractHydrator | |
$dqlAlias = $cache[$key]['dqlAlias']; | |
+ if ($cache[$key]['isIdentifier']) { | |
+ $id[$dqlAlias] .= '|' . $value; | |
+ } | |
+ | |
if (isset($cache[$key]['isMetaColumn'])) { | |
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; | |
continue; | |
} | |
- if ($cache[$key]['isIdentifier']) { | |
- $id[$dqlAlias] .= '|' . $value; | |
- } | |
- | |
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); | |
if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { | |
diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | |
index 2d6ec7c..53042d0 100644 | |
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | |
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | |
@@ -710,6 +710,18 @@ class ClassMetadataInfo | |
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; | |
} | |
+ // Complete id mapping | |
+ if (isset($mapping['id']) && $mapping['id'] === true) { | |
+ if ( ! in_array($mapping['fieldName'], $this->identifier)) { | |
+ $this->identifier[] = $mapping['fieldName']; | |
+ } | |
+ // Check for composite key | |
+ if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { | |
+ $this->isIdentifierComposite = true; | |
+ } | |
+ } | |
+ | |
+ // Mandatory attributes for both sides | |
// Mandatory: fieldName, targetEntity | |
if ( ! isset($mapping['fieldName'])) { | |
throw MappingException::missingFieldName(); | |
@@ -981,7 +993,11 @@ class ClassMetadataInfo | |
if ($this->isIdentifierComposite) { | |
$columnNames = array(); | |
foreach ($this->identifier as $idField) { | |
- $columnNames[] = $this->fieldMappings[$idField]['columnName']; | |
+ if (isset($this->associationMappings[$idField])) { | |
+ $columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name']; | |
+ } else { | |
+ $columnNames[] = $this->fieldMappings[$idField]['columnName']; | |
+ } | |
} | |
return $columnNames; | |
} else { | |
@@ -1250,6 +1266,34 @@ class ClassMetadataInfo | |
} | |
/** | |
+ * Makes some automatic additions to the association mapping to make the life | |
+ * easier for the user, and store join columns in the metadata. | |
+ * | |
+ * @param array $mapping | |
+ * @todo Pass param by ref? | |
+ */ | |
+ private function _completeAssociationMapping(array $mapping) | |
+ { | |
+ $mapping['sourceEntity'] = $this->name; | |
+ if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false && strlen($this->namespace) > 0) { | |
+ $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; | |
+ } | |
+ | |
+ // Complete id mapping | |
+ if (isset($mapping['id']) && $mapping['id'] === true) { | |
+ if ( ! in_array($mapping['fieldName'], $this->identifier)) { | |
+ $this->identifier[] = $mapping['fieldName']; | |
+ } | |
+ // Check for composite key | |
+ if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { | |
+ $this->isIdentifierComposite = true; | |
+ } | |
+ } | |
+ | |
+ return $mapping; | |
+ } | |
+ | |
+ /** | |
* Adds a mapped field to the class. | |
* | |
* @param array $mapping The field mapping. | |
@@ -1592,4 +1636,4 @@ class ClassMetadataInfo | |
{ | |
$this->versionField = $versionField; | |
} | |
-} | |
\ No newline at end of file | |
+} | |
diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | |
index 115d6db..81dcc90 100644 | |
--- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | |
+++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | |
@@ -288,6 +288,10 @@ class AnnotationDriver implements Driver | |
throw MappingException::tableIdGeneratorNotImplemented($className); | |
} | |
} else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) { | |
+ if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { | |
+ $mapping['id'] = true; | |
+ } | |
+ | |
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity; | |
$mapping['joinColumns'] = $joinColumns; | |
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy; | |
@@ -309,6 +313,10 @@ class AnnotationDriver implements Driver | |
$metadata->mapOneToMany($mapping); | |
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) { | |
+ if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { | |
+ $mapping['id'] = true; | |
+ } | |
+ | |
$mapping['joinColumns'] = $joinColumns; | |
$mapping['cascade'] = $manyToOneAnnot->cascade; | |
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy; | |
diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | |
index 2903ac9..8e98191 100644 | |
--- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | |
+++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | |
@@ -313,9 +313,16 @@ class BasicEntityPersister | |
$where = array(); | |
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); | |
foreach ($this->_class->identifier as $idField) { | |
- $where[] = $this->_class->getQuotedColumnName($idField, $this->_platform); | |
- $params[] = $id[$idField]; | |
- $types[] = $this->_class->fieldMappings[$idField]['type']; | |
+ if (isset($this->_class->associationMappings[$idField])) { | |
+ $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); | |
+ $where[] = $this->_class->associationMappings[$idField]['joinColumns'][0]['name']; | |
+ $params[] = $id[$idField]; | |
+ $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; | |
+ } else { | |
+ $where[] = $this->_class->getQuotedColumnName($idField, $this->_platform); | |
+ $params[] = $id[$idField]; | |
+ $types[] = $this->_class->fieldMappings[$idField]['type']; | |
+ } | |
} | |
if ($versioned) { | |
@@ -1124,6 +1131,7 @@ class BasicEntityPersister | |
} | |
$conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); | |
} else if (isset($this->_class->associationMappings[$field])) { | |
+ // TODO: Composite Keys as Foreign Key PK? That would be super ugly! And should probably be disallowed ;) | |
if (!$this->_class->associationMappings[$field]['isOwningSide']) { | |
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); | |
} | |
@@ -1133,7 +1141,6 @@ class BasicEntityPersister | |
} else { | |
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; | |
} | |
- | |
$conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name']; | |
} else if ($assoc !== null) { | |
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php | |
index 31f75f0..1512da2 100644 | |
--- a/lib/Doctrine/ORM/Query/SqlWalker.php | |
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php | |
@@ -970,6 +970,27 @@ class SqlWalker implements TreeWalker | |
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); | |
} | |
+ // Add double entry for association identifier columns to simplify hydrator code | |
+ foreach ($class->identifier AS $idField) { | |
+ if (isset($class->associationMappings[$idField])) { | |
+ if (isset($mapping['inherited'])) { | |
+ $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name']; | |
+ } else { | |
+ $tableName = $class->table['name']; | |
+ } | |
+ | |
+ if ($beginning) $beginning = false; else $sql .= ', '; | |
+ | |
+ $joinColumnName = $class->associationMappings[$idField]['joinColumns'][0]['name']; | |
+ $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias); | |
+ $columnAlias = $this->getSqlColumnAlias($joinColumnName); | |
+ $sql .= $sqlTableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; | |
+ | |
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); | |
+ $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $idField); | |
+ } | |
+ } | |
+ | |
// Add any additional fields of subclasses (excluding inherited fields) | |
// 1) on Single Table Inheritance: always, since its marginal overhead | |
// 2) on Class Table Inheritance only if partial objects are disallowed, | |
diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php | |
index e87a99a..30d6db2 100644 | |
--- a/lib/Doctrine/ORM/Tools/SchemaTool.php | |
+++ b/lib/Doctrine/ORM/Tools/SchemaTool.php | |
@@ -199,6 +199,22 @@ class SchemaTool | |
$this->_gatherRelationsSql($class, $table, $schema); | |
} | |
+ $pkColumns = array(); | |
+ foreach ($class->identifier AS $identifierField) { | |
+ if (isset($class->fieldMappings[$identifierField])) { | |
+ $pkColumns[] = $class->getQuotedColumnName($identifierField, $this->_platform); | |
+ } else if (isset($class->associationMappings[$identifierField])) { | |
+ /* @var $assoc \Doctrine\ORM\Mapping\OneToOne */ | |
+ $assoc = $class->associationMappings[$identifierField]; | |
+ foreach ($assoc['joinColumns'] AS $joinColumn) { | |
+ $pkColumns[] = $joinColumn['name']; | |
+ } | |
+ } | |
+ } | |
+ if (!$table->hasIndex('primary')) { | |
+ $table->setPrimaryKey($pkColumns); | |
+ } | |
+ | |
if (isset($class->table['indexes'])) { | |
foreach ($class->table['indexes'] AS $indexName => $indexData) { | |
$table->addIndex($indexData['columns'], $indexName); | |
@@ -281,10 +297,11 @@ class SchemaTool | |
$pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform); | |
} | |
} | |
+ | |
// For now, this is a hack required for single table inheritence, since this method is called | |
// twice by single table inheritence relations | |
if(!$table->hasIndex('primary')) { | |
- $table->setPrimaryKey($pkColumns); | |
+ //$table->setPrimaryKey($pkColumns); | |
} | |
return $columns; | |
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php | |
index 76ce521..1f05ced 100644 | |
--- a/lib/Doctrine/ORM/UnitOfWork.php | |
+++ b/lib/Doctrine/ORM/UnitOfWork.php | |
@@ -1837,11 +1837,19 @@ class UnitOfWork implements PropertyChangedListener | |
if ($class->isIdentifierComposite) { | |
$id = array(); | |
foreach ($class->identifier as $fieldName) { | |
- $id[$fieldName] = $data[$fieldName]; | |
+ if (isset($class->associationMappings[$fieldName])) { | |
+ $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; | |
+ } else { | |
+ $id[$fieldName] = $data[$fieldName]; | |
+ } | |
} | |
$idHash = implode(' ', $id); | |
} else { | |
- $idHash = $data[$class->identifier[0]]; | |
+ if (isset($class->associationMappings[$class->identifier[0]])) { | |
+ $idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]; | |
+ } else { | |
+ $idHash = $data[$class->identifier[0]]; | |
+ } | |
$id = array($class->identifier[0] => $idHash); | |
} | |
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
new file mode 100644 | |
index 0000000..e7e9947 | |
--- /dev/null | |
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php | |
@@ -0,0 +1,427 @@ | |
+<?php | |
+ | |
+namespace Doctrine\Tests\ORM\Functional\Ticket; | |
+ | |
+require_once __DIR__ . '/../../../TestInit.php'; | |
+ | |
+class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase | |
+{ | |
+ private $article1; | |
+ private $article2; | |
+ private $reference; | |
+ private $translation; | |
+ private $articleDetails; | |
+ | |
+ protected function setUp() { | |
+ parent::setUp(); | |
+ | |
+ try { | |
+ $this->_schemaTool->createSchema(array( | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117Article'), | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117Reference'), | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117Translation'), | |
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC117ArticleDetails'), | |
+ )); | |
+ } catch(\Exception $e) { | |
+ | |
+ } | |
+ | |
+ $this->article1 = new DDC117Article("Foo"); | |
+ $this->article2 = new DDC117Article("Bar"); | |
+ | |
+ $this->_em->persist($this->article1); | |
+ $this->_em->persist($this->article2); | |
+ $this->_em->flush(); | |
+ | |
+ $this->reference = new DDC117Reference($this->article1, $this->article2, "Test-Description"); | |
+ $this->_em->persist($this->reference); | |
+ | |
+ $this->translation = new DDC117Translation($this->article1, "en", "Bar"); | |
+ $this->_em->persist($this->translation); | |
+ | |
+ $this->articleDetails = new DDC117ArticleDetails($this->article1, "Very long text"); | |
+ $this->_em->persist($this->articleDetails); | |
+ $this->_em->flush(); | |
+ | |
+ $this->_em->clear(); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testAssociationOnlyCompositeKey() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $mapRef); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->target()); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->source()); | |
+ $this->assertSame($mapRef, $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE r.source = ?1"; | |
+ $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 1)->getSingleResult(); | |
+ | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $mapRef); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->target()); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $mapRef->source()); | |
+ $this->assertSame($dqlRef, $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; | |
+ $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getSingleResult(); | |
+ | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $dqlRef); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $dqlRef->target()); | |
+ $this->assertType(__NAMESPACE__."\DDC117Article", $dqlRef->source()); | |
+ $this->assertSame($dqlRef, $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; | |
+ $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getSingleResult(); | |
+ | |
+ $this->_em->contains($dqlRef); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testUpdateAssocationEntity() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ $this->assertNotNull($mapRef); | |
+ $mapRef->setDescription("New Description!!"); | |
+ $this->_em->flush(); | |
+ $this->_em->clear(); | |
+ | |
+ $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ | |
+ $this->assertEquals('New Description!!', $mapRef->getDescription()); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testFetchDql() | |
+ { | |
+ $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; | |
+ $refs = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getResult(); | |
+ | |
+ $this->assertTrue(count($refs) > 0, "Has to contain at least one Reference."); | |
+ foreach ($refs AS $ref) { | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $ref, "Contains only Reference instances."); | |
+ $this->assertTrue($this->_em->contains($ref), "Contains Reference in the IdentityMap."); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testRemoveCompositeElement() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $refRep = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); | |
+ | |
+ $this->_em->remove($refRep); | |
+ $this->_em->flush(); | |
+ $this->_em->clear(); | |
+ | |
+ $this->assertNull($this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testDqlRemoveCompositeElement() | |
+ { | |
+ $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); | |
+ | |
+ $dql = "DELETE ".__NAMESPACE__."\DDC117Reference r WHERE r.source = ?1 AND r.target = ?2"; | |
+ $this->_em->createQuery($dql) | |
+ ->setParameter(1, $this->article1->id()) | |
+ ->setParameter(2, $this->article2->id()) | |
+ ->execute(); | |
+ | |
+ $this->assertNull($this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testInverseSideAccess() | |
+ { | |
+ $this->article1 = $this->_em->find(__NAMESPACE__."\DDC117Article", $this->article1->id()); | |
+ | |
+ $this->assertEquals(1, count($this->article1->references())); | |
+ foreach ($this->article1->references() AS $this->reference) { | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $this->reference); | |
+ $this->assertSame($this->article1, $this->reference->source()); | |
+ } | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = 'SELECT a, r FROM '. __NAMESPACE__ . '\DDC117Article a INNER JOIN a.references r WHERE a.id = ?1'; | |
+ $articleDql = $this->_em->createQuery($dql) | |
+ ->setParameter(1, $this->article1->id()) | |
+ ->getSingleResult(); | |
+ | |
+ $this->assertEquals(1, count($this->article1->references())); | |
+ foreach ($this->article1->references() AS $this->reference) { | |
+ $this->assertType(__NAMESPACE__."\DDC117Reference", $this->reference); | |
+ $this->assertSame($this->article1, $this->reference->source()); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testMixedCompositeKey() | |
+ { | |
+ $idCriteria = array('article' => $this->article1->id(), 'language' => 'en'); | |
+ | |
+ $this->translation = $this->_em->find(__NAMESPACE__ . '\DDC117Translation', $idCriteria); | |
+ $this->assertType(__NAMESPACE__ . '\DDC117Translation', $this->translation); | |
+ | |
+ $this->assertSame($this->translation, $this->_em->find(__NAMESPACE__ . '\DDC117Translation', $idCriteria)); | |
+ | |
+ $this->_em->clear(); | |
+ | |
+ $dql = 'SELECT t, a FROM ' . __NAMESPACE__ . '\DDC117Translation t JOIN t.article a WHERE t.article = ?1 AND t.language = ?2'; | |
+ $dqlTrans = $this->_em->createQuery($dql) | |
+ ->setParameter(1, $this->article1->id()) | |
+ ->setParameter(2, 'en') | |
+ ->getSingleResult(); | |
+ | |
+ $this->assertType(__NAMESPACE__ . '\DDC117Translation', $this->translation); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testMixedCompositeKeyViolateUniqueness() | |
+ { | |
+ $this->article1 = $this->_em->find(__NAMESPACE__ . '\DDC117Article', $this->article1->id()); | |
+ $this->article1->addTranslation('en', 'Bar'); | |
+ $this->article1->addTranslation('en', 'Baz'); | |
+ | |
+ $this->setExpectedException('Exception'); | |
+ $this->_em->flush(); | |
+ } | |
+ | |
+ /** | |
+ * @group DDC-117 | |
+ */ | |
+ public function testOneToOneForeignObjectId() | |
+ { | |
+ $this->article1 = new DDC117Article("Foo"); | |
+ $this->_em->persist($this->article1); | |
+ $this->_em->flush(); | |
+ | |
+ $this->articleDetails = new DDC117ArticleDetails($this->article1, "Very long text"); | |
+ $this->_em->persist($this->articleDetails); | |
+ $this->_em->flush(); | |
+ | |
+ $this->articleDetails->update("not so very long text!"); | |
+ $this->_em->flush(); | |
+ $this->_em->clear(); | |
+ | |
+ /* @var $article DDC117Article */ | |
+ $article = $this->_em->find(get_class($this->article1), $this->article1->id()); | |
+ $this->assertEquals('not so very long text!', $article->getText()); | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117Article | |
+{ | |
+ /** @Id @Column(type="integer", name="article_id") @GeneratedValue */ | |
+ private $id; | |
+ /** @Column */ | |
+ private $title; | |
+ | |
+ /** | |
+ * @OneToMany(targetEntity="DDC117Reference", mappedBy="source") | |
+ */ | |
+ private $references; | |
+ | |
+ /** | |
+ * @OneToOne(targetEntity="DDC117ArticleDetails", mappedBy="article") | |
+ */ | |
+ private $details; | |
+ | |
+ /** | |
+ * @OneToMany(targetEntity="DDC117Translation", mappedBy="article", cascade={"persist"}) | |
+ */ | |
+ private $translations; | |
+ | |
+ public function __construct($title) | |
+ { | |
+ $this->title = $title; | |
+ $this->references = new \Doctrine\Common\Collections\ArrayCollection(); | |
+ $this->translations = new \Doctrine\Common\Collections\ArrayCollection(); | |
+ } | |
+ | |
+ public function setDetails($details) | |
+ { | |
+ $this->details = $details; | |
+ } | |
+ | |
+ public function id() | |
+ { | |
+ return $this->id; | |
+ } | |
+ | |
+ public function addReference($reference) | |
+ { | |
+ $this->references[] = $reference; | |
+ } | |
+ | |
+ public function references() | |
+ { | |
+ return $this->references; | |
+ } | |
+ | |
+ public function addTranslation($language, $title) | |
+ { | |
+ $this->translations[] = new DDC117Translation($this, $language, $title); | |
+ } | |
+ | |
+ public function getText() | |
+ { | |
+ return $this->details->getText(); | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117ArticleDetails | |
+{ | |
+ /** | |
+ * @Id | |
+ * @OneToOne(targetEntity="DDC117Article", inversedBy="details") | |
+ * @JoinColumn(name="article_id", referencedColumnName="article_id") | |
+ */ | |
+ private $article; | |
+ | |
+ /** | |
+ * @Column(type="text") | |
+ */ | |
+ private $text; | |
+ | |
+ public function __construct($article, $text) | |
+ { | |
+ $this->article = $article; | |
+ $article->setDetails($this); | |
+ | |
+ $this->update($text); | |
+ } | |
+ | |
+ public function update($text) | |
+ { | |
+ $this->text = $text; | |
+ } | |
+ | |
+ public function getText() | |
+ { | |
+ return $this->text; | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117Reference | |
+{ | |
+ /** | |
+ * @Id | |
+ * @ManyToOne(targetEntity="DDC117Article", inversedBy="references") | |
+ * @JoinColumn(name="source_id", referencedColumnName="article_id") | |
+ */ | |
+ private $source; | |
+ | |
+ /** | |
+ * @Id | |
+ * @ManyToOne(targetEntity="DDC117Article", inversedBy="references") | |
+ * @JoinColumn(name="target_id", referencedColumnName="article_id") | |
+ */ | |
+ private $target; | |
+ | |
+ /** | |
+ * @column(type="string") | |
+ */ | |
+ private $description; | |
+ | |
+ /** | |
+ * @column(type="datetime") | |
+ */ | |
+ private $created; | |
+ | |
+ public function __construct($source, $target, $description) | |
+ { | |
+ $source->addReference($this); | |
+ $target->addReference($this); | |
+ | |
+ $this->source = $source; | |
+ $this->target = $target; | |
+ $this->description = $description; | |
+ $this->created = new \DateTime("now"); | |
+ } | |
+ | |
+ public function source() | |
+ { | |
+ return $this->source; | |
+ } | |
+ | |
+ public function target() | |
+ { | |
+ return $this->target; | |
+ } | |
+ | |
+ public function setDescription($desc) | |
+ { | |
+ $this->description = $desc; | |
+ } | |
+ | |
+ public function getDescription() | |
+ { | |
+ return $this->description; | |
+ } | |
+} | |
+ | |
+/** | |
+ * @Entity | |
+ */ | |
+class DDC117Translation | |
+{ | |
+ /** | |
+ * @Id | |
+ * @ManyToOne(targetEntity="DDC117Article") | |
+ * @JoinColumn(name="article_id", referencedColumnName="article_id") | |
+ */ | |
+ private $article; | |
+ | |
+ /** | |
+ * @Id @column(type="string") | |
+ */ | |
+ private $language; | |
+ | |
+ /** | |
+ * @column(type="string") | |
+ */ | |
+ private $title; | |
+ | |
+ public function __construct($article, $language, $title) | |
+ { | |
+ $this->article = $article; | |
+ $this->language = $language; | |
+ $this->title = $title; | |
+ } | |
+} | |
\ No newline at end of file |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment