Merge branch DDC-117 into master
This commit is contained in:
commit
78d4277e4b
@ -187,8 +187,9 @@
|
||||
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="column" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="association-key" type="xs:boolean" default="false" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sequence-generator">
|
||||
|
@ -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)) {
|
||||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
|
||||
$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)) {
|
||||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
|
||||
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
} else {
|
||||
$identifier[$idField] = $value;
|
||||
}
|
||||
} else {
|
||||
throw ORMException::entityMissingAssignedId($entity);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -317,6 +317,10 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$serialized[] = 'isMappedSuperclass';
|
||||
}
|
||||
|
||||
if ($this->containsForeignIdentifier) {
|
||||
$serialized[] = 'containsForeignIdentifier';
|
||||
}
|
||||
|
||||
if ($this->isVersioned) {
|
||||
$serialized[] = 'isVersioned';
|
||||
$serialized[] = 'versionField';
|
||||
|
@ -255,7 +255,7 @@ class ClassMetadataInfo
|
||||
* - <b>scale</b> (integer, optional, schema-only)
|
||||
* The scale of a decimal column. Only valid if the column type is decimal.
|
||||
*
|
||||
* - <b>unique (string, optional, schema-only)</b>
|
||||
[* - <b>'unique'] (string, optional, schema-only)</b>
|
||||
* 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.
|
||||
*
|
||||
@ -710,6 +719,29 @@ class ClassMetadataInfo
|
||||
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
|
||||
}
|
||||
|
||||
// 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(
|
||||
$mapping['targetEntity'], $this->name, $mapping['fieldName']
|
||||
);
|
||||
}
|
||||
|
||||
$this->identifier[] = $mapping['fieldName'];
|
||||
$this->containsForeignIdentifier = true;
|
||||
}
|
||||
// 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();
|
||||
@ -729,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'])) {
|
||||
@ -779,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';
|
||||
@ -793,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']);
|
||||
}
|
||||
|
||||
@ -800,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;
|
||||
}
|
||||
|
||||
@ -990,11 +1046,19 @@ class ClassMetadataInfo
|
||||
if ($this->isIdentifierComposite) {
|
||||
$columnNames = array();
|
||||
foreach ($this->identifier as $idField) {
|
||||
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
|
||||
if (isset($this->associationMappings[$idField])) {
|
||||
// no composite pk as fk entity assumption:
|
||||
$columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name'];
|
||||
} else {
|
||||
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
|
||||
}
|
||||
}
|
||||
return $columnNames;
|
||||
} else {
|
||||
} else if(isset($this->fieldMappings[$this->identifier[0]])) {
|
||||
return array($this->fieldMappings[$this->identifier[0]]['columnName']);
|
||||
} else {
|
||||
// no composite pk as fk entity assumption:
|
||||
return array($this->associationMappings[$this->identifier[0]]['joinColumns'][0]['name']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1530,6 +1594,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.
|
||||
*
|
||||
@ -1601,4 +1733,4 @@ class ClassMetadataInfo
|
||||
{
|
||||
$this->versionField = $versionField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -194,7 +194,13 @@ class XmlDriver extends AbstractFileDriver
|
||||
}
|
||||
|
||||
// Evaluate <id ...> mappings
|
||||
$associationIds = array();
|
||||
foreach ($xmlRoot->id as $idElement) {
|
||||
if ((bool)$idElement['association-key'] == true) {
|
||||
$associationIds[(string)$idElement['fieldName']] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping = array(
|
||||
'id' => true,
|
||||
'fieldName' => (string)$idElement['name'],
|
||||
@ -235,6 +241,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
'targetEntity' => (string)$oneToOneElement['target-entity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToOneElement['fetch']);
|
||||
}
|
||||
@ -311,6 +321,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
'targetEntity' => (string)$manyToOneElement['target-entity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToOneElement['fetch']);
|
||||
}
|
||||
|
@ -135,9 +135,15 @@ class YamlDriver extends AbstractFileDriver
|
||||
}
|
||||
}
|
||||
|
||||
$associationIds = array();
|
||||
if (isset($element['id'])) {
|
||||
// Evaluate identifier settings
|
||||
foreach ($element['id'] as $name => $idElement) {
|
||||
if (isset($idElement['associationKey']) && $idElement['associationKey'] == true) {
|
||||
$associationIds[$name] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($idElement['type'])) {
|
||||
throw MappingException::propertyTypeIsRequired($className, $name);
|
||||
}
|
||||
@ -234,6 +240,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
'targetEntity' => $oneToOneElement['targetEntity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']);
|
||||
}
|
||||
@ -303,6 +313,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
'targetEntity' => $manyToOneElement['targetEntity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']);
|
||||
}
|
||||
|
@ -231,4 +231,43 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
{
|
||||
return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $targetEntity
|
||||
* @param string $targetField
|
||||
* @return self
|
||||
*/
|
||||
public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId($className, $targetEntity, $targetField)
|
||||
{
|
||||
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'.");
|
||||
}
|
||||
}
|
@ -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) {
|
||||
@ -466,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]];
|
||||
}
|
||||
@ -716,13 +725,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(
|
||||
@ -734,7 +751,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(
|
||||
@ -910,7 +935,22 @@ class BasicEntityPersister
|
||||
$columnList .= $this->_getSelectColumnSQL($field, $this->_class);
|
||||
}
|
||||
|
||||
$this->_selectColumnListSql = $columnList . $this->_getSelectJoinColumnsSQL($this->_class);
|
||||
foreach ($this->_class->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList) $columnList .= ', ';
|
||||
|
||||
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
|
||||
$columnList .= $this->_getSQLTableAlias($this->_class->name) . ".$srcColumn AS $columnAlias";
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
|
||||
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_selectColumnListSql = $columnList;
|
||||
|
||||
return $this->_selectColumnListSql;
|
||||
}
|
||||
@ -937,8 +977,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;
|
||||
}
|
||||
|
||||
@ -1023,33 +1070,6 @@ class BasicEntityPersister
|
||||
return "$sql AS $columnAlias";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL snippet for all join columns of the given class that are to be
|
||||
* placed in an SQL SELECT statement.
|
||||
*
|
||||
* @param $class
|
||||
* @return string
|
||||
* @todo Not reused... inline?
|
||||
*/
|
||||
private function _getSelectJoinColumnsSQL(ClassMetadata $class)
|
||||
{
|
||||
$sql = '';
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
|
||||
$sql .= ', ' . $this->_getSQLTableAlias($this->_class->name) . ".$srcColumn AS $columnAlias";
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
|
||||
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL table alias for the given class name.
|
||||
*
|
||||
@ -1137,7 +1157,6 @@ class BasicEntityPersister
|
||||
} else {
|
||||
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
|
||||
}
|
||||
|
||||
|
||||
$conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
|
||||
} else if ($assoc !== null) {
|
||||
@ -1169,7 +1188,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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -970,6 +994,29 @@ class SqlWalker implements TreeWalker
|
||||
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
|
||||
}
|
||||
|
||||
if ($class->containsForeignIdentifier) {
|
||||
// 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,
|
||||
|
@ -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);
|
||||
@ -285,10 +301,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;
|
||||
@ -409,13 +426,47 @@ class SchemaTool
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class metadata that is responsible for the definition of the referenced column name.
|
||||
*
|
||||
* Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its
|
||||
* not a simple field, go through all identifier field names that are associations recursivly and
|
||||
* find that referenced column name.
|
||||
*
|
||||
* TODO: Is there any way to make this code more pleasing?
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param string $referencedColumnName
|
||||
* @return array(ClassMetadata, referencedFieldName)
|
||||
*/
|
||||
private function getDefiningClass($class, $referencedColumnName)
|
||||
{
|
||||
$referencedFieldName = $class->getFieldName($referencedColumnName);
|
||||
|
||||
if ($class->hasField($referencedFieldName)) {
|
||||
return array($class, $referencedFieldName);
|
||||
} 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->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) {
|
||||
return $this->getDefiningClass(
|
||||
$this->_em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']),
|
||||
$class->getSingleAssociationReferencedJoinColumnName($fieldName)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather columns and fk constraints that are required for one part of relationship.
|
||||
*
|
||||
* @param array $joinColumns
|
||||
* @param \Doctrine\DBAL\Schema\Table $theJoinTable
|
||||
* @param ClassMetadata $class
|
||||
* @param \Doctrine\ORM\Mapping\AssociationMapping $mapping
|
||||
* @param array $mapping
|
||||
* @param array $primaryKeyColumns
|
||||
* @param array $uniqueConstraints
|
||||
*/
|
||||
@ -424,12 +475,13 @@ class SchemaTool
|
||||
$localColumns = array();
|
||||
$foreignColumns = array();
|
||||
$fkOptions = array();
|
||||
$foreignTableName = $class->getTableName();
|
||||
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
$columnName = $joinColumn['name'];
|
||||
$referencedFieldName = $class->getFieldName($joinColumn['referencedColumnName']);
|
||||
list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']);
|
||||
|
||||
if ( ! $class->hasField($referencedFieldName)) {
|
||||
if (!$definingClass) {
|
||||
throw new \Doctrine\ORM\ORMException(
|
||||
"Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ".
|
||||
$mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist."
|
||||
@ -445,7 +497,7 @@ class SchemaTool
|
||||
// It might exist already if the foreign key is mapped into a regular
|
||||
// property as well.
|
||||
|
||||
$fieldMapping = $class->getFieldMapping($referencedFieldName);
|
||||
$fieldMapping = $definingClass->getFieldMapping($referencedFieldName);
|
||||
|
||||
$columnDef = null;
|
||||
if (isset($joinColumn['columnDefinition'])) {
|
||||
@ -464,9 +516,7 @@ class SchemaTool
|
||||
$columnOptions['precision'] = $fieldMapping['precision'];
|
||||
}
|
||||
|
||||
$theJoinTable->addColumn(
|
||||
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
|
||||
);
|
||||
$theJoinTable->addColumn($columnName, $fieldMapping['type'], $columnOptions);
|
||||
}
|
||||
|
||||
if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
|
||||
@ -483,7 +533,7 @@ class SchemaTool
|
||||
}
|
||||
|
||||
$theJoinTable->addUnnamedForeignKeyConstraint(
|
||||
$class->getTableName(), $localColumns, $foreignColumns, $fkOptions
|
||||
$foreignTableName, $localColumns, $foreignColumns, $fkOptions
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -1894,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) {
|
||||
|
65
tests/Doctrine/Tests/Models/DDC117/DDC117ApproveChanges.php
Normal file
65
tests/Doctrine/Tests/Models/DDC117/DDC117ApproveChanges.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC117;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC117ApproveChanges
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="DDC117ArticleDetails")
|
||||
* @JoinColumn(name="details_id", referencedColumnName="article_id")
|
||||
*/
|
||||
private $articleDetails;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="DDC117Reference")
|
||||
* @JoinColumns({
|
||||
* @JoinColumn(name="source_id", referencedColumnName="source_id"),
|
||||
* @JoinColumn(name="target_id", referencedColumnName="target_id")
|
||||
* })
|
||||
*/
|
||||
private $reference;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="DDC117Translation")
|
||||
* @JoinColumns({
|
||||
* @JoinColumn(name="trans_article_id", referencedColumnName="article_id"),
|
||||
* @JoinColumn(name="trans_language", referencedColumnName="language")
|
||||
* })
|
||||
*/
|
||||
private $translation;
|
||||
|
||||
public function __construct($details, $reference, $translation)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
81
tests/Doctrine/Tests/Models/DDC117/DDC117Article.php
Normal file
81
tests/Doctrine/Tests/Models/DDC117/DDC117Article.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC117;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC117Article
|
||||
{
|
||||
/** @Id @Column(type="integer", name="article_id") @GeneratedValue */
|
||||
private $id;
|
||||
/** @Column */
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="DDC117Reference", mappedBy="source", cascade={"remove"})
|
||||
*/
|
||||
private $references;
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity="DDC117ArticleDetails", mappedBy="article", cascade={"persist", "remove"})
|
||||
*/
|
||||
private $details;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="DDC117Translation", mappedBy="article", cascade={"persist", "remove"})
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
public function getDetails()
|
||||
{
|
||||
return $this->details;
|
||||
}
|
||||
|
||||
public function resetText()
|
||||
{
|
||||
$this->details = null;
|
||||
}
|
||||
|
||||
public function getTranslations()
|
||||
{
|
||||
return $this->translations;
|
||||
}
|
||||
}
|
39
tests/Doctrine/Tests/Models/DDC117/DDC117ArticleDetails.php
Normal file
39
tests/Doctrine/Tests/Models/DDC117/DDC117ArticleDetails.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC117;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
54
tests/Doctrine/Tests/Models/DDC117/DDC117Editor.php
Normal file
54
tests/Doctrine/Tests/Models/DDC117/DDC117Editor.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC117;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC117Editor
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="DDC117Translation", inversedBy="reviewedByEditors")
|
||||
* @JoinTable(
|
||||
* inverseJoinColumns={
|
||||
* @JoinColumn(name="article_id", referencedColumnName="article_id"),
|
||||
* @JoinColumn(name="language", referencedColumnName="language")
|
||||
* },
|
||||
* joinColumns={
|
||||
* @JoinColumn(name="editor_id", referencedColumnName="id")
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $reviewingTranslations;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="DDC117Translation", inversedBy="lastTranslatedBy")
|
||||
* @JoinColumns({
|
||||
* @JoinColumn(name="lt_article_id", referencedColumnName="article_id"),
|
||||
* @JoinColumn(name="lt_language", referencedColumnName="language")
|
||||
* })
|
||||
*/
|
||||
public $lastTranslation;
|
||||
|
||||
public function __construct($name = "")
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->reviewingTranslations = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
}
|
||||
|
||||
public function addLastTranslation(DDC117Translation $t)
|
||||
{
|
||||
$this->lastTranslation = $t;
|
||||
$t->lastTranslatedBy[] = $this;
|
||||
}
|
||||
}
|
64
tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php
Normal file
64
tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC117;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
65
tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php
Normal file
65
tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC117;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="DDC117Editor", mappedBy="reviewingTranslations")
|
||||
*/
|
||||
public $reviewedByEditors;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="DDC117Editor", mappedBy="lastTranslation")
|
||||
*/
|
||||
public $lastTranslatedBy;
|
||||
|
||||
public function __construct($article, $language, $title)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
410
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php
Normal file
410
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php
Normal file
@ -0,0 +1,410 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
private $article1;
|
||||
private $article2;
|
||||
private $reference;
|
||||
private $translation;
|
||||
private $articleDetails;
|
||||
|
||||
protected function setUp() {
|
||||
$this->useModelSet('ddc117');
|
||||
parent::setUp();
|
||||
|
||||
$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("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 "."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("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 "."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("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 "."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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testUpdateAssocationEntity()
|
||||
{
|
||||
$idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id());
|
||||
|
||||
$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("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria);
|
||||
|
||||
$this->assertEquals('New Description!!', $mapRef->getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testFetchDql()
|
||||
{
|
||||
$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("Doctrine\Tests\Models\DDC117\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("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria);
|
||||
|
||||
$this->_em->remove($refRep);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$this->assertNull($this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testDqlRemoveCompositeElement()
|
||||
{
|
||||
$idCriteria = array('source' => $this->article1->id(), 'target' => $this->article2->id());
|
||||
|
||||
$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("Doctrine\Tests\Models\DDC117\DDC117Reference", $idCriteria));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testInverseSideAccess()
|
||||
{
|
||||
$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("Doctrine\Tests\Models\DDC117\DDC117Reference", $this->reference);
|
||||
$this->assertSame($this->article1, $this->reference->source());
|
||||
}
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$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("Doctrine\Tests\Models\DDC117\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('Doctrine\Tests\Models\DDC117\DDC117Translation', $idCriteria);
|
||||
$this->assertType('Doctrine\Tests\Models\DDC117\DDC117Translation', $this->translation);
|
||||
|
||||
$this->assertSame($this->translation, $this->_em->find('Doctrine\Tests\Models\DDC117\DDC117Translation', $idCriteria));
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$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('Doctrine\Tests\Models\DDC117\DDC117Translation', $this->translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testMixedCompositeKeyViolateUniqueness()
|
||||
{
|
||||
$this->article1 = $this->_em->find('Doctrine\Tests\Models\DDC117\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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
public function testOneToOneCascadePersist()
|
||||
{
|
||||
if (!$this->_em->getConnection()->getDatabasePlatform()->prefersSequences()) {
|
||||
$this->markTestSkipped('Test only works with databases that prefer sequences as ID strategy.');
|
||||
}
|
||||
|
||||
$this->article1 = new DDC117Article("Foo");
|
||||
|
||||
$this->articleDetails = new DDC117ArticleDetails($this->article1, "Very long text");
|
||||
|
||||
$this->_em->persist($this->article1);
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testLoadManyToManyCollectionOfForeignKeyEntities()
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testClearManyToManyCollectionOfForeignKeyEntities()
|
||||
{
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testLoadInverseManyToManyCollection()
|
||||
{
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
public function testLoadOneToManyOfSourceEntityWithAssociationIdentifier()
|
||||
{
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DDC117Editor
|
||||
*/
|
||||
private function loadEditorFixture()
|
||||
{
|
||||
$editor = new DDC117Editor("beberlei");
|
||||
|
||||
/* @var $article1 DDC117Article */
|
||||
$article1 = $this->_em->find(get_class($this->article1), $this->article1->id());
|
||||
foreach ($article1->getTranslations() AS $translation) {
|
||||
$editor->reviewingTranslations[] = $translation;
|
||||
}
|
||||
|
||||
/* @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);
|
||||
}
|
||||
}
|
215
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC881Test.php
Normal file
215
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC881Test.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
require_once __DIR__ . '/../../../TestInit.php';
|
||||
|
||||
class DDC881Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
try {
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC881User'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC881Phonenumber'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC881Phonecall'),
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
* @group DDC-881
|
||||
*/
|
||||
public function testIssue()
|
||||
{
|
||||
/* Create two test users: albert and alfons */
|
||||
$albert = new DDC881User;
|
||||
$albert->setName("albert");
|
||||
$this->_em->persist($albert);
|
||||
|
||||
$alfons = new DDC881User;
|
||||
$alfons->setName("alfons");
|
||||
$this->_em->persist($alfons);
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
/* Assign two phone numbers to each user */
|
||||
$phoneAlbert1 = new DDC881PhoneNumber();
|
||||
$phoneAlbert1->setUser($albert);
|
||||
$phoneAlbert1->setId(1);
|
||||
$phoneAlbert1->setPhoneNumber("albert home: 012345");
|
||||
$this->_em->persist($phoneAlbert1);
|
||||
|
||||
$phoneAlbert2 = new DDC881PhoneNumber();
|
||||
$phoneAlbert2->setUser($albert);
|
||||
$phoneAlbert2->setId(2);
|
||||
$phoneAlbert2->setPhoneNumber("albert mobile: 67890");
|
||||
$this->_em->persist($phoneAlbert2);
|
||||
|
||||
$phoneAlfons1 = new DDC881PhoneNumber();
|
||||
$phoneAlfons1->setId(1);
|
||||
$phoneAlfons1->setUser($alfons);
|
||||
$phoneAlfons1->setPhoneNumber("alfons home: 012345");
|
||||
$this->_em->persist($phoneAlfons1);
|
||||
|
||||
$phoneAlfons2 = new DDC881PhoneNumber();
|
||||
$phoneAlfons2->setId(2);
|
||||
$phoneAlfons2->setUser($alfons);
|
||||
$phoneAlfons2->setPhoneNumber("alfons mobile: 67890");
|
||||
$this->_em->persist($phoneAlfons2);
|
||||
|
||||
/* We call alfons and albert once on their mobile numbers */
|
||||
$call1 = new DDC881PhoneCall();
|
||||
$call1->setPhoneNumber($phoneAlfons2);
|
||||
$this->_em->persist($call1);
|
||||
|
||||
$call2 = new DDC881PhoneCall();
|
||||
$call2->setPhoneNumber($phoneAlbert2);
|
||||
$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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC881User
|
||||
{
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* @OneToMany(targetEntity="DDC881PhoneNumber",mappedBy="id")
|
||||
*/
|
||||
private $phoneNumbers;
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC881PhoneNumber
|
||||
{
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="DDC881User",cascade={"all"})
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
public function setUser(DDC881User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setPhoneNumber($phoneNumber)
|
||||
{
|
||||
$this->phonenumber = $phoneNumber;
|
||||
}
|
||||
|
||||
public function getCalls()
|
||||
{
|
||||
return $this->calls;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC881PhoneCall
|
||||
{
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* @ManyToOne(targetEntity="DDC881PhoneNumber", inversedBy="calls", cascade={"all"})
|
||||
* @JoinColumns({
|
||||
* @JoinColumn(name="phonenumber_id", referencedColumnName="id"),
|
||||
* @JoinColumn(name="user_id", referencedColumnName="user_id")
|
||||
* })
|
||||
*/
|
||||
private $phonenumber;
|
||||
/**
|
||||
* @Column(type="string",nullable=true)
|
||||
*/
|
||||
private $callDate;
|
||||
|
||||
public function setPhoneNumber(DDC881PhoneNumber $phoneNumber)
|
||||
{
|
||||
$this->phonenumber = $phoneNumber;
|
||||
}
|
||||
|
||||
public function getPhoneNumber()
|
||||
{
|
||||
return $this->phonenumber;
|
||||
}
|
||||
}
|
@ -312,4 +312,71 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase
|
||||
|
||||
$this->assertEquals('doctrineglobal_article_cmsuser', $cm->associationMappings['author']['joinTable']['name']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user