From 909e98c6f293249d2a148ab4dbd71e35551d9d9c Mon Sep 17 00:00:00 2001 From: romanb Date: Wed, 9 Dec 2009 12:37:57 +0000 Subject: [PATCH] [2.0][DDC-202][DDC-132][DDC-185] Fixed. --- lib/Doctrine/DBAL/Connection.php | 96 ++++++++++--------- .../DBAL/Driver/OCI8/OCI8Statement.php | 53 +--------- lib/Doctrine/DBAL/Driver/PDOConnection.php | 1 - lib/Doctrine/DBAL/Driver/Statement.php | 89 ++--------------- .../ORM/Internal/Hydration/ObjectHydrator.php | 2 +- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 3 +- .../ORM/Mapping/ClassMetadataFactory.php | 40 +------- .../ORM/Mapping/ClassMetadataInfo.php | 13 +-- .../Persisters/JoinedSubclassPersister.php | 35 ++++++- .../ORM/Persisters/SingleTablePersister.php | 15 +++ .../Persisters/StandardEntityPersister.php | 44 ++++----- .../ORM/Functional/LifecycleCallbackTest.php | 2 +- .../ORM/Functional/Ticket/DDC168Test.php | 5 +- .../ORM/Functional/Ticket/DDC199Test.php | 2 +- .../ORM/Hydration/ObjectHydratorTest.php | 58 +++++++++++ 15 files changed, 194 insertions(+), 264 deletions(-) diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index bef2ae689..5e4fb546a 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -292,7 +292,8 @@ class Connection /** * Establishes the connection with the database. * - * @return boolean + * @return boolean TRUE if the connection was successfully established, FALSE if + * the connection is already open. */ public function connect() { @@ -311,11 +312,13 @@ class Connection } /** - * Convenience method for PDO::query("...") followed by $stmt->fetch(PDO::FETCH_ASSOC). - * + * Prepares and executes an SQL query and returns the first row of the result + * as an associative array. + * * @param string $statement The SQL query. * @param array $params The query parameters. * @return array + * @todo Rename: fetchAssoc */ public function fetchRow($statement, array $params = array()) { @@ -323,7 +326,8 @@ class Connection } /** - * Convenience method for PDO::query("...") followed by $stmt->fetch(PDO::FETCH_NUM). + * Prepares and executes an SQL query and returns the first row of the result + * as a numerically indexed array. * * @param string $statement sql query to be executed * @param array $params prepared statement params @@ -335,12 +339,13 @@ class Connection } /** - * Convenience method for PDO::query("...") followed by $stmt->fetchColumn(...). - * + * Prepares and executes an SQL query and returns the value of a single column + * of the first row of the result. + * * @param string $statement sql query to be executed * @param array $params prepared statement params * @param int $colnum 0-indexed column number to retrieve - * @return array + * @return mixed */ public function fetchColumn($statement, array $params = array(), $colnum = 0) { @@ -357,22 +362,10 @@ class Connection return $this->_isConnected; } - /** - * Convenience method for PDO::query("...") followed by $stmt->fetchAll(PDO::FETCH_BOTH). - * - * @param string $statement sql query to be executed - * @param array $params prepared statement params - * @return array - */ - public function fetchBoth($statement, array $params = array()) - { - return $this->execute($statement, $params)->fetchAll(Connection::FETCH_BOTH); - } - /** * Deletes table row(s) matching the specified identifier. * - * @param string $table The table to delete data from + * @param string $table The table to delete data from. * @param array $identifier An associateve array containing identifier fieldname-value pairs. * @return integer The number of affected rows */ @@ -532,7 +525,7 @@ class Connection } /** - * Convenience method for PDO::query("...") followed by $stmt->fetchAll(PDO::FETCH_ASSOC). + * Prepares and executes an SQL query and returns the result as an associative array. * * @param string $sql The SQL query. * @param array $params The query parameters. @@ -546,8 +539,8 @@ class Connection /** * Prepares an SQL statement. * - * @param string $statement - * @return Statement + * @param string $statement The SQL statement to prepare. + * @return Statement The prepared statement. */ public function prepare($statement) { @@ -557,29 +550,11 @@ class Connection } /** - * Queries the database with limit and offset added to the query and returns - * a Statement object. + * Prepares and executes an SQL query. * - * @param string $query - * @param integer $limit - * @param integer $offset - * @return Statement - */ - public function select($query, $limit = 0, $offset = 0) - { - if ($limit > 0 || $offset > 0) { - $query = $this->_platform->modifyLimitQuery($query, $limit, $offset); - } - - return $this->execute($query); - } - - /** - * Executes an SQL SELECT query with the given parameters. - * - * @param string $query sql query - * @param array $params query parameters - * @return PDOStatement + * @param string $query The SQL query to prepare and execute. + * @param array $params The parameters, if any. + * @return Statement The prepared and executed statement. */ public function execute($query, array $params = array()) { @@ -598,6 +573,35 @@ class Connection return $stmt; } + + /** + * Prepares and executes an SQL query and returns the result, optionally applying a + * transformation on the rows of the result. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters, if any. + * @param Closure $mapper The transformation function that is applied on each row. + * The function receives a single paramater, an array, that + * represents a row of the result set. + * @return mixed The (possibly transformed) result of the query. + */ + public function query($query, array $params = array(), \Closure $mapper = null) + { + $result = array(); + $stmt = $this->execute($query, $params); + + while ($row = $stmt->fetch()) { + if ($mapper === null) { + $result[] = $row; + } else { + $result[] = $mapper($row); + } + } + + $stmt->closeCursor(); + + return $result; + } /** * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters. @@ -726,7 +730,7 @@ class Connection * this method can be listened with onPreTransactionRollback and onTransactionRollback * eventlistener methods * - * @throws ConnectionException If the rollback operation fails at database level. + * @throws ConnectionException If the rollback operation failed. */ public function rollback() { diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php index d0644b19a..184f39e64 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php @@ -143,7 +143,7 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement /** * {@inheritdoc} */ - public function fetch($fetchStyle = Connection::FETCH_BOTH, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + public function fetch($fetchStyle = Connection::FETCH_BOTH) { if ( ! isset(self::$fetchStyleMap[$fetchStyle])) { throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle); @@ -176,60 +176,11 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement return $row[$columnIndex]; } - /** - * {@inheritdoc} - */ - public function fetchObject($className = 'stdClass', $args = array()) - { - throw new \Exception(__METHOD__ . " not supported."); - } - - /** - * {@inheritdoc} - */ - public function getAttribute($attribute) - { - throw new \Exception(__METHOD__ . " not supported."); - } - - /** - * {@inheritdoc} - */ - public function getColumnMeta($column) - { - throw new \Exception(__METHOD__ . " not supported."); - } - - /** - * {@inheritdoc} - */ - public function nextRowset() - { - throw new \Exception(__METHOD__ . " not supported."); - } - /** * {@inheritdoc} */ public function rowCount() { return oci_num_rows($this->_sth); - } - - /** - * {@inheritdoc} - */ - public function setAttribute($attribute, $value) - { - throw new \Exception(__METHOD__ . " not supported."); - } - - /** - * {@inheritdoc} - */ - public function setFetchMode($mode, $arg1) - { - throw new \Exception(__METHOD__ . " not supported."); - } - + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/PDOConnection.php b/lib/Doctrine/DBAL/Driver/PDOConnection.php index 319a36df7..f0068077e 100644 --- a/lib/Doctrine/DBAL/Driver/PDOConnection.php +++ b/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -36,6 +36,5 @@ class PDOConnection extends PDO implements Connection parent::__construct($dsn, $user, $password, $options); $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Doctrine\DBAL\Driver\PDOStatement', array())); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - //$this->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/Statement.php b/lib/Doctrine/DBAL/Driver/Statement.php index 752d14579..007a52246 100644 --- a/lib/Doctrine/DBAL/Driver/Statement.php +++ b/lib/Doctrine/DBAL/Driver/Statement.php @@ -21,11 +21,13 @@ namespace Doctrine\DBAL\Driver; +use Doctrine\DBAL\Connection as DBALConnection; + /** * Statement interface. * Drivers must implement this interface. * - * This resembles the PDOStatement interface. + * This resembles (a subset of) the PDOStatement interface. * * @author Konsta Vesterinen * @author Roman Borschel @@ -35,7 +37,7 @@ namespace Doctrine\DBAL\Driver; * @version $Revision$ */ interface Statement -{ +{ /** * Bind a column to a PHP variable * @@ -92,7 +94,6 @@ interface Statement function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array()); /** - * closeCursor * Closes the cursor, enabling the statement to be executed again. * * @return boolean Returns TRUE on success or FALSE on failure. @@ -170,12 +171,9 @@ interface Statement * * @return mixed */ - function fetch($fetchStyle = Query::HYDRATE_BOTH, - $cursorOrientation = Query::HYDRATE_ORI_NEXT, - $cursorOffset = null); + function fetch($fetchStyle = DBALConnection::FETCH_BOTH); /** - * fetchAll * Returns an array containing all of the result set rows * * @param integer $fetchStyle Controls how the next row will be returned to the caller. @@ -187,7 +185,7 @@ interface Statement * * @return array */ - function fetchAll($fetchStyle = Query::HYDRATE_BOTH); + function fetchAll($fetchStyle = DBALConnection::FETCH_BOTH); /** * fetchColumn @@ -202,62 +200,6 @@ interface Statement */ function fetchColumn($columnIndex = 0); - /** - * fetchObject - * Fetches the next row and returns it as an object. - * - * Fetches the next row and returns it as an object. This function is an alternative to - * PDOStatement->fetch() with Query::HYDRATE_CLASS or Query::HYDRATE_OBJ style. - * - * @param string $className Name of the created class, defaults to stdClass. - * @param array $args Elements of this array are passed to the constructor. - * - * @return mixed an instance of the required class with property names that correspond - * to the column names or FALSE in case of an error. - */ - function fetchObject($className = 'stdClass', $args = array()); - - /** - * getAttribute - * Retrieve a statement attribute - * - * @param integer $attribute - * @see Doctrine::ATTR_* constants - * @return mixed the attribute value - */ - function getAttribute($attribute); - - /** - * getColumnMeta - * Returns metadata for a column in a result set - * - * @param integer $column The 0-indexed column in the result set. - * - * @return array Associative meta data array with the following structure: - * - * native_type The PHP native type used to represent the column value. - * driver:decl_ type The SQL type used to represent the column value in the database. If the column in the result set is the result of a function, this value is not returned by PDOStatement->getColumnMeta(). - * flags Any flags set for this column. - * name The name of this column as returned by the database. - * len The length of this column. Normally -1 for types other than floating point decimals. - * precision The numeric precision of this column. Normally 0 for types other than floating point decimals. - * pdo_type The type of this column as represented by the PDO::PARAM_* constants. - */ - function getColumnMeta($column); - - /** - * nextRowset - * Advances to the next rowset in a multi-rowset statement handle - * - * Some database servers support stored procedures that return more than one rowset - * (also known as a result set). The nextRowset() method enables you to access the second - * and subsequent rowsets associated with a PDOStatement object. Each rowset can have a - * different set of columns from the preceding rowset. - * - * @return boolean Returns TRUE on success or FALSE on failure. - */ - function nextRowset(); - /** * rowCount * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement @@ -271,23 +213,4 @@ interface Statement * @return integer Returns the number of rows. */ function rowCount(); - - /** - * setAttribute - * Set a statement attribute - * - * @param integer $attribute - * @param mixed $value the value of given attribute - * @return boolean Returns TRUE on success or FALSE on failure. - */ - function setAttribute($attribute, $value); - - /** - * setFetchMode - * Set the default fetch mode for this statement - * - * @param integer $mode The fetch mode must be one of the Query::HYDRATE_* constants. - * @return boolean Returns 1 on success or FALSE on failure. - */ - function setFetchMode($mode, $arg1); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 50dbbc449..b42a10210 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -62,7 +62,7 @@ class ObjectHydrator extends AbstractHydrator foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); - $this->_resultPointers[$dqlAlias] = array(); + //$this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; $class = $this->_em->getClassMetadata($className); diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index cc3c3e852..6e0c60de3 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -334,7 +334,7 @@ final class ClassMetadata extends ClassMetadataInfo 'inheritanceType', 'inheritedAssociationFields', 'insertSql', - 'inverseMappings', + 'inverseMappings', //TODO: Remove! 'isIdentifierComposite', 'isMappedSuperclass', 'isVersioned', @@ -343,7 +343,6 @@ final class ClassMetadata extends ClassMetadataInfo 'namespace', 'parentClasses', 'primaryTable', - 'resultColumnNames', 'rootEntityName', 'sequenceGeneratorDefinition', 'subClasses', diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 5c5e986ed..a1fca89b8 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM\Mapping; -use Doctrine\Common\DoctrineException, +use Doctrine\ORM\ORMException, Doctrine\DBAL\Platforms\AbstractPlatform, Doctrine\ORM\Events; @@ -189,7 +189,6 @@ class ClassMetadataFactory $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); $class->setDiscriminatorMap($parent->discriminatorMap); - $class->setResultColumnNames($parent->resultColumnNames); } // Invoke driver @@ -230,13 +229,6 @@ class ClassMetadataFactory $this->_generateStaticSql($class); } - if ($parent) { - foreach ($visited as $parentClassName) { - $parentClass = $this->_loadedMetadata[$parentClassName]; - $parentClass->setResultColumnNames(array_merge($parentClass->resultColumnNames, $class->resultColumnNames)); - } - } - $this->_loadedMetadata[$className] = $class; $parent = $class; @@ -321,11 +313,6 @@ 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; } @@ -334,19 +321,10 @@ class ClassMetadataFactory if ($assoc->isOneToOne() && $assoc->isOwningSide) { foreach ($assoc->targetToSourceKeyColumns as $sourceCol) { $columns[] = $assoc->getQuotedJoinColumnName($sourceCol, $this->_targetPlatform); - // 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); - // 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 { @@ -360,19 +338,10 @@ class ClassMetadataFactory if ($assoc->isOwningSide && $assoc->isOneToOne()) { foreach ($assoc->targetToSourceKeyColumns as $sourceCol) { $columns[] = $assoc->getQuotedJoinColumnName($sourceCol, $this->_targetPlatform); - // 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); - // 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; } } } @@ -383,9 +352,6 @@ class ClassMetadataFactory && $class->name == $class->rootEntityName) { $columns[] = $class->getQuotedDiscriminatorColumnName($this->_targetPlatform); } - // Add column mapping for SQL result sets - $columnName = $class->discriminatorColumn['name']; - $class->resultColumnNames[$this->_targetPlatform->getSqlResultCasing($columnName)] = $columnName; } if (empty($columns)) { @@ -448,10 +414,10 @@ class ClassMetadataFactory $class->setIdGenerator(new \Doctrine\ORM\Id\Assigned()); break; case ClassMetadata::GENERATOR_TYPE_TABLE: - throw new DoctrineException("DoctrineTableGenerator not yet implemented."); + throw new ORMException("TableGenerator not yet implemented."); break; default: - throw new DoctrineException("Unexhaustive match."); + throw new ORMException("Unknown generator type: " . $class->generatorType); } } } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 569634f9e..feb3442a2 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -257,18 +257,6 @@ class ClassMetadataInfo * @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 - * @todo Remove. Or at least remove from serialization/unserialization and instead - * populate them during runtime. - * See http://www.doctrine-project.org/jira/browse/DDC-132. - */ - public $resultColumnNames = array(); /** * Whether to automatically OUTER JOIN subtypes when a basetype is queried. @@ -340,6 +328,7 @@ class ClassMetadataInfo * List of inverse association mappings, indexed by mappedBy field name. * * @var array + * @todo Remove! See http://www.doctrine-project.org/jira/browse/DDC-193 */ public $inverseMappings = array(); diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 83e70a60e..3fecf8d8a 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM\Persisters; -use Doctrine\Common\DoctrineException; +use Doctrine\ORM\ORMException; /** * The joined subclass persister maps a single entity instance to several tables in the @@ -52,8 +52,7 @@ class JoinedSubclassPersister extends StandardEntityPersister if ($isInsert) { $discColumn = $this->_class->discriminatorColumn; $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); - $result[$rootClass->primaryTable['name']][$discColumn['name']] = - $this->_class->discriminatorValue; + $result[$rootClass->primaryTable['name']][$discColumn['name']] = $this->_class->discriminatorValue; } } @@ -61,7 +60,7 @@ class JoinedSubclassPersister extends StandardEntityPersister * This function finds the ClassMetadata instance in a inheritance hierarchy * that is responsible for enabling versioning. * - * @return mixed $versionedClass ClassMetadata instance or false if versioning is not enabled + * @return mixed $versionedClass ClassMetadata instance or false if versioning is not enabled */ private function _getVersionedClassMetadata() { @@ -280,6 +279,7 @@ class JoinedSubclassPersister extends StandardEntityPersister $aliasIndex = 1; $idColumns = $this->_class->getIdentifierColumnNames(); $baseTableAlias = 't0'; + $setResultColumnNames = empty($this->_resultColumnNames); foreach (array_merge($this->_class->subClasses, $this->_class->parentClasses) as $className) { $tableAliases[$className] = 't' . $aliasIndex++; @@ -292,6 +292,11 @@ class JoinedSubclassPersister extends StandardEntityPersister $tableAliases[$mapping['inherited']] : $baseTableAlias; if ($columnList != '') $columnList .= ', '; $columnList .= $tableAlias . '.' . $this->_class->getQuotedColumnName($fieldName, $this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($mapping['columnName']); + $this->_resultColumnNames[$resultColumnName] = $mapping['columnName']; + } } // Add foreign key columns @@ -299,6 +304,11 @@ class JoinedSubclassPersister extends StandardEntityPersister if ($assoc2->isOwningSide && $assoc2->isOneToOne()) { foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { $columnList .= ', ' . $assoc2->getQuotedJoinColumnName($srcColumn, $this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($srcColumn); + $this->_resultColumnNames[$resultColumnName] = $srcColumn; + } } } } @@ -311,6 +321,11 @@ class JoinedSubclassPersister extends StandardEntityPersister $columnList .= ', ' . $tableAliases[$this->_class->rootEntityName] . '.' . $this->_class->getQuotedDiscriminatorColumnName($this->_platform); } + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($this->_class->discriminatorColumn['name']); + $this->_resultColumnNames[$resultColumnName] = $this->_class->discriminatorColumn['name']; + } // INNER JOIN parent tables $joinSql = ''; @@ -336,6 +351,11 @@ class JoinedSubclassPersister extends StandardEntityPersister continue; } $columnList .= ', ' . $tableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($mapping['columnName']); + $this->_resultColumnNames[$resultColumnName] = $mapping['columnName']; + } } // Add join columns (foreign keys) @@ -343,6 +363,11 @@ class JoinedSubclassPersister extends StandardEntityPersister if ($assoc2->isOwningSide && $assoc2->isOneToOne() && ! isset($subClass->inheritedAssociationFields[$assoc2->sourceFieldName])) { foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { $columnList .= ', ' . $tableAlias . '.' . $assoc2->getQuotedJoinColumnName($srcColumn, $this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($srcColumn); + $this->_resultColumnNames[$resultColumnName] = $srcColumn; + } } } } @@ -365,7 +390,7 @@ class JoinedSubclassPersister extends StandardEntityPersister } else if ($assoc !== null) { $conditionSql .= $assoc->getQuotedJoinColumnName($field, $this->_platform); } else { - throw DoctrineException::unrecognizedField($field); + throw ORMException::unrecognizedField($field); } $conditionSql .= ' = ?'; } diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index 5e433b42e..cb2cdc079 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -50,9 +50,16 @@ class SingleTablePersister extends StandardEntityPersister /** @override */ protected function _getSelectColumnList() { + $setResultColumnNames = empty($this->_resultColumnNames); $columnList = parent::_getSelectColumnList(); // Append discriminator column $columnList .= ', ' . $this->_class->getQuotedDiscriminatorColumnName($this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($this->_class->discriminatorColumn['name']); + $this->_resultColumnNames[$resultColumnName] = $this->_class->discriminatorColumn['name']; + } + ///$tableAlias = $this->_class->getQuotedTableName($this->_platform); foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); @@ -60,6 +67,10 @@ class SingleTablePersister extends StandardEntityPersister foreach ($subClass->fieldMappings as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { $columnList .= ', ' . $subClass->getQuotedColumnName($fieldName, $this->_platform); + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($mapping['columnName']); + $this->_resultColumnNames[$resultColumnName] = $mapping['columnName']; + } } } @@ -68,6 +79,10 @@ class SingleTablePersister extends StandardEntityPersister if ($assoc->isOwningSide && $assoc->isOneToOne() && ! isset($subClass->inheritedAssociationFields[$assoc->sourceFieldName])) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnList .= ', ' /*. $tableAlias . '.'*/ . $assoc->getQuotedJoinColumnName($srcColumn, $this->_platform); + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($srcColumn); + $this->_resultColumnNames[$resultColumnName] = $srcColumn; + } } } } diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index d18326e06..7d6c7381c 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -86,6 +86,8 @@ class StandardEntityPersister * @var array */ protected $_queuedInserts = array(); + + protected $_resultColumnNames = array(); /** * Initializes a new instance of a class derived from AbstractEntityPersister @@ -189,8 +191,8 @@ class StandardEntityPersister $identifier = $this->_class->getIdentifierColumnNames(); $versionFieldColumnName = $this->_class->getColumnName($versionField); //FIXME: Order with composite keys might not be correct - $sql = "SELECT " . $versionFieldColumnName . " FROM " . $class->getQuotedTableName($this->_platform) . - " WHERE " . implode(' = ? AND ', $identifier) . " = ?"; + $sql = "SELECT " . $versionFieldColumnName . " FROM " . $class->getQuotedTableName($this->_platform) + . " WHERE " . implode(' = ? AND ', $identifier) . " = ?"; $value = $this->_conn->fetchColumn($sql, array_values((array)$id)); $this->_class->setFieldValue($entity, $versionField, $value); } @@ -453,7 +455,7 @@ class StandardEntityPersister // Refresh simple state foreach ($result as $column => $value) { - $column = $this->_class->resultColumnNames[$column]; + $column = isset($this->_resultColumnNames[$column]) ? $this->_resultColumnNames[$column] : $column; if (isset($this->_class->fieldNames[$column])) { $fieldName = $this->_class->fieldNames[$column]; $type = Type::getType($this->_class->fieldMappings[$fieldName]['type']); @@ -636,7 +638,7 @@ class StandardEntityPersister { $data = array(); foreach ($sqlResult as $column => $value) { - $column = $this->_class->resultColumnNames[$column]; + $column = isset($this->_resultColumnNames[$column]) ? $this->_resultColumnNames[$column] : $column; if (isset($this->_class->fieldNames[$column])) { $field = $this->_class->fieldNames[$column]; $data[$field] = Type::getType($this->_class->fieldMappings[$field]['type']) @@ -690,11 +692,18 @@ class StandardEntityPersister protected function _getSelectColumnList() { $columnList = ''; + $tableName = $this->_class->getQuotedTableName($this->_platform); + $setResultColumnNames = empty($this->_resultColumnNames); // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { if ($columnList != '') $columnList .= ', '; - $columnList .= $this->_class->getQuotedColumnName($field, $this->_platform); + $columnList .= $tableName . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($this->_class->columnNames[$field]); + $this->_resultColumnNames[$resultColumnName] = $this->_class->columnNames[$field]; + } } // Add join columns (foreign keys) to select list @@ -702,6 +711,11 @@ class StandardEntityPersister if ($assoc->isOwningSide && $assoc->isOneToOne()) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnList .= ', ' . $assoc->getQuotedJoinColumnName($srcColumn, $this->_platform); + + if ($setResultColumnNames) { + $resultColumnName = $this->_platform->getSqlResultCasing($srcColumn); + $this->_resultColumnNames[$resultColumnName] = $srcColumn; + } } } } @@ -717,22 +731,6 @@ class StandardEntityPersister */ protected function _getSelectManyToManyEntityCollectionSql($manyToMany, array &$criteria) { - $columnList = ''; - $tableName = $this->_class->getQuotedTableName($this->_platform); - - foreach ($this->_class->fieldNames as $field) { - if ($columnList != '') $columnList .= ', '; - $columnList .= $tableName . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); - } - - foreach ($this->_class->associationMappings as $assoc) { - if ($assoc->isOwningSide && $assoc->isOneToOne()) { - foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { - $columnList .= ', ' . $assoc->getQuotedJoinColumnName($srcColumn, $this->_platform); - } - } - } - if ($manyToMany->isOwningSide) { $owningAssoc = $manyToMany; $joinClauses = $manyToMany->relationToTargetKeyColumns; @@ -762,7 +760,7 @@ class StandardEntityPersister $conditionSql .= $columnName . ' = ?'; } - return 'SELECT ' . $columnList + return 'SELECT ' . $this->_getSelectColumnList() . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . $joinSql . ' WHERE ' . $conditionSql; @@ -774,7 +772,7 @@ class StandardEntityPersister $data = array(); $entityName = $this->_class->name; foreach ($sqlResult as $column => $value) { - $column = $this->_class->resultColumnNames[$column]; + $column = isset($this->_resultColumnNames[$column]) ? $this->_resultColumnNames[$column] : $column; if (($class = $this->_findDeclaringClass($column)) !== false) { $field = $class->fieldNames[$column]; $data[$field] = Type::getType($class->fieldMappings[$field]['type']) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index 2208dfd34..9f751601d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -45,7 +45,7 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase { $user = new LifecycleCallbackTestUser; $user->setName('Bob'); - $user->setValue(''); + $user->setValue('value'); $this->_em->persist($user); $this->_em->flush(); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC168Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC168Test.php index a90111226..0e4f0f2d5 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC168Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC168Test.php @@ -10,7 +10,10 @@ class DDC168Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->useModelSet('company'); parent::setUp(); } - + + /** + * @group DDC-168 + */ public function testJoinedSubclassPersisterRequiresSpecificOrderOfMetadataReflFieldsArray() { $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyEmployee'); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC199Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC199Test.php index c9e129a03..08ed3ef94 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC199Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC199Test.php @@ -84,7 +84,7 @@ class DDC199ChildClass extends DDC199ParentClass public $childData; } -/** @Entity @Table(name="ddcxxx_relatedclass") */ +/** @Entity @Table(name="ddc199_relatedclass") */ class DDC199RelatedClass { /** @Id @Column(type="integer") @GeneratedValue(strategy="AUTO") */ diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index a4bf62a68..8cfb8d14f 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -8,6 +8,8 @@ use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Query; +use Doctrine\Tests\Models\CMS\CmsUser; + require_once __DIR__ . '/../../TestInit.php'; class ObjectHydratorTest extends HydrationTestCase @@ -685,6 +687,62 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(1, count($result[1]->boards)); } + + public function testChainedJoinWithEmptyCollections() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' + ); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsComment', + 'c', + 'a', + 'comments' + ); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + $rsm->addFieldResult('c', 'c__id', 'id'); + $rsm->addFieldResult('c', 'c__topic', 'topic'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'a__id' => null, + 'a__topic' => null, + 'c__id' => null, + 'c__topic' => null + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'a__id' => null, + 'a__topic' => null, + 'c__id' => null, + 'c__topic' => null + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + $this->assertTrue($result[0] instanceof CmsUser); + $this->assertTrue($result[1] instanceof CmsUser); + $this->assertEquals(0, $result[0]->articles->count()); + $this->assertEquals(0, $result[1]->articles->count()); + } public function testResultIteration() {