diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 15112adbe..f1b374c5d 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -317,6 +317,10 @@ class ClassMetadata extends ClassMetadataInfo $serialized[] = 'isMappedSuperclass'; } + if ($this->containsForeignIdentifier) { + $serialized[] = 'containsForeignIdentifier'; + } + if ($this->isVersioned) { $serialized[] = 'isVersioned'; $serialized[] = 'versionField'; diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 298d5c02d..255b59012 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -255,7 +255,7 @@ class ClassMetadataInfo * - scale (integer, optional, schema-only) * The scale of a decimal column. Only valid if the column type is decimal. * - * - unique (string, optional, schema-only) + [* - 'unique'] (string, optional, schema-only) * Whether a unique constraint should be generated for the column. * * @var array @@ -392,6 +392,15 @@ class ClassMetadataInfo */ public $isIdentifierComposite = false; + /** + * READ-ONLY: Flag indicating wheather the identifier/primary key contains at least one foreign key association. + * + * This flag is necessary because some code blocks require special treatment of this cases. + * + * @var boolean + */ + public $containsForeignIdentifier = false; + /** * READ-ONLY: The ID generator used for generating IDs for this class. * @@ -712,6 +721,10 @@ class ClassMetadataInfo // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { + if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { + throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); + } + if ( ! in_array($mapping['fieldName'], $this->identifier)) { if (count($mapping['joinColumns']) >= 2) { throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( @@ -720,6 +733,7 @@ class ClassMetadataInfo } $this->identifier[] = $mapping['fieldName']; + $this->containsForeignIdentifier = true; } // Check for composite key if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { @@ -747,6 +761,10 @@ class ClassMetadataInfo } else { $mapping['isOwningSide'] = false; } + + if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & ClassMetadata::TO_MANY) { + throw MappingException::illegalToManyIdentifierAssoaction($this->name, $mapping['fieldName']); + } // Fetch mode. Default fetch mode to LAZY, if not set. if ( ! isset($mapping['fetch'])) { @@ -797,9 +815,15 @@ class ClassMetadataInfo 'referencedColumnName' => 'id' )); } + + $uniqueContraintColumns = array(); foreach ($mapping['joinColumns'] as $key => &$joinColumn) { if ($mapping['type'] === self::ONE_TO_ONE) { - $joinColumn['unique'] = true; + if (count($mapping['joinColumns']) == 1) { + $joinColumn['unique'] = true; + } else { + $uniqueContraintColumns[] = $joinColumn['name']; + } } if (empty($joinColumn['name'])) { $joinColumn['name'] = $mapping['fieldName'] . '_id'; @@ -811,6 +835,16 @@ class ClassMetadataInfo $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName']) ? $joinColumn['fieldName'] : $joinColumn['name']; } + + if ($uniqueContraintColumns) { + if (!$this->table) { + throw new \RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship."); + } + $this->table['uniqueConstraints'][$mapping['fieldName']."_uniq"] = array( + 'columns' => $uniqueContraintColumns + ); + } + $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']); } @@ -818,6 +852,10 @@ class ClassMetadataInfo $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; + if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) { + throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']); + } + return $mapping; } @@ -1547,6 +1585,74 @@ class ClassMetadataInfo ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } + /** + * Is this an association that only has a single join column? + * + * @param string $fieldName + * @return bool + */ + public function isAssociationWithSingleJoinColumn($fieldName) + { + return ( + isset($this->associationMappings[$fieldName]) && + isset($this->associationMappings[$fieldName]['joinColumns'][0]) && + !isset($this->associationMappings[$fieldName]['joinColumns'][1]) + ); + } + + /** + * Return the single association join column (if any). + * + * @param string $fieldName + * @return string + */ + public function getSingleAssociationJoinColumnName($fieldName) + { + if (!$this->isAssociationWithSingleJoinColumn($fieldName)) { + throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); + } + return $this->associationMappings[$fieldName]['joinColumns'][0]['name']; + } + + /** + * Return the single association referenced join column name (if any). + * + * @param string $fieldName + * @return string + */ + public function getSingleAssociationReferencedJoinColumnName($fieldName) + { + if (!$this->isAssociationWithSingleJoinColumn($fieldName)) { + throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); + } + return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName']; + } + + /** + * Used to retrieve a fieldname for either field or association from a given column, + * + * This method is used in foreign-key as primary-key contexts. + * + * @param string $columnName + * @return string + */ + public function getFieldForColumn($columnName) + { + if (isset($this->fieldNames[$columnName])) { + return $this->fieldNames[$columnName]; + } else { + foreach ($this->associationMappings AS $assocName => $mapping) { + if ($this->isAssociationWithSingleJoinColumn($assocName) && + $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) { + + return $assocName; + } + } + + throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); + } + } + /** * Sets the ID generator used to generate IDs for instances of this class. * diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 3d0e5e1a9..adf8237b7 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -243,4 +243,31 @@ class MappingException extends \Doctrine\ORM\ORMException return new self("It is not possible to map entity '".$className."' with a composite primary key ". "as part of the primary key of another entity '".$targetEntity."#".$targetField."'."); } + + public static function noSingleAssociationJoinColumnFound($className, $field) + { + return new self("'$className#$field' is not an association with a single join column."); + } + + public static function noFieldNameFoundForColumn($className, $column) + { + return new self("Cannot find a field on '$className' that is mapped to column '$column'. Either the ". + "field does not exist or an association exists but it has multiple join columns."); + } + + public static function illegalOrphanRemovalOnIdentifierAssociation($className, $field) + { + return new self("The orphan removal option is not allowed on an association that is ". + "part of the identifier in '$className#$field'."); + } + + public static function illegalInverseIdentifierAssocation($className, $field) + { + return new self("An inverse association is not allowed to be identifier in '$className#$field'."); + } + + public static function illegalToManyIdentifierAssoaction($className, $field) + { + return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'."); + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 1c7e14034..2673a1fd0 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -473,6 +473,8 @@ class BasicEntityPersister foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) { if ($newVal === null) { $result[$owningTable][$sourceColumn] = null; + } else if ($targetClass->containsForeignIdentifier) { + $result[$owningTable][$sourceColumn] = $newValId[$targetClass->getFieldForColumn($targetColumn)]; } else { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; } @@ -720,13 +722,21 @@ class BasicEntityPersister * @param PersistentCollection $coll The collection to fill. */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) - { + { $criteria = array(); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $joinTableConditions = array(); if ($assoc['isOwningSide']) { foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + if ($sourceClass->containsForeignIdentifier) { + $field = $sourceClass->getFieldForColumn($sourceKeyColumn); + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + if (isset($sourceClass->associationMappings[$field])) { + $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); + $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; + } + $criteria[$relationKeyColumn] = $value; + } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { throw MappingException::joinColumnMustPointToMappedField( @@ -738,7 +748,15 @@ class BasicEntityPersister $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']]; // TRICKY: since the association is inverted source and target are flipped foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + if ($sourceClass->containsForeignIdentifier) { + $field = $sourceClass->getFieldForColumn($sourceKeyColumn); + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + if (isset($sourceClass->associationMappings[$field])) { + $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); + $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; + } + $criteria[$relationKeyColumn] = $value; + } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { throw MappingException::joinColumnMustPointToMappedField( @@ -955,8 +973,15 @@ class BasicEntityPersister $joinSql = ''; foreach ($joinClauses as $joinTableColumn => $sourceColumn) { if ($joinSql != '') $joinSql .= ' AND '; + + if ($this->_class->containsForeignIdentifier && !isset($this->_class->fieldNames[$sourceColumn])) { + $quotedColumn = $sourceColumn; // join columns cannot be quoted + } else { + $quotedColumn = $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform); + } + $joinSql .= $this->_getSQLTableAlias($this->_class->name) . - '.' . $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform) . ' = ' + '.' . $quotedColumn . ' = ' . $joinTableName . '.' . $joinTableColumn; } @@ -1160,7 +1185,17 @@ class BasicEntityPersister $owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']]; $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { - $criteria[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + if ($sourceClass->containsForeignIdentifier) { + $field = $sourceClass->getFieldForColumn($sourceKeyColumn); + $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); + if (isset($sourceClass->associationMappings[$field])) { + $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); + $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; + } + $criteria[$targetKeyColumn] = $value; + } else { + $criteria[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } } $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 27e9a57fd..5f24188ca 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -117,13 +117,21 @@ class ManyToManyPersister extends AbstractCollectionPersister foreach ($mapping['joinTableColumns'] as $joinTableColumn) { if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) { if ($isComposite) { - $params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; + if ($class1->containsForeignIdentifier) { + $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; + } else { + $params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; + } } else { $params[] = array_pop($identifier1); } } else { if ($isComposite) { - $params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; + if ($class2->containsForeignIdentifier) { + $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; + } else { + $params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; + } } else { $params[] = array_pop($identifier2); } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 1512da236..c745d56e6 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -738,7 +738,11 @@ class SqlWalker implements TreeWalker } } + // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot + // be the owning side and previously we ensured that $assoc is always the owning side of the associations. + // The owning side is necessary at this point because only it contains the JoinColumn information. if ($assoc['type'] & ClassMetadata::TO_ONE) { + $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $first = true; @@ -746,15 +750,19 @@ class SqlWalker implements TreeWalker if ( ! $first) $sql .= ' AND '; else $first = false; if ($relation['isOwningSide']) { - $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); - $sql .= $sourceTableAlias . '.' . $sourceColumn - . ' = ' - . $targetTableAlias . '.' . $quotedTargetColumn; + if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) { + $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. + } else { + $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); + } + $sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; } else { - $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); - $sql .= $sourceTableAlias . '.' . $quotedTargetColumn - . ' = ' - . $targetTableAlias . '.' . $sourceColumn; + if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) { + $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. + } else { + $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); + } + $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } } } else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) { @@ -768,17 +776,25 @@ class SqlWalker implements TreeWalker foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { if ( ! $first) $sql .= ' AND '; else $first = false; - $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform) - . ' = ' - . $joinTableAlias . '.' . $relationColumn; + if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) { + $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted. + } else { + $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform); + } + + $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; } } else { foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { if ( ! $first) $sql .= ' AND '; else $first = false; - $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform) - . ' = ' - . $joinTableAlias . '.' . $relationColumn; + if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) { + $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. + } else { + $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); + } + + $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; } } @@ -792,17 +808,25 @@ class SqlWalker implements TreeWalker foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { if ( ! $first) $sql .= ' AND '; else $first = false; - $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform) - . ' = ' - . $joinTableAlias . '.' . $relationColumn; + if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) { + $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. + } else { + $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); + } + + $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; } } else { foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { if ( ! $first) $sql .= ' AND '; else $first = false; - $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform) - . ' = ' - . $joinTableAlias . '.' . $relationColumn; + if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) { + $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted. + } else { + $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform); + } + + $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; } } } diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 16042d398..f28490e7d 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -444,10 +444,10 @@ class SchemaTool } else if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) { // it seems to be an entity as foreign key foreach ($class->getIdentifierFieldNames() AS $fieldName) { - if ($class->hasAssociation($fieldName) && $class->associationMappings[$fieldName]['joinColumns'][0]['name'] == $referencedColumnName) { + if ($class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) { return $this->getDefiningClass( $this->_em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']), - $class->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'] + $class->getSingleAssociationReferencedJoinColumnName($fieldName) ); } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1f05cedf8..ead7e20b9 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1902,7 +1902,11 @@ class UnitOfWork implements PropertyChangedListener foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null; if ($joinColumnValue !== null) { - $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; + if ($targetClass->containsForeignIdentifier) { + $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; + } else { + $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; + } } } if ( ! $associatedId) { diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117ApproveChanges.php b/tests/Doctrine/Tests/Models/DDC117/DDC117ApproveChanges.php new file mode 100644 index 000000000..0a65b62fa --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117ApproveChanges.php @@ -0,0 +1,65 @@ +articleDetails = $details; + $this->reference = $reference; + $this->translation = $translation; + } + + public function getId() + { + return $this->id; + } + + public function getArticleDetails() + { + return $this->articleDetails; + } + + public function getReference() + { + return $this->reference; + } + + public function getTranslation() + { + return $this->translation; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php new file mode 100644 index 000000000..111c4fabe --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php @@ -0,0 +1,81 @@ +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(); + } + + public function getDetails() + { + return $this->details; + } + + public function resetText() + { + $this->details = null; + } + + public function getTranslations() + { + return $this->translations; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117ArticleDetails.php b/tests/Doctrine/Tests/Models/DDC117/DDC117ArticleDetails.php new file mode 100644 index 000000000..ae13df036 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117ArticleDetails.php @@ -0,0 +1,39 @@ +article = $article; + $article->setDetails($this); + + $this->update($text); + } + + public function update($text) + { + $this->text = $text; + } + + public function getText() + { + return $this->text; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Editor.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Editor.php new file mode 100644 index 000000000..a323bb304 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Editor.php @@ -0,0 +1,54 @@ +name = $name; + $this->reviewingTranslations = new \Doctrine\Common\Collections\ArrayCollection(); + } + + public function addLastTranslation(DDC117Translation $t) + { + $this->lastTranslation = $t; + $t->lastTranslatedBy[] = $this; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php new file mode 100644 index 000000000..80cfb3a83 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php @@ -0,0 +1,64 @@ +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; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php new file mode 100644 index 000000000..1d38710c9 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php @@ -0,0 +1,65 @@ +article = $article; + $this->language = $language; + $this->title = $title; + $this->reviewedByEditors = new \Doctrine\Common\Collections\ArrayCollection(); + $this->lastTranslatedBy = new \Doctrine\Common\Collections\ArrayCollection(); + } + + public function getArticleId() + { + return $this->article->id(); + } + + public function getLanguage() + { + return $this->language; + } + + public function getLastTranslatedBy() + { + return $this->lastTranslatedBy; + } + + public function getReviewedByEditors() + { + return $this->reviewedByEditors; + } +} \ 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 64c75870f..309ebf339 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php @@ -2,6 +2,13 @@ namespace Doctrine\Tests\ORM\Functional\Ticket; +use Doctrine\Tests\Models\DDC117\DDC117ArticleDetails; +use Doctrine\Tests\Models\DDC117\DDC117Article; +use Doctrine\Tests\Models\DDC117\DDC117Reference; +use Doctrine\Tests\Models\DDC117\DDC117Translation; +use Doctrine\Tests\Models\DDC117\DDC117ApproveChanges; +use Doctrine\Tests\Models\DDC117\DDC117Editor; + require_once __DIR__ . '/../../../TestInit.php'; class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase @@ -13,19 +20,9 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase private $articleDetails; protected function setUp() { + $this->useModelSet('ddc117'); 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"); @@ -53,33 +50,33 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase { $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)); + $mapRef = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Reference", $mapRef); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Article", $mapRef->target()); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Article", $mapRef->source()); + $this->assertSame($mapRef, $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria)); $this->_em->clear(); - $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE r.source = ?1"; + $dql = "SELECT r, s FROM "."Doctrine\Tests\Models\DDC117\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->assertType("Doctrine\Tests\Models\DDC117\DDC117Reference", $mapRef); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Article", $mapRef->target()); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Article", $mapRef->source()); + $this->assertSame($dqlRef, $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria)); $this->_em->clear(); - $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; + $dql = "SELECT r, s FROM "."Doctrine\Tests\Models\DDC117\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)); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Reference", $dqlRef); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Article", $dqlRef->target()); + $this->assertType("Doctrine\Tests\Models\DDC117\DDC117Article", $dqlRef->source()); + $this->assertSame($dqlRef, $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria)); - $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; + $dql = "SELECT r, s FROM "."Doctrine\Tests\Models\DDC117\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; $dqlRef = $this->_em->createQuery($dql)->setParameter(1, 'Foo')->getSingleResult(); $this->_em->contains($dqlRef); @@ -92,13 +89,13 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase { $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); - $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); + $mapRef = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria); $this->assertNotNull($mapRef); $mapRef->setDescription("New Description!!"); $this->_em->flush(); $this->_em->clear(); - $mapRef = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); + $mapRef = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria); $this->assertEquals('New Description!!', $mapRef->getDescription()); } @@ -108,12 +105,12 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testFetchDql() { - $dql = "SELECT r, s FROM ".__NAMESPACE__."\DDC117Reference r JOIN r.source s WHERE s.title = ?1"; + $dql = "SELECT r, s FROM "."Doctrine\Tests\Models\DDC117\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->assertType("Doctrine\Tests\Models\DDC117\DDC117Reference", $ref, "Contains only Reference instances."); $this->assertTrue($this->_em->contains($ref), "Contains Reference in the IdentityMap."); } } @@ -125,13 +122,13 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase { $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); - $refRep = $this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria); + $refRep = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria); $this->_em->remove($refRep); $this->_em->flush(); $this->_em->clear(); - $this->assertNull($this->_em->find(__NAMESPACE__."\DDC117Reference", $idCriteria)); + $this->assertNull($this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria)); } /** @@ -141,13 +138,13 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase { $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); - $dql = "DELETE ".__NAMESPACE__."\DDC117Reference r WHERE r.source = ?1 AND r.target = ?2"; + $dql = "DELETE "."Doctrine\Tests\Models\DDC117\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)); + $this->assertNull($this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria)); } /** @@ -155,24 +152,24 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testInverseSideAccess() { - $this->article1 = $this->_em->find(__NAMESPACE__."\DDC117Article", $this->article1->id()); + $this->article1 = $this->_em->find("Doctrine\Tests\Models\DDC117\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->assertType("Doctrine\Tests\Models\DDC117\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'; + $dql = 'SELECT a, r FROM '. 'Doctrine\Tests\Models\DDC117\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->assertType("Doctrine\Tests\Models\DDC117\DDC117Reference", $this->reference); $this->assertSame($this->article1, $this->reference->source()); } } @@ -184,20 +181,20 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase { $idCriteria = array('article' => $this->article1->id(), 'language' => 'en'); - $this->translation = $this->_em->find(__NAMESPACE__ . '\DDC117Translation', $idCriteria); - $this->assertType(__NAMESPACE__ . '\DDC117Translation', $this->translation); + $this->translation = $this->_em->find('Doctrine\Tests\Models\DDC117\DDC117Translation', $idCriteria); + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117Translation', $this->translation); - $this->assertSame($this->translation, $this->_em->find(__NAMESPACE__ . '\DDC117Translation', $idCriteria)); + $this->assertSame($this->translation, $this->_em->find('Doctrine\Tests\Models\DDC117\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'; + $dql = 'SELECT t, a FROM ' . 'Doctrine\Tests\Models\DDC117\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); + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117Translation', $this->translation); } /** @@ -205,7 +202,7 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testMixedCompositeKeyViolateUniqueness() { - $this->article1 = $this->_em->find(__NAMESPACE__ . '\DDC117Article', $this->article1->id()); + $this->article1 = $this->_em->find('Doctrine\Tests\Models\DDC117\DDC117Article', $this->article1->id()); $this->article1->addTranslation('en', 'Bar'); $this->article1->addTranslation('en', 'Baz'); @@ -235,6 +232,18 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('not so very long text!', $article->getText()); } + /** + * @group DDC-117 + */ + public function testOneToOneCascadeRemove() + { + $article = $this->_em->find(get_class($this->article1), $this->article1->id()); + $this->_em->remove($article); + $this->_em->flush(); + + $this->assertFalse($this->_em->contains($article->getDetails())); + } + /** * @group DDC-117 */ @@ -251,194 +260,151 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($this->article1); $this->_em->flush(); } -} - -/** - * @Entity - */ -class DDC117Article -{ - /** @Id @Column(type="integer", name="article_id") @GeneratedValue */ - private $id; - /** @Column */ - private $title; /** - * @OneToMany(targetEntity="DDC117Reference", mappedBy="source") + * @group DDC-117 */ - private $references; + public function testReferencesToForeignKeyEntities() + { + $idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id()); + $reference = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria); + + $idCriteria = array('article' => $this->article1->id(), 'language' => 'en'); + $translation = $this->_em->find('Doctrine\Tests\Models\DDC117\DDC117Translation', $idCriteria); + + $approveChanges = new DDC117ApproveChanges($reference->source()->getDetails(), $reference, $translation); + $this->_em->persist($approveChanges); + $this->_em->flush(); + $this->_em->clear(); + + $approveChanges = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117ApproveChanges", $approveChanges->getId()); + + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117ArticleDetails', $approveChanges->getArticleDetails()); + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117Reference', $approveChanges->getReference()); + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117Translation', $approveChanges->getTranslation()); + } /** - * @OneToOne(targetEntity="DDC117ArticleDetails", mappedBy="article", cascade={"persist"}) + * @group DDC-117 */ - private $details; + public function testLoadOneToManyCollectionOfForeignKeyEntities() + { + /* @var $article DDC117Article */ + $article = $this->_em->find(get_class($this->article1), $this->article1->id()); + + $translations = $article->getTranslations(); + $this->assertFalse($translations->isInitialized()); + $this->assertContainsOnly('Doctrine\Tests\Models\DDC117\DDC117Translation', $translations); + $this->assertTrue($translations->isInitialized()); + } /** - * @OneToMany(targetEntity="DDC117Translation", mappedBy="article", cascade={"persist"}) + * @group DDC-117 */ - private $translations; - - public function __construct($title) + public function testLoadManyToManyCollectionOfForeignKeyEntities() { - $this->title = $title; - $this->references = new \Doctrine\Common\Collections\ArrayCollection(); - $this->translations = new \Doctrine\Common\Collections\ArrayCollection(); + $editor = $this->loadEditorFixture(); + + $this->assertFalse($editor->reviewingTranslations->isInitialized()); + $this->assertContainsOnly("Doctrine\Tests\Models\DDC117\DDC117Translation", $editor->reviewingTranslations); + $this->assertTrue($editor->reviewingTranslations->isInitialized()); + + $this->_em->clear(); + + $dql = "SELECT e, t FROM Doctrine\Tests\Models\DDC117\DDC117Editor e JOIN e.reviewingTranslations t WHERE e.id = ?1"; + $editor = $this->_em->createQuery($dql)->setParameter(1, $editor->id)->getSingleResult(); + $this->assertTrue($editor->reviewingTranslations->isInitialized()); + $this->assertContainsOnly("Doctrine\Tests\Models\DDC117\DDC117Translation", $editor->reviewingTranslations); } - public function setDetails($details) + /** + * @group DDC-117 + */ + public function testClearManyToManyCollectionOfForeignKeyEntities() { - $this->details = $details; + $editor = $this->loadEditorFixture(); + $this->assertEquals(3, count($editor->reviewingTranslations)); + + $editor->reviewingTranslations->clear(); + $this->_em->flush(); + $this->_em->clear(); + + $editor = $this->_em->find(get_class($editor), $editor->id); + $this->assertEquals(0, count($editor->reviewingTranslations)); } - public function id() + /** + * @group DDC-117 + */ + public function testLoadInverseManyToManyCollection() { - return $this->id; + $editor = $this->loadEditorFixture(); + + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117Translation', $editor->reviewingTranslations[0]); + + $reviewedBy = $editor->reviewingTranslations[0]->getReviewedByEditors(); + $this->assertEquals(1, count($reviewedBy)); + $this->assertSame($editor, $reviewedBy[0]); + + $this->_em->clear(); + + $dql = "SELECT t, e FROM Doctrine\Tests\Models\DDC117\DDC117Translation t ". + "JOIN t.reviewedByEditors e WHERE t.article = ?1 AND t.language = ?2"; + $trans = $this->_em->createQuery($dql) + ->setParameter(1, $this->translation->getArticleId()) + ->setParameter(2, $this->translation->getLanguage()) + ->getSingleResult(); + + $this->assertType('Doctrine\Tests\Models\DDC117\DDC117Translation', $trans); + $this->assertContainsOnly('Doctrine\Tests\Models\DDC117\DDC117Editor', $trans->reviewedByEditors); + $this->assertEquals(1, count($trans->reviewedByEditors)); } - public function addReference($reference) + /** + * @group DDC-117 + */ + public function testLoadOneToManyOfSourceEntityWithAssociationIdentifier() { - $this->references[] = $reference; + $editor = $this->loadEditorFixture(); + + $editor->addLastTranslation($editor->reviewingTranslations[0]); + $this->_em->flush(); + $this->_em->clear(); + + $editor = $this->_em->find(get_class($editor), $editor->id); + $lastTranslatedBy = $editor->reviewingTranslations[0]->getLastTranslatedBy(); + $lastTranslatedBy->count(); + + $this->assertEquals(1, count($lastTranslatedBy)); } - public function references() + /** + * @return DDC117Editor + */ + private function loadEditorFixture() { - return $this->references; - } + $editor = new DDC117Editor("beberlei"); - public function addTranslation($language, $title) - { - $this->translations[] = new DDC117Translation($this, $language, $title); - } + /* @var $article1 DDC117Article */ + $article1 = $this->_em->find(get_class($this->article1), $this->article1->id()); + foreach ($article1->getTranslations() AS $translation) { + $editor->reviewingTranslations[] = $translation; + } - public function getText() - { - return $this->details->getText(); + /* @var $article2 DDC117Article */ + $article2 = $this->_em->find(get_class($this->article2), $this->article2->id()); + $article2->addTranslation("de", "Vanille-Krapferl"); // omnomnom + $article2->addTranslation("fr", "Sorry can't speak french!"); + + foreach ($article2->getTranslations() AS $translation) { + $this->_em->persist($translation); // otherwise persisting the editor won't work, reachability! + $editor->reviewingTranslations[] = $translation; + } + + $this->_em->persist($editor); + $this->_em->flush(); + $this->_em->clear(); + + return $this->_em->find(get_class($editor), $editor->id); } } - -/** - * @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/DDC881Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC881Test.php index 8b8aba843..fc43c207a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC881Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC881Test.php @@ -18,7 +18,7 @@ class DDC881Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC881Phonecall'), )); } catch (\Exception $e) { - + } } @@ -74,6 +74,22 @@ class DDC881Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($call2); $this->_em->flush(); + $this->_em->clear(); + + // fetch-join that foreign-key/primary-key entity association + $dql = "SELECT c, p FROM " . __NAMESPACE__ . "\DDC881PhoneCall c JOIN c.phonenumber p"; + $calls = $this->_em->createQuery($dql)->getResult(); + + $this->assertEquals(2, count($calls)); + $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $calls[0]->getPhoneNumber()); + $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $calls[1]->getPhoneNumber()); + + $dql = "SELECT p, c FROM " . __NAMESPACE__ . "\DDC881PhoneNumber p JOIN p.calls c"; + $numbers = $this->_em->createQuery($dql)->getResult(); + + $this->assertEquals(2, count($numbers)); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $numbers[0]->getCalls()); + $this->assertTrue($numbers[0]->getCalls()->isInitialized()); } } @@ -131,6 +147,16 @@ class DDC881PhoneNumber */ private $phonenumber; + /** + * @OneToMany(targetEntity="DDC881PhoneCall", mappedBy="phonenumber") + */ + private $calls; + + public function __construct() + { + $this->calls = new \Doctrine\Common\Collections\ArrayCollection(); + } + public function setId($id) { $this->id = $id; @@ -146,6 +172,10 @@ class DDC881PhoneNumber $this->phonenumber = $phoneNumber; } + public function getCalls() + { + return $this->calls; + } } /** @@ -161,7 +191,7 @@ class DDC881PhoneCall */ private $id; /** - * @OneToOne(targetEntity="DDC881PhoneNumber",cascade={"all"}) + * @ManyToOne(targetEntity="DDC881PhoneNumber", inversedBy="calls", cascade={"all"}) * @JoinColumns({ * @JoinColumn(name="phonenumber_id", referencedColumnName="id"), * @JoinColumn(name="user_id", referencedColumnName="user_id") @@ -178,4 +208,8 @@ class DDC881PhoneCall $this->phonenumber = $phoneNumber; } + public function getPhoneNumber() + { + return $this->phonenumber; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index f8425b42f..f09d4d72c 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -300,4 +300,71 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "No mapping found for field 'foo' on class 'Doctrine\Tests\Models\CMS\CmsUser'."); $cm->getFieldMapping('foo'); } + + /** + * @group DDC-117 + */ + public function testMapIdentifierAssociation() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\DDC117\DDC117ArticleDetails'); + $cm->mapOneToOne(array( + 'fieldName' => 'article', + 'id' => true, + 'targetEntity' => 'Doctrine\Tests\Models\DDC117\DDC117Article', + 'joinColumns' => array(), + )); + + $this->assertTrue($cm->containsForeignIdentifier, "Identifier Association should set 'containsForeignIdentifier' boolean flag."); + $this->assertEquals(array("article"), $cm->identifier); + } + + /** + * @group DDC-117 + */ + public function testOrphanRemovalIdentifierAssociation() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\DDC117\DDC117ArticleDetails'); + + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', 'The orphan removal option is not allowed on an association that'); + $cm->mapOneToOne(array( + 'fieldName' => 'article', + 'id' => true, + 'targetEntity' => 'Doctrine\Tests\Models\DDC117\DDC117Article', + 'orphanRemoval' => true, + 'joinColumns' => array(), + )); + } + + /** + * @group DDC-117 + */ + public function testInverseIdentifierAssocation() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\DDC117\DDC117ArticleDetails'); + + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', 'An inverse association is not allowed to be identifier in'); + $cm->mapOneToOne(array( + 'fieldName' => 'article', + 'id' => true, + 'mappedBy' => 'details', // INVERSE! + 'targetEntity' => 'Doctrine\Tests\Models\DDC117\DDC117Article', + 'joinColumns' => array(), + )); + } + + /** + * @group DDC-117 + */ + public function testIdentifierAssocationManyToMany() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\DDC117\DDC117ArticleDetails'); + + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', 'Many-to-many or one-to-many associations are not allowed to be identifier in'); + $cm->mapManyToMany(array( + 'fieldName' => 'article', + 'id' => true, + 'targetEntity' => 'Doctrine\Tests\Models\DDC117\DDC117Article', + 'joinColumns' => array(), + )); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index 0ebe1aafe..fae0d7350 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -495,6 +495,22 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase { $this->assertInvalidDQL('SELECT g FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g'); } + + /** + * @group DDC-117 + */ + public function testSizeOfForeignKeyOneToManyPrimaryKeyEntity() + { + $this->assertValidDQL("SELECT a, t FROM Doctrine\Tests\Models\DDC117\DDC117Article a JOIN a.translations t WHERE SIZE(a.translations) > 0"); + } + + /** + * @group DDC-117 + */ + public function testSizeOfForeignKeyManyToManyPrimaryKeyEntity() + { + $this->assertValidDQL("SELECT e, t FROM Doctrine\Tests\Models\DDC117\DDC117Editor e JOIN e.reviewingTranslations t WHERE SIZE(e.reviewingTranslations) > 0"); + } } /** @Entity */ diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index f7674ed4e..abf04efab 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -91,6 +91,14 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\DirectoryTree\File', 'Doctrine\Tests\Models\DirectoryTree\Directory', ), + 'ddc117' => array( + 'Doctrine\Tests\Models\DDC117\DDC117Article', + 'Doctrine\Tests\Models\DDC117\DDC117Reference', + 'Doctrine\Tests\Models\DDC117\DDC117Translation', + 'Doctrine\Tests\Models\DDC117\DDC117ArticleDetails', + 'Doctrine\Tests\Models\DDC117\DDC117ApproveChanges', + 'Doctrine\Tests\Models\DDC117\DDC117Editor', + ), ); protected function useModelSet($setName) @@ -173,6 +181,16 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $conn->executeUpdate('DELETE FROM Directory WHERE parentDirectory_id IS NOT NULL'); $conn->executeUpdate('DELETE FROM Directory'); } + if (isset($this->_usedModelSets['ddc117'])) { + return; + $conn->executeUpdate('DELETE FROM ddc117editor_ddc117translation'); + $conn->executeUpdate('DELETE FROM DDC117Editor'); + $conn->executeUpdate('DELETE FROM DDC117ApproveChanges'); + $conn->executeUpdate('DELETE FROM DDC117Reference'); + $conn->executeUpdate('DELETE FROM DDC117ArticleDetails'); + $conn->executeUpdate('DELETE FROM DDC117Translation'); + $conn->executeUpdate('DELETE FROM DDC117Article'); + } $this->_em->clear(); } diff --git a/tests/Doctrine/Tests/TestUtil.php b/tests/Doctrine/Tests/TestUtil.php index 185bb65a7..5a187e595 100644 --- a/tests/Doctrine/Tests/TestUtil.php +++ b/tests/Doctrine/Tests/TestUtil.php @@ -93,6 +93,10 @@ class TestUtil 'driver' => 'pdo_sqlite', 'memory' => true ); + if (isset($GLOBALS['db_path'])) { + $params['path'] = $GLOBALS['db_path']; + unlink($GLOBALS['db_path']); + } $conn = \Doctrine\DBAL\DriverManager::getConnection($params); }