From 354ede1e04a8d99bbc1a58d2206d29ebf9ddab4e Mon Sep 17 00:00:00 2001 From: romanb Date: Mon, 29 Mar 2010 13:20:41 +0000 Subject: [PATCH] [2.0][DDC-354][DDC-425] Fixed. Encapsulated SQL logging better in the DBAL. Added binding types to DBAL mapping types as well as using these binding types in the persisters. Query and NativeQuery now support PDO binding types as well as DBAL mapping types when binding parameters. --- UPGRADE_TO_2_0 | 37 ++ lib/Doctrine/Common/EventArgs.php | 18 +- lib/Doctrine/DBAL/Configuration.php | 3 +- lib/Doctrine/DBAL/Connection.php | 349 +++++++++++------ lib/Doctrine/DBAL/Driver.php | 2 +- lib/Doctrine/DBAL/Driver/Connection.php | 4 +- .../DBAL/Driver/OCI8/OCI8Statement.php | 46 ++- lib/Doctrine/DBAL/Driver/Statement.php | 26 +- lib/Doctrine/DBAL/Schema/AbstractAsset.php | 1 + .../DBAL/Schema/SqliteSchemaManager.php | 4 +- lib/Doctrine/DBAL/Statement.php | 230 ++++++++++++ lib/Doctrine/DBAL/Types/ArrayType.php | 21 +- lib/Doctrine/DBAL/Types/BigIntType.php | 31 +- lib/Doctrine/DBAL/Types/BooleanType.php | 26 +- lib/Doctrine/DBAL/Types/DateTimeType.php | 21 +- lib/Doctrine/DBAL/Types/DateType.php | 21 +- lib/Doctrine/DBAL/Types/DecimalType.php | 27 +- lib/Doctrine/DBAL/Types/IntegerType.php | 31 +- lib/Doctrine/DBAL/Types/ObjectType.php | 2 +- lib/Doctrine/DBAL/Types/SmallIntType.php | 33 +- lib/Doctrine/DBAL/Types/StringType.php | 8 +- lib/Doctrine/DBAL/Types/TextType.php | 25 +- lib/Doctrine/DBAL/Types/TimeType.php | 27 +- lib/Doctrine/DBAL/Types/Type.php | 73 ++-- .../ORM/{Query => }/AbstractQuery.php | 144 ++++--- .../Internal/Hydration/AbstractHydrator.php | 28 +- .../ORM/Internal/Hydration/ArrayHydrator.php | 6 +- .../ORM/Internal/Hydration/IterableResult.php | 12 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 16 +- .../ORM/Internal/Hydration/ScalarHydrator.php | 4 +- .../Hydration/SingleScalarHydrator.php | 2 +- .../ORM/Mapping/AssociationMapping.php | 17 +- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 27 +- .../ORM/Mapping/ClassMetadataInfo.php | 13 +- .../ORM/Mapping/Driver/DatabaseDriver.php | 2 +- lib/Doctrine/ORM/Mapping/Driver/Driver.php | 1 + lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php | 1 + lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 8 +- .../ORM/Mapping/Driver/YamlDriver.php | 8 +- lib/Doctrine/ORM/NativeQuery.php | 51 +-- lib/Doctrine/ORM/PersistentCollection.php | 7 +- .../AbstractCollectionPersister.php | 12 +- .../AbstractEntityInheritancePersister.php | 105 ++++++ .../Persisters/JoinedSubclassPersister.php | 183 +++------ .../ORM/Persisters/SingleTablePersister.php | 39 +- .../Persisters/StandardEntityPersister.php | 350 ++++++++---------- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- lib/Doctrine/ORM/Query.php | 72 ++-- .../ORM/Query/AST/Functions/SizeFunction.php | 8 +- .../ORM/Query/Exec/AbstractSqlExecutor.php | 9 +- .../Query/Exec/MultiTableDeleteExecutor.php | 25 +- .../Query/Exec/MultiTableUpdateExecutor.php | 19 +- .../ORM/Query/Exec/SingleSelectExecutor.php | 14 +- .../Exec/SingleTableDeleteUpdateExecutor.php | 7 +- lib/Doctrine/ORM/Query/Parser.php | 55 ++- lib/Doctrine/ORM/Query/SqlWalker.php | 47 ++- lib/Doctrine/ORM/Query/TreeWalkerAdapter.php | 2 +- .../ORM/Tools/ConvertDoctrine1Schema.php | 8 +- lib/Doctrine/ORM/Tools/EntityGenerator.php | 10 +- .../ORM/Tools/Export/Driver/PhpExporter.php | 4 +- .../ORM/Tools/Export/Driver/XmlExporter.php | 20 +- .../ORM/Tools/Export/Driver/YamlExporter.php | 14 +- lib/Doctrine/ORM/Tools/SchemaTool.php | 14 +- lib/Doctrine/ORM/UnitOfWork.php | 6 +- .../SchemaManagerFunctionalTestCase.php | 4 +- .../Tests/Mocks/DriverConnectionMock.php | 2 +- .../ORM/Associations/OneToOneMappingTest.php | 2 +- .../ORM/Functional/BasicFunctionalTest.php | 10 + .../Functional/ClassTableInheritanceTest.php | 5 +- .../Tests/ORM/Functional/QueryTest.php | 4 +- .../ORM/Functional/Ticket/DDC425Test.php | 43 +++ .../ORM/Functional/Ticket/DDC444Test.php | 1 + .../ORM/Mapping/AbstractMappingDriverTest.php | 1 - .../ORM/Tools/ConvertDoctrine1SchemaTest.php | 2 +- .../Tests/ORM/Tools/EntityGeneratorTest.php | 2 +- .../AbstractClassMetadataExporterTest.php | 2 +- 76 files changed, 1552 insertions(+), 964 deletions(-) create mode 100644 lib/Doctrine/DBAL/Statement.php rename lib/Doctrine/ORM/{Query => }/AbstractQuery.php (80%) create mode 100644 lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC425Test.php diff --git a/UPGRADE_TO_2_0 b/UPGRADE_TO_2_0 index d9828b41b..9243070ec 100644 --- a/UPGRADE_TO_2_0 +++ b/UPGRADE_TO_2_0 @@ -1,6 +1,43 @@ # Upgrade from 2.0-ALPHA4 to 2.0-BETA1 +## New inversedBy attribute + +It is now *mandatory* that the owning side of a bidirectional association specifies the +'inversedBy' attribute that points to the name of the field on the inverse side that completes +the association. Example: + + [php] + // BEFORE (ALPHA4 AND EARLIER) + class User + { + //... + /** @OneToOne(targetEntity="Address", mappedBy="user") */ + private $address; + //... + } + class Address + { + //... + /** @OneToOne(targetEntity="User") */ + private $address; + //... + } + + // SINCE BETA1 + // User class DOES NOT CHANGE + class Address + { + //... + /** @OneToOne(targetEntity="User", inversedBy="address") */ + private $user; + //... + } + +Thus, the inversedBy attribute is the counterpart to the mappedBy attribute. This change +was necessary to enable some simplifications and further performance improvements. We +apologize for the inconvenience. + ## Default Property for Field Mappings The "default" option for database column defaults has been removed. If desired, database column defaults can diff --git a/lib/Doctrine/Common/EventArgs.php b/lib/Doctrine/Common/EventArgs.php index 122892a87..bfd05b4dc 100644 --- a/lib/Doctrine/Common/EventArgs.php +++ b/lib/Doctrine/Common/EventArgs.php @@ -43,10 +43,18 @@ class EventArgs * @static */ private static $_emptyEventArgsInstance; - + /** - * Gets the single, empty EventArgs instance. - * + * Gets the single, empty and immutable EventArgs instance. + * + * This instance will be used when events are dispatched without any parameter, + * like this: EventManager::dispatchEvent('eventname'); + * + * The benefit from this is that only one empty instance is instantiated and shared + * (otherwise there would be instances for every dispatched in the abovementioned form) + * + * @see EventManager::dispatchEvent + * @link http://msdn.microsoft.com/en-us/library/system.eventargs.aspx * @static * @return EventArgs */ @@ -55,7 +63,7 @@ class EventArgs if ( ! self::$_emptyEventArgsInstance) { self::$_emptyEventArgsInstance = new EventArgs; } - + return self::$_emptyEventArgsInstance; } -} +} diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index 08a61243f..c0793e893 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -33,7 +33,6 @@ use Doctrine\DBAL\Types\Type; * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel - * * @internal When adding a new configuration option just write a getter/setter * pair and add the option to the _attributes array with a proper default value. */ @@ -61,6 +60,7 @@ class Configuration * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @param SqlLogger $logger + * @todo Rename to setSQLLogger() */ public function setSqlLogger($logger) { @@ -71,6 +71,7 @@ class Configuration * Gets the SQL logger that is used. * * @return SqlLogger + * @todo Rename to getSQLLogger() */ public function getSqlLogger() { diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 87ffbf1c8..8770543d2 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -21,7 +21,11 @@ namespace Doctrine\DBAL; -use Doctrine\Common\EventManager, +use PDO, Closure, + Doctrine\DBAL\Types\Type, + Doctrine\DBAL\Driver\Statement as DriverStatement, + Doctrine\DBAL\Driver\Connection as DriverConnection, + Doctrine\Common\EventManager, Doctrine\DBAL\DBALException; /** @@ -39,7 +43,7 @@ use Doctrine\Common\EventManager, * @author Konsta Vesterinen * @author Lukas Smith (MDB2 library) */ -class Connection +class Connection implements DriverConnection { /** * Constant for transaction isolation level READ UNCOMMITTED. @@ -61,15 +65,6 @@ class Connection */ const TRANSACTION_SERIALIZABLE = 4; - /** - * Derived PDO constants - */ - const FETCH_ASSOC = 2; - const FETCH_BOTH = 4; - //const FETCH_COLUMN = 7; Apparently not used. - const FETCH_NUM = 3; - const ATTR_AUTOCOMMIT = 0; - /** * The wrapped driver connection. * @@ -78,15 +73,11 @@ class Connection protected $_conn; /** - * The Configuration. - * * @var Doctrine\DBAL\Configuration */ protected $_config; /** - * The EventManager. - * * @var Doctrine\Common\EventManager */ protected $_eventManager; @@ -308,7 +299,7 @@ class Connection $this->_isConnected = true; if ($this->_eventManager->hasListeners(Events::postConnect)) { - $eventArgs = new \Doctrine\DBAL\Event\ConnectionEventArgs($this); + $eventArgs = new Event\ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } @@ -326,7 +317,7 @@ class Connection */ public function fetchRow($statement, array $params = array()) { - return $this->execute($statement, $params)->fetch(Connection::FETCH_ASSOC); + return $this->execute($statement, $params)->fetch(PDO::FETCH_ASSOC); } /** @@ -339,7 +330,7 @@ class Connection */ public function fetchArray($statement, array $params = array()) { - return $this->execute($statement, $params)->fetch(Connection::FETCH_NUM); + return $this->execute($statement, $params)->fetch(PDO::FETCH_NUM); } /** @@ -367,20 +358,30 @@ class Connection } /** - * Deletes table row(s) matching the specified identifier. + * Checks whether a transaction is currently active. + * + * @return boolean TRUE if a transaction is currently active, FALSE otherwise. + */ + public function isTransactionActive() + { + return $this->_transactionNestingLevel > 0; + } + + /** + * Executes an SQL DELETE statement on a table. * - * @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 + * @param string $table The name of the table on which to delete. + * @param array $identifier The deletion criteria. An associateve array containing column-value pairs. + * @return integer The number of affected rows. */ public function delete($tableName, array $identifier) { $this->connect(); - + $criteria = array(); - - foreach (array_keys($identifier) as $id) { - $criteria[] = $id . ' = ?'; + + foreach (array_keys($identifier) as $columnName) { + $criteria[] = $columnName . ' = ?'; } $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria); @@ -423,24 +424,16 @@ class Connection } /** - * Updates table row(s) with specified data + * Executes an SQL UPDATE statement on a table. * - * @throws Doctrine\DBAL\ConnectionException if something went wrong at the database level - * @param string $table The table to insert data into - * @param array $values An associateve array containing column-value pairs. - * @return mixed boolean false if empty value array was given, - * otherwise returns the number of affected rows + * @param string $table The name of the table to update. + * @param array $identifier The update criteria. An associative array containing column-value pairs. + * @return integer The number of affected rows. */ public function update($tableName, array $data, array $identifier) { $this->connect(); - - if (empty($data)) { - return false; - } - $set = array(); - foreach ($data as $columnName => $value) { $set[] = $columnName . ' = ?'; } @@ -457,39 +450,34 @@ class Connection /** * Inserts a table row with specified data. * - * @param string $table The table to insert data into. - * @param array $fields An associateve array containing fieldname-value pairs. - * @return mixed boolean false if empty value array was given, - * otherwise returns the number of affected rows + * @param string $table The name of the table to insert data into. + * @param array $data An associative array containing column-value pairs. + * @return integer The number of affected rows. */ public function insert($tableName, array $data) { $this->connect(); - - if (empty($data)) { - return false; - } // column names are specified as array keys $cols = array(); - $a = array(); + $placeholders = array(); foreach ($data as $columnName => $value) { $cols[] = $columnName; - $a[] = '?'; + $placeholders[] = '?'; } $query = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $cols) . ')' - . ' VALUES (' . implode(', ', $a) . ')'; + . ' VALUES (' . implode(', ', $placeholders) . ')'; return $this->executeUpdate($query, array_values($data)); } /** - * Set the charset on the current connection + * Sets the given charset on the current connection. * - * @param string charset + * @param string $charset The charset to set. */ public function setCharset($charset) { @@ -502,12 +490,12 @@ class Connection * * Delimiting style depends on the underlying database platform that is being used. * - * NOTE: Just because you CAN use delimited identifiers doesn't mean - * you SHOULD use them. In general, they end up causing way more + * NOTE: Just because you CAN use quoted identifiers does not mean + * you SHOULD use them. In general, they end up causing way more * problems than they solve. * - * @param string $str identifier name to be quoted - * @return string quoted identifier string + * @param string $str The name to be quoted. + * @return string The quoted name. */ public function quoteIdentifier($str) { @@ -517,9 +505,9 @@ class Connection /** * Quotes a given input parameter. * - * @param mixed $input Parameter to be quoted. - * @param string $type Type of the parameter. - * @return string The quoted parameter. + * @param mixed $input Parameter to be quoted. + * @param string $type Type of the parameter. + * @return string The quoted parameter. */ public function quote($input, $type = null) { @@ -537,106 +525,146 @@ class Connection */ public function fetchAll($sql, array $params = array()) { - return $this->execute($sql, $params)->fetchAll(Connection::FETCH_ASSOC); + return $this->execute($sql, $params)->fetchAll(PDO::FETCH_ASSOC); } /** * Prepares an SQL statement. * * @param string $statement The SQL statement to prepare. - * @return Statement The prepared statement. + * @return Doctrine\DBAL\Driver\Statement The prepared statement. */ public function prepare($statement) { $this->connect(); - - return $this->_conn->prepare($statement); + + return new Statement($statement, $this); } /** - * Prepares and executes an SQL query. + * Executes an, optionally parameterized, SQL query. * - * @param string $query The SQL query to prepare and execute. - * @param array $params The parameters, if any. - * @return Statement The prepared and executed statement. + * If the query is parameterized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @return Doctrine\DBAL\Driver\Statement The executed statement. + * @todo Rename to executeQuery ? + * @internal PERF: Directly prepares a driver statement, not a wrapper. */ - public function execute($query, array $params = array()) + public function execute($query, array $params = array(), $types = array()) { $this->connect(); - if ($this->_config->getSqlLogger()) { - $this->_config->getSqlLogger()->logSql($query, $params); + if ($this->_config->getSQLLogger() !== null) { + $this->_config->getSQLLogger()->logSQL($query, $params); } - - if ( ! empty($params)) { + + if ($params) { $stmt = $this->_conn->prepare($query); - $stmt->execute($params); + if ($types) { + $this->_bindTypedValues($stmt, $params, $types); + $stmt->execute(); + } else { + $stmt->execute($params); + } } else { $stmt = $this->_conn->query($query); } - + return $stmt; } - + /** - * Prepares and executes an SQL query and returns the result, optionally applying a - * transformation on the rows of the result. + * Executes an, optionally parameterized, SQL query and returns the result, + * applying a given projection/transformation function on each row 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. + * @return mixed The projected result of the query. */ - public function query($query, array $params = array(), \Closure $mapper = null) + public function project($query, array $params = array(), Closure $function) { $result = array(); $stmt = $this->execute($query, $params); - + while ($row = $stmt->fetch()) { - if ($mapper === null) { - $result[] = $row; - } else { - $result[] = $mapper($row); - } + $result[] = $function($row); } - + $stmt->closeCursor(); - + return $result; } /** - * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters. - * - * @param string $query sql query - * @param array $params query parameters - * @return integer + * Executes an SQL statement, returning a result set as a Statement object. + * + * @param string $statement + * @param integer $fetchType + * @return Doctrine\DBAL\Driver\Statement */ - public function executeUpdate($query, array $params = array()) + public function query() + { + return call_user_func_array(array($this->_conn, 'query'), func_get_args()); + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters + * and returns the number of affected rows. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $query The SQL query. + * @param array $params The query parameters. + * @param array $types The parameter types. + * @return integer The number of affected rows. + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeUpdate($query, array $params = array(), array $types = array()) { $this->connect(); - if ($this->_config->getSqlLogger()) { - $this->_config->getSqlLogger()->logSql($query, $params); + if ($this->_config->getSQLLogger() !== null) { + $this->_config->getSQLLogger()->logSQL($query, $params); } - if ( ! empty($params)) { + if ($params) { $stmt = $this->_conn->prepare($query); - $stmt->execute($params); + if ($types) { + $this->_bindTypedValues($stmt, $params, $types); + $stmt->execute(); + } else { + $stmt->execute($params); + } $result = $stmt->rowCount(); } else { $result = $this->_conn->exec($query); } - + return $result; } + /** + * Execute an SQL statement and return the number of affected rows. + * + * @param string $statement + * @return integer The number of affected rows. + */ + public function exec($statement) + { + $this->connect(); + return $this->_conn->exec($statement); + } + /** * Returns the current transaction nesting level. * - * @return integer The nesting level. A value of 0 means theres no active transaction. + * @return integer The nesting level. A value of 0 means there's no active transaction. */ public function getTransactionNestingLevel() { @@ -644,26 +672,24 @@ class Connection } /** - * Fetch the SQLSTATE associated with the last operation on the database handle + * Fetch the SQLSTATE associated with the last database operation. * - * @return integer + * @return integer The last error code. */ public function errorCode() { $this->connect(); - return $this->_conn->errorCode(); } /** - * Fetch extended error information associated with the last operation on the database handle + * Fetch extended error information associated with the last database operation. * - * @return array + * @return array The last error information. */ public function errorInfo() { $this->connect(); - return $this->_conn->errorInfo(); } @@ -672,31 +698,31 @@ class Connection * depending on the underlying driver. * * Note: This method may not return a meaningful or consistent result across different drivers, - * because the underlying database may not even support the notion of auto-increment fields or sequences. + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. * - * @param string $table Name of the table into which a new row was inserted. - * @param string $field Name of the field into which a new row was inserted. + * @param string $seqName Name of the sequence object from which the ID should be returned. + * @return string A string representation of the last inserted ID. */ public function lastInsertId($seqName = null) { $this->connect(); - return $this->_conn->lastInsertId($seqName); } /** - * Start a transaction by suspending auto-commit mode. + * Starts a transaction by suspending auto-commit mode. * * @return void */ public function beginTransaction() { $this->connect(); - + if ($this->_transactionNestingLevel == 0) { $this->_conn->beginTransaction(); } - + ++$this->_transactionNestingLevel; } @@ -721,15 +747,12 @@ class Connection if ($this->_transactionNestingLevel == 1) { $this->_conn->commit(); } - + --$this->_transactionNestingLevel; } /** - * Cancel any database changes done during a transaction or since a specific - * savepoint that is in progress. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after canceling the pending changes. + * Cancel any database changes done during the current transaction. * * this method can be listened with onPreTransactionRollback and onTransactionRollback * eventlistener methods @@ -762,7 +785,7 @@ class Connection public function getWrappedConnection() { $this->connect(); - + return $this->_conn; } @@ -777,15 +800,15 @@ class Connection if ( ! $this->_schemaManager) { $this->_schemaManager = $this->_driver->getSchemaManager($this); } - + return $this->_schemaManager; } - + /** * Marks the current transaction so that the only possible * outcome for the transaction to be rolled back. * - * @throws BadMethodCallException If no transaction is active. + * @throws ConnectionException If no transaction is active. */ public function setRollbackOnly() { @@ -794,12 +817,12 @@ class Connection } $this->_isRollbackOnly = true; } - + /** * Check whether the current transaction is marked for rollback only. * * @return boolean - * @throws BadMethodCallException If no transaction is active. + * @throws ConnectionException If no transaction is active. */ public function getRollbackOnly() { @@ -808,4 +831,86 @@ class Connection } return $this->_isRollbackOnly; } + + /** + * Converts a given value to its database representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * @return mixed The converted value. + */ + public function convertToDatabaseValue($value, $type) + { + return Type::getType($type)->convertToDatabaseValue($value, $this->_platform); + } + + /** + * Converts a given value to its PHP representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * @return mixed The converted type. + */ + public function convertToPHPValue($value, $type) + { + return Type::getType($type)->convertToPHPValue($value, $this->_platform); + } + + /** + * Binds a set of parameters, some or all of which are typed with a PDO binding type + * or DBAL mapping type, to a given statement. + * + * @param DriverStatement $stmt The statement to bind the values to. + * @param array $params The map/list of named/positional parameters. + * @param array $types The parameter types (PDO binding types or DBAL mapping types). + */ + private function _bindTypedValues(DriverStatement $stmt, array $params, array $types) + { + // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. + if (is_int(key($params))) { + // Positional parameters + $typeOffset = isset($types[0]) ? -1 : 0; + $bindIndex = 1; + foreach ($params as $position => $value) { + $typeIndex = $bindIndex + $typeOffset; + if (isset($types[$typeIndex])) { + $type = $types[$typeIndex]; + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->_platform); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + $stmt->bindValue($bindIndex, $value, $bindingType); + } else { + $stmt->bindValue($bindIndex, $value); + } + ++$bindIndex; + } + } else { + // Named parameters + foreach ($params as $name => $value) { + if (isset($types[$name])) { + $type = $types[$name]; + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->_platform); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + $stmt->bindValue($name, $value, $bindingType); + } else { + $stmt->bindValue($name, $value); + } + } + } + } } diff --git a/lib/Doctrine/DBAL/Driver.php b/lib/Doctrine/DBAL/Driver.php index 06a235882..535601e0f 100644 --- a/lib/Doctrine/DBAL/Driver.php +++ b/lib/Doctrine/DBAL/Driver.php @@ -46,7 +46,7 @@ interface Driver public function getName(); /** - * Get the name of the database connected to for this driver instance + * Get the name of the database connected to for this driver. * * @param Doctrine\DBAL\Connection $conn * @return string $database diff --git a/lib/Doctrine/DBAL/Driver/Connection.php b/lib/Doctrine/DBAL/Driver/Connection.php index bbda199eb..cee11f31a 100644 --- a/lib/Doctrine/DBAL/Driver/Connection.php +++ b/lib/Doctrine/DBAL/Driver/Connection.php @@ -25,7 +25,7 @@ namespace Doctrine\DBAL\Driver; * Connection interface. * Driver connections must implement this interface. * - * This resembles the PDO interface. + * This resembles (a subset of) the PDO interface. * * @since 2.0 */ @@ -33,7 +33,7 @@ interface Connection { function prepare($prepareString); function query(); - function quote($input); + function quote($input, $type=\PDO::PARAM_STR); function exec($statement); function lastInsertId($name = null); function beginTransaction(); diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php index b4f3b586b..0fc6775dc 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php @@ -21,7 +21,7 @@ namespace Doctrine\DBAL\Driver\OCI8; -use Doctrine\DBAL\Connection; +use \PDO; /** * The OCI8 implementation of the Statement interface. @@ -36,17 +36,31 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement private $_paramCounter = 0; private static $_PARAM = ':param'; private static $fetchStyleMap = array( - Connection::FETCH_BOTH => OCI_BOTH, - Connection::FETCH_ASSOC => OCI_ASSOC, - Connection::FETCH_NUM => OCI_NUM + PDO::FETCH_BOTH => OCI_BOTH, + PDO::FETCH_ASSOC => OCI_ASSOC, + PDO::FETCH_NUM => OCI_NUM ); private $_paramMap = array(); - + + /** + * Creates a new OCI8Statement that uses the given connection handle and SQL statement. + * + * @param resource $dbh The connection handle. + * @param string $statement The SQL statement. + */ public function __construct($dbh, $statement) { $this->_sth = oci_parse($dbh, $this->_convertPositionalToNamedPlaceholders($statement)); } - + + /** + * Oracle does not support positional parameters, hence this method converts all + * positional parameters into artificially named parameters. Note that this conversion + * is not perfect. All question marks (?) in the original statement are treated as + * placeholders and converted to a named parameter. + * + * @param string $statement The SQL statement to convert. + */ private function _convertPositionalToNamedPlaceholders($statement) { $count = 1; @@ -55,17 +69,9 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement $statement = substr_replace($statement, ":param$count", $pos, 1); ++$count; } - + return $statement; } - - /** - * {@inheritdoc} - */ - public function bindColumn($column, &$param, $type = null) - { - return oci_define_by_name($this->_sth, strtoupper($column), $param, $type); - } /** * {@inheritdoc} @@ -78,7 +84,7 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement /** * {@inheritdoc} */ - public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array()) + public function bindParam($column, &$variable, $type = null) { $column = isset($this->_paramMap[$column]) ? $this->_paramMap[$column] : $column; @@ -136,9 +142,9 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement $this->bindValue($key, $val); } } - + $ret = @oci_execute($this->_sth, OCI_DEFAULT); - if (!$ret) { + if ( ! $ret) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } return $ret; @@ -147,7 +153,7 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement /** * {@inheritdoc} */ - public function fetch($fetchStyle = Connection::FETCH_BOTH) + public function fetch($fetchStyle = PDO::FETCH_BOTH) { if ( ! isset(self::$fetchStyleMap[$fetchStyle])) { throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle); @@ -159,7 +165,7 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement /** * {@inheritdoc} */ - public function fetchAll($fetchStyle = Connection::FETCH_BOTH) + public function fetchAll($fetchStyle = PDO::FETCH_BOTH) { if ( ! isset(self::$fetchStyleMap[$fetchStyle])) { throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle); diff --git a/lib/Doctrine/DBAL/Driver/Statement.php b/lib/Doctrine/DBAL/Driver/Statement.php index 007a52246..6cb8b6402 100644 --- a/lib/Doctrine/DBAL/Driver/Statement.php +++ b/lib/Doctrine/DBAL/Driver/Statement.php @@ -21,7 +21,7 @@ namespace Doctrine\DBAL\Driver; -use Doctrine\DBAL\Connection as DBALConnection; +use \PDO; /** * Statement interface. @@ -38,18 +38,6 @@ use Doctrine\DBAL\Connection as DBALConnection; */ interface Statement { - /** - * Bind a column to a PHP variable - * - * @param mixed $column Number of the column (1-indexed) or name of the column in the result set. - * If using the column name, be aware that the name should match - * the case of the column, as returned by the driver. - * @param string $param Name of the PHP variable to which the column will be bound. - * @param integer $type Data type of the parameter, specified by the PDO::PARAM_* constants. - * @return boolean Returns TRUE on success or FALSE on failure - */ - function bindColumn($column, &$param, $type = null); - /** * Binds a value to a corresponding named or positional * placeholder in the SQL statement that was used to prepare the statement. @@ -85,13 +73,9 @@ interface Statement * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. - * - * @param integer $length Length of the data type. To indicate that a parameter is an OUT parameter - * from a stored procedure, you must explicitly set the length. - * @param mixed $driverOptions * @return boolean Returns TRUE on success or FALSE on failure. */ - function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array()); + function bindParam($column, &$variable, $type = null); /** * Closes the cursor, enabling the statement to be executed again. @@ -142,7 +126,7 @@ interface Statement * bound parameters in the SQL statement being executed. * @return boolean Returns TRUE on success or FALSE on failure. */ - function execute($params = array()); + function execute($params = null); /** * fetch @@ -171,7 +155,7 @@ interface Statement * * @return mixed */ - function fetch($fetchStyle = DBALConnection::FETCH_BOTH); + function fetch($fetchStyle = PDO::FETCH_BOTH); /** * Returns an array containing all of the result set rows @@ -185,7 +169,7 @@ interface Statement * * @return array */ - function fetchAll($fetchStyle = DBALConnection::FETCH_BOTH); + function fetchAll($fetchStyle = PDO::FETCH_BOTH); /** * fetchColumn diff --git a/lib/Doctrine/DBAL/Schema/AbstractAsset.php b/lib/Doctrine/DBAL/Schema/AbstractAsset.php index 60acfc276..ae79014a0 100644 --- a/lib/Doctrine/DBAL/Schema/AbstractAsset.php +++ b/lib/Doctrine/DBAL/Schema/AbstractAsset.php @@ -80,6 +80,7 @@ abstract class AbstractAsset return substr($columnName, -floor(($maxSize-$postfixLen)/$columnCount - 1)); }, $columnNames); $parts[] = $postfix; + return trim(implode("_", $parts), '_'); } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php index 3ad0a4f21..695e0161b 100644 --- a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -81,7 +81,7 @@ class SqliteSchemaManager extends AbstractSchemaManager // fetch primary $stmt = $this->_conn->execute( "PRAGMA TABLE_INFO ('$tableName')" ); - $indexArray = $stmt->fetchAll(\Doctrine\DBAL\Connection::FETCH_ASSOC); + $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach($indexArray AS $indexColumnRow) { if($indexColumnRow['pk'] == "1") { $indexBuffer[] = array( @@ -102,7 +102,7 @@ class SqliteSchemaManager extends AbstractSchemaManager $idx['non_unique'] = $tableIndex['unique']?false:true; $stmt = $this->_conn->execute( "PRAGMA INDEX_INFO ( '{$keyName}' )" ); - $indexArray = $stmt->fetchAll(\Doctrine\DBAL\Connection::FETCH_ASSOC); + $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ( $indexArray as $indexColumnRow ) { $idx['column_name'] = $indexColumnRow['name']; diff --git a/lib/Doctrine/DBAL/Statement.php b/lib/Doctrine/DBAL/Statement.php new file mode 100644 index 000000000..2cfd8071a --- /dev/null +++ b/lib/Doctrine/DBAL/Statement.php @@ -0,0 +1,230 @@ +. + */ + +namespace Doctrine\DBAL; + +use PDO, + Doctrine\DBAL\Types\Type, + Doctrine\DBAL\Driver\Statement as DriverStatement; + +/** + * A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support + * for logging, DBAL mapping types, etc. + * + * @author Roman Borschel + * @since 2.0 + */ +class Statement implements DriverStatement +{ + /** + * @var string The SQL statement. + */ + private $_sql; + /** + * @var array The bound parameters. + */ + private $_params = array(); + /** + * @var Doctrine\DBAL\Driver\Statement The underlying driver statement. + */ + private $_stmt; + /** + * @var Doctrine\DBAL\Platforms\AbstractPlatform The underlying database platform. + */ + private $_platform; + /** + * @var Doctrine\DBAL\Connection The connection this statement is bound to and executed on. + */ + private $_conn; + + /** + * Creates a new Statement for the given SQL and Connection. + * + * @param string $sql The SQL of the statement. + * @param Doctrine\DBAL\Connection The connection on which the statement should be executed. + */ + public function __construct($sql, Connection $conn) + { + $this->_sql = $sql; + $this->_stmt = $conn->getWrappedConnection()->prepare($sql); + $this->_conn = $conn; + $this->_platform = $conn->getDatabasePlatform(); + } + + /** + * Binds a parameter value to the statement. + * + * The value can optionally be bound with a PDO binding type or a DBAL mapping type. + * If bound with a DBAL mapping type, the binding type is derived from the mapping + * type and the value undergoes the conversion routines of the mapping type before + * being bound. + * + * @param $name The name or position of the parameter. + * @param $value The value of the parameter. + * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. + * @return boolean TRUE on success, FALSE on failure. + */ + public function bindValue($name, $value, $type = null) + { + $this->_params[$name] = $value; + if ($type !== null) { + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->_platform); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + return $this->_stmt->bindValue($name, $value, $bindingType); + } else { + return $this->_stmt->bindValue($name, $value); + } + } + + /** + * Binds a parameter to a value by reference. + * + * Binding a parameter by reference does not support DBAL mapping types. + * + * @param string $name The name or position of the parameter. + * @param mixed $value The reference to the variable to bind + * @param integer $type The PDO binding type. + * @return boolean TRUE on success, FALSE on failure. + */ + public function bindParam($name, &$var, $type = PDO::PARAM_STR) + { + return $this->_stmt->bindParam($name, $var, $type); + } + + /** + * Executes the statement with the currently bound parameters. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function execute($params = null) + { + if ($this->_conn->getConfiguration()->getSQLLogger()) { + $this->_conn->getConfiguration()->getSQLLogger()->logSQL($this->_sql, $this->_params); + } + $this->_params = array(); + return $this->_stmt->execute($params); + } + + /** + * Closes the cursor, freeing the database resources used by this statement. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function closeCursor() + { + return $this->_stmt->closeCursor(); + } + + /** + * Returns the number of columns in the result set. + * + * @return integer + */ + public function columnCount() + { + return $this->_stmt->columnCount(); + } + + /** + * Fetches the SQLSTATE associated with the last operation on the statement. + * + * @return string + */ + public function errorCode() + { + return $this->_stmt->errorCode(); + } + + /** + * Fetches extended error information associated with the last operation on the statement. + * + * @return array + */ + public function errorInfo() + { + return $this->_stmt->errorInfo(); + } + + /** + * Fetches the next row from a result set. + * + * @param integer $fetchStyle + * @return mixed The return value of this function on success depends on the fetch type. + * In all cases, FALSE is returned on failure. + */ + public function fetch($fetchStyle = PDO::FETCH_BOTH) + { + return $this->_stmt->fetch($fetchStyle); + } + + /** + * Returns an array containing all of the result set rows. + * + * @param integer $fetchStyle + * @param integer $columnIndex + * @return array An array containing all of the remaining rows in the result set. + */ + public function fetchAll($fetchStyle = PDO::FETCH_BOTH, $columnIndex = 0) + { + if ($columnIndex != 0) { + return $this->_stmt->fetchAll($fetchStyle, $columnIndex); + } + return $this->_stmt->fetchAll($fetchStyle); + } + + /** + * Returns a single column from the next row of a result set. + * + * @param integer $columnIndex + * @return mixed A single column from the next row of a result set or FALSE if there are no more rows. + */ + public function fetchColumn($columnIndex = 0) + { + return $this->_stmt->fetchColumn($columnIndex); + } + + /** + * Returns the number of rows affected by the last execution of this statement. + * + * @return integer The number of affected rows. + */ + public function rowCount() + { + return $this->_stmt->rowCount(); + } + + /** + * Gets the wrapped driver statement. + * + * @return Doctrine\DBAL\Driver\Statement + */ + public function getWrappedStatement() + { + return $this->_stmt; + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/ArrayType.php b/lib/Doctrine/DBAL/Types/ArrayType.php index fb21c6434..4eb79af24 100644 --- a/lib/Doctrine/DBAL/Types/ArrayType.php +++ b/lib/Doctrine/DBAL/Types/ArrayType.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\DBAL\Types; @@ -26,6 +45,6 @@ class ArrayType extends Type public function getName() { - return 'Array'; + return Type::TARRAY; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/BigIntType.php b/lib/Doctrine/DBAL/Types/BigIntType.php index 549bde6b2..99c1948db 100644 --- a/lib/Doctrine/DBAL/Types/BigIntType.php +++ b/lib/Doctrine/DBAL/Types/BigIntType.php @@ -1,7 +1,28 @@ . + */ namespace Doctrine\DBAL\Types; +use Doctrine\DBAL\Platforms\AbstractPlatform; + /** * Type that maps a database BIGINT to a PHP string. * @@ -12,16 +33,16 @@ class BigIntType extends Type { public function getName() { - return 'BigInteger'; + return Type::BIGINT; } - public function getSqlDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getBigIntTypeDeclarationSQL($fieldDeclaration); } - - public function getTypeCode() + + public function getBindingType() { - return self::CODE_INT; + return \PDO::PARAM_INT; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/BooleanType.php b/lib/Doctrine/DBAL/Types/BooleanType.php index aa325686f..da2694ebf 100644 --- a/lib/Doctrine/DBAL/Types/BooleanType.php +++ b/lib/Doctrine/DBAL/Types/BooleanType.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\DBAL\Types; @@ -28,6 +47,11 @@ class BooleanType extends Type public function getName() { - return 'boolean'; + return Type::BOOLEAN; + } + + public function getBindingType() + { + return \PDO::PARAM_BOOL; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/DateTimeType.php b/lib/Doctrine/DBAL/Types/DateTimeType.php index 5d725c01f..91bc9905d 100644 --- a/lib/Doctrine/DBAL/Types/DateTimeType.php +++ b/lib/Doctrine/DBAL/Types/DateTimeType.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\DBAL\Types; @@ -13,7 +32,7 @@ class DateTimeType extends Type { public function getName() { - return 'DateTime'; + return Type::DATETIME; } public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) diff --git a/lib/Doctrine/DBAL/Types/DateType.php b/lib/Doctrine/DBAL/Types/DateType.php index 17620acfd..5db2ec0f6 100644 --- a/lib/Doctrine/DBAL/Types/DateType.php +++ b/lib/Doctrine/DBAL/Types/DateType.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\DBAL\Types; @@ -13,7 +32,7 @@ class DateType extends Type { public function getName() { - return 'Date'; + return Type::DATE; } public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) diff --git a/lib/Doctrine/DBAL/Types/DecimalType.php b/lib/Doctrine/DBAL/Types/DecimalType.php index 486bbb4c9..44e97c865 100644 --- a/lib/Doctrine/DBAL/Types/DecimalType.php +++ b/lib/Doctrine/DBAL/Types/DecimalType.php @@ -1,7 +1,28 @@ . + */ namespace Doctrine\DBAL\Types; +use Doctrine\DBAL\Platforms\AbstractPlatform; + /** * Type that maps an SQL DECIMAL to a PHP double. * @@ -11,15 +32,15 @@ class DecimalType extends Type { public function getName() { - return 'Decimal'; + return Type::DECIMAL; } - public function getSqlDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); } - public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function convertToPHPValue($value, AbstractPlatform $platform) { return (double) $value; } diff --git a/lib/Doctrine/DBAL/Types/IntegerType.php b/lib/Doctrine/DBAL/Types/IntegerType.php index 6d9f92640..abd946c51 100644 --- a/lib/Doctrine/DBAL/Types/IntegerType.php +++ b/lib/Doctrine/DBAL/Types/IntegerType.php @@ -1,7 +1,28 @@ . + */ namespace Doctrine\DBAL\Types; +use Doctrine\DBAL\Platforms\AbstractPlatform; + /** * Type that maps an SQL INT to a PHP integer. * @@ -12,21 +33,21 @@ class IntegerType extends Type { public function getName() { - return 'Integer'; + return Type::INTEGER; } - public function getSqlDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration); } - public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function convertToPHPValue($value, AbstractPlatform $platform) { return (int) $value; } - public function getTypeCode() + public function getBindingType() { - return self::CODE_INT; + return \PDO::PARAM_INT; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/ObjectType.php b/lib/Doctrine/DBAL/Types/ObjectType.php index 9c0c0e754..6b59f5757 100644 --- a/lib/Doctrine/DBAL/Types/ObjectType.php +++ b/lib/Doctrine/DBAL/Types/ObjectType.php @@ -26,6 +26,6 @@ class ObjectType extends Type public function getName() { - return 'Object'; + return Type::OBJECT; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/SmallIntType.php b/lib/Doctrine/DBAL/Types/SmallIntType.php index 3be3f61fc..0647687ce 100644 --- a/lib/Doctrine/DBAL/Types/SmallIntType.php +++ b/lib/Doctrine/DBAL/Types/SmallIntType.php @@ -1,7 +1,28 @@ . + */ namespace Doctrine\DBAL\Types; +use Doctrine\DBAL\Platforms\AbstractPlatform; + /** * Type that maps a database SMALLINT to a PHP integer. * @@ -11,21 +32,21 @@ class SmallIntType extends Type { public function getName() { - return "SmallInteger"; + return Type::SMALLINT; } - public function getSqlDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration); } - public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function convertToPHPValue($value, AbstractPlatform $platform) { return (int) $value; } - - public function getTypeCode() + + public function getBindingType() { - return self::CODE_INT; + return \PDO::PARAM_INT; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/StringType.php b/lib/Doctrine/DBAL/Types/StringType.php index 3af7aae6f..5702cb18d 100644 --- a/lib/Doctrine/DBAL/Types/StringType.php +++ b/lib/Doctrine/DBAL/Types/StringType.php @@ -21,6 +21,8 @@ namespace Doctrine\DBAL\Types; +use Doctrine\DBAL\Platforms\AbstractPlatform; + /** * Type that maps an SQL VARCHAR to a PHP string. * @@ -29,13 +31,13 @@ namespace Doctrine\DBAL\Types; class StringType extends Type { /** @override */ - public function getSqlDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration); } /** @override */ - public function getDefaultLength(\Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getDefaultLength(AbstractPlatform $platform) { return $platform->getVarcharDefaultLength(); } @@ -43,6 +45,6 @@ class StringType extends Type /** @override */ public function getName() { - return 'string'; + return Type::STRING; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/TextType.php b/lib/Doctrine/DBAL/Types/TextType.php index d7007eeca..1ef008e04 100644 --- a/lib/Doctrine/DBAL/Types/TextType.php +++ b/lib/Doctrine/DBAL/Types/TextType.php @@ -1,7 +1,28 @@ . + */ namespace Doctrine\DBAL\Types; +use Doctrine\DBAL\Platforms\AbstractPlatform; + /** * Type that maps an SQL CLOB to a PHP string. * @@ -10,13 +31,13 @@ namespace Doctrine\DBAL\Types; class TextType extends Type { /** @override */ - public function getSqlDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) + public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($fieldDeclaration); } public function getName() { - return 'text'; + return Type::TEXT; } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/TimeType.php b/lib/Doctrine/DBAL/Types/TimeType.php index 62c17e174..920578db7 100644 --- a/lib/Doctrine/DBAL/Types/TimeType.php +++ b/lib/Doctrine/DBAL/Types/TimeType.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\DBAL\Types; @@ -13,7 +32,7 @@ class TimeType extends Type { public function getName() { - return 'Time'; + return Type::TIME; } /** @@ -26,19 +45,15 @@ class TimeType extends Type /** * {@inheritdoc} - * - * @override */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return ($value !== null) ? $value->format($platform->getTimeFormatString()) : null; } - + /** * {@inheritdoc} - * - * @override */ public function convertToPHPValue($value, AbstractPlatform $platform) { diff --git a/lib/Doctrine/DBAL/Types/Type.php b/lib/Doctrine/DBAL/Types/Type.php index 5f7692aa3..be110132d 100644 --- a/lib/Doctrine/DBAL/Types/Type.php +++ b/lib/Doctrine/DBAL/Types/Type.php @@ -34,32 +34,36 @@ use Doctrine\DBAL\Platforms\AbstractPlatform, */ abstract class Type { - /* The following constants represent type codes and mirror the PDO::PARAM_X constants - * to decouple ourself from PDO. - */ - const CODE_BOOL = 5; - const CODE_NULL = 0; - const CODE_INT = 1; - const CODE_STR = 2; - const CODE_LOB = 3; + const TARRAY = 'array'; + const BIGINT = 'bigint'; + const BOOLEAN = 'boolean'; + const DATETIME = 'datetime'; + const DATE = 'date'; + const TIME = 'time'; + const DECIMAL = 'decimal'; + const INTEGER = 'integer'; + const OBJECT = 'object'; + const SMALLINT = 'smallint'; + const STRING = 'string'; + const TEXT = 'text'; /** Map of already instantiated type objects. One instance per type (flyweight). */ private static $_typeObjects = array(); /** The map of supported doctrine mapping types. */ private static $_typesMap = array( - 'array' => 'Doctrine\DBAL\Types\ArrayType', - 'object' => 'Doctrine\DBAL\Types\ObjectType', - 'boolean' => 'Doctrine\DBAL\Types\BooleanType', - 'integer' => 'Doctrine\DBAL\Types\IntegerType', - 'smallint' => 'Doctrine\DBAL\Types\SmallIntType', - 'bigint' => 'Doctrine\DBAL\Types\BigIntType', - 'string' => 'Doctrine\DBAL\Types\StringType', - 'text' => 'Doctrine\DBAL\Types\TextType', - 'datetime' => 'Doctrine\DBAL\Types\DateTimeType', - 'date' => 'Doctrine\DBAL\Types\DateType', - 'time' => 'Doctrine\DBAL\Types\TimeType', - 'decimal' => 'Doctrine\DBAL\Types\DecimalType' + self::TARRAY => 'Doctrine\DBAL\Types\ArrayType', + self::OBJECT => 'Doctrine\DBAL\Types\ObjectType', + self::BOOLEAN => 'Doctrine\DBAL\Types\BooleanType', + self::INTEGER => 'Doctrine\DBAL\Types\IntegerType', + self::SMALLINT => 'Doctrine\DBAL\Types\SmallIntType', + self::BIGINT => 'Doctrine\DBAL\Types\BigIntType', + self::STRING => 'Doctrine\DBAL\Types\StringType', + self::TEXT => 'Doctrine\DBAL\Types\TextType', + self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType', + self::DATE => 'Doctrine\DBAL\Types\DateType', + self::TIME => 'Doctrine\DBAL\Types\TimeType', + self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType' ); /* Prevent instantiation and force use of the factory method. */ @@ -117,16 +121,6 @@ abstract class Type */ abstract public function getName(); - /** - * Gets the type code of this type. - * - * @return integer - */ - public function getTypeCode() - { - return self::CODE_STR; - } - /** * Factory method to create type instances. * Type instances are implemented as flyweights. @@ -194,6 +188,25 @@ abstract class Type self::$_typesMap[$name] = $className; } + /** + * Gets the (preferred) binding type for values of this type that + * can be used when binding parameters to prepared statements. + * + * This method should return one of the PDO::PARAM_* constants, that is, one of: + * + * PDO::PARAM_BOOL + * PDO::PARAM_NULL + * PDO::PARAM_INT + * PDO::PARAM_STR + * PDO::PARAM_LOB + * + * @return integer + */ + public function getBindingType() + { + return \PDO::PARAM_STR; + } + /** * Get the types array map which holds all registered types and the corresponding * type class diff --git a/lib/Doctrine/ORM/Query/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php similarity index 80% rename from lib/Doctrine/ORM/Query/AbstractQuery.php rename to lib/Doctrine/ORM/AbstractQuery.php index 86fc3aa0e..bd0cdf837 100644 --- a/lib/Doctrine/ORM/Query/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -19,12 +19,13 @@ * . */ -namespace Doctrine\ORM\Query; +namespace Doctrine\ORM; -use Doctrine\ORM\Query\QueryException; +use Doctrine\DBAL\Types\Type, + Doctrine\ORM\Query\QueryException; /** - * Base class for Query and NativeQuery. + * Base contract for ORM queries. Base class for Query and NativeQuery. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com @@ -53,20 +54,19 @@ abstract class AbstractQuery * Hydrates a single scalar value. */ const HYDRATE_SINGLE_SCALAR = 4; - /** - * Hydrates nothing. - */ - //const HYDRATE_NONE = 5; /** - * @var array $params Parameters of this query. + * @var array The parameter map of this query. */ protected $_params = array(); /** - * The user-specified ResultSetMapping to use. - * - * @var ResultSetMapping + * @var array The parameter type map of this query. + */ + protected $_paramTypes = array(); + + /** + * @var ResultSetMapping The user-specified ResultSetMapping to use. */ protected $_resultSetMapping; @@ -76,9 +76,7 @@ abstract class AbstractQuery protected $_em; /** - * A set of query hints. - * - * @var array + * @var array The map of query hints. */ protected $_hints = array(); @@ -95,16 +93,14 @@ abstract class AbstractQuery protected $_resultCacheDriver; /** - * Boolean flag for whether or not to cache the result sets of this query. + * Boolean flag for whether or not to cache the results of this query. * * @var boolean */ protected $_useResultCache; /** - * The id to store the result cache entry under. - * - * @var string + * @var string The id to store the result cache entry under. */ protected $_resultCacheId; @@ -123,9 +119,9 @@ abstract class AbstractQuery * * @param Doctrine\ORM\EntityManager $entityManager */ - public function __construct(\Doctrine\ORM\EntityManager $entityManager) + public function __construct(EntityManager $em) { - $this->_em = $entityManager; + $this->_em = $em; } /** @@ -149,13 +145,10 @@ abstract class AbstractQuery /** * Get all defined parameters. * - * @return array Defined parameters + * @return array The defined query parameters. */ - public function getParameters($params = array()) + public function getParameters() { - if ($params) { - return ($this->_params + $params); - } return $this->_params; } @@ -177,17 +170,23 @@ abstract class AbstractQuery * * @return string SQL query */ - abstract public function getSql(); + abstract public function getSQL(); /** * Sets a query parameter. * * @param string|integer $key The parameter position or name. * @param mixed $value The parameter value. - * @return Doctrine\ORM\AbstractQuery + * @param string $type The parameter type. If specified, the given value will be run through + * the type conversion of this type. This is usually not needed for + * strings and numeric types. + * @return Doctrine\ORM\AbstractQuery This query instance. */ - public function setParameter($key, $value) + public function setParameter($key, $value, $type = null) { + if ($type !== null) { + $this->_paramTypes[$key] = $type; + } $this->_params[$key] = $value; return $this; } @@ -196,12 +195,17 @@ abstract class AbstractQuery * Sets a collection of query parameters. * * @param array $params - * @return Doctrine\ORM\AbstractQuery + * @param array $types + * @return Doctrine\ORM\AbstractQuery This query instance. */ - public function setParameters(array $params) + public function setParameters(array $params, array $types = array()) { foreach ($params as $key => $value) { - $this->setParameter($key, $value); + if (isset($types[$key])) { + $this->setParameter($key, $value, $types[$key]); + } else { + $this->setParameter($key, $value); + } } return $this; } @@ -212,7 +216,7 @@ abstract class AbstractQuery * @param ResultSetMapping $rsm * @return Doctrine\ORM\AbstractQuery */ - public function setResultSetMapping($rsm) + public function setResultSetMapping(Query\ResultSetMapping $rsm) { $this->_resultSetMapping = $rsm; return $this; @@ -222,12 +226,12 @@ abstract class AbstractQuery * Defines a cache driver to be used for caching result sets. * * @param Doctrine\Common\Cache\Cache $driver Cache driver - * @return Doctrine\ORM\Query\AbstractQuery + * @return Doctrine\ORM\AbstractQuery */ public function setResultCacheDriver($resultCacheDriver = null) { if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { - throw \Doctrine\ORM\ORMException::invalidResultCacheDriver(); + throw ORMException::invalidResultCacheDriver(); } $this->_resultCacheDriver = $resultCacheDriver; if ($resultCacheDriver) { @@ -251,9 +255,13 @@ abstract class AbstractQuery } /** - * Set whether or not to cache the result sets for this query + * Set whether or not to cache the results of this query and if so, for + * how long and which ID to use for the cache entry. * * @param boolean $bool + * @param integer $timeToLive + * @param string $resultCacheId + * @return This query instance. */ public function useResultCache($bool, $timeToLive = null, $resultCacheId = null) { @@ -264,13 +272,14 @@ abstract class AbstractQuery if ($resultCacheId) { $this->_resultCacheId = $resultCacheId; } + return $this; } /** * Defines how long the result cache will be active before expire. * - * @param integer $timeToLive How long the cache entry is valid - * @return Doctrine\ORM\Query\AbstractQuery + * @param integer $timeToLive How long the cache entry is valid. + * @return Doctrine\ORM\AbstractQuery This query instance. */ public function setResultCacheLifetime($timeToLive) { @@ -285,7 +294,7 @@ abstract class AbstractQuery /** * Retrieves the lifetime of resultset cache. * - * @return int + * @return integer */ public function getResultCacheLifetime() { @@ -296,7 +305,7 @@ abstract class AbstractQuery * Defines if the result cache is active or not. * * @param boolean $expire Whether or not to force resultset cache expiration. - * @return Doctrine\ORM\Query\AbstractQuery + * @return Doctrine\ORM\AbstractQuery This query instance. */ public function expireResultCache($expire = true) { @@ -307,7 +316,7 @@ abstract class AbstractQuery /** * Retrieves if the resultset cache is active or not. * - * @return bool + * @return boolean */ public function getExpireResultCache() { @@ -315,11 +324,11 @@ abstract class AbstractQuery } /** - * Defines the processing mode to be used during hydration. + * Defines the processing mode to be used during hydration / result set transformation. * * @param integer $hydrationMode Doctrine processing mode to be used during hydration process. * One of the Query::HYDRATE_* constants. - * @return Doctrine\ORM\Query\AbstractQuery + * @return Doctrine\ORM\AbstractQuery This query instance. */ public function setHydrationMode($hydrationMode) { @@ -383,25 +392,25 @@ abstract class AbstractQuery * * @param integer $hydrationMode * @return mixed - * @throws Doctrine\ORM\NonUniqueResultException If the query result is not unique. - * @throws Doctrine\ORM\NoResultException If the query returned no result. + * @throws NonUniqueResultException If the query result is not unique. + * @throws NoResultException If the query returned no result. */ public function getSingleResult($hydrationMode = null) { $result = $this->execute(array(), $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { - throw new \Doctrine\ORM\NoResultException; + throw new NoResultException; } if (is_array($result)) { if (count($result) > 1) { - throw new \Doctrine\ORM\NonUniqueResultException; + throw new NonUniqueResultException; } return array_shift($result); } else if (is_object($result)) { if (count($result) > 1) { - throw new \Doctrine\ORM\NonUniqueResultException; + throw new NonUniqueResultException; } return $result->first(); } @@ -415,8 +424,7 @@ abstract class AbstractQuery * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). * * @return mixed - * @throws Doctrine\ORM\NonUniqueResultException If the query result is not unique. - * @throws Doctrine\ORM\NoResultException If the query returned no result. + * @throws QueryException If the query result is not unique. */ public function getSingleScalarResult() { @@ -428,7 +436,7 @@ abstract class AbstractQuery * * @param string $name The name of the hint. * @param mixed $value The value of the hint. - * @return Doctrine\ORM\Query\AbstractQuery + * @return Doctrine\ORM\AbstractQuery */ public function setHint($name, $value) { @@ -484,20 +492,22 @@ abstract class AbstractQuery $this->setHydrationMode($hydrationMode); } - $params = $this->getParameters($params); + if ($params) { + $this->setParameters($params); + } - if (isset($params[0])) { + if (isset($this->_params[0])) { throw QueryException::invalidParameterPosition(0); } // Check result cache if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) { - $id = $this->_getResultCacheId($params); + $id = $this->_getResultCacheId(); $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($id); if ($cached === false) { // Cache miss. - $stmt = $this->_doExecute($params); + $stmt = $this->_doExecute(); $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( $stmt, $this->_resultSetMapping, $this->_hints @@ -512,7 +522,7 @@ abstract class AbstractQuery } } - $stmt = $this->_doExecute($params); + $stmt = $this->_doExecute(); if (is_numeric($stmt)) { return $stmt; @@ -529,7 +539,7 @@ abstract class AbstractQuery * generated for you. * * @param string $id - * @return Doctrine\ORM\Query\AbstractQuery + * @return Doctrine\ORM\AbstractQuery This query instance. */ public function setResultCacheId($id) { @@ -542,36 +552,24 @@ abstract class AbstractQuery * Will return the configured id if it exists otherwise a hash will be * automatically generated for you. * - * @param array $params * @return string $id */ - protected function _getResultCacheId(array $params) + protected function _getResultCacheId() { if ($this->_resultCacheId) { return $this->_resultCacheId; } else { $sql = $this->getSql(); ksort($this->_hints); - return md5(implode(";", (array)$sql) . var_export($params, true) . + return md5(implode(";", (array)$sql) . var_export($this->_params, true) . var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode); } } /** - * Prepares the given parameters for execution in an SQL statement. + * Executes the query and returns a the resulting Statement object. * - * Note to inheritors: This method must return a numerically, continuously indexed array, - * starting with index 0 where the values (the parameter values) are in the order - * in which the parameters appear in the SQL query. - * - * @return array The SQL parameter array. + * @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results. */ - abstract protected function _prepareParams(array $params); - - /** - * Executes the query and returns a reference to the resulting Statement object. - * - * @param array $params - */ - abstract protected function _doExecute(array $params); + abstract protected function _doExecute(); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 66758bbf6..63e399a7f 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -21,8 +21,10 @@ namespace Doctrine\ORM\Internal\Hydration; -use Doctrine\DBAL\Connection, - Doctrine\DBAL\Types\Type; +use PDO, + Doctrine\DBAL\Connection, + Doctrine\DBAL\Types\Type, + Doctrine\ORM\EntityManager; /** * Base class for all hydrators. A hydrator is a class that provides some form @@ -54,7 +56,7 @@ abstract class AbstractHydrator /** @var Statement The statement that provides the data to hydrate. */ protected $_stmt; - + /** @var array The query hints. */ protected $_hints; @@ -63,7 +65,7 @@ abstract class AbstractHydrator * * @param Doctrine\ORM\EntityManager $em The EntityManager to use. */ - public function __construct(\Doctrine\ORM\EntityManager $em) + public function __construct(EntityManager $em) { $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); @@ -112,18 +114,18 @@ abstract class AbstractHydrator */ public function hydrateRow() { - $row = $this->_stmt->fetch(Connection::FETCH_ASSOC); + $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); if ( ! $row) { $this->_cleanup(); return false; } - $result = $this->_getRowContainer(); + $result = array(); $this->_hydrateRow($row, $this->_cache, $result); return $result; } /** - * Excutes one-time preparation tasks once each time hydration is started + * Excutes one-time preparation tasks, once each time hydration is started * through {@link hydrateAll} or {@link iterate()}. */ protected function _prepare() @@ -149,7 +151,7 @@ abstract class AbstractHydrator * @param array $cache The cache to use. * @param mixed $result The result to fill. */ - protected function _hydrateRow(array &$data, array &$cache, array &$result) + protected function _hydrateRow(array $data, array &$cache, array &$result) { throw new HydrationException("_hydrateRow() not implemented by this hydrator."); } @@ -159,14 +161,6 @@ abstract class AbstractHydrator */ abstract protected function _hydrateAll(); - /** - * Gets the row container used during row-by-row hydration through {@link iterate()}. - */ - protected function _getRowContainer() - { - return array(); - } - /** * Processes a row of the result set. * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). @@ -178,7 +172,7 @@ abstract class AbstractHydrator * @return array An array with all the fields (name => value) of the data row, * grouped by their component alias. */ - protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents) + protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) { $rowData = array(); diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 8ba3b18ac..9e5dcf2e9 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM\Internal\Hydration; -use Doctrine\DBAL\Connection; +use PDO, Doctrine\DBAL\Connection; /** * The ArrayHydrator produces a nested array "graph" that is often (not always) @@ -60,7 +60,7 @@ class ArrayHydrator extends AbstractHydrator { $result = array(); $cache = array(); - while ($data = $this->_stmt->fetch(Connection::FETCH_ASSOC)) { + while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->_hydrateRow($data, $cache, $result); } @@ -68,7 +68,7 @@ class ArrayHydrator extends AbstractHydrator } /** @override */ - protected function _hydrateRow(array &$data, array &$cache, array &$result) + protected function _hydrateRow(array $data, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory diff --git a/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php b/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php index b4be29e74..e7c1248b4 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php +++ b/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php @@ -31,18 +31,17 @@ namespace Doctrine\ORM\Internal\Hydration; class IterableResult implements \Iterator { /** - * - * @var \Doctrine\ORM\Internal\Hydration\AbstractHydrator + * @var Doctrine\ORM\Internal\Hydration\AbstractHydrator */ private $_hydrator; /** - * @var bool + * @var boolean */ private $_rewinded = false; /** - * @var int + * @var integer */ private $_key = -1; @@ -52,8 +51,7 @@ class IterableResult implements \Iterator private $_current = null; /** - * - * @param \Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator + * @param Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator */ public function __construct($hydrator) { @@ -62,7 +60,7 @@ class IterableResult implements \Iterator public function rewind() { - if($this->_rewinded == true) { + if ($this->_rewinded == true) { throw new HydrationException("Can only iterate a Result once."); } else { $this->_current = $this->next(); diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 3c399a8e6..cc13676d6 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Internal\Hydration; -use Doctrine\ORM\PersistentCollection, +use PDO, + Doctrine\ORM\PersistentCollection, Doctrine\ORM\Query, Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection; @@ -102,7 +103,7 @@ class ObjectHydrator extends AbstractHydrator } } } - + /** * {@inheritdoc} */ @@ -122,8 +123,9 @@ class ObjectHydrator extends AbstractHydrator { $result = array(); $cache = array(); - while ($data = $this->_stmt->fetch(\Doctrine\DBAL\Connection::FETCH_ASSOC)) { - $this->_hydrateRow($data, $cache, $result); + + while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { + $this->_hydrateRow($row, $cache, $result); } // Take snapshots from all newly initialized collections @@ -189,7 +191,6 @@ class ObjectHydrator extends AbstractHydrator $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } - return $this->_uow->createEntity($className, $data, $this->_hints); } @@ -244,7 +245,7 @@ class ObjectHydrator extends AbstractHydrator * @param array $cache * @param array $result */ - protected function _hydrateRow(array &$data, array &$cache, array &$result) + protected function _hydrateRow(array $data, array &$cache, array &$result) { // Initialize $id = $this->_idTemplate; // initialize the id-memory @@ -263,12 +264,11 @@ class ObjectHydrator extends AbstractHydrator // Hydrate the data chunks foreach ($rowData as $dqlAlias => $data) { - $index = false; $entityName = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { // It's a joined result - + $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias]; // Get a reference to the parent object to which the joined element belongs. diff --git a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php index da52c6d13..3038f70fe 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php @@ -38,14 +38,14 @@ class ScalarHydrator extends AbstractHydrator { $result = array(); $cache = array(); - while ($data = $this->_stmt->fetch(Connection::FETCH_ASSOC)) { + while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) { $result[] = $this->_gatherScalarRowData($data, $cache); } return $result; } /** @override */ - protected function _hydrateRow(array &$data, array &$cache, array &$result) + protected function _hydrateRow(array $data, array &$cache, array &$result) { $result[] = $this->_gatherScalarRowData($data, $cache); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php index 67f4e9aac..dbcc8c813 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php @@ -35,7 +35,7 @@ class SingleScalarHydrator extends AbstractHydrator protected function _hydrateAll() { $cache = array(); - $result = $this->_stmt->fetchAll(Connection::FETCH_ASSOC); + $result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); if (count($result) > 1 || count($result[key($result)]) > 1) { throw new \Doctrine\ORM\NonUniqueResultException; } diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index 5c117d28c..0e5daaff7 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -249,16 +249,6 @@ abstract class AssociationMapping return $this->fetchMode == self::FETCH_LAZY; } - /** - * Whether the source entity of this association represents the inverse side. - * - * @return boolean - */ - public function isInverseSide() - { - return ! $this->isOwningSide; - } - /** * Whether the association is a one-to-one association. * @@ -298,7 +288,12 @@ abstract class AssociationMapping { return (bool) $this->joinTable; } - + + /** + * Checks whether the association has any cascades configured. + * + * @return boolean + */ public function hasCascades() { return $this->isCascadePersist || diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 0f852f2c6..92f403324 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -21,6 +21,8 @@ namespace Doctrine\ORM\Mapping; +use ReflectionClass, ReflectionProperty; + /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and it's associations. @@ -73,7 +75,7 @@ class ClassMetadata extends ClassMetadataInfo $this->name = $entityName; $this->reflClass = new \ReflectionClass($entityName); $this->namespace = $this->reflClass->getNamespaceName(); - $this->primaryTable['name'] = $this->reflClass->getShortName(); + $this->table['name'] = $this->reflClass->getShortName(); $this->rootEntityName = $entityName; } @@ -271,9 +273,9 @@ class ClassMetadata extends ClassMetadataInfo */ public function getQuotedTableName($platform) { - return isset($this->primaryTable['quoted']) ? - $platform->quoteIdentifier($this->primaryTable['name']) : - $this->primaryTable['name']; + return isset($this->table['quoted']) ? + $platform->quoteIdentifier($this->table['name']) : + $this->table['name']; } /** @@ -306,8 +308,8 @@ class ClassMetadata extends ClassMetadataInfo 'discriminatorColumn', 'discriminatorValue', 'discriminatorMap', - 'fieldMappings', - 'fieldNames', //TODO: Not all of this stuff needs to be serialized. Only type, columnName and fieldName. + 'fieldMappings',//TODO: Not all of this stuff needs to be serialized. Only type, columnName and fieldName. + 'fieldNames', 'generatorType', 'identifier', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. @@ -320,13 +322,13 @@ class ClassMetadata extends ClassMetadataInfo 'lifecycleCallbacks', 'name', 'parentClasses', - 'primaryTable', + 'table', 'rootEntityName', 'subClasses', 'versionField' ); } - + /** * Restores some state that can not be serialized/unserialized. * @@ -335,22 +337,21 @@ class ClassMetadata extends ClassMetadataInfo public function __wakeup() { // Restore ReflectionClass and properties - $this->reflClass = new \ReflectionClass($this->name); + $this->reflClass = new ReflectionClass($this->name); foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['inherited'])) { - $reflField = new \ReflectionProperty($mapping['inherited'], $field); + $reflField = new ReflectionProperty($mapping['inherited'], $field); } else { $reflField = $this->reflClass->getProperty($field); } - $reflField->setAccessible(true); $this->reflFields[$field] = $reflField; } - + foreach ($this->associationMappings as $field => $mapping) { if (isset($this->inheritedAssociationFields[$field])) { - $reflField = new \ReflectionProperty($this->inheritedAssociationFields[$field], $field); + $reflField = new ReflectionProperty($this->inheritedAssociationFields[$field], $field); } else { $reflField = $this->reflClass->getProperty($field); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 89dc54935..a38bb1b99 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -294,8 +294,9 @@ class ClassMetadataInfo * uniqueConstraints => array * * @var array + * @todo Rename to just $table */ - public $primaryTable; + public $table; /** * READ-ONLY: The registered lifecycle callbacks for entities of this class. @@ -862,7 +863,7 @@ class ClassMetadataInfo */ public function getTableName() { - return $this->primaryTable['name']; + return $this->table['name']; } /** @@ -872,7 +873,7 @@ class ClassMetadataInfo */ public function getTemporaryIdTableName() { - return $this->primaryTable['name'] . '_id_tmp'; + return $this->table['name'] . '_id_tmp'; } /** @@ -966,7 +967,7 @@ class ClassMetadataInfo */ public function setTableName($tableName) { - $this->primaryTable['name'] = $tableName; + $this->table['name'] = $tableName; } /** @@ -981,7 +982,7 @@ class ClassMetadataInfo */ public function setPrimaryTable(array $primaryTableDefinition) { - $this->primaryTable = $primaryTableDefinition; + $this->table = $primaryTableDefinition; } /** @@ -1101,7 +1102,7 @@ class ClassMetadataInfo */ private function _registerMappingIfInverse(AssociationMapping $assoc) { - if ($assoc->isInverseSide()) { + if ( ! $assoc->isOwningSide) { $this->inverseMappings[$assoc->targetEntityName][$assoc->mappedBy] = $assoc; } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php index 02a33bda4..ecfc71102 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php @@ -63,7 +63,7 @@ class DatabaseDriver implements Driver $className = Inflector::classify($tableName); $metadata->name = $className; - $metadata->primaryTable['name'] = $tableName; + $metadata->table['name'] = $tableName; $columns = $this->_sm->listTableColumns($tableName); diff --git a/lib/Doctrine/ORM/Mapping/Driver/Driver.php b/lib/Doctrine/ORM/Mapping/Driver/Driver.php index 01ece3470..1faaac964 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/Driver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/Driver.php @@ -31,6 +31,7 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo; * @since 2.0 * @version $Revision: 1393 $ * @author Jonathan H. Wage + * @todo Rename: MetadataDriver */ interface Driver { diff --git a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php b/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php index ecc4dd896..288637505 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php @@ -41,6 +41,7 @@ use Doctrine\Common\Cache\ArrayCache, * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel + * @todo Rename: PHPDriver */ class PhpDriver extends AbstractFileDriver { diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 05099e26c..e02c3bdf5 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -62,11 +62,11 @@ class XmlDriver extends AbstractFileDriver // Evaluate attributes if (isset($xmlRoot['table'])) { - $metadata->primaryTable['name'] = (string)$xmlRoot['table']; + $metadata->table['name'] = (string)$xmlRoot['table']; } if (isset($xmlRoot['schema'])) { - $metadata->primaryTable['schema'] = (string)$xmlRoot['schema']; + $metadata->table['schema'] = (string)$xmlRoot['schema']; } if (isset($xmlRoot['inheritance-type'])) { @@ -108,7 +108,7 @@ class XmlDriver extends AbstractFileDriver $columns = $index['columns']; } - $metadata->primaryTable['indexes'][$index['name']] = array( + $metadata->table['indexes'][$index['name']] = array( 'columns' => $columns ); } @@ -123,7 +123,7 @@ class XmlDriver extends AbstractFileDriver $columns = $unique['columns']; } - $metadata->primaryTable['uniqueConstraints'][$unique['name']] = array( + $metadata->table['uniqueConstraints'][$unique['name']] = array( 'columns' => $columns ); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index e342541f9..a1e9d8804 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -62,11 +62,11 @@ class YamlDriver extends AbstractFileDriver // Evaluate root level properties if (isset($element['table'])) { - $metadata->primaryTable['name'] = $element['table']; + $metadata->table['name'] = $element['table']; } if (isset($element['schema'])) { - $metadata->primaryTable['schema'] = $element['schema']; + $metadata->table['schema'] = $element['schema']; } if (isset($element['inheritanceType'])) { @@ -107,7 +107,7 @@ class YamlDriver extends AbstractFileDriver $columns = $index['columns']; } - $metadata->primaryTable['indexes'][$index['name']] = array( + $metadata->table['indexes'][$index['name']] = array( 'columns' => $columns ); } @@ -126,7 +126,7 @@ class YamlDriver extends AbstractFileDriver $columns = $unique['columns']; } - $metadata->primaryTable['uniqueConstraints'][$unique['name']] = array( + $metadata->table['uniqueConstraints'][$unique['name']] = array( 'columns' => $columns ); } diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index b747351dc..c3e0b43bb 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM; -use Doctrine\ORM\Query\AbstractQuery; +use Doctrine\DBAL\Types\Type; /** * Represents a native SQL query. @@ -33,24 +33,13 @@ final class NativeQuery extends AbstractQuery { private $_sql; - /** - * Initializes a new instance of the NativeQuery class that is bound - * to the given EntityManager. - * - * @param EntityManager $em The EntityManager to use. - */ - public function __construct(EntityManager $em) - { - parent::__construct($em); - } - /** * Sets the SQL of the query. * * @param string $sql - * @return Doctrine\ORM\AbstractQuery + * @return NativeQuery This query instance. */ - public function setSql($sql) + public function setSQL($sql) { $this->_sql = $sql; return $this; @@ -62,37 +51,27 @@ final class NativeQuery extends AbstractQuery * @return mixed The built SQL query or an array of all SQL queries. * @override */ - public function getSql() + public function getSQL() { return $this->_sql; } - /** - * Executes the query. - * - * @param array $params - * @return Statement The Statement handle. - * @override - */ - protected function _doExecute(array $params) - { - return $this->_em->getConnection()->execute($this->_sql, $this->_prepareParams($params)); - } - /** * {@inheritdoc} - * - * @override */ - protected function _prepareParams(array $params) + protected function _doExecute() { - $sqlParams = array(); - + $stmt = $this->_em->getConnection()->prepare($this->_sql); + $params = $this->_params; foreach ($params as $key => $value) { - $sqlParams[$key] = $value; + if (isset($this->_paramTypes[$key])) { + $stmt->bindValue($key, $value, $this->_paramTypes[$key]); + } else { + $stmt->bindValue($key, $value); + } } - ksort($sqlParams); - - return array_values($sqlParams); + $stmt->execute(); + + return $stmt; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 52dc3b096..886d3b0b4 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -290,7 +290,12 @@ final class PersistentCollection implements \Doctrine\Common\Collections\Collect */ private function _changed() { - $this->_isDirty = true; + if ( ! $this->_isDirty) { + $this->_isDirty = true; + //if ($this->_isNotifyRequired) { + //$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); + //} + } } /** diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 9697d9a00..9b40e6372 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -61,14 +61,6 @@ abstract class AbstractCollectionPersister $this->_conn = $em->getConnection(); } - /*public function recreate(PersistentCollection $coll) - { - if ($coll->getRelation()->isInverseSide()) { - return; - } - //... - }*/ - /** * Deletes the persistent state represented by the given collection. * @@ -76,7 +68,7 @@ abstract class AbstractCollectionPersister */ public function delete(PersistentCollection $coll) { - if ($coll->getMapping()->isInverseSide()) { + if ( ! $coll->getMapping()->isOwningSide) { return; // ignore inverse side } $sql = $this->_getDeleteSql($coll); @@ -106,7 +98,7 @@ abstract class AbstractCollectionPersister */ public function update(PersistentCollection $coll) { - if ($coll->getMapping()->isInverseSide()) { + if ( ! $coll->getMapping()->isOwningSide) { return; // ignore inverse side } $this->deleteRows($coll); diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php new file mode 100644 index 000000000..f5469ceb5 --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php @@ -0,0 +1,105 @@ +. + */ + +namespace Doctrine\ORM\Persisters; + +use Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\DBAL\Types\Type; + +/** + * Base class for entity persisters that implement a certain inheritance mapping strategy. + * All these persisters are assumed to use a discriminator column to discriminate entity + * types in the hierarchy. + * + * @author Roman Borschel + * @since 2.0 + */ +abstract class AbstractEntityInheritancePersister extends StandardEntityPersister +{ + /** + * Map from column names to class names that declare the field the column is mapped to. + * + * @var array + */ + private $_declaringClassMap = array(); + + /** + * {@inheritdoc} + */ + protected function _prepareInsertData($entity) + { + $data = parent::_prepareInsertData($entity); + // Populate the discriminator column + $discColumn = $this->_class->discriminatorColumn; + $this->_columnTypes[$discColumn['name']] = $discColumn['type']; + $data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue; + return $data; + } + + /** + * Gets the name of the table that contains the discriminator column. + * + * @return string The table name. + */ + abstract protected function _getDiscriminatorColumnTableName(); + + /** + * {@inheritdoc} + */ + protected function _processSQLResult(array $sqlResult) + { + $data = array(); + $discrColumnName = $this->_platform->getSQLResultCasing($this->_class->discriminatorColumn['name']); + $entityName = $this->_class->discriminatorMap[$sqlResult[$discrColumnName]]; + unset($sqlResult[$discrColumnName]); + foreach ($sqlResult as $column => $value) { + $realColumnName = $this->_resultColumnNames[$column]; + if (isset($this->_declaringClassMap[$column])) { + $class = $this->_declaringClassMap[$column]; + if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) { + $field = $class->fieldNames[$realColumnName]; + $data[$field] = Type::getType($class->fieldMappings[$field]['type']) + ->convertToPHPValue($value, $this->_platform); + } + } else { + $data[$realColumnName] = $value; + } + } + + return array($entityName, $data); + } + + /** + * {@inheritdoc} + */ + protected function _getSelectColumnSQL($field, ClassMetadata $class) + { + $columnName = $class->columnNames[$field]; + $sql = $this->_getSQLTableAlias($class) . '.' . $class->getQuotedColumnName($field, $this->_platform); + $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); + if ( ! isset($this->_resultColumnNames[$columnAlias])) { + $this->_resultColumnNames[$columnAlias] = $columnName; + $this->_declaringClassMap[$columnAlias] = $class; + } + + return "$sql AS $columnAlias"; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index a1424d113..f6795e6b8 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -25,60 +25,51 @@ use Doctrine\ORM\ORMException; /** * The joined subclass persister maps a single entity instance to several tables in the - * database as it is defined by Class Table Inheritance. + * database as it is defined by the Class Table Inheritance strategy. * - * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @version $Revision$ - * @link www.doctrine-project.org - * @since 2.0 + * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html */ -class JoinedSubclassPersister extends StandardEntityPersister +class JoinedSubclassPersister extends AbstractEntityInheritancePersister { - /** Map that maps column names to the table names that own them. - * This is mainly a temporary cache, used during a single request. + /** + * Map that maps column names to the table names that own them. + * This is mainly a temporary cache, used during a single request. */ private $_owningTableMap = array(); /** * {@inheritdoc} - * - * @override */ - protected function _prepareData($entity, array &$result, $isInsert = false) + protected function _getDiscriminatorColumnTableName() { - parent::_prepareData($entity, $result, $isInsert); - // Populate the discriminator column - if ($isInsert) { - $discColumn = $this->_class->discriminatorColumn; - $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); - $result[$rootClass->primaryTable['name']][$discColumn['name']] = $this->_class->discriminatorValue; + if ($this->_class->name == $this->_class->rootEntityName) { + return $this->_class->table['name']; + } else { + return $this->_em->getClassMetadata($this->_class->rootEntityName)->table['name']; } } /** - * This function finds the ClassMetadata instance in a inheritance hierarchy + * This function finds the ClassMetadata instance in an inheritance hierarchy * that is responsible for enabling versioning. * - * @return mixed ClassMetadata instance or false if versioning is not enabled. + * @return Doctrine\ORM\Mapping\ClassMetadata */ private function _getVersionedClassMetadata() { - if ($this->_class->isVersioned) { - if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { - $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; - $versionedClass = $this->_em->getClassMetadata($definingClassName); - } else { - $versionedClass = $this->_class; - } - return $versionedClass; + if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { + $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; + return $this->_em->getClassMetadata($definingClassName); } - return false; + return $this->_class; } /** * Gets the name of the table that owns the column the given field is mapped to. - * Does only look upwards in the hierarchy, not downwards. * * @param string $fieldName * @return string @@ -91,16 +82,16 @@ class JoinedSubclassPersister extends StandardEntityPersister if (isset($this->_class->inheritedAssociationFields[$fieldName])) { $this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( $this->_class->inheritedAssociationFields[$fieldName] - )->primaryTable['name']; + )->table['name']; } else { - $this->_owningTableMap[$fieldName] = $this->_class->primaryTable['name']; + $this->_owningTableMap[$fieldName] = $this->_class->table['name']; } } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { $this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( $this->_class->fieldMappings[$fieldName]['inherited'] - )->primaryTable['name']; + )->table['name']; } else { - $this->_owningTableMap[$fieldName] = $this->_class->primaryTable['name']; + $this->_owningTableMap[$fieldName] = $this->_class->table['name']; } } @@ -109,8 +100,6 @@ class JoinedSubclassPersister extends StandardEntityPersister /** * {@inheritdoc} - * - * @override */ public function executeInserts() { @@ -118,7 +107,7 @@ class JoinedSubclassPersister extends StandardEntityPersister return; } - if ($isVersioned = $this->_class->isVersioned) { + if ($this->_class->isVersioned) { $versionedClass = $this->_getVersionedClassMetadata(); } @@ -130,53 +119,33 @@ class JoinedSubclassPersister extends StandardEntityPersister $rootClass = $this->_class->name == $this->_class->rootEntityName ? $this->_class : $this->_em->getClassMetadata($this->_class->rootEntityName); $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name); - $rootTableName = $rootClass->primaryTable['name']; + $rootTableName = $rootClass->table['name']; $rootTableStmt = $this->_conn->prepare($rootPersister->getInsertSQL()); - if ($this->_sqlLogger !== null) { - $sql = array(); - $sql[$rootTableName] = $rootPersister->getInsertSQL(); - } - + // Prepare statements for sub tables. $subTableStmts = array(); if ($rootClass !== $this->_class) { - $subTableStmts[$this->_class->primaryTable['name']] = $this->_conn->prepare($this->getInsertSQL()); - if ($this->_sqlLogger !== null) { - $sql[$this->_class->primaryTable['name']] = $this->getInsertSQL(); - } + $subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->getInsertSQL()); } foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $parentTableName = $parentClass->primaryTable['name']; + $parentTableName = $parentClass->table['name']; if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->getInsertSQL()); - if ($this->_sqlLogger !== null) { - $sql[$parentTableName] = $parentPersister->getInsertSQL(); - } } } - + // Execute all inserts. For each entity: // 1) Insert on root table // 2) Insert on sub tables foreach ($this->_queuedInserts as $entity) { - $insertData = array(); - $this->_prepareData($entity, $insertData, true); + $insertData = $this->_prepareInsertData($entity); // Execute insert on root table $paramIndex = 1; - if ($this->_sqlLogger !== null) { - $params = array(); - foreach ($insertData[$rootTableName] as $columnName => $value) { - $params[$paramIndex] = $value; - $rootTableStmt->bindValue($paramIndex++, $value); - } - $this->_sqlLogger->logSql($sql[$rootTableName], $params); - } else { - foreach ($insertData[$rootTableName] as $columnName => $value) { - $rootTableStmt->bindValue($paramIndex++, $value); - } + foreach ($insertData[$rootTableName] as $columnName => $value) { + $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } $rootTableStmt->execute(); @@ -192,24 +161,11 @@ class JoinedSubclassPersister extends StandardEntityPersister foreach ($subTableStmts as $tableName => $stmt) { $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); $paramIndex = 1; - if ($this->_sqlLogger !== null) { - $params = array(); - foreach ((array) $id as $idVal) { - $params[$paramIndex] = $idVal; - $stmt->bindValue($paramIndex++, $idVal); - } - foreach ($data as $columnName => $value) { - $params[$paramIndex] = $value; - $stmt->bindValue($paramIndex++, $value); - } - $this->_sqlLogger->logSql($sql[$tableName], $params); - } else { - foreach ((array) $id as $idVal) { - $stmt->bindValue($paramIndex++, $idVal); - } - foreach ($data as $columnName => $value) { - $stmt->bindValue($paramIndex++, $value); - } + foreach ((array) $id as $idVal) { + $stmt->bindValue($paramIndex++, $idVal); + } + foreach ($data as $columnName => $value) { + $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } $stmt->execute(); } @@ -220,7 +176,7 @@ class JoinedSubclassPersister extends StandardEntityPersister $stmt->closeCursor(); } - if ($isVersioned) { + if (isset($versionedClass)) { $this->_assignDefaultVersionValue($versionedClass, $entity, $id); } @@ -230,45 +186,30 @@ class JoinedSubclassPersister extends StandardEntityPersister } /** - * Updates an entity. - * - * @param object $entity The entity to update. - * @override + * {@inheritdoc} */ public function update($entity) { - $updateData = array(); - $this->_prepareData($entity, $updateData); - - $id = array_combine( - $this->_class->getIdentifierColumnNames(), - $this->_em->getUnitOfWork()->getEntityIdentifier($entity) - ); + $updateData = $this->_prepareUpdateData($entity); if ($isVersioned = $this->_class->isVersioned) { - $versionedClass = $this->_getVersionedClassMetadata(); - $versionedTable = $versionedClass->primaryTable['name']; + $versionedTable = $this->_getVersionedClassMetadata()->table['name']; } if ($updateData) { foreach ($updateData as $tableName => $data) { - if ($isVersioned && $versionedTable == $tableName) { - $this->_doUpdate($entity, $tableName, $data, $id); - } else { - $this->_conn->update($tableName, $data, $id); - } + $this->_updateTable($entity, $tableName, $data, $isVersioned && $versionedTable == $tableName); } + // Make sure the table with the version column is updated even if no columns on that + // table were affected. if ($isVersioned && ! isset($updateData[$versionedTable])) { - $this->_doUpdate($entity, $versionedTable, array(), $id); + $this->_updateTable($entity, $versionedTable, array(), true); } } } /** - * Deletes an entity. - * - * @param object $entity The entity to delete. - * @override + * {@inheritdoc} */ public function delete($entity) { @@ -279,26 +220,20 @@ class JoinedSubclassPersister extends StandardEntityPersister // If the database platform supports FKs, just // delete the row from the root table. Cascades do the rest. - if ($this->_conn->getDatabasePlatform()->supportsForeignKeyConstraints()) { + if ($this->_platform->supportsForeignKeyConstraints()) { $this->_conn->delete($this->_em->getClassMetadata($this->_class->rootEntityName) - ->primaryTable['name'], $id); + ->getQuotedTableName($this->_platform), $id); } else { // Delete from all tables individually, starting from this class' table up to the root table. - $this->_conn->delete($this->_class->primaryTable['name'], $id); + $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id); foreach ($this->_class->parentClasses as $parentClass) { - $this->_conn->delete($this->_em->getClassMetadata($parentClass)->primaryTable['name'], $id); + $this->_conn->delete($this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id); } } } /** - * Gets the SELECT SQL to select one or more entities by a set of field criteria. - * - * @param array $criteria - * @param AssociationMapping $assoc - * @param string $orderBy - * @return string - * @override + * {@inheritdoc} */ protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null) { @@ -331,7 +266,7 @@ class JoinedSubclassPersister extends StandardEntityPersister } } - // Add discriminator column (DO NOT ALIAS THIS COLUMN). + // Add discriminator column (DO NOT ALIAS THIS COLUMN, see StandardEntityPersister#_processSQLResultInheritanceAware). $discrColumn = $this->_class->discriminatorColumn['name']; if ($this->_class->rootEntityName == $this->_class->name) { $columnList .= ", $baseTableAlias.$discrColumn"; @@ -428,19 +363,13 @@ class JoinedSubclassPersister extends StandardEntityPersister . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql; } - /** Ensure this is never called. This persister overrides _getSelectEntitiesSQL directly. */ + /** Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { throw new \BadMethodCallException("Illegal invocation of ".__METHOD__." on JoinedSubclassPersister."); } - /** @override */ - protected function _processSQLResult(array $sqlResult) - { - return $this->_processSQLResultInheritanceAware($sqlResult); - } - - /** @override */ + /** {@inheritdoc} */ protected function _getInsertColumnList() { // Identifier columns must always come first in the column list of subclasses. diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index 61472f682..81bf6a1f4 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -27,27 +27,20 @@ use Doctrine\ORM\Mapping\ClassMetadata; * Persister for entities that participate in a hierarchy mapped with the * SINGLE_TABLE strategy. * - * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @version $Revision: 3406 $ - * @link www.doctrine-project.org - * @since 2.0 + * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 2.0 + * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html */ -class SingleTablePersister extends StandardEntityPersister +class SingleTablePersister extends AbstractEntityInheritancePersister { - /** @override */ - protected function _prepareData($entity, array &$result, $isInsert = false) + /** {@inheritdoc} */ + protected function _getDiscriminatorColumnTableName() { - parent::_prepareData($entity, $result, $isInsert); - // Populate the discriminator column - if ($isInsert) { - $discColumn = $this->_class->discriminatorColumn['name']; - $result[$this->_class->getQuotedTableName($this->_platform)][$discColumn] = - $this->_class->discriminatorValue; - } + return $this->_class->table['name']; } - - /** @override */ + + /** {@inheritdoc} */ protected function _getSelectColumnListSQL() { $columnList = parent::_getSelectColumnListSQL(); @@ -86,7 +79,7 @@ class SingleTablePersister extends StandardEntityPersister return $columnList; } - /** @override */ + /** {@inheritdoc} */ protected function _getInsertColumnList() { $columns = parent::_getInsertColumnList(); @@ -96,19 +89,13 @@ class SingleTablePersister extends StandardEntityPersister return $columns; } - /** @override */ - protected function _processSQLResult(array $sqlResult) - { - return $this->_processSQLResultInheritanceAware($sqlResult); - } - - /** @override */ + /** {@inheritdoc} */ protected function _getSQLTableAlias(ClassMetadata $class) { if (isset($this->_sqlTableAliases[$class->rootEntityName])) { return $this->_sqlTableAliases[$class->rootEntityName]; } - $tableAlias = $this->_em->getClassMetadata($class->rootEntityName)->primaryTable['name'][0] . $this->_sqlAliasCounter++; + $tableAlias = $this->_em->getClassMetadata($class->rootEntityName)->table['name'][0] . $this->_sqlAliasCounter++; $this->_sqlTableAliases[$class->rootEntityName] = $tableAlias; return $tableAlias; diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 8e9ddb0ca..f79f8a5ab 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -21,25 +21,22 @@ namespace Doctrine\ORM\Persisters; -use Doctrine\ORM\ORMException, - Doctrine\Common\Collections\ArrayCollection, - Doctrine\DBAL\Connection, +use PDO, + Doctrine\ORM\ORMException, + Doctrine\ORM\OptimisticLockException, Doctrine\DBAL\Types\Type, Doctrine\ORM\EntityManager, Doctrine\ORM\Query, Doctrine\ORM\PersistentCollection, - Doctrine\ORM\Mapping\ClassMetadata, - Doctrine\ORM\Events; + Doctrine\ORM\Mapping\ClassMetadata; /** - * Base class for all EntityPersisters. An EntityPersister is a class that knows - * how to persist and load entities of a specific type. + * A basic entity persister that maps an entity with no (mapped) inheritance to a single table + * in the relational database. * * @author Roman Borschel * @author Giorgio Sironi * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @version $Revision: 3406 $ - * @link www.doctrine-project.org * @since 2.0 * @todo Rename: BasicEntityPersister */ @@ -72,13 +69,6 @@ class StandardEntityPersister * @var Doctrine\ORM\EntityManager */ protected $_em; - - /** - * The SqlLogger to use, if any. - * - * @var Doctrine\DBAL\Logging\SqlLogger - */ - protected $_sqlLogger; /** * Queued inserts. @@ -88,15 +78,27 @@ class StandardEntityPersister protected $_queuedInserts = array(); /** - * Mappings of column names as they appear in an SQL result set to - * column names as they are defined in the mapping. + * Case-sensitive mappings of column names as they appear in an SQL result set + * to column names as they are defined in the mapping. This is necessary because different + * RDBMS vendors return column names in result sets in different casings. * * @var array */ protected $_resultColumnNames = array(); + /** + * The map of column names to DBAL mapping types of all prepared columns used when INSERTing + * or UPDATEing an entity. + * + * @var array + * @see _prepareInsertData($entity) + * @see _prepareUpdateData($entity) + */ + protected $_columnTypes = array(); + /** * The INSERT SQL statement used for entities handled by this persister. + * This SQL is only generated once per request, if at all. * * @var string */ @@ -104,18 +106,12 @@ class StandardEntityPersister /** * The SELECT column list SQL fragment used for querying entities by this persister. + * This SQL fragment is only generated once per request, if at all. * * @var string */ protected $_selectColumnListSql; - /** - * Map from column names to class names that declare the field the column is mapped to. - * - * @var array - */ - protected $_declaringClassMap = array(); - /** * Counter for creating unique SQL table and column aliases. * @@ -124,7 +120,7 @@ class StandardEntityPersister protected $_sqlAliasCounter = 0; /** - * Map from class names to the corresponding generated SQL table aliases. + * Map from class names (FQCN) to the corresponding generated SQL table aliases. * * @var array */ @@ -132,17 +128,16 @@ class StandardEntityPersister /** * Initializes a new StandardEntityPersister that uses the given EntityManager - * and persists instances of the class described by the given class metadata descriptor. + * and persists instances of the class described by the given ClassMetadata descriptor. * - * @param EntityManager $em - * @param ClassMetadata $class + * @param Doctrine\ORM\EntityManager $em + * @param Doctrine\ORM\Mapping\ClassMetadata $class */ public function __construct(EntityManager $em, ClassMetadata $class) { $this->_em = $em; $this->_class = $class; $this->_conn = $em->getConnection(); - $this->_sqlLogger = $this->_conn->getConfiguration()->getSqlLogger(); $this->_platform = $this->_conn->getDatabasePlatform(); } @@ -158,9 +153,13 @@ class StandardEntityPersister } /** - * Executes all queued inserts. + * Executes all queued entity insertions and returns any generated post-insert + * identifiers that were created as a result of the insertions. + * + * If no inserts are queued, invoking this method is a NOOP. * - * @return array An array of any generated post-insert IDs. + * @return array An array of any generated post-insert IDs. This will be an empty array + * if the entity class does not use the IDENTITY generation strategy. */ public function executeInserts() { @@ -168,37 +167,23 @@ class StandardEntityPersister return; } - $isVersioned = $this->_class->isVersioned; - $postInsertIds = array(); $idGen = $this->_class->idGenerator; $isPostInsertId = $idGen->isPostInsertGenerator(); $stmt = $this->_conn->prepare($this->getInsertSQL()); - $primaryTableName = $this->_class->primaryTable['name']; + $tableName = $this->_class->table['name']; foreach ($this->_queuedInserts as $entity) { - $insertData = array(); - $this->_prepareData($entity, $insertData, true); + $insertData = $this->_prepareInsertData($entity); - if (isset($insertData[$primaryTableName])) { + if (isset($insertData[$tableName])) { $paramIndex = 1; - if ($this->_sqlLogger !== null) { - $params = array(); - foreach ($insertData[$primaryTableName] as $value) { - $params[$paramIndex] = $value; - $stmt->bindValue($paramIndex++, $value); - } - $this->_sqlLogger->logSql($this->getInsertSQL(), $params); - } else { - foreach ($insertData[$primaryTableName] as $value) { - $stmt->bindValue($paramIndex++, $value); - } + foreach ($insertData[$tableName] as $column => $value) { + $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); } - } else if ($this->_sqlLogger !== null) { - $this->_sqlLogger->logSql($this->getInsertSQL()); } - + $stmt->execute(); if ($isPostInsertId) { @@ -208,11 +193,11 @@ class StandardEntityPersister $id = $this->_class->getIdentifierValues($entity); } - if ($isVersioned) { + if ($this->_class->isVersioned) { $this->_assignDefaultVersionValue($this->_class, $entity, $id); } } - + $stmt->closeCursor(); $this->_queuedInserts = array(); @@ -220,11 +205,13 @@ class StandardEntityPersister } /** - * This function retrieves the default version value which was created - * by the DBMS INSERT statement. The value is assigned back in to the - * $entity versionField property. + * Retrieves the default version value which was created + * by the preceding INSERT statement and assigns it back in to the + * entities version field. * - * @return void + * @param $class + * @param $entity + * @param $id */ protected function _assignDefaultVersionValue($class, $entity, $id) { @@ -245,60 +232,65 @@ class StandardEntityPersister */ public function update($entity) { - $updateData = array(); - $this->_prepareData($entity, $updateData); - $id = array_combine( - $this->_class->getIdentifierColumnNames(), - $this->_em->getUnitOfWork()->getEntityIdentifier($entity) - ); - $tableName = $this->_class->primaryTable['name']; - + $updateData = $this->_prepareUpdateData($entity); + $tableName = $this->_class->table['name']; if (isset($updateData[$tableName]) && $updateData[$tableName]) { - $this->_doUpdate($entity, $tableName, $updateData[$tableName], $id); + $this->_updateTable($entity, $tableName, $updateData[$tableName], $this->_class->isVersioned); } } /** - * Perform UPDATE statement for an entity. This function has support for - * optimistic locking if the entities ClassMetadata has versioning enabled. + * Performs an UPDATE statement for an entity on a specific table. + * The UPDATE can be optionally versioned, which requires the entity to have a version field. * - * @param object $entity The entity object being updated - * @param string $tableName The name of the table being updated - * @param array $data The array of data to set - * @param array $where The condition used to update - * @return void + * @param object $entity The entity object being updated. + * @param string $tableName The name of the table to apply the UPDATE on. + * @param array $updateData The map of columns to update (column => value). + * @param boolean $versioned Whether the UPDATE should be versioned. */ - protected function _doUpdate($entity, $tableName, $data, $where) + protected function _updateTable($entity, $tableName, $updateData, $versioned = false) { - // Note: $tableName and column names in $data are already quoted for SQL. - $set = array(); - foreach ($data as $columnName => $value) { - $set[] = $columnName . ' = ?'; + $set = $params = $types = array(); + + foreach ($updateData as $columnName => $value) { + if (isset($this->_class->fieldNames[$columnName])) { + $set[] = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?'; + } else { + $set[] = $columnName . ' = ?'; + } + $params[] = $value; + $types[] = $this->_columnTypes[$columnName]; + } + + $where = array(); + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); + foreach ($this->_class->identifier as $idField) { + $where[] = $this->_class->getQuotedColumnName($idField, $this->_platform); + $params[] = $id[$idField]; + $types[] = $this->_class->fieldMappings[$idField]['type']; } - if ($isVersioned = $this->_class->isVersioned) { + if ($versioned) { $versionField = $this->_class->versionField; $versionFieldType = $this->_class->getTypeOfField($versionField); - $where[$versionField] = Type::getType($versionFieldType) - ->convertToDatabaseValue($this->_class->reflFields[$versionField]->getValue($entity), $this->_platform); - $versionFieldColumnName = $this->_class->getQuotedColumnName($versionField, $this->_platform); - if ($versionFieldType == 'integer') { - $set[] = $versionFieldColumnName . ' = ' . $versionFieldColumnName . ' + 1'; - } else if ($versionFieldType == 'datetime') { - $set[] = $versionFieldColumnName . ' = CURRENT_TIMESTAMP'; + $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); + if ($versionFieldType == Type::INTEGER) { + $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; + } else if ($versionFieldType == Type::DATETIME) { + $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; } + $where[] = $versionColumn; + $params[] = $this->_class->reflFields[$versionField]->getValue($entity); + $types[] = $this->_class->fieldMappings[$versionField]['type']; } - $params = array_merge(array_values($data), array_values($where)); + $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; - $sql = 'UPDATE ' . $tableName - . ' SET ' . implode(', ', $set) - . ' WHERE ' . implode(' = ? AND ', array_keys($where)) . ' = ?'; + $result = $this->_conn->executeUpdate($sql, $params, $types); - $result = $this->_conn->executeUpdate($sql, $params); - - if ($isVersioned && ! $result) { - throw \Doctrine\ORM\OptimisticLockException::lockFailed(); + if ($this->_class->isVersioned && ! $result) { + throw OptimisticLockException::lockFailed(); } } @@ -313,7 +305,7 @@ class StandardEntityPersister $this->_class->getIdentifierColumnNames(), $this->_em->getUnitOfWork()->getEntityIdentifier($entity) ); - $this->_conn->delete($this->_class->primaryTable['name'], $id); + $this->_conn->delete($this->_class->table['name'], $id); } /** @@ -327,7 +319,7 @@ class StandardEntityPersister } /** - * Prepares the data changeset of an entity for database insertion (INSERT/UPDATE). + * Prepares the data changeset of an entity for database insertion. * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. @@ -344,11 +336,11 @@ class StandardEntityPersister * Notes to inheritors: Be sure to call parent::_prepareData($entity, $result, $isInsert); * * @param object $entity The entity for which to prepare the data. - * @param array $result The reference to the data array. - * @param boolean $isInsert Whether the preparation is for an INSERT (or UPDATE, if FALSE). + * @return array The prepared data. */ - protected function _prepareData($entity, array &$result, $isInsert = false) + protected function _prepareUpdateData($entity) { + $result = array(); $uow = $this->_em->getUnitOfWork(); if ($versioned = $this->_class->isVersioned) { @@ -359,7 +351,7 @@ class StandardEntityPersister if ($versioned && $versionField == $field) { continue; } - + $oldVal = $change[0]; $newVal = $change[1]; @@ -382,31 +374,34 @@ class StandardEntityPersister $newVal = null; } } - + if ($newVal !== null) { $newValId = $uow->getEntityIdentifier($newVal); } - + $targetClass = $this->_em->getClassMetadata($assocMapping->targetEntityName); $owningTable = $this->getOwningTable($field); - + foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { if ($newVal === null) { $result[$owningTable][$sourceColumn] = null; } else { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; } + $this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); } - } else if ($newVal === null) { - $columnName = $this->_class->getQuotedColumnName($field, $this->_platform); - $result[$this->getOwningTable($field)][$columnName] = null; } else { - $columnName = $this->_class->getQuotedColumnName($field, $this->_platform); - $result[$this->getOwningTable($field)][$columnName] = Type::getType( - $this->_class->fieldMappings[$field]['type']) - ->convertToDatabaseValue($newVal, $this->_platform); + $columnName = $this->_class->columnNames[$field]; + $this->_columnTypes[$columnName] = $this->_class->fieldMappings[$field]['type']; + $result[$this->getOwningTable($field)][$columnName] = $newVal; } } + return $result; + } + + protected function _prepareInsertData($entity) + { + return $this->_prepareUpdateData($entity); } /** @@ -417,7 +412,7 @@ class StandardEntityPersister */ public function getOwningTable($fieldName) { - return $this->_class->getQuotedTableName($this->_platform); + return $this->_class->table['name']; } /** @@ -434,16 +429,10 @@ class StandardEntityPersister { $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); $params = array_values($criteria); - - if ($this->_sqlLogger !== null) { - $this->_sqlLogger->logSql($sql, $params); - } - - $stmt = $this->_conn->prepare($sql); - $stmt->execute($params); - $result = $stmt->fetch(Connection::FETCH_ASSOC); + $stmt = $this->_conn->execute($sql, $params); + $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); - + return $this->_createEntity($result, $entity, $hints); } @@ -453,37 +442,31 @@ class StandardEntityPersister * @param array $id The identifier of the entity as an associative array from column names to values. * @param object $entity The entity to refresh. */ - final public function refresh(array $id, $entity) + public function refresh(array $id, $entity) { $sql = $this->_getSelectEntitiesSQL($id); $params = array_values($id); - if ($this->_sqlLogger !== null) { - $this->_sqlLogger->logSql($sql, $params); - } - - $stmt = $this->_conn->prepare($sql); - $stmt->execute($params); - $result = $stmt->fetch(Connection::FETCH_ASSOC); + $stmt = $this->_conn->execute($sql, $params); + $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); - + $metaColumns = array(); $newData = array(); - + // Refresh simple state foreach ($result as $column => $value) { $column = $this->_resultColumnNames[$column]; if (isset($this->_class->fieldNames[$column])) { $fieldName = $this->_class->fieldNames[$column]; - $type = Type::getType($this->_class->fieldMappings[$fieldName]['type']); - $newValue = $type->convertToPHPValue($value, $this->_platform); + $newValue = $this->_conn->convertToPHPValue($value, $this->_class->fieldMappings[$fieldName]['type']); $this->_class->reflFields[$fieldName]->setValue($entity, $newValue); $newData[$fieldName] = $newValue; } else { $metaColumns[$column] = $value; } } - + // Refresh associations foreach ($this->_class->associationMappings as $field => $assoc) { $value = $this->_class->reflFields[$field]->getValue($entity); @@ -531,12 +514,12 @@ class StandardEntityPersister $newData[$field] = $value; } } - + $this->_em->getUnitOfWork()->setOriginalEntityData($entity, $newData); } - + /** - * Loads all entities by a list of field criteria. + * Loads a list of entities by a list of field criteria. * * @param array $criteria * @return array @@ -547,23 +530,17 @@ class StandardEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria); $params = array_values($criteria); - - if ($this->_sqlLogger !== null) { - $this->_sqlLogger->logSql($sql, $params); - } - - $stmt = $this->_conn->prepare($sql); - $stmt->execute($params); - $result = $stmt->fetchAll(Connection::FETCH_ASSOC); + $stmt = $this->_conn->execute($sql, $params); + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); - + foreach ($result as $row) { $entities[] = $this->_createEntity($row); } - + return $entities; } - + /** * Loads a collection of entities in a one-to-many association. * @@ -574,23 +551,15 @@ class StandardEntityPersister public function loadOneToManyCollection($assoc, array $criteria, PersistentCollection $coll) { $owningAssoc = $this->_class->associationMappings[$coll->getMapping()->mappedBy]; - $sql = $this->_getSelectEntitiesSQL($criteria, $owningAssoc, $assoc->orderBy); - $params = array_values($criteria); - - if ($this->_sqlLogger !== null) { - $this->_sqlLogger->logSql($sql, $params); - } - - $stmt = $this->_conn->prepare($sql); - $stmt->execute($params); - while ($result = $stmt->fetch(Connection::FETCH_ASSOC)) { + $stmt = $this->_conn->execute($sql, $params); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { $coll->hydrateAdd($this->_createEntity($result)); } $stmt->closeCursor(); } - + /** * Loads a collection of entities of a many-to-many association. * @@ -602,19 +571,13 @@ class StandardEntityPersister { $sql = $this->_getSelectManyToManyEntityCollectionSQL($assoc, $criteria); $params = array_values($criteria); - - if ($this->_sqlLogger !== null) { - $this->_sqlLogger->logSql($sql, $params); - } - - $stmt = $this->_conn->prepare($sql); - $stmt->execute($params); - while ($result = $stmt->fetch(Connection::FETCH_ASSOC)) { + $stmt = $this->_conn->execute($sql, $params); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { $coll->hydrateAdd($this->_createEntity($result)); } $stmt->closeCursor(); } - + /** * Creates or fills a single entity object from an SQL result. * @@ -629,8 +592,8 @@ class StandardEntityPersister return null; } - list($entityName, $data) = $this->_processSqlResult($result); - + list($entityName, $data) = $this->_processSQLResult($result); + if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $id = array(); @@ -643,17 +606,17 @@ class StandardEntityPersister } $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } - + return $this->_em->getUnitOfWork()->createEntity($entityName, $data, $hints); } - + /** * Processes an SQL result set row that contains data for an entity of the type * this persister is responsible for. * * @param array $sqlResult The SQL result set row to process. * @return array A tuple where the first value is the actual type of the entity and - * the second value the data of the entity. + * the second value the prepared data of the entity. */ protected function _processSQLResult(array $sqlResult) { @@ -715,9 +678,10 @@ class StandardEntityPersister } /** - * Generate ORDER BY Sql Snippet for ordered collections + * Generate ORDER BY SQL snippet for ordered collections. * * @param array $orderBy + * @param string $baseTableAlias * @return string */ protected function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias) @@ -819,30 +783,8 @@ class StandardEntityPersister . ' WHERE ' . $conditionSql . $orderBySql; } - final protected function _processSQLResultInheritanceAware(array $sqlResult) - { - $data = array(); - $entityName = $this->_class->discriminatorMap[$sqlResult[$this->_class->discriminatorColumn['name']]]; - unset($sqlResult[$this->_class->discriminatorColumn['name']]); - foreach ($sqlResult as $column => $value) { - $realColumnName = $this->_resultColumnNames[$column]; - if (isset($this->_declaringClassMap[$column])) { - $class = $this->_declaringClassMap[$column]; - if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) { - $field = $class->fieldNames[$realColumnName]; - $data[$field] = Type::getType($class->fieldMappings[$field]['type']) - ->convertToPHPValue($value, $this->_platform); - } - } else { - $data[$realColumnName] = $value; - } - } - - return array($entityName, $data); - } - /** - * Gets the INSERT SQL used by the persister to persist entities. + * Gets the INSERT SQL used by the persister to persist a new entity. * * @return string */ @@ -851,10 +793,9 @@ class StandardEntityPersister if ($this->_insertSql === null) { $this->_insertSql = $this->_generateInsertSQL(); } - return $this->_insertSql; } - + /** * Gets the list of columns to put in the INSERT SQL statement. * @@ -880,10 +821,10 @@ class StandardEntityPersister $columns[] = $this->_class->getQuotedColumnName($name, $this->_platform); } } - + return $columns; } - + /** * Generates the INSERT SQL used by the persister to persist entities. * @@ -907,7 +848,7 @@ class StandardEntityPersister . ' (' . implode(', ', $columns) . ') ' . 'VALUES (' . implode(', ', $values) . ')'; } - + return $insertSql; } @@ -915,7 +856,7 @@ class StandardEntityPersister * Gets the SQL snippet of a qualified column name for the given field name. * * @param string $field The field name. - * @param ClassMetadata $class The class that declares this field. The table this class if + * @param ClassMetadata $class The class that declares this field. The table this class is * mapped to must own the column for the given field. */ protected function _getSelectColumnSQL($field, ClassMetadata $class) @@ -925,7 +866,6 @@ class StandardEntityPersister $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); if ( ! isset($this->_resultColumnNames[$columnAlias])) { $this->_resultColumnNames[$columnAlias] = $columnName; - $this->_declaringClassMap[$columnAlias] = $class; } return "$sql AS $columnAlias"; @@ -967,7 +907,7 @@ class StandardEntityPersister if (isset($this->_sqlTableAliases[$class->name])) { return $this->_sqlTableAliases[$class->name]; } - $tableAlias = $class->primaryTable['name'][0] . $this->_sqlAliasCounter++; + $tableAlias = $class->table['name'][0] . $this->_sqlAliasCounter++; $this->_sqlTableAliases[$class->name] = $tableAlias; return $tableAlias; diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 88cf4d212..d4e255dcb 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -114,7 +114,7 @@ class ProxyFactory } /** - * Generates a (reference or association) proxy class. + * Generates a proxy class file. * * @param $class * @param $originalClassName diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 35dd5a785..7bbb663f4 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -21,8 +21,7 @@ namespace Doctrine\ORM; -use Doctrine\ORM\Query\AbstractQuery, - Doctrine\ORM\Query\Parser, +use Doctrine\ORM\Query\Parser, Doctrine\ORM\Query\QueryException; /** @@ -124,7 +123,7 @@ final class Query extends AbstractQuery private $_maxResults = null; /** - * @var CacheDriver The cache driver used for caching queries. + * @var CacheDriver The cache driver used for caching queries. */ private $_queryCache; @@ -161,17 +160,17 @@ final class Query extends AbstractQuery * @return mixed The built sql query or an array of all sql queries. * @override */ - public function getSql() + public function getSQL() { return $this->_parse()->getSqlExecutor()->getSqlStatements(); } /** - * Returns the correspondent AST for this Query. + * Returns the corresponding AST for this DQL query. * - * @return \Doctrine\ORM\Query\AST\SelectStatement | - * \Doctrine\ORM\Query\AST\UpdateStatement | - * \Doctrine\ORM\Query\AST\DeleteStatement + * @return Doctrine\ORM\Query\AST\SelectStatement | + * Doctrine\ORM\Query\AST\UpdateStatement | + * Doctrine\ORM\Query\AST\DeleteStatement */ public function getAST() { @@ -216,62 +215,51 @@ final class Query extends AbstractQuery /** * {@inheritdoc} - * - * @param array $params - * @return Statement The resulting Statement. - * @override */ - protected function _doExecute(array $params) + protected function _doExecute() { $executor = $this->_parse()->getSqlExecutor(); - $params = $this->_prepareParams($params); - if ( ! $this->_resultSetMapping) { - $this->_resultSetMapping = $this->_parserResult->getResultSetMapping(); - } - return $executor->execute($this->_em->getConnection(), $params); - } - - /** - * {@inheritdoc} - * - * @override - */ - protected function _prepareParams(array $params) - { - $sqlParams = array(); - + // Prepare parameters $paramMappings = $this->_parserResult->getParameterMappings(); - if (count($paramMappings) != count($params)) { + if (count($paramMappings) != count($this->_params)) { throw QueryException::invalidParameterNumber(); } - foreach ($params as $key => $value) { + $sqlParams = $types = array(); + + foreach ($this->_params as $key => $value) { if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } + if (isset($this->_paramTypes[$key])) { + foreach ($paramMappings[$key] as $position) { + $types[$position] = $this->_paramTypes[$key]; + } + } - if (is_object($value)) { - //$values = $this->_em->getClassMetadata(get_class($value))->getIdentifierValues($value); + if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) { $values = $this->_em->getUnitOfWork()->getEntityIdentifier($value); - //var_dump($this->_em->getUnitOfWork()->getEntityIdentifier($value)); $sqlPositions = $paramMappings[$key]; $sqlParams = array_merge($sqlParams, array_combine((array)$sqlPositions, $values)); - } else if (is_bool($value)) { - $boolValue = $this->_em->getConnection()->getDatabasePlatform()->convertBooleans($value); - foreach ($paramMappings[$key] as $position) { - $sqlParams[$position] = $boolValue; - } } else { foreach ($paramMappings[$key] as $position) { $sqlParams[$position] = $value; } } } - ksort($sqlParams); - - return array_values($sqlParams); + + if ($sqlParams) { + ksort($sqlParams); + $sqlParams = array_values($sqlParams); + } + + if ($this->_resultSetMapping === null) { + $this->_resultSetMapping = $this->_parserResult->getResultSetMapping(); + } + + return $executor->execute($this->_em->getConnection(), $sqlParams, $types); } /** diff --git a/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php b/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php index 8026ea825..cbed1adb9 100644 --- a/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php +++ b/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php @@ -57,8 +57,8 @@ class SizeFunction extends FunctionNode $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc->targetEntityName); $targetAssoc = $targetClass->associationMappings[$assoc->mappedBy]; - $targetTableAlias = $sqlWalker->getSqlTableAlias($targetClass->primaryTable['name']); - $sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->primaryTable['name'], $dqlAlias); + $targetTableAlias = $sqlWalker->getSqlTableAlias($targetClass->table['name']); + $sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->table['name'], $dqlAlias); $whereSql = ''; @@ -68,10 +68,10 @@ class SizeFunction extends FunctionNode . $sourceTableAlias . '.' . $targetKeyColumn; } - $tableName = $targetClass->primaryTable['name']; + $tableName = $targetClass->table['name']; } else if ($assoc->isManyToMany()) { $targetTableAlias = $sqlWalker->getSqlTableAlias($assoc->joinTable['name']); - $sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->primaryTable['name'], $dqlAlias); + $sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->table['name'], $dqlAlias); $whereSql = ''; diff --git a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php index 851c07a56..7879b0ff2 100644 --- a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php @@ -21,6 +21,8 @@ namespace Doctrine\ORM\Query\Exec; +use Doctrine\DBAL\Connection; + /** * Base class for SQL statement executors. * @@ -28,7 +30,7 @@ namespace Doctrine\ORM\Query\Exec; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 - * @version $Revision$ + * @todo Rename: AbstractSQLExecutor */ abstract class AbstractSqlExecutor { @@ -47,8 +49,9 @@ abstract class AbstractSqlExecutor /** * Executes all sql statements. * - * @param Doctrine_Connection $conn The database connection that is used to execute the queries. + * @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries. * @param array $params The parameters. + * @return Doctrine\DBAL\Driver\Statement */ - abstract public function execute(\Doctrine\DBAL\Connection $conn, array $params); + abstract public function execute(Connection $conn, array $params, array $types); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index d97019e37..26453b2b9 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Query\Exec; -use Doctrine\ORM\Query\AST; +use Doctrine\DBAL\Connection, + Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL DELETE statements on classes in @@ -52,11 +53,11 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); - + $primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName); $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); - + $tempTable = $rootClass->getTemporaryIdTableName(); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); @@ -64,17 +65,17 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); - $sqlWalker->setSqlTableAlias($primaryClass->primaryTable['name'] . $primaryDqlAlias, 't0'); + $sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $primaryDqlAlias, 't0'); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias); $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array()))); $this->_insertSql .= $sqlWalker->walkFromClause($fromClause); - + // Append WHERE clause, if there is one. if ($AST->whereClause) { $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } - // 2. Create ID subselect statement used in DELETE .... WHERE ... IN (subselect) + // 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store DELETE statements @@ -106,24 +107,24 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor * @param array $params The parameters. * @override */ - public function execute(\Doctrine\DBAL\Connection $conn, array $params) + public function execute(Connection $conn, array $params, array $types) { $numDeleted = 0; - + // Create temporary id table $conn->executeUpdate($this->_createTempTableSql); - + // Insert identifiers - $numDeleted = $conn->executeUpdate($this->_insertSql, $params); + $numDeleted = $conn->executeUpdate($this->_insertSql, $params, $types); // Execute DELETE statements foreach ($this->_sqlStatements as $sql) { $conn->executeUpdate($sql); } - + // Drop temporary table $conn->executeUpdate($this->_dropTempTableSql); - + return $numDeleted; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php index c2af37cc1..add39dc83 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php @@ -21,7 +21,9 @@ namespace Doctrine\ORM\Query\Exec; -use Doctrine\ORM\Query\AST; +use Doctrine\DBAL\Connection, + Doctrine\DBAL\Types\Type, + Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL UPDATE statements on classes in @@ -69,7 +71,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); - $sqlWalker->setSqlTableAlias($primaryClass->primaryTable['name'] . $updateClause->aliasIdentificationVariable, 't0'); + $sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $updateClause->aliasIdentificationVariable, 't0'); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable); $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array()))); $this->_insertSql .= $sqlWalker->walkFromClause($fromClause); @@ -101,6 +103,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor $updateSql .= $sqlWalker->walkUpdateItem($updateItem); //FIXME: parameters can be more deeply nested. traverse the tree. + //FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage. if ($newValue instanceof AST\InputParameter) { $paramKey = $newValue->name; $this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey); @@ -124,7 +127,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = array( 'notnull' => true, - 'type' => \Doctrine\DBAL\Types\Type::getType($rootClass->getTypeOfColumn($idColumnName)) + 'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName)) ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' @@ -134,13 +137,13 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor } /** - * Executes all sql statements. + * Executes all SQL statements. * - * @param Doctrine_Connection $conn The database connection that is used to execute the queries. - * @param array $params The parameters. + * @param Connection $conn The database connection that is used to execute the queries. + * @param array $params The parameters. * @override */ - public function execute(\Doctrine\DBAL\Connection $conn, array $params) + public function execute(Connection $conn, array $params, array $types) { $numUpdated = 0; @@ -148,7 +151,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor $conn->executeUpdate($this->_createTempTableSql); // Insert identifiers. Parameters from the update clause are cut off. - $numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause)); + $numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), $types); // Execute UPDATE statements for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) { diff --git a/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php index 328a9727c..e096bed10 100644 --- a/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php @@ -21,6 +21,10 @@ namespace Doctrine\ORM\Query\Exec; +use Doctrine\DBAL\Connection, + Doctrine\ORM\Query\AST\SelectStatement, + Doctrine\ORM\Query\SqlWalker; + /** * Executor that executes the SQL statement for simple DQL SELECT statements. * @@ -31,14 +35,14 @@ namespace Doctrine\ORM\Query\Exec; * @since 2.0 */ class SingleSelectExecutor extends AbstractSqlExecutor -{ - public function __construct(\Doctrine\ORM\Query\AST\SelectStatement $AST, $sqlWalker) +{ + public function __construct(SelectStatement $AST, SqlWalker $sqlWalker) { $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } - - public function execute(\Doctrine\DBAL\Connection $conn, array $params) + + public function execute(Connection $conn, array $params, array $types) { - return $conn->execute($this->_sqlStatements, $params); + return $conn->execute($this->_sqlStatements, $params, $types); } } diff --git a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php index a797032d1..94db13b05 100644 --- a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Query\Exec; -use Doctrine\ORM\Query\AST; +use Doctrine\DBAL\Connection, + Doctrine\ORM\Query\AST; /** * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes @@ -45,8 +46,8 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor } } - public function execute(\Doctrine\DBAL\Connection $conn, array $params) + public function execute(Connection $conn, array $params, array $types) { - return $conn->executeUpdate($this->_sqlStatements, $params); + return $conn->executeUpdate($this->_sqlStatements, $params, $types); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 8f372a45f..d3c7e3808 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -38,7 +38,7 @@ use Doctrine\ORM\Query; */ class Parser { - /** Maps BUILT-IN string function names to AST class names. */ + /** READ-ONLY: Maps BUILT-IN string function names to AST class names. */ private static $_STRING_FUNCTIONS = array( 'concat' => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction', 'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction', @@ -47,7 +47,7 @@ class Parser 'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction' ); - /** Maps BUILT-IN numeric function names to AST class names. */ + /** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */ private static $_NUMERIC_FUNCTIONS = array( 'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction', 'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction', @@ -57,7 +57,7 @@ class Parser 'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction' ); - /** Maps BUILT-IN datetime function names to AST class names. */ + /** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */ private static $_DATETIME_FUNCTIONS = array( 'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction', 'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction', @@ -2587,18 +2587,12 @@ class Parser // Check for custom functions afterwards $config = $this->_em->getConfiguration(); - if (($func = $config->getCustomStringFunction($funcName)) !== null) { - self::$_STRING_FUNCTIONS[$funcName] = $func; - - return $this->FunctionsReturningStrings(); - } else if (($func = $config->getCustomNumericFunction($funcName)) !== null) { - self::$_NUMERIC_FUNCTIONS[$funcName] = $func; - - return $this->FunctionsReturningNumerics(); - } else if (($func = $config->getCustomDatetimeFunction($funcName)) !== null) { - self::$_DATETIME_FUNCTIONS[$funcName] = $func; - - return $this->FunctionsReturningDatetime(); + if ($config->getCustomStringFunction($funcName) !== null) { + return $this->CustomFunctionsReturningStrings(); + } else if ($config->getCustomNumericFunction($funcName) !== null) { + return $this->CustomFunctionsReturningNumerics(); + } else if ($config->getCustomDatetimeFunction($funcName) !== null) { + return $this->CustomFunctionsReturningDatetime(); } $this->syntaxError('known function', $token); @@ -2623,6 +2617,16 @@ class Parser return $function; } + public function CustomFunctionsReturningNumerics() + { + $funcNameLower = strtolower($this->_lexer->lookahead['value']); + $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcNameLower); + $function = new $funcClass($funcNameLower); + $function->parse($this); + + return $function; + } + /** * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" */ @@ -2636,6 +2640,16 @@ class Parser return $function; } + public function CustomFunctionsReturningDatetime() + { + $funcNameLower = strtolower($this->_lexer->lookahead['value']); + $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcNameLower); + $function = new $funcClass($funcNameLower); + $function->parse($this); + + return $function; + } + /** * FunctionsReturningStrings ::= * "CONCAT" "(" StringPrimary "," StringPrimary ")" | @@ -2648,7 +2662,16 @@ class Parser { $funcNameLower = strtolower($this->_lexer->lookahead['value']); $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; - //$funcClass = $this->_em->getConfiguration()->getDQLStringFunctionClassName($funcNameLower); + $function = new $funcClass($funcNameLower); + $function->parse($this); + + return $function; + } + + public function CustomFunctionsReturningStrings() + { + $funcNameLower = strtolower($this->_lexer->lookahead['value']); + $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcNameLower); $function = new $funcClass($funcNameLower); $function->parse($this); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 17460360b..0a6cde95b 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -240,12 +240,12 @@ class SqlWalker implements TreeWalker { $sql = ''; - $baseTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + $baseTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias); // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->getSqlTableAlias($parentClass->primaryTable['name'], $dqlAlias); + $tableAlias = $this->getSqlTableAlias($parentClass->table['name'], $dqlAlias); $sql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; @@ -264,7 +264,7 @@ class SqlWalker implements TreeWalker if ( ! $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); + $tableAlias = $this->getSqlTableAlias($subClass->table['name'], $dqlAlias); $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; @@ -295,7 +295,7 @@ class SqlWalker implements TreeWalker if ($qComp['metadata']->isInheritanceTypeJoined()) { $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); } else { - $tableName = $qComp['metadata']->primaryTable['name']; + $tableName = $qComp['metadata']->table['name']; } if ($sql != '') { @@ -331,7 +331,7 @@ class SqlWalker implements TreeWalker } $sql .= (($this->_useSqlTableAliases) - ? $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias) . '.' : '' + ? $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.' : '' ) . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } @@ -432,7 +432,7 @@ class SqlWalker implements TreeWalker $class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); } - return $this->getSqlTableAlias($class->primaryTable['name'], $identificationVariable); + return $this->getSqlTableAlias($class->table['name'], $identificationVariable); } /** @@ -519,7 +519,7 @@ class SqlWalker implements TreeWalker if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { // Add discriminator columns to SQL $rootClass = $this->_em->getClassMetadata($class->rootEntityName); - $tblAlias = $this->getSqlTableAlias($rootClass->primaryTable['name'], $dqlAlias); + $tblAlias = $this->getSqlTableAlias($rootClass->table['name'], $dqlAlias); $discrColumn = $rootClass->discriminatorColumn; $columnAlias = $this->getSqlColumnAlias($discrColumn['name']); $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias; @@ -535,9 +535,9 @@ class SqlWalker implements TreeWalker if ($assoc->isOwningSide && $assoc->isOneToOne()) { if (isset($class->inheritedAssociationFields[$assoc->sourceFieldName])) { $owningClass = $this->_em->getClassMetadata($class->inheritedAssociationFields[$assoc->sourceFieldName]); - $sqlTableAlias = $this->getSqlTableAlias($owningClass->primaryTable['name'], $dqlAlias); + $sqlTableAlias = $this->getSqlTableAlias($owningClass->table['name'], $dqlAlias); } else { - $sqlTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + $sqlTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias); } foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { @@ -552,7 +552,7 @@ class SqlWalker implements TreeWalker } else { // Add foreign key columns to SQL, if necessary if ($addMetaColumns) { - $sqlTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + $sqlTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias); foreach ($class->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne()) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { @@ -587,7 +587,7 @@ class SqlWalker implements TreeWalker $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $sql .= $class->getQuotedTableName($this->_platform) . ' ' - . $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + . $this->getSqlTableAlias($class->table['name'], $dqlAlias); if ($class->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); @@ -908,9 +908,9 @@ class SqlWalker implements TreeWalker } if (isset($mapping['inherited'])) { - $tableName = $this->_em->getClassMetadata($mapping['inherited'])->primaryTable['name']; + $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name']; } else { - $tableName = $class->primaryTable['name']; + $tableName = $class->table['name']; } if ($beginning) $beginning = false; else $sql .= ', '; @@ -931,7 +931,7 @@ class SqlWalker implements TreeWalker if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - $sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); + $sqlTableAlias = $this->getSqlTableAlias($subClass->table['name'], $dqlAlias); foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; @@ -1016,7 +1016,7 @@ class SqlWalker implements TreeWalker $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $sql = ' FROM ' . $class->getQuotedTableName($this->_platform) . ' ' - . $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + . $this->getSqlTableAlias($class->table['name'], $dqlAlias); if ($class->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); @@ -1302,8 +1302,8 @@ class SqlWalker implements TreeWalker if ($assoc->isOneToMany()) { $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); - $targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']); - $sourceTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + $targetTableAlias = $this->getSqlTableAlias($targetClass->table['name']); + $sourceTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias); $sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE '; @@ -1338,8 +1338,8 @@ class SqlWalker implements TreeWalker // SQL table aliases $joinTableAlias = $this->getSqlTableAlias($joinTable['name']); - $targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']); - $sourceTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias); + $targetTableAlias = $this->getSqlTableAlias($targetClass->table['name']); + $sourceTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias); // join to target table $sql .= $assoc->getQuotedJoinTableName($this->_platform) @@ -1351,8 +1351,7 @@ class SqlWalker implements TreeWalker $joinColumns = $assoc->isOwningSide ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; - - //$referencedColumnClass = $assoc->isOwningSide ? $targetClass : $class; + $first = true; foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; @@ -1362,13 +1361,13 @@ class SqlWalker implements TreeWalker $targetClass->fieldNames[$joinColumn['referencedColumnName']], $this->_platform); } - + $sql .= ' WHERE '; - + $joinColumns = $assoc->isOwningSide ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; - + $first = true; foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; diff --git a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php index 8ea0eb476..16a3c8f99 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php @@ -35,7 +35,7 @@ abstract class TreeWalkerAdapter implements TreeWalker private $_queryComponents; /** - * @inheritdoc + * {@inheritdoc} */ public function __construct($query, $parserResult, array $queryComponents) { diff --git a/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php b/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php index 996500479..89744c0f1 100644 --- a/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php +++ b/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php @@ -101,10 +101,10 @@ class ConvertDoctrine1Schema if (isset($model['tableName']) && $model['tableName']) { $e = explode('.', $model['tableName']); if (count($e) > 1) { - $metadata->primaryTable['schema'] = $e[0]; - $metadata->primaryTable['name'] = $e[1]; + $metadata->table['schema'] = $e[0]; + $metadata->table['name'] = $e[1]; } else { - $metadata->primaryTable['name'] = $e[0]; + $metadata->table['name'] = $e[0]; } } } @@ -208,7 +208,7 @@ class ConvertDoctrine1Schema $type = (isset($index['type']) && $index['type'] == 'unique') ? 'uniqueConstraints' : 'indexes'; - $metadata->primaryTable[$type][$name] = array( + $metadata->table[$type][$name] = array( 'columns' => $index['fields'] ); } diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 534604493..9dcc27d4b 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -443,12 +443,12 @@ public function () private function _generateTableAnnotation($metadata) { $table = array(); - if ($metadata->primaryTable['name']) { - $table[] = 'name="' . $metadata->primaryTable['name'] . '"'; + if ($metadata->table['name']) { + $table[] = 'name="' . $metadata->table['name'] . '"'; } - if (isset($metadata->primaryTable['schema'])) { - $table[] = 'schema="' . $metadata->primaryTable['schema'] . '"'; + if (isset($metadata->table['schema'])) { + $table[] = 'schema="' . $metadata->table['schema'] . '"'; } return '@Table(' . implode(', ', $table) . ')'; @@ -570,7 +570,7 @@ public function () . ($associationMapping->isManyToMany() ? ' = array()' : null) . ";\n"; } - return implode("\n", $lines) + return implode("\n", $lines); } private function _generateEntityFieldMappingProperties(ClassMetadataInfo $metadata) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php index acd8e80b6..201d1072d 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php @@ -64,8 +64,8 @@ class PhpExporter extends AbstractExporter $lines[] = "\$metadata->customRepositoryClassName = '" . $metadata->customRepositoryClassName . "';"; } - if ($metadata->primaryTable) { - $lines[] = '$metadata->setPrimaryTable(' . $this->_varExport($metadata->primaryTable) . ');'; + if ($metadata->table) { + $lines[] = '$metadata->setPrimaryTable(' . $this->_varExport($metadata->table) . ');'; } if ($metadata->discriminatorColumn) { diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 982755db5..366e14de8 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -67,16 +67,16 @@ class XmlExporter extends AbstractExporter $root->addAttribute('name', $metadata->name); - if (isset($metadata->primaryTable['name'])) { - $root->addAttribute('table', $metadata->primaryTable['name']); + if (isset($metadata->table['name'])) { + $root->addAttribute('table', $metadata->table['name']); } - if (isset($metadata->primaryTable['schema'])) { - $root->addAttribute('schema', $metadata->primaryTable['schema']); + if (isset($metadata->table['schema'])) { + $root->addAttribute('schema', $metadata->table['schema']); } - if (isset($metadata->primaryTable['inheritance-type'])) { - $root->addAttribute('inheritance-type', $metadata->primaryTable['inheritance-type']); + if (isset($metadata->table['inheritance-type'])) { + $root->addAttribute('inheritance-type', $metadata->table['inheritance-type']); } if ($metadata->discriminatorColumn) { @@ -97,20 +97,20 @@ class XmlExporter extends AbstractExporter $root->addChild('change-tracking-policy', $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy)); - if (isset($metadata->primaryTable['indexes'])) { + if (isset($metadata->table['indexes'])) { $indexesXml = $root->addChild('indexes'); - foreach ($metadata->primaryTable['indexes'] as $name => $index) { + foreach ($metadata->table['indexes'] as $name => $index) { $indexXml = $indexesXml->addChild('index'); $indexXml->addAttribute('name', $name); $indexXml->addAttribute('columns', implode(',', $index['columns'])); } } - if (isset($metadata->primaryTable['uniqueConstraints'])) { + if (isset($metadata->table['uniqueConstraints'])) { $uniqueConstraintsXml = $root->addChild('unique-constraints'); - foreach ($metadata->primaryTable['uniqueConstraints'] as $unique) { + foreach ($metadata->table['uniqueConstraints'] as $unique) { $uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint'); $uniqueConstraintXml->addAttribute('name', $name); $uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns'])); diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php index eac5f7bdc..0eb749ec6 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php @@ -57,10 +57,10 @@ class YamlExporter extends AbstractExporter } else { $array['type'] = 'entity'; } - $array['table'] = $metadata->primaryTable['name']; + $array['table'] = $metadata->table['name']; - if (isset($metadata->primaryTable['schema'])) { - $array['schema'] = $metadata->primaryTable['schema']; + if (isset($metadata->table['schema'])) { + $array['schema'] = $metadata->table['schema']; } $inheritanceType = $metadata->inheritanceType; @@ -80,12 +80,12 @@ class YamlExporter extends AbstractExporter $array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); } - if (isset($metadata->primaryTable['indexes'])) { - $array['indexes'] = $metadata->primaryTable['indexes']; + if (isset($metadata->table['indexes'])) { + $array['indexes'] = $metadata->table['indexes']; } - if (isset($metadata->primaryTable['uniqueConstraints'])) { - $array['uniqueConstraints'] = $metadata->primaryTable['uniqueConstraints']; + if (isset($metadata->table['uniqueConstraints'])) { + $array['uniqueConstraints'] = $metadata->table['uniqueConstraints']; } $fieldMappings = $metadata->fieldMappings; diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index e0382955a..968f5e94a 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -112,11 +112,9 @@ class SchemaTool { $processedClasses = array(); // Reminder for processed classes, used for hierarchies - $metadataSchemaConfig = new \Doctrine\DBAL\Schema\SchemaConfig(); - $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); - $metadataSchemaConfig->setMaxIdentifierLength(63); - $sm = $this->_em->getConnection()->getSchemaManager(); + $metadataSchemaConfig = $sm->createSchemaConfig(); + $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); $schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $metadataSchemaConfig); $evm = $this->_em->getEventManager(); @@ -202,14 +200,14 @@ class SchemaTool $this->_gatherRelationsSql($class, $table, $schema); } - if (isset($class->primaryTable['indexes'])) { - foreach ($class->primaryTable['indexes'] AS $indexName => $indexData) { + if (isset($class->table['indexes'])) { + foreach ($class->table['indexes'] AS $indexName => $indexData) { $table->addIndex($indexData['columns'], $indexName); } } - if (isset($class->primaryTable['uniqueConstraints'])) { - foreach ($class->primaryTable['uniqueConstraints'] AS $indexName => $indexData) { + if (isset($class->table['uniqueConstraints'])) { + foreach ($class->table['uniqueConstraints'] AS $indexName => $indexData) { $table->addUniqueIndex($indexData['columns'], $indexName); } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d4aff352a..e71a057e0 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -280,7 +280,7 @@ class UnitOfWork implements PropertyChangedListener $commitOrder = $this->_getCommitOrder(); $conn = $this->_em->getConnection(); - + $conn->beginTransaction(); try { if ($this->_entityInsertions) { @@ -645,7 +645,7 @@ class UnitOfWork implements PropertyChangedListener $actualData[$name] = $refProp->getValue($entity); } } - + $originalData = $this->_originalEntityData[$oid]; $changeSet = array(); @@ -693,7 +693,7 @@ class UnitOfWork implements PropertyChangedListener } $postInsertIds = $persister->executeInserts(); - + if ($postInsertIds) { // Persister returned post-insert IDs foreach ($postInsertIds as $id => $entity) { diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php index d663ea58d..ef770c756 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -147,13 +147,13 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->assertType('array', $columns['baz1']->getPlatformOptions()); $this->assertEquals('baz2', strtolower($columns['baz2']->getname())); - $this->assertContains($columns['baz2']->gettype()->getName(), array('Time', 'Date', 'DateTime')); + $this->assertContains($columns['baz2']->gettype()->getName(), array('time', 'date', 'datetime')); $this->assertEquals(true, $columns['baz2']->getnotnull()); $this->assertEquals(null, $columns['baz2']->getdefault()); $this->assertType('array', $columns['baz2']->getPlatformOptions()); $this->assertEquals('baz3', strtolower($columns['baz3']->getname())); - $this->assertContains($columns['baz2']->gettype()->getName(), array('Time', 'Date', 'DateTime')); + $this->assertContains($columns['baz2']->gettype()->getName(), array('time', 'date', 'datetime')); $this->assertEquals(true, $columns['baz3']->getnotnull()); $this->assertEquals(null, $columns['baz3']->getdefault()); $this->assertType('array', $columns['baz3']->getPlatformOptions()); diff --git a/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php b/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php index ad9050b2a..03d44caae 100644 --- a/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php +++ b/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php @@ -6,7 +6,7 @@ class DriverConnectionMock implements \Doctrine\DBAL\Driver\Connection { public function prepare($prepareString) {} public function query() {} - public function quote($input) {} + public function quote($input, $type=\PDO::PARAM_STR) {} public function exec($statement) {} public function lastInsertId($name = null) {} public function beginTransaction() {} diff --git a/tests/Doctrine/Tests/ORM/Associations/OneToOneMappingTest.php b/tests/Doctrine/Tests/ORM/Associations/OneToOneMappingTest.php index 93d92c942..ea8a028df 100644 --- a/tests/Doctrine/Tests/ORM/Associations/OneToOneMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Associations/OneToOneMappingTest.php @@ -35,6 +35,6 @@ class OneToOneMappingTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('address', $oneToOneMapping->mappedBy); $this->assertEquals('Address', $oneToOneMapping->sourceEntityName); $this->assertEquals('Person', $oneToOneMapping->targetEntityName); - $this->assertTrue($oneToOneMapping->isInverseSide()); + $this->assertTrue( ! $oneToOneMapping->isOwningSide); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 116c8a14f..e31164858 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -123,6 +123,16 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $userId = $this->_em->getConnection()->execute("SELECT user_id FROM cms_addresses WHERE id=?", array($address->id))->fetchColumn(); $this->assertTrue(is_numeric($userId)); + + $this->_em->clear(); + + $user2 = $this->_em->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.id=?1') + ->setParameter(1, $userId) + ->getSingleResult(); + + // Address has been eager-loaded because it cant be lazy + $this->assertTrue($user2->address instanceof CmsAddress); + $this->assertFalse($user2->address instanceof \Doctrine\ORM\Proxy\Proxy); } public function testBasicManyToMany() diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php index eb607b93c..eb2d9e67f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php @@ -23,10 +23,11 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase protected function setUp() { $this->useModelSet('company'); parent::setUp(); + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSqlLogger); } public function testCRUD() - { + { $person = new CompanyPerson; $person->setName('Roman S. Borschel'); @@ -78,7 +79,7 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); $query = $this->_em->createQuery("update Doctrine\Tests\Models\Company\CompanyEmployee p set p.name = ?1, p.department = ?2 where p.name='Guilherme Blanco' and p.salary = ?3"); - $query->setParameter(1, 'NewName'); + $query->setParameter(1, 'NewName', 'string'); $query->setParameter(2, 'NewDepartment'); $query->setParameter(3, 100000); $query->getSql(); diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index e02c3e44d..4a13b0b7a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -32,14 +32,14 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testGetParameters() { $query = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1"); - $this->assertEquals(array(1 => 42), $query->getParameters(array(1 => 42))); + $this->assertEquals(array(), $query->getParameters()); } public function testGetParameters_HasSomeAlready() { $query = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username = ?1"); $query->setParameter(2, 84); - $this->assertEquals(array(2 => 84, 1 => 42), $query->getParameters(array(1 => 42))); + $this->assertEquals(array(2 => 84), $query->getParameters()); } public function testSimpleQueries() diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC425Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC425Test.php new file mode 100644 index 000000000..7624f27ad --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC425Test.php @@ -0,0 +1,43 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC425Entity'), + //$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC425Other') + )); + } + + /** + * @group DDC-425 + */ + public function testIssue() + { + //$this->_em->getConnection()->getConfiguration()->setSqlLogger(new \Doctrine\DBAL\Logging\EchoSqlLogger); + + $num = $this->_em->createQuery('DELETE '.__NAMESPACE__.'\DDC425Entity e WHERE e.someDatetimeField > ?1') + ->setParameter(1, new DateTime, Type::DATETIME) + ->getResult(); + $this->assertEquals(0, $num); + } +} + +/** @Entity */ +class DDC425Entity { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + public $id; + + /** @Column(type="datetime") */ + public $someDatetimeField; +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC444Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC444Test.php index 4257c7376..da059eb1e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC444Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC444Test.php @@ -9,6 +9,7 @@ class DDC444Test extends \Doctrine\Tests\OrmFunctionalTestCase public function setUp() { parent::setUp(); + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSqlLogger); $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC444User'), )); diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 4133ffd35..9e03211a6 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -106,7 +106,6 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($class->associationMappings['phonenumbers'] instanceof \Doctrine\ORM\Mapping\OneToManyMapping); $this->assertTrue(isset($class->associationMappings['phonenumbers'])); $this->assertFalse($class->associationMappings['phonenumbers']->isOwningSide); - $this->assertTrue($class->associationMappings['phonenumbers']->isInverseSide()); $this->assertTrue($class->associationMappings['phonenumbers']->isCascadePersist); $this->assertFalse($class->associationMappings['phonenumbers']->isCascadeRemove); $this->assertFalse($class->associationMappings['phonenumbers']->isCascadeRefresh); diff --git a/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php b/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php index 1a35d0497..088c871f7 100644 --- a/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php @@ -65,7 +65,7 @@ class ConvertDoctrine1SchemaTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('Profile', $metadatas['Profile']->associationMappings['User']->sourceEntityName); $this->assertEquals('User', $metadatas['Profile']->associationMappings['User']->targetEntityName); - $this->assertEquals('username', $metadatas['User']->primaryTable['uniqueConstraints']['username']['columns'][0]); + $this->assertEquals('username', $metadatas['User']->table['uniqueConstraints']['username']['columns'][0]); unlink(__DIR__ . '/convert/User.dcm.yml'); unlink(__DIR__ . '/convert/Profile.dcm.yml'); diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index b28e165dd..ef4f51353 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -25,7 +25,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testWriteEntityClass() { $metadata = new ClassMetadataInfo('EntityGeneratorBook'); - $metadata->primaryTable['name'] = 'book'; + $metadata->table['name'] = 'book'; $metadata->mapField(array('fieldName' => 'name', 'type' => 'string')); $metadata->mapField(array('fieldName' => 'status', 'type' => 'string', 'default' => 'published')); $metadata->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true)); diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index 6ef4dbd9f..41741dc4f 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -124,7 +124,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest */ public function testTableIsExported($metadata) { - $this->assertEquals('cms_users', $metadata->primaryTable['name']); + $this->assertEquals('cms_users', $metadata->table['name']); return $metadata; }