From 3d17cb0d607e1c8a876905fc6b9a444656acff9d Mon Sep 17 00:00:00 2001 From: romanb Date: Mon, 17 Aug 2009 17:58:16 +0000 Subject: [PATCH] [2.0] Applied fixes for character casing issues. Simplified inheritance mapping and improved handling of outer joins in Class Table Inheritance. --- lib/Doctrine/Common/Annotations/Parser.php | 7 ++ .../DBAL/Schema/MySqlSchemaManager.php | 30 +++---- .../ORM/Internal/Hydration/ObjectHydrator.php | 16 +--- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 53 +++++++++--- .../ORM/Mapping/ClassMetadataFactory.php | 36 +++++++- .../ORM/Mapping/Driver/AnnotationDriver.php | 8 +- .../Mapping/Driver/DoctrineAnnotations.php | 59 +++++++------ .../Persisters/JoinedSubclassPersister.php | 19 +++-- .../Persisters/StandardEntityPersister.php | 25 ++---- lib/Doctrine/ORM/Query/Parser.php | 2 +- lib/Doctrine/ORM/Query/SqlWalker.php | 83 ++++++++++--------- lib/Doctrine/ORM/Tools/SchemaTool.php | 14 +++- .../Tests/Models/Company/CompanyEmployee.php | 2 - .../Tests/Models/Company/CompanyManager.php | 1 - .../Tests/Models/Company/CompanyPerson.php | 14 ++-- .../Functional/ClassTableInheritanceTest.php | 3 + .../ORM/Functional/Locking/OptimisticTest.php | 4 +- .../Functional/SingleTableInheritanceTest.php | 4 +- .../Tests/ORM/Mapping/ClassMetadataTest.php | 2 +- .../ORM/Query/SelectSqlGenerationTest.php | 7 +- 20 files changed, 229 insertions(+), 160 deletions(-) diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index fa57fe9d2..854e8018e 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -269,6 +269,13 @@ class Parser public function Values() { $values = array(); + + // Handle the case of a single array as value, i.e. @Foo({....}) + if ($this->_lexer->isNextToken('{')) { + $values['value'] = $this->Value(); + return $values; + } + $values[] = $this->Value(); while ($this->_lexer->isNextToken(',')) { diff --git a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php index 4dc29eae4..34b0404f0 100644 --- a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php @@ -34,7 +34,7 @@ class MySqlSchemaManager extends AbstractSchemaManager { protected function _getPortableViewDefinition($view) { - return $view['table_name']; + return $view['TABLE_NAME']; } protected function _getPortableTableDefinition($table) @@ -45,8 +45,8 @@ class MySqlSchemaManager extends AbstractSchemaManager protected function _getPortableUserDefinition($user) { return array( - 'user' => $user['user'], - 'password' => $user['password'], + 'user' => $user['User'], + 'password' => $user['Password'], ); } @@ -83,12 +83,12 @@ class MySqlSchemaManager extends AbstractSchemaManager protected function _getPortableDatabaseDefinition($database) { - return $database['database']; + return $database['Database']; } protected function _getPortableTableColumnDefinition($tableColumn) { - $dbType = strtolower($tableColumn['type']); + $dbType = strtolower($tableColumn['Type']); $dbType = strtok($dbType, '(), '); if ($dbType == 'national') { $dbType = strtok('(), '); @@ -117,28 +117,28 @@ class MySqlSchemaManager extends AbstractSchemaManager if (preg_match('/^(is|has)/', $tableColumn['name'])) { $type = array_reverse($type); } - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); $length = 1; break; case 'smallint': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); $length = 2; break; case 'mediumint': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); $length = 3; break; case 'int': case 'integer': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); $length = 4; break; case 'bigint': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); + $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); $length = 8; break; case 'tinytext': @@ -249,12 +249,12 @@ class MySqlSchemaManager extends AbstractSchemaManager $values = isset($def['values']) ? $def['values'] : array(); $column = array( - 'name' => $tableColumn['field'], + 'name' => $tableColumn['Field'], 'values' => $values, - 'primary' => (bool) (strtolower($tableColumn['key']) == 'pri'), - 'default' => $tableColumn['default'], - 'notnull' => (bool) ($tableColumn['null'] != 'YES'), - 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false), + 'primary' => (bool) (strtolower($tableColumn['Key']) == 'pri'), + 'default' => $tableColumn['Default'], + 'notnull' => (bool) ($tableColumn['Null'] != 'YES'), + 'autoincrement' => (bool) (strpos($tableColumn['Extra'], 'auto_increment') !== false), ); $column = array_merge($column, $def); diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index f73b8cc35..bb5b62063 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -40,7 +40,6 @@ class ObjectHydrator extends AbstractHydrator */ /* Local ClassMetadata cache to avoid going to the EntityManager all the time. */ private $_ce = array(); - private $_discriminatorMap = array(); /* * The following parts are reinitialized on every hydration run. */ @@ -76,17 +75,6 @@ class ObjectHydrator extends AbstractHydrator if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $class; - // Gather class descriptors and discriminator values of subclasses, if necessary - if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { - $this->_discriminatorMap[$className][$class->discriminatorValue] = $className; - foreach (array_merge($class->parentClasses, $class->subClasses) as $className) { - $otherClass = $this->_em->getClassMetadata($className); - $value = $otherClass->discriminatorValue; - $this->_ce[$className] = $otherClass; - $this->_discriminatorMap[$class->name][$value] = $className; - $this->_discriminatorMap[$className][$value] = $className; - } - } } // Remember which associations are "fetch joined" @@ -172,7 +160,7 @@ class ObjectHydrator extends AbstractHydrator $className = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; - $className = $this->_discriminatorMap[$className][$data[$discrColumn]]; + $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } @@ -180,7 +168,7 @@ class ObjectHydrator extends AbstractHydrator // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! $this->_allowPartialObjects) { - foreach ($this->_ce[$className]->associationMappings as $field => $assoc) { + foreach ($this->_getClassMetadata($className)->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associatons already. if ( ! isset($this->_fetchedAssociations[$className][$field])) { if ($assoc->isOneToOne()) { diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 78dafc8c9..059abcd76 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -254,13 +254,22 @@ final class ClassMetadata public $fieldNames = array(); /** - * An array of column names. Keys are field names and values column names. + * A map of field names to column names. Keys are field names and values column names. * Used to look up column names from field names. * This is the reverse lookup map of $_fieldNames. * * @var array */ public $columnNames = array(); + + /** + * A map of column names as they appear in an SQL result set to column names as they + * are defined in the mapping. This includes the columns of all mapped fields as well + * as any join columns and discriminator columns. + * + * @var array + */ + public $resultColumnNames = array(); /** * Whether to automatically OUTER JOIN subtypes when a basetype is queried. @@ -281,6 +290,17 @@ final class ClassMetadata * @see _discriminatorColumn */ public $discriminatorValue; + + /** + * The discriminator map of all mapped classes in the hierarchy. + * + * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies + * where a discriminator column is used. + * + * @var mixed + * @see _discriminatorColumn + */ + public $discriminatorMap = array(); /** * The definition of the descriminator column used in JOINED and SINGLE_TABLE @@ -1139,16 +1159,19 @@ final class ClassMetadata } /** - * Sets the subclasses of the mapped class. - * - * All entity classes that participate in a hierarchy and have subclasses - * need to declare them this way. + * Sets the mapped subclasses of this class. * - * @param array $subclasses The names of all subclasses. + * @param array $subclasses The names of all mapped subclasses. */ public function setSubclasses(array $subclasses) { - $this->subClasses = $subclasses; + foreach ($subclasses as $subclass) { + if (strpos($subclass, '\\') === false) { + $this->subClasses[] = $this->namespace . '\\' . $subclass; + } else { + $this->subClasses[] = $subclass; + } + } } /** @@ -1572,14 +1595,24 @@ final class ClassMetadata } /** - * Sets the dsicriminator value used by this class. + * Sets the dsicriminator values used by this class. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. * * @param array $map */ - public function setDiscriminatorValue($value) + public function setDiscriminatorMap(array $map) { - $this->discriminatorValue = $value; + foreach ($map as $value => $className) { + if (strpos($className, '\\') === false) { + $className = $this->namespace . '\\' . $className; + } + $this->discriminatorMap[$value] = $className; + if ($this->name == $className) { + $this->discriminatorValue = $value; + } else if (is_subclass_of($className, $this->name)) { + $this->subClasses[] = $className; + } + } } /** diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index e4b74a6ca..93ec7fab1 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -177,6 +177,7 @@ class ClassMetadataFactory $class->setIdentifier($parent->identifier); $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); + $class->setDiscriminatorMap($parent->discriminatorMap); } // Invoke driver @@ -291,6 +292,11 @@ class ClassMetadataFactory if (isset($class->fieldMappings[$name]['inherited']) && ! isset($class->fieldMappings[$name]['id']) || isset($class->inheritedAssociationFields[$name]) || ($versioned && $versionField == $name)) { + if (isset($class->columnNames[$name])) { + // Add column mapping for SQL result sets + $columnName = $class->columnNames[$name]; + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; + } continue; } @@ -300,11 +306,20 @@ class ClassMetadataFactory foreach ($assoc->targetToSourceKeyColumns as $sourceCol) { $columns[] = $assoc->getQuotedJoinColumnName($sourceCol, $this->_targetPlatform); $values[] = '?'; + // Add column mapping for SQL result sets + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($sourceCol)] = $sourceCol; } } } else if ($class->name != $class->rootEntityName || ! $class->isIdGeneratorIdentity() || $class->identifier[0] != $name) { $columns[] = $class->getQuotedColumnName($name, $this->_targetPlatform); $values[] = '?'; + // Add column mapping for SQL result sets + $columnName = $class->columnNames[$name]; + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; + } else { + // Add column mapping for SQL result sets + $columnName = $class->columnNames[$name]; + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; } } } else { @@ -319,19 +334,34 @@ class ClassMetadataFactory foreach ($assoc->targetToSourceKeyColumns as $sourceCol) { $columns[] = $assoc->getQuotedJoinColumnName($sourceCol, $this->_targetPlatform); $values[] = '?'; + // Add column mapping for SQL result sets + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($sourceCol)] = $sourceCol; } } } else if ($class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $class->identifier[0] != $name) { $columns[] = $class->getQuotedColumnName($name, $this->_targetPlatform); $values[] = '?'; + // Add column mapping for SQL result sets + $columnName = $class->columnNames[$name]; + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; + } else { + // Add column mapping for SQL result sets + $columnName = $class->columnNames[$name]; + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; } } } // Add discriminator column to the INSERT SQL if necessary - if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined() && $class->name == $class->rootEntityName) { - $columns[] = $class->getQuotedDiscriminatorColumnName($this->_targetPlatform); - $values[] = '?'; + if (isset($class->discriminatorColumn['name'])) { + if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined() + && $class->name == $class->rootEntityName) { + $columns[] = $class->getQuotedDiscriminatorColumnName($this->_targetPlatform); + $values[] = '?'; + } + // Add column mapping for SQL result sets + $columnName = $class->discriminatorColumn['name']; + $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; } $class->insertSql = 'INSERT INTO ' . diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index c64a57723..682d1b4f0 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -95,10 +95,10 @@ class AnnotationDriver implements Driver )); } - // Evaluate DiscriminatorValue annotation - if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorValue'])) { - $discrValueAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorValue']; - $metadata->setDiscriminatorValue($discrValueAnnot->value); + // Evaluate DiscriminatorMap annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) { + $discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap']; + $metadata->setDiscriminatorMap($discrMapAnnot->value); } // Evaluate DoctrineSubClasses annotation diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 8c1004f39..499a9cc23 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -25,26 +25,25 @@ use \Doctrine\Common\Annotations\Annotation; /* Annotations */ -final class Entity extends \Doctrine\Common\Annotations\Annotation { +final class Entity extends Annotation { public $repositoryClass; } final class MappedSuperclass extends Annotation {} -final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {} -final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation { +final class InheritanceType extends Annotation {} +final class DiscriminatorColumn extends Annotation { public $name; public $fieldName; // field name used in non-object hydration (array/scalar) public $type; public $length; } -final class DiscriminatorValue extends \Doctrine\Common\Annotations\Annotation {} -final class SubClasses extends \Doctrine\Common\Annotations\Annotation {} -final class Id extends \Doctrine\Common\Annotations\Annotation {} -final class GeneratedValue extends \Doctrine\Common\Annotations\Annotation { +final class DiscriminatorMap extends Annotation {} +/*final class SubClasses extends Annotation {}*/ +final class Id extends Annotation {} +final class GeneratedValue extends Annotation { public $strategy; - //public $generator; } -final class Version extends \Doctrine\Common\Annotations\Annotation {} -final class JoinColumn extends \Doctrine\Common\Annotations\Annotation { +final class Version extends Annotation {} +final class JoinColumn extends Annotation { public $name; public $fieldName; // field name used in non-object hydration (array/scalar) public $referencedColumnName; @@ -53,8 +52,8 @@ final class JoinColumn extends \Doctrine\Common\Annotations\Annotation { public $onDelete; public $onUpdate; } -final class JoinColumns extends \Doctrine\Common\Annotations\Annotation {} -final class Column extends \Doctrine\Common\Annotations\Annotation { +final class JoinColumns extends Annotation {} +final class Column extends Annotation { public $type; public $length; public $unique = false; @@ -62,7 +61,7 @@ final class Column extends \Doctrine\Common\Annotations\Annotation { public $default; public $name; } -final class OneToOne extends \Doctrine\Common\Annotations\Annotation { +final class OneToOne extends Annotation { public $targetEntity; public $mappedBy; public $cascade; @@ -70,54 +69,54 @@ final class OneToOne extends \Doctrine\Common\Annotations\Annotation { public $optional; public $orphanRemoval = false; } -final class OneToMany extends \Doctrine\Common\Annotations\Annotation { +final class OneToMany extends Annotation { public $mappedBy; public $targetEntity; public $cascade; public $fetch; public $orphanRemoval = false; } -final class ManyToOne extends \Doctrine\Common\Annotations\Annotation { +final class ManyToOne extends Annotation { public $targetEntity; public $cascade; public $fetch; public $optional; } -final class ManyToMany extends \Doctrine\Common\Annotations\Annotation { +final class ManyToMany extends Annotation { public $targetEntity; public $mappedBy; public $cascade; public $fetch; } -final class ElementCollection extends \Doctrine\Common\Annotations\Annotation { +final class ElementCollection extends Annotation { public $tableName; } -final class Table extends \Doctrine\Common\Annotations\Annotation { +final class Table extends Annotation { public $name; public $schema; } -final class JoinTable extends \Doctrine\Common\Annotations\Annotation { +final class JoinTable extends Annotation { public $name; public $schema; public $joinColumns; public $inverseJoinColumns; } -final class SequenceGenerator extends \Doctrine\Common\Annotations\Annotation { +final class SequenceGenerator extends Annotation { public $sequenceName; public $allocationSize = 10; public $initialValue = 1; } -final class ChangeTrackingPolicy extends \Doctrine\Common\Annotations\Annotation {} +final class ChangeTrackingPolicy extends Annotation {} /* Annotations for lifecycle callbacks */ -final class LifecycleListener extends \Doctrine\Common\Annotations\Annotation {} -final class PrePersist extends \Doctrine\Common\Annotations\Annotation {} -final class PostPersist extends \Doctrine\Common\Annotations\Annotation {} -final class PreUpdate extends \Doctrine\Common\Annotations\Annotation {} -final class PostUpdate extends \Doctrine\Common\Annotations\Annotation {} -final class PreRemove extends \Doctrine\Common\Annotations\Annotation {} -final class PostRemove extends \Doctrine\Common\Annotations\Annotation {} -final class PostLoad extends \Doctrine\Common\Annotations\Annotation {} +final class LifecycleListener extends Annotation {} +final class PrePersist extends Annotation {} +final class PostPersist extends Annotation {} +final class PreUpdate extends Annotation {} +final class PostUpdate extends Annotation {} +final class PreRemove extends Annotation {} +final class PostRemove extends Annotation {} +final class PostLoad extends Annotation {} /* Generic annotation for Doctrine extensions */ -final class DoctrineX extends \Doctrine\Common\Annotations\Annotation {} +final class DoctrineX extends Annotation {} diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 8636f089a..7da15eae6 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -288,16 +288,25 @@ class JoinedSubclassPersister extends StandardEntityPersister $tableAlias = isset($mapping['inherited']) ? $tableAliases[$mapping['inherited']] : $baseTableAlias; if ($columnList != '') $columnList .= ', '; - $columnList .= $tableAlias . '.' . $this->_class->columnNames[$fieldName]; + $columnList .= $tableAlias . '.' . $this->_class->getQuotedColumnName($fieldName, $this->_platform); + } + + // Add discriminator column + if ($this->_class->rootEntityName == $this->_class->name) { + $columnList .= ', ' . $baseTableAlias . '.' . + $this->_class->getQuotedDiscriminatorColumnName($this->_platform); + } else { + $columnList .= ', ' . $tableAliases[$this->_class->rootEntityName] . '.' . + $this->_class->getQuotedDiscriminatorColumnName($this->_platform); } - $sql = 'SELECT ' . $columnList . ' FROM ' . $this->_class->primaryTable['name']. ' ' . $baseTableAlias; + $sql = 'SELECT ' . $columnList . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias; // INNER JOIN parent tables foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $tableAliases[$parentClassName]; - $sql .= ' INNER JOIN ' . $parentClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; + $sql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) $first = false; else $sql .= ' AND '; @@ -309,7 +318,7 @@ class JoinedSubclassPersister extends StandardEntityPersister foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); $tableAlias = $tableAliases[$subClassName]; - $sql .= ' LEFT JOIN ' . $subClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; + $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) $first = false; else $sql .= ' AND '; @@ -323,6 +332,6 @@ class JoinedSubclassPersister extends StandardEntityPersister $conditionSql .= $baseTableAlias . '.' . $this->_class->columnNames[$field] . ' = ?'; } - return $sql . ' WHERE ' . $conditionSql; + return $sql . ($conditionSql != '' ? ' WHERE ' . $conditionSql : ''); } } diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 09cbedcb4..debf3cc46 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -85,8 +85,6 @@ class StandardEntityPersister * @var array */ protected $_queuedInserts = array(); - - //protected $_rsm; /** * Initializes a new instance of a class derived from AbstractEntityPersister @@ -483,36 +481,25 @@ class StandardEntityPersister if ($result === false) { return null; } - + $data = $joinColumnValues = array(); - - /*if ($this->_rsm === null) { - $this->_rsm = array(); - foreach ($this->_class->columnNames as $column) { - $this->_rsm[$this->_platform->getSqlResultCasing($column)] = $column; - } - foreach ($this->_class->associationMappings as $assoc) { - if ($assoc->isOwningSide && $assoc->isOneToOne()) { - foreach ($assoc->targetToSourceKeyColumns as $keyColumn) { - $this->_rsm[$this->_platform->getSqlResultCasing($keyColumn)] = $keyColumn; - } - } - } - }*/ + $entityName = $this->_entityName; foreach ($result as $column => $value) { - //$column = $this->_rsm[$column]; + $column = $this->_class->resultColumnNames[$column]; if (isset($this->_class->fieldNames[$column])) { $fieldName = $this->_class->fieldNames[$column]; $data[$fieldName] = Type::getType($this->_class->fieldMappings[$fieldName]['type']) ->convertToPHPValue($value, $this->_platform); + } else if ($this->_class->discriminatorColumn !== null && $column == $this->_class->discriminatorColumn['name']) { + $entityName = $this->_class->discriminatorMap[$value]; } else { $joinColumnValues[$column] = $value; } } if ($entity === null) { - $entity = $this->_em->getUnitOfWork()->createEntity($this->_entityName, $data); + $entity = $this->_em->getUnitOfWork()->createEntity($entityName, $data); } else { foreach ($data as $field => $value) { $this->_class->reflFields[$field]->setValue($entity, $value); diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index cb70099f4..821ee9ded 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1689,7 +1689,7 @@ class Parser /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * - * @return \Doctrine\ORM\Query\AST\ConditionalPrimary + * @return Doctrine\ORM\Query\AST\ConditionalPrimary */ public function ConditionalPrimary() { diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index c0892481f..0547392c8 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -248,21 +248,23 @@ class SqlWalker implements TreeWalker } } - // LEFT JOIN subclass tables - foreach ($class->subClasses as $subClassName) { - $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); - $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) - . ' ' . $tableAlias . ' ON '; - $first = true; - - foreach ($class->identifier as $idField) { - if ($first) $first = false; else $sql .= ' AND '; - - $columnName = $class->getQuotedColumnName($idField, $this->_platform); - $sql .= $baseTableAlias . '.' . $columnName - . ' = ' - . $tableAlias . '.' . $columnName; + // LEFT JOIN subclass tables, only if partial objects disallowed + if ( ! $this->_em->getConfiguration()->getAllowPartialObjects() && ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { + foreach ($class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + $tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); + $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) + . ' ' . $tableAlias . ' ON '; + $first = true; + + foreach ($class->identifier as $idField) { + if ($first) $first = false; else $sql .= ' AND '; + + $columnName = $class->getQuotedColumnName($idField, $this->_platform); + $sql .= $baseTableAlias . '.' . $columnName + . ' = ' + . $tableAlias . '.' . $columnName; + } } } @@ -471,7 +473,7 @@ class SqlWalker implements TreeWalker $sql .= ", $tblAlias." . $rootClass->getQuotedDiscriminatorColumnName($this->_platform) . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); @@ -490,7 +492,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSqlColumnAlias($srcColumn); $sql .= ", $sqlTableAlias." . $assoc->getQuotedJoinColumnName($srcColumn, $this->_platform) . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } @@ -506,7 +508,7 @@ class SqlWalker implements TreeWalker foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $this->getSqlColumnAlias($srcColumn); $sql .= ', ' . $sqlTableAlias . '.' . $assoc->getQuotedJoinColumnName($srcColumn, $this->_platform) . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } @@ -748,7 +750,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSqlColumnAlias($class->columnNames[$fieldName]); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } else { throw DoctrineException::updateMe( @@ -765,7 +767,7 @@ class SqlWalker implements TreeWalker $columnAlias = 'sclr' . $this->_aliasCounter++; $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias); } else if ($expr instanceof AST\Subselect) { $sql .= $this->walkSubselect($expr); @@ -779,7 +781,7 @@ class SqlWalker implements TreeWalker $columnAlias = 'sclr' . $this->_aliasCounter++; $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias); } else { // IdentificationVariable @@ -807,28 +809,33 @@ class SqlWalker implements TreeWalker $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) . ' AS ' . $columnAlias; - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } // Add any additional fields of subclasses (not inherited fields) - foreach ($class->subClasses as $subClassName) { - $subClass = $this->_em->getClassMetadata($subClassName); - - foreach ($subClass->fieldMappings as $fieldName => $mapping) { - if (isset($mapping['inherited'])) { - continue; + // 1) on Single Table Inheritance: always, since its marginal overhead + // 2) on Class Table Inheritance only if partial objects are disallowed, + // since it requires outer joining subtables. + if ($class->isInheritanceTypeSingleTable() || ! $this->_em->getConfiguration()->getAllowPartialObjects() + && ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { + foreach ($class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + foreach ($subClass->fieldMappings as $fieldName => $mapping) { + if (isset($mapping['inherited'])) { + continue; + } + + if ($beginning) $beginning = false; else $sql .= ', '; + + $sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); + $columnAlias = $this->getSqlColumnAlias($mapping['columnName']); + $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) + . ' AS ' . $columnAlias; + + $columnAlias = $this->_platform->getSqlResultCasing($columnAlias); + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } - - if ($beginning) $beginning = false; else $sql .= ', '; - - $sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); - $columnAlias = $this->getSqlColumnAlias($mapping['columnName']); - $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) - . ' AS ' . $columnAlias; - - //$columnAlias = $this->_platform->getSqlResultCasing($columnAlias); - $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } } } diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 189100fcd..4dc17ff5c 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -21,8 +21,8 @@ namespace Doctrine\ORM\Tools; -use Doctrine\DBAL\Types\Type; -use Doctrine\ORM\EntityManager; +use Doctrine\DBAL\Types\Type, + Doctrine\ORM\EntityManager; /** * The SchemaTool is a tool to create and/or drop database schemas based on @@ -306,4 +306,14 @@ class SchemaTool { //TODO } + + public function updateSchema(array $classes) + { + //TODO + } + + public function getUpdateSchemaSql(array $classes) + { + //TODO + } } diff --git a/tests/Doctrine/Tests/Models/Company/CompanyEmployee.php b/tests/Doctrine/Tests/Models/Company/CompanyEmployee.php index eb8917ae8..46b66cecc 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyEmployee.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyEmployee.php @@ -5,8 +5,6 @@ namespace Doctrine\Tests\Models\Company; /** * @Entity * @Table(name="company_employees") - * @DiscriminatorValue("employee") - * @SubClasses({"Doctrine\Tests\Models\Company\CompanyManager"}) */ class CompanyEmployee extends CompanyPerson { diff --git a/tests/Doctrine/Tests/Models/Company/CompanyManager.php b/tests/Doctrine/Tests/Models/Company/CompanyManager.php index 81964a9b0..cde06ce6c 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyManager.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyManager.php @@ -5,7 +5,6 @@ namespace Doctrine\Tests\Models\Company; /** * @Entity * @Table(name="company_managers") - * @DiscriminatorValue("manager") */ class CompanyManager extends CompanyEmployee { diff --git a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php index 21395dc88..c38dc7428 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php @@ -8,11 +8,12 @@ namespace Doctrine\Tests\Models\Company; * @author robo * @Entity * @Table(name="company_persons") - * @DiscriminatorValue("person") * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") - * @SubClasses({"Doctrine\Tests\Models\Company\CompanyEmployee", - "Doctrine\Tests\Models\Company\CompanyManager"}) + * @DiscriminatorMap({ + * "person" = "CompanyPerson", + * "manager" = "CompanyManager", + * "employee" = "CompanyEmployee"}) */ class CompanyPerson { @@ -39,6 +40,10 @@ class CompanyPerson inverseJoinColumns={@JoinColumn(name="friend_id", referencedColumnName="id")}) */ private $friends; + + public function __construct() { + $this->friends = new \Doctrine\Common\Collections\ArrayCollection; + } public function getId() { return $this->id; @@ -61,9 +66,6 @@ class CompanyPerson } public function addFriend(CompanyPerson $friend) { - if ( ! $this->friends) { - $this->friends = new \Doctrine\Common\Collections\ArrayCollection; - } if ( ! $this->friends->contains($friend)) { $this->friends->add($friend); $friend->addFriend($this); diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php index ebcb91dd3..0ea6f9e07 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php @@ -39,6 +39,7 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); + $this->_em->getConfiguration()->setAllowPartialObjects(false); $query = $this->_em->createQuery("select p from Doctrine\Tests\Models\Company\CompanyPerson p order by p.id asc"); $entities = $query->getResult(); @@ -51,6 +52,7 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Roman S. Borschel', $entities[0]->getName()); $this->assertEquals('Guilherme Blanco', $entities[1]->getName()); $this->assertEquals(100000, $entities[1]->getSalary()); + $this->_em->getConfiguration()->setAllowPartialObjects(true); $this->_em->clear(); @@ -99,6 +101,7 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $manager = $this->_em->find('Doctrine\Tests\Models\Company\CompanyManager', $manager->getId()); + $this->assertTrue($manager instanceof CompanyManager); $this->assertEquals('Roman B.', $manager->getName()); $this->assertEquals(119000, $manager->getSalary()); $this->assertEquals('CEO', $manager->getTitle()); diff --git a/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php b/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php index 1333f7dd0..fc9042904 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php @@ -124,10 +124,9 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase /** * @Entity * @Table(name="optimistic_joined_parent") - * @DiscriminatorValue("parent") * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") - * @SubClasses({"Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedChild"}) + * @DiscriminatorMap({"parent" = "OptimisticJoinedParent", "child" = "OptimisticJoinedChild"}) */ class OptimisticJoinedParent { @@ -151,7 +150,6 @@ class OptimisticJoinedParent /** * @Entity * @Table(name="optimistic_joined_child") - * @DiscriminatorValue("child") */ class OptimisticJoinedChild extends OptimisticJoinedParent { diff --git a/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php index e67e34fa4..eb5e1fee2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php @@ -101,8 +101,7 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase * @Entity * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr", type="string") - * @SubClasses({"Doctrine\Tests\ORM\Functional\ChildEntity"}) - * @DiscriminatorValue("parent") + * @DiscriminatorMap({"parent"="ParentEntity", "child"="ChildEntity"}) */ class ParentEntity { /** @@ -132,7 +131,6 @@ class ParentEntity { /** * @Entity - * @DiscriminatorValue("child") */ class ChildEntity extends ParentEntity { /** diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index f8835254b..1477c0ec5 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -38,7 +38,7 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($cm->reflClass instanceof \ReflectionClass); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $cm->name); $this->assertEquals('UserParent', $cm->rootEntityName); - $this->assertEquals(array('One', 'Two', 'Three'), $cm->subClasses); + $this->assertEquals(array('Doctrine\Tests\Models\CMS\One', 'Doctrine\Tests\Models\CMS\Two', 'Doctrine\Tests\Models\CMS\Three'), $cm->subClasses); $this->assertEquals(array('UserParent'), $cm->parentClasses); $this->assertEquals('UserRepository', $cm->getCustomRepositoryClass()); $this->assertEquals(array('name' => 'disc', 'type' => 'integer', 'fieldName' => 'disc'), $cm->discriminatorColumn); diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 182c0d441..aae29e47f 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -296,15 +296,16 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase // "Get all persons who have $person as a friend." // Tough one: Many-many self-referencing ("friends") with class table inheritance - $q3 = $this->_em->createQuery('SELECT p.id FROM Doctrine\Tests\Models\Company\CompanyPerson p WHERE :param MEMBER OF p.friends'); + $this->_em->getConfiguration()->setAllowPartialObjects(false); + $q3 = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p WHERE :param MEMBER OF p.friends'); $person = new \Doctrine\Tests\Models\Company\CompanyPerson; $this->_em->getClassMetadata(get_class($person))->setIdentifierValues($person, 101); $q3->setParameter('param', $person); - $this->assertEquals( - 'SELECT c0_.id AS id0, c0_.discr AS discr1 FROM company_persons c0_ LEFT JOIN company_employees c1_ ON c0_.id = c1_.id LEFT JOIN company_managers c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.person_id = c0_.id WHERE c3_.friend_id = c4_.id AND c4_.id = ?)', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c0_.discr AS discr5, c0_.spouse_id AS spouse_id6 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.person_id = c0_.id WHERE c3_.friend_id = c4_.id AND c4_.id = ?)', $q3->getSql() ); + $this->_em->getConfiguration()->setAllowPartialObjects(true); } public function testSupportsCurrentDateFunction()