diff --git a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php index 81aee6584..40b1be958 100644 --- a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php @@ -25,8 +25,6 @@ use Doctrine\ORM\Persisters\SqlExpressionVisitor; use Doctrine\ORM\Persisters\SqlValueVisitor; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; -use Doctrine\ORM\UnitOfWork; -use Doctrine\ORM\Utility\PersisterHelper; /** * Persister for many-to-many collections. @@ -376,20 +374,22 @@ class ManyToManyPersister extends AbstractCollectionPersister $mapping = $collection->getMapping(); $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); - $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); - $params = array(); - $types = array(); - - foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { - $field = isset($sourceClass->fieldNames[$refColumnName]) - ? $sourceClass->fieldNames[$refColumnName] - : $sourceClass->getFieldForColumn($columnName); - - $params[] = $identifier[$field]; - $types[] = PersisterHelper::getTypeOfField($field, $sourceClass, $this->em); + // Optimization for single column identifier + if (count($mapping['relationToSourceKeyColumns']) === 1) { + return array(reset($identifier)); } - return array($params, $types); + // Composite identifier + $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); + $params = array(); + + foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { + $params[] = isset($sourceClass->fieldNames[$refColumnName]) + ? $identifier[$sourceClass->fieldNames[$refColumnName]] + : $identifier[$sourceClass->getFieldForColumn($columnName)]; + } + + return $params; } /** @@ -534,28 +534,24 @@ class ManyToManyPersister extends AbstractCollectionPersister $indexBy = $mapping['indexBy']; $id = $this->uow->getEntityIdentifier($collection->getOwner()); - $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); - $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); + $targetEntity = $this->em->getClassMetadata($mapping['targetEntity']); - if ( ! $mapping['isOwningSide']) { + if (! $mapping['isOwningSide']) { $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']); - $mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']]; + $mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']]; $joinColumns = $mapping['joinTable']['joinColumns']; - $sourceRelationMode = 'relationToTargetKeyColumns'; - $targetRelationMode = 'relationToSourceKeyColumns'; + $relationMode = 'relationToTargetKeyColumns'; } else { - $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $joinColumns = $mapping['joinTable']['inverseJoinColumns']; - $sourceRelationMode = 'relationToSourceKeyColumns'; - $targetRelationMode = 'relationToTargetKeyColumns'; + $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); + $relationMode = 'relationToSourceKeyColumns'; } - $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform) . ' t'; + $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t'; $whereClauses = array(); $params = array(); - $types = array(); - $joinNeeded = ! in_array($indexBy, $targetClass->identifier); + $joinNeeded = !in_array($indexBy, $targetEntity->identifier); if ($joinNeeded) { // extra join needed if indexBy is not a @id $joinConditions = array(); @@ -563,25 +559,21 @@ class ManyToManyPersister extends AbstractCollectionPersister foreach ($joinColumns as $joinTableColumn) { $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName']; } - $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); + $tableName = $this->quoteStrategy->getTableName($targetEntity, $this->platform); $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); - $columnName = $targetClass->getColumnName($indexBy); - $whereClauses[] = 'tr.' . $columnName . ' = ?'; + $whereClauses[] = 'tr.' . $targetEntity->getColumnName($indexBy) . ' = ?'; $params[] = $key; - $types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em); + } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { - if (isset($mapping[$sourceRelationMode][$joinTableColumn])) { - $column = $mapping[$sourceRelationMode][$joinTableColumn]; - + if (isset($mapping[$relationMode][$joinTableColumn])) { $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; $params[] = $id[$targetEntity->getFieldForColumn($mapping[$relationMode][$joinTableColumn])]; } elseif (!$joinNeeded) { $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; $params[] = $key; - $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); } } @@ -594,7 +586,7 @@ class ManyToManyPersister extends AbstractCollectionPersister } } - return array($quotedJoinTable, $whereClauses, $params, $types); + return array($quotedJoinTable, $whereClauses, $params); } /** @@ -626,7 +618,6 @@ class ManyToManyPersister extends AbstractCollectionPersister $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); $whereClauses = array(); $params = array(); - $types = array(); foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; @@ -634,11 +625,6 @@ class ManyToManyPersister extends AbstractCollectionPersister if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { $params[] = $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; - $params[] = $targetClass->containsForeignIdentifier - ? $targetId[$targetClass->getFieldForColumn($column)] - : $targetId[$targetClass->fieldNames[$column]]; - $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); - continue; } diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 679bb7857..537961420 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -710,7 +710,6 @@ class BasicEntityPersister implements EntityPersister { $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); list($params, $types) = $this->expandParameters($criteria); - $stmt = $this->conn->executeQuery($sql, $params, $types); if ($entity !== null) { @@ -863,12 +862,12 @@ class BasicEntityPersister implements EntityPersister list($params, $types) = $valueVisitor->getParamsAndTypes(); foreach ($params as $param) { - $sqlParams[] = PersisterHelper::getValue($param, $this->em); + $sqlParams[] = $this->getValue($param); } foreach ($types as $type) { list($field, $value) = $type; - $sqlTypes[] = $this->getType($field, $value, $this->class); + $sqlTypes[] = $this->getType($field, $value); } return array($sqlParams, $sqlTypes); @@ -966,12 +965,11 @@ class BasicEntityPersister implements EntityPersister */ private function getManyToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { - $criteria = array(); - $parameters = array(); + $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); + $class = $sourceClass; + $association = $assoc; + $criteria = array(); - $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); - $class = $sourceClass; - $association = $assoc; if ( ! $assoc['isOwningSide']) { $class = $this->em->getClassMetadata($assoc['targetEntity']); @@ -986,8 +984,8 @@ class BasicEntityPersister implements EntityPersister foreach ($joinColumns as $joinColumn) { - $sourceKeyColumn = $joinColumn['referencedColumnName']; - $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); + $sourceKeyColumn = $joinColumn['referencedColumnName']; + $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); switch (true) { case $sourceClass->containsForeignIdentifier: @@ -1014,11 +1012,10 @@ class BasicEntityPersister implements EntityPersister } $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; - $parameters[] = array('value' => $value, 'field' => $field, 'class' => $sourceClass); } $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); - list($params, $types) = $this->expandToManyParameters($parameters); + list($params, $types) = $this->expandParameters($criteria); return $this->conn->executeQuery($sql, $params, $types); } @@ -1306,13 +1303,16 @@ class BasicEntityPersister implements EntityPersister $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($assoc['joinColumns'] as $joinColumn) { + $type = null; $isIdentifier = isset($assoc['id']) && $assoc['id'] === true; $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $resultColumnName = $this->getSQLColumnAlias($joinColumn['name']); $columnList[] = $this->getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $quotedColumn . ' AS ' . $resultColumnName; - $type = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); + if (isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) { + $type = $targetClass->fieldMappings[$targetClass->fieldNames[$joinColumn['referencedColumnName']]]['type']; + } $this->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, $isIdentifier, $type); } @@ -1713,9 +1713,7 @@ class BasicEntityPersister implements EntityPersister */ private function getOneToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { - $criteria = array(); - $parameters = array(); - + $criteria = array(); $owningAssoc = $this->class->associationMappings[$assoc['mappedBy']]; $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); @@ -1732,20 +1730,15 @@ class BasicEntityPersister implements EntityPersister } $criteria[$tableAlias . "." . $targetKeyColumn] = $value; - $parameters[] = array('value' => $value, 'field' => $field, 'class' => $sourceClass); continue; } - $field = $sourceClass->fieldNames[$sourceKeyColumn]; - $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - - $criteria[$tableAlias . "." . $targetKeyColumn] = $value; - $parameters[] = array('value' => $value, 'field' => $field, 'class' => $sourceClass); + $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); - list($params, $types) = $this->expandToManyParameters($parameters); + list($params, $types) = $this->expandParameters($criteria); return $this->conn->executeQuery($sql, $params, $types); } @@ -1763,53 +1756,25 @@ class BasicEntityPersister implements EntityPersister continue; // skip null values. } - $types[] = $this->getType($field, $value, $this->class); - $params[] = PersisterHelper::getValue($value, $this->em); + $types[] = $this->getType($field, $value); + $params[] = $this->getValue($value); } return array($params, $types); } /** - * Expands the parameters from the given criteria and use the correct binding types if found, - * specialized for OneToMany or ManyToMany associations. + * Infers field type to be used by parameter type casting. * - * @param array $criteria + * @param string $field + * @param mixed $value * - * @return array - */ - private function expandToManyParameters($criteria) - { - $params = array(); - $types = array(); - - foreach ($criteria as $criterion) { - if ($criterion['value'] === null) { - continue; // skip null values. - } - - $types[] = $this->getType($criterion['field'], $criterion['value'], $criterion['class']); - $params[] = PersisterHelper::getValue($criterion['value'], $this->em); - } - - return array($params, $types); - } - - /** - * Infers the binding type of a field by parameter type casting. - * - * @param string $fieldName - * @param mixed $value - * @param ClassMetadata|null $class - * - * @return int|string|null + * @return integer * * @throws \Doctrine\ORM\Query\QueryException */ - private function getType($fieldName, $value, ClassMetadata $class) + private function getType($field, $value) { - $type = Helper::getTypeOfField($fieldName, $class, $this->em); - switch (true) { case (isset($this->class->fieldMappings[$field])): $type = $this->class->fieldMappings[$field]['type']; @@ -1837,8 +1802,8 @@ class BasicEntityPersister implements EntityPersister break; - case (isset($class->associationMappings[$field])): - $assoc = $class->associationMappings[$field]; + case (isset($this->class->associationMappings[$field])): + $assoc = $this->class->associationMappings[$field]; if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw Query\QueryException::associationPathCompositeKeyNotSupported(); @@ -1866,6 +1831,44 @@ class BasicEntityPersister implements EntityPersister return $type; } + /** + * Retrieves parameter value. + * + * @param mixed $value + * + * @return mixed + */ + private function getValue($value) + { + if ( ! is_array($value)) { + return $this->getIndividualValue($value); + } + + $newValue = array(); + + foreach ($value as $itemValue) { + $newValue[] = $this->getIndividualValue($itemValue); + } + + return $newValue; + } + + /** + * Retrieves an individual parameter value. + * + * @param mixed $value + * + * @return mixed + */ + private function getIndividualValue($value) + { + if ( ! is_object($value) || ! $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) { + return $value; + } + + return $this->em->getUnitOfWork()->getSingleIdentifierValue($value); + } + /** * {@inheritdoc} */ @@ -1883,21 +1886,20 @@ class BasicEntityPersister implements EntityPersister . $this->getLockTablesSql(null) . ' WHERE ' . $this->getSelectConditionSQL($criteria); - list($params, $types) = $this->expandParameters($criteria); + list($params) = $this->expandParameters($criteria); if (null !== $extraConditions) { - $sql .= ' AND ' . $this->getSelectConditionCriteriaSQL($extraConditions); - list($extraParams, $extraTypes) = $this->expandCriteriaParameters($extraConditions); + $sql .= ' AND ' . $this->getSelectConditionCriteriaSQL($extraConditions); + list($criteriaParams, $values) = $this->expandCriteriaParameters($extraConditions); - $params = array_merge($params, $extraParams); - $types = array_merge($types, $extraTypes); + $params = array_merge($params, $criteriaParams); } if ($filterSql = $this->generateFilterConditionSQL($this->class, $alias)) { $sql .= ' AND ' . $filterSql; } - return (bool) $this->conn->fetchColumn($sql, $params, 0, $types); + return (bool) $this->conn->fetchColumn($sql, $params); } /** @@ -1920,9 +1922,7 @@ class BasicEntityPersister implements EntityPersister } /** - * @param string $columnName - * - * @return string + * {@inheritdoc} */ public function getSQLColumnAlias($columnName) { diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php index ad5a2dd34..948aad2ef 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php @@ -19,16 +19,6 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmFunctionalTestCase if ($modelSet == "customtype") { continue; } - - // DDC-3380: Register DBAL type for these modelsets - if (substr($modelSet, 0, 4) == 'vct_') { - if (DBALType::hasType('rot13')) { - DBALType::overrideType('rot13', 'Doctrine\Tests\DbalTypes\Rot13Type'); - } else { - DBALType::addType('rot13', 'Doctrine\Tests\DbalTypes\Rot13Type'); - } - } - $modelSets[] = array($modelSet); } return $modelSets; diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 0520a15ef..cfced6d61 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -2,6 +2,7 @@ namespace Doctrine\Tests; +use Doctrine\DBAL\Types\Type; use Doctrine\Tests\EventListener\CacheMetadataListener; use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Cache\DefaultCacheFactory; @@ -504,6 +505,8 @@ abstract class OrmFunctionalTestCase extends OrmTestCase */ protected function setUp() { + $this->setUpDBALTypes(); + $forceCreateTables = false; if ( ! isset(static::$_sharedConn)) { @@ -691,4 +694,16 @@ abstract class OrmFunctionalTestCase extends OrmTestCase { return count($this->_sqlLoggerStack->queries); } + + /** + * Configures DBAL types required in tests + */ + protected function setUpDBALTypes() + { + if (Type::hasType('rot13')) { + Type::overrideType('rot13', 'Doctrine\Tests\DbalTypes\Rot13Type'); + } else { + Type::addType('rot13', 'Doctrine\Tests\DbalTypes\Rot13Type'); + } + } }