Skip to content

Instantly share code, notes, and snippets.

@doctrinebot
Created December 13, 2015 18:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doctrinebot/b683ca5f10bf1aad232c to your computer and use it in GitHub Desktop.
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
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
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;
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