From a758b56551f01e6ab36c85a03157f63f8fe77645 Mon Sep 17 00:00:00 2001 From: romanb Date: Thu, 28 May 2009 11:13:12 +0000 Subject: [PATCH] [2.0] Introduced SQL logging facilities. Made Type constructor private to prevent instantiation and force use of the factory method getType(). --- lib/Doctrine/DBAL/Configuration.php | 23 +- lib/Doctrine/DBAL/Connection.php | 1186 ++++++++--------- lib/Doctrine/DBAL/Logging/EchoSqlLogger.php | 20 + lib/Doctrine/DBAL/Logging/SqlLogger.php | 14 + lib/Doctrine/DBAL/Types/Type.php | 3 + lib/Doctrine/ORM/EntityManager.php | 19 +- lib/Doctrine/ORM/Events.php | 9 +- .../Internal/Hydration/AbstractHydrator.php | 2 +- .../ORM/Internal/Hydration/ArrayHydrator.php | 5 - .../ORM/Internal/Hydration/ObjectHydrator.php | 14 +- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 34 - .../ORM/Mapping/ClassMetadataFactory.php | 4 + .../Mapping/Driver/DoctrineAnnotations.php | 1 + .../Persisters/JoinedSubclassPersister.php | 441 +++--- .../Persisters/StandardEntityPersister.php | 631 ++++----- lib/Doctrine/ORM/Query/ResultSetMapping.php | 36 +- lib/Doctrine/ORM/UnitOfWork.php | 41 +- .../Schema/MySqlSchemaManagerTest.php | 25 +- .../Schema/SqliteSchemaManagerTest.php | 29 +- .../DBAL/Platforms/SqlitePlatformTest.php | 5 +- .../Doctrine/Tests/Models/Forum/ForumUser.php | 16 + .../ORM/Functional/BasicFunctionalTest.php | 3 - .../Performance/HydrationPerformanceTest.php | 17 +- 23 files changed, 1318 insertions(+), 1260 deletions(-) create mode 100644 lib/Doctrine/DBAL/Logging/EchoSqlLogger.php create mode 100644 lib/Doctrine/DBAL/Logging/SqlLogger.php diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index c4fdff058..7fe31df04 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -47,9 +47,30 @@ class Configuration public function __construct() { $this->_attributes = array( - 'quoteIdentifiers' => false + 'quoteIdentifiers' => false, + 'sqlLogger' => null ); } + + /** + * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. + * + * @param SqlLogger $logger + */ + public function setSqlLogger($logger) + { + $this->_attributes['sqlLogger'] = $logger; + } + + /** + * Gets the SQL logger that is used. + * + * @return SqlLogger + */ + public function getSqlLogger() + { + return $this->_attributes['sqlLogger']; + } public function getQuoteIdentifiers() { diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 2af3d4ed0..84aa081ed 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -49,149 +49,149 @@ use Doctrine\Common\DoctrineException; * 'slaveConnectionResolver' => new MySlaveConnectionResolver(), * 'masters' => array(...), * 'masterConnectionResolver' => new MyMasterConnectionResolver() - * + * * Doctrine\DBAL could ship with a simple standard broker that uses a primitive * round-robin approach to distribution. User can provide its own brokers. */ class Connection { - /** - * Constant for transaction isolation level READ UNCOMMITTED. - */ - const TRANSACTION_READ_UNCOMMITTED = 1; - /** - * Constant for transaction isolation level READ COMMITTED. - */ - const TRANSACTION_READ_COMMITTED = 2; - /** - * Constant for transaction isolation level REPEATABLE READ. - */ - const TRANSACTION_REPEATABLE_READ = 3; - /** - * Constant for transaction isolation level SERIALIZABLE. - */ - const TRANSACTION_SERIALIZABLE = 4; - - /** - * The wrapped driver connection. - * - * @var Doctrine\DBAL\Driver\Connection - */ - protected $_conn; - - /** - * The Configuration. - * - * @var Doctrine\DBAL\Configuration - */ - protected $_config; - - /** - * The EventManager. - * - * @var Doctrine\Common\EventManager - */ - protected $_eventManager; - - /** - * Whether or not a connection has been established. - * - * @var boolean - */ - protected $_isConnected = false; - - /** - * The transaction nesting level. - * - * @var integer - */ - protected $_transactionNestingLevel = 0; - - /** - * The currently active transaction isolation level. - * - * @var integer - */ - protected $_transactionIsolationLevel; - - /** - * The parameters used during creation of the Connection instance. - * - * @var array - */ - protected $_params = array(); - - /** - * The query count. Represents the number of executed database queries by the connection. - * - * @var integer - */ - protected $_queryCount = 0; - - /** - * The DatabasePlatform object that provides information about the - * database platform used by the connection. - * - * @var Doctrine\DBAL\Platforms\AbstractPlatform - */ - protected $_platform; - - /** - * The schema manager. - * - * @var Doctrine\DBAL\Schema\SchemaManager - */ - protected $_schemaManager; - - /** - * The used DBAL driver. - * - * @var Doctrine\DBAL\Driver - */ - protected $_driver; + /** + * Constant for transaction isolation level READ UNCOMMITTED. + */ + const TRANSACTION_READ_UNCOMMITTED = 1; + /** + * Constant for transaction isolation level READ COMMITTED. + */ + const TRANSACTION_READ_COMMITTED = 2; + /** + * Constant for transaction isolation level REPEATABLE READ. + */ + const TRANSACTION_REPEATABLE_READ = 3; + /** + * Constant for transaction isolation level SERIALIZABLE. + */ + const TRANSACTION_SERIALIZABLE = 4; - /** - * Whether to quote identifiers. Read from the configuration upon construction. - * - * @var boolean - */ - protected $_quoteIdentifiers = false; - - /** - * Initializes a new instance of the Connection class. - * - * @param array $params The connection parameters. - * @param Driver $driver - * @param Configuration $config - * @param EventManager $eventManager - */ - public function __construct(array $params, Driver $driver, Configuration $config = null, - EventManager $eventManager = null) - { - $this->_driver = $driver; - $this->_params = $params; - - if (isset($params['pdo'])) { - $this->_conn = $params['pdo']; - $this->_isConnected = true; - } - - // Create default config and event manager if none given - if ( ! $config) { - $config = new Configuration(); - } - if ( ! $eventManager) { - $eventManager = new EventManager(); - } + /** + * The wrapped driver connection. + * + * @var Doctrine\DBAL\Driver\Connection + */ + protected $_conn; - $this->_config = $config; - $this->_eventManager = $eventManager; - $this->_platform = $driver->getDatabasePlatform(); - $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel(); - $this->_quoteIdentifiers = $config->getQuoteIdentifiers(); - $this->_platform->setQuoteIdentifiers($this->_quoteIdentifiers); - } + /** + * The Configuration. + * + * @var Doctrine\DBAL\Configuration + */ + protected $_config; + /** + * The EventManager. + * + * @var Doctrine\Common\EventManager + */ + protected $_eventManager; + + /** + * Whether or not a connection has been established. + * + * @var boolean + */ + protected $_isConnected = false; + + /** + * The transaction nesting level. + * + * @var integer + */ + protected $_transactionNestingLevel = 0; + + /** + * The currently active transaction isolation level. + * + * @var integer + */ + protected $_transactionIsolationLevel; + + /** + * The parameters used during creation of the Connection instance. + * + * @var array + */ + protected $_params = array(); + + /** + * The query count. Represents the number of executed database queries by the connection. + * + * @var integer + */ + protected $_queryCount = 0; + + /** + * The DatabasePlatform object that provides information about the + * database platform used by the connection. + * + * @var Doctrine\DBAL\Platforms\AbstractPlatform + */ + protected $_platform; + + /** + * The schema manager. + * + * @var Doctrine\DBAL\Schema\SchemaManager + */ + protected $_schemaManager; + + /** + * The used DBAL driver. + * + * @var Doctrine\DBAL\Driver + */ + protected $_driver; + + /** + * Whether to quote identifiers. Read from the configuration upon construction. + * + * @var boolean + */ + protected $_quoteIdentifiers = false; + + /** + * Initializes a new instance of the Connection class. + * + * @param array $params The connection parameters. + * @param Driver $driver + * @param Configuration $config + * @param EventManager $eventManager + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, + EventManager $eventManager = null) + { + $this->_driver = $driver; + $this->_params = $params; + + if (isset($params['pdo'])) { + $this->_conn = $params['pdo']; + $this->_isConnected = true; + } + + // Create default config and event manager if none given + if ( ! $config) { + $config = new Configuration(); + } + if ( ! $eventManager) { + $eventManager = new EventManager(); + } + + $this->_config = $config; + $this->_eventManager = $eventManager; + $this->_platform = $driver->getDatabasePlatform(); + $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel(); + $this->_quoteIdentifiers = $config->getQuoteIdentifiers(); + $this->_platform->setQuoteIdentifiers($this->_quoteIdentifiers); + } + /** * Get the array of parameters used to instantiated this connection instance * @@ -212,46 +212,46 @@ class Connection return $this->_driver->getDatabase($this); } - /** - * Gets the DBAL driver instance. - * - * @return Doctrine\DBAL\Driver - */ - public function getDriver() - { - return $this->_driver; - } - - /** - * Gets the Configuration used by the Connection. - * - * @return Doctrine\DBAL\Configuration - */ - public function getConfiguration() - { - return $this->_config; - } - - /** - * Gets the EventManager used by the Connection. - * - * @return Doctrine\Common\EventManager - */ - public function getEventManager() - { - return $this->_eventManager; - } - - /** - * Gets the DatabasePlatform for the connection. - * - * @return Doctrine\DBAL\Platforms\AbstractPlatform - */ - public function getDatabasePlatform() - { - return $this->_platform; - } - + /** + * Gets the DBAL driver instance. + * + * @return Doctrine\DBAL\Driver + */ + public function getDriver() + { + return $this->_driver; + } + + /** + * Gets the Configuration used by the Connection. + * + * @return Doctrine\DBAL\Configuration + */ + public function getConfiguration() + { + return $this->_config; + } + + /** + * Gets the EventManager used by the Connection. + * + * @return Doctrine\Common\EventManager + */ + public function getEventManager() + { + return $this->_eventManager; + } + + /** + * Gets the DatabasePlatform for the connection. + * + * @return Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_platform; + } + /** * Establishes the connection with the database. * @@ -260,190 +260,25 @@ class Connection public function connect() { if ($this->_isConnected) return false; - + $driverOptions = isset($this->_params['driverOptions']) ? - $this->_params['driverOptions'] : array(); + $this->_params['driverOptions'] : array(); $user = isset($this->_params['user']) ? - $this->_params['user'] : null; + $this->_params['user'] : null; $password = isset($this->_params['password']) ? - $this->_params['password'] : null; - + $this->_params['password'] : null; + $this->_conn = $this->_driver->connect( - $this->_params, - $user, - $password, - $driverOptions - ); + $this->_params, + $user, + $password, + $driverOptions + ); $this->_isConnected = true; return true; } - - /** - * Whether an actual connection to the database is established. - * - * @return boolean - */ - public function isConnected() - { - return $this->_isConnected; - } - - /** - * Deletes table row(s) matching the specified identifier. - * - * @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 - */ - public function delete($tableName, array $identifier) - { - $this->connect(); - $criteria = array(); - foreach (array_keys($identifier) as $id) { - $criteria[] = $this->quoteIdentifier($id) . ' = ?'; - } - - $query = 'DELETE FROM ' - . $this->quoteIdentifier($tableName) - . ' WHERE ' . implode(' AND ', $criteria); - - return $this->exec($query, array_values($identifier)); - } - - /** - * Updates table row(s) with specified data - * - * @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 - */ - public function update($tableName, array $data, array $identifier) - { - $this->connect(); - if (empty($data)) { - return false; - } - - $set = array(); - foreach ($data as $columnName => $value) { - $set[] = $this->quoteIdentifier($columnName) . ' = ?'; - } - - $params = array_merge(array_values($data), array_values($identifier)); - - $sql = 'UPDATE ' . $this->quoteIdentifier($tableName) - . ' SET ' . implode(', ', $set) - . ' WHERE ' . implode(' = ? AND ', array_keys($identifier)) - . ' = ?'; - - return $this->exec($sql, $params); - } - - /** - * 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 - */ - public function insert($tableName, array $data) - { - $this->connect(); - if (empty($data)) { - return false; - } - - // column names are specified as array keys - $cols = array(); - $a = array(); - foreach ($data as $columnName => $value) { - $cols[] = $this->quoteIdentifier($columnName); - $a[] = '?'; - } - - $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName) - . ' (' . implode(', ', $cols) . ') ' - . 'VALUES ('; - $query .= implode(', ', $a) . ')'; - - return $this->exec($query, array_values($data)); - } - - /** - * Set the charset on the current connection - * - * @param string charset - */ - public function setCharset($charset) - { - $this->exec($this->_platform->getSetCharsetSql($charset)); - } - - /** - * Quote a string so it can be safely used as a table or column name, even if - * it is a reserved name. - * - * 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 - * problems than they solve. - * - * @param string $str identifier name to be quoted - * @param bool $checkOption check the 'quote_identifier' option - * - * @return string quoted identifier string - */ - public function quoteIdentifier($str) - { - if ($this->_quoteIdentifiers) { - return $this->_platform->quoteIdentifier($str); - } - return $str; - } - - /** - * Quotes a given input 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) - { - $this->connect(); - return $this->_conn->quote($input, $type); - } - - /** - * Convenience method for PDO::query("...") followed by $stmt->fetchAll(PDO::FETCH_ASSOC). - * - * @param string $sql The SQL query. - * @param array $params The query parameters. - * @return array - */ - public function fetchAll($sql, array $params = array()) - { - return $this->execute($sql, $params)->fetchAll(\PDO::FETCH_ASSOC); - } - - /** - * Convenience method for PDO::query("...") followed by $stmt->fetchColumn(). - * - * @param string $statement The SQL query. - * @param array $params The query parameters. - * @param int $colnum 0-indexed column number to retrieve - * @return mixed - */ - public function fetchOne($statement, array $params = array(), $colnum = 0) - { - return $this->execute($statement, $params)->fetchColumn($colnum); - } /** * Convenience method for PDO::query("...") followed by $stmt->fetch(PDO::FETCH_ASSOC). @@ -468,7 +303,7 @@ class Connection { return $this->execute($statement, $params)->fetch(\PDO::FETCH_NUM); } - + /** * Convenience method for PDO::query("...") followed by $stmt->fetchAll(PDO::FETCH_COLUMN, ...). * @@ -482,6 +317,16 @@ class Connection return $this->execute($statement, $params)->fetchAll(\PDO::FETCH_COLUMN, $colnum); } + /** + * Whether an actual connection to the database is established. + * + * @return boolean + */ + public function isConnected() + { + return $this->_isConnected; + } + /** * Convenience method for PDO::query("...") followed by $stmt->fetchAll(PDO::FETCH_BOTH). * @@ -493,108 +338,27 @@ class Connection { return $this->execute($statement, $params)->fetchAll(\PDO::FETCH_BOTH); } - - /** - * Prepares an SQL statement. - * - * @param string $statement - * @return Statement - */ - public function prepare($statement) - { - $this->connect(); - return $this->_conn->prepare($statement); - } - - /** - * Queries the database with limit and offset added to the query and returns - * a Statement object. - * - * @param string $query - * @param integer $limit - * @param integer $offset - * @return Statement - */ - public function select($query, $limit = 0, $offset = 0) - { - if ($limit > 0 || $offset > 0) { - $query = $this->_platform->modifyLimitQuery($query, $limit, $offset); - } - return $this->execute($query); - } /** - * Executes an SQL SELECT query with the given parameters. - * - * @param string $query sql query - * @param array $params query parameters + * Deletes table row(s) matching the specified identifier. * - * @return PDOStatement + * @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 */ - public function execute($query, array $params = array()) + public function delete($tableName, array $identifier) { $this->connect(); - try { - if ( ! empty($params)) { - $stmt = $this->prepare($query); - $stmt->execute($params); - return $stmt; - } else { - $stmt = $this->_conn->query($query); - $this->_queryCount++; - return $stmt; - } - } catch (PDOException $e) { - $this->rethrowException($e, $this); + $criteria = array(); + foreach (array_keys($identifier) as $id) { + $criteria[] = $this->quoteIdentifier($id) . ' = ?'; } - } - /** - * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters. - * - * @param string $query sql query - * @param array $params query parameters - * - * @return PDOStatement - * @todo Rename to executeUpdate(). - */ - public function exec($query, array $params = array()) { - $this->connect(); - try { - if ( ! empty($params)) { - var_dump($params); - $stmt = $this->prepare($query); - $stmt->execute($params); - return $stmt->rowCount(); - } else { - $count = $this->_conn->exec($query); - $this->_queryCount++; - return $count; - } - } catch (PDOException $e) { - //TODO: Wrap - throw $e; - } - } + $query = 'DELETE FROM ' + . $this->quoteIdentifier($tableName) + . ' WHERE ' . implode(' AND ', $criteria); - /** - * Wraps the given exception into a driver-specific exception and rethrows it. - * - * @throws Doctrine\DBAL\ConnectionException - */ - public function rethrowException(\Exception $e, $invoker) - { - throw $e; - } - - /** - * Returns the number of queries executed by the connection. - * - * @return integer - */ - public function getQueryCount() - { - return $this->_queryCount; + return $this->exec($query, array_values($identifier)); } /** @@ -629,165 +393,391 @@ class Connection return $this->_transactionIsolationLevel; } - /** - * Returns the current transaction nesting level. - * - * @return integer The nesting level. A value of 0 means theres no active transaction. - */ - public function getTransactionNestingLevel() - { - return $this->_transactionNestingLevel; - } - - /** - * Fetch the SQLSTATE associated with the last operation on the database handle - * - * @return integer - */ - public function errorCode() - { - $this->connect(); - return $this->_conn->errorCode(); - } + /** + * Updates table row(s) with specified data + * + * @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 + */ + public function update($tableName, array $data, array $identifier) + { + $this->connect(); + if (empty($data)) { + return false; + } - /** - * Fetch extended error information associated with the last operation on the database handle - * - * @return array - */ - public function errorInfo() - { - $this->connect(); - return $this->_conn->errorInfo(); - } - - /** - * Returns the ID of the last inserted row, or the last value from a sequence object, - * 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. - * - * @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. - */ - public function lastInsertId($seqName = null) - { - $this->connect(); - return $this->_conn->lastInsertId($seqName); - } + $set = array(); + foreach ($data as $columnName => $value) { + $set[] = $this->quoteIdentifier($columnName) . ' = ?'; + } - /** - * Start a transaction or set a savepoint. - * - * if trying to set a savepoint and there is no active transaction - * a new transaction is being started. - * - * @return boolean - */ - public function beginTransaction() - { - $this->connect(); - if ($this->_transactionNestingLevel == 0) { - $this->_conn->beginTransaction(); - } - ++$this->_transactionNestingLevel; - return true; - } - - /** - * Commits the database changes done during a transaction that is in - * progress or release a savepoint. This function may only be called when - * auto-committing is disabled, otherwise it will fail. - * - * @return boolean FALSE if commit couldn't be performed, TRUE otherwise - */ - public function commit() - { - if ($this->_transactionNestingLevel == 0) { - throw ConnectionException::commitFailedNoActiveTransaction(); - } - - $this->connect(); + $params = array_merge(array_values($data), array_values($identifier)); - if ($this->_transactionNestingLevel == 1) { - $this->_conn->commit(); - } - --$this->_transactionNestingLevel; - - return true; - } + $sql = 'UPDATE ' . $this->quoteIdentifier($tableName) + . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' = ? AND ', array_keys($identifier)) + . ' = ?'; - /** - * 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. - * - * this method can be listened with onPreTransactionRollback and onTransactionRollback - * eventlistener methods - * - * @param string $savepoint Name of a savepoint to rollback to. - * @throws Doctrine\DBAL\ConnectionException If the rollback operation fails at database level. - * @return boolean FALSE if rollback couldn't be performed, TRUE otherwise. - */ - public function rollback() - { - if ($this->_transactionNestingLevel == 0) { - throw ConnectionException::rollbackFailedNoActiveTransaction(); - } - - $this->connect(); + return $this->exec($sql, $params); + } - if ($this->_transactionNestingLevel == 1) { - $this->_transactionNestingLevel = 0; - $this->_conn->rollback(); - - } - --$this->_transactionNestingLevel; - - return true; - } - - /** - * Quotes pattern (% and _) characters in a string) - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change signature at - * any time until labelled as non-experimental - * - * @param string the input string to quote - * - * @return string quoted string - */ - protected function _escapePattern($text) - { - return $text; - } + /** + * 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 + */ + public function insert($tableName, array $data) + { + $this->connect(); + if (empty($data)) { + return false; + } - /** - * Gets the wrapped driver connection. - * - * @return Doctrine\DBAL\Driver\Connection - */ - public function getWrappedConnection() - { - $this->connect(); - return $this->_conn; - } - - /** - * Gets the SchemaManager that can be used to inspect or change the - * database schema through the connection. - * - * @return Doctrine\DBAL\Schema\SchemaManager - */ - public function getSchemaManager() - { - if ( ! $this->_schemaManager) { - $this->_schemaManager = $this->_driver->getSchemaManager($this); + // column names are specified as array keys + $cols = array(); + $a = array(); + foreach ($data as $columnName => $value) { + $cols[] = $this->quoteIdentifier($columnName); + $a[] = '?'; + } + + $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName) + . ' (' . implode(', ', $cols) . ') ' + . 'VALUES ('; + $query .= implode(', ', $a) . ')'; + + return $this->exec($query, array_values($data)); + } + + /** + * Set the charset on the current connection + * + * @param string charset + */ + public function setCharset($charset) + { + $this->exec($this->_platform->getSetCharsetSql($charset)); + } + + /** + * Quote a string so it can be safely used as a table or column name, even if + * it is a reserved name. + * + * 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 + * problems than they solve. + * + * @param string $str identifier name to be quoted + * @param bool $checkOption check the 'quote_identifier' option + * + * @return string quoted identifier string + */ + public function quoteIdentifier($str) + { + if ($this->_quoteIdentifiers) { + return $this->_platform->quoteIdentifier($str); + } + return $str; + } + + /** + * Quotes a given input 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) + { + $this->connect(); + return $this->_conn->quote($input, $type); + } + + /** + * Convenience method for PDO::query("...") followed by $stmt->fetchAll(PDO::FETCH_ASSOC). + * + * @param string $sql The SQL query. + * @param array $params The query parameters. + * @return array + */ + public function fetchAll($sql, array $params = array()) + { + return $this->execute($sql, $params)->fetchAll(\PDO::FETCH_ASSOC); + } + + /** + * Convenience method for PDO::query("...") followed by $stmt->fetchColumn(). + * + * @param string $statement The SQL query. + * @param array $params The query parameters. + * @param int $colnum 0-indexed column number to retrieve + * @return mixed + */ + public function fetchOne($statement, array $params = array(), $colnum = 0) + { + return $this->execute($statement, $params)->fetchColumn($colnum); + } + + /** + * Prepares an SQL statement. + * + * @param string $statement + * @return Statement + */ + public function prepare($statement) + { + $this->connect(); + return $this->_conn->prepare($statement); + } + + /** + * Queries the database with limit and offset added to the query and returns + * a Statement object. + * + * @param string $query + * @param integer $limit + * @param integer $offset + * @return Statement + */ + public function select($query, $limit = 0, $offset = 0) + { + if ($limit > 0 || $offset > 0) { + $query = $this->_platform->modifyLimitQuery($query, $limit, $offset); + } + return $this->execute($query); + } + + /** + * Executes an SQL SELECT query with the given parameters. + * + * @param string $query sql query + * @param array $params query parameters + * + * @return PDOStatement + */ + public function execute($query, array $params = array()) + { + $this->connect(); + + if ($this->_config->getSqlLogger()) { + $this->_config->getSqlLogger()->logSql($query, $params); } - return $this->_schemaManager; - } + + if ( ! empty($params)) { + $stmt = $this->prepare($query); + $stmt->execute($params); + return $stmt; + } else { + $stmt = $this->_conn->query($query); + $this->_queryCount++; + return $stmt; + } + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters. + * + * @param string $query sql query + * @param array $params query parameters + * + * @return PDOStatement + * @todo Rename to executeUpdate(). + */ + public function exec($query, array $params = array()) { + $this->connect(); + + if ($this->_config->getSqlLogger()) { + $this->_config->getSqlLogger()->logSql($query, $params); + } + + if ( ! empty($params)) { + $stmt = $this->prepare($query); + $stmt->execute($params); + return $stmt->rowCount(); + } else { + $count = $this->_conn->exec($query); + $this->_queryCount++; + return $count; + } + } + + /** + * Returns the number of queries executed by the connection. + * + * @return integer + */ + public function getQueryCount() + { + return $this->_queryCount; + } + + /** + * Returns the current transaction nesting level. + * + * @return integer The nesting level. A value of 0 means theres no active transaction. + */ + public function getTransactionNestingLevel() + { + return $this->_transactionNestingLevel; + } + + /** + * Fetch the SQLSTATE associated with the last operation on the database handle + * + * @return integer + */ + public function errorCode() + { + $this->connect(); + return $this->_conn->errorCode(); + } + + /** + * Fetch extended error information associated with the last operation on the database handle + * + * @return array + */ + public function errorInfo() + { + $this->connect(); + return $this->_conn->errorInfo(); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * 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. + * + * @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. + */ + public function lastInsertId($seqName = null) + { + $this->connect(); + return $this->_conn->lastInsertId($seqName); + } + + /** + * Start a transaction or set a savepoint. + * + * if trying to set a savepoint and there is no active transaction + * a new transaction is being started. + * + * @return boolean + */ + public function beginTransaction() + { + $this->connect(); + if ($this->_transactionNestingLevel == 0) { + $this->_conn->beginTransaction(); + } + ++$this->_transactionNestingLevel; + return true; + } + + /** + * Commits the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. + * + * @return boolean FALSE if commit couldn't be performed, TRUE otherwise + */ + public function commit() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::commitFailedNoActiveTransaction(); + } + + $this->connect(); + + if ($this->_transactionNestingLevel == 1) { + $this->_conn->commit(); + } + --$this->_transactionNestingLevel; + + return true; + } + + /** + * 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. + * + * this method can be listened with onPreTransactionRollback and onTransactionRollback + * eventlistener methods + * + * @param string $savepoint Name of a savepoint to rollback to. + * @throws Doctrine\DBAL\ConnectionException If the rollback operation fails at database level. + * @return boolean FALSE if rollback couldn't be performed, TRUE otherwise. + */ + public function rollback() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::rollbackFailedNoActiveTransaction(); + } + + $this->connect(); + + if ($this->_transactionNestingLevel == 1) { + $this->_transactionNestingLevel = 0; + $this->_conn->rollback(); + + } + --$this->_transactionNestingLevel; + + return true; + } + + /** + * Quotes pattern (% and _) characters in a string) + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @param string the input string to quote + * + * @return string quoted string + */ + protected function _escapePattern($text) + { + return $text; + } + + /** + * Gets the wrapped driver connection. + * + * @return Doctrine\DBAL\Driver\Connection + */ + public function getWrappedConnection() + { + $this->connect(); + return $this->_conn; + } + + /** + * Gets the SchemaManager that can be used to inspect or change the + * database schema through the connection. + * + * @return Doctrine\DBAL\Schema\SchemaManager + */ + public function getSchemaManager() + { + if ( ! $this->_schemaManager) { + $this->_schemaManager = $this->_driver->getSchemaManager($this); + } + return $this->_schemaManager; + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Logging/EchoSqlLogger.php b/lib/Doctrine/DBAL/Logging/EchoSqlLogger.php new file mode 100644 index 000000000..b6e873819 --- /dev/null +++ b/lib/Doctrine/DBAL/Logging/EchoSqlLogger.php @@ -0,0 +1,20 @@ + + * @since 2.0 + */ +class EchoSqlLogger implements SqlLogger +{ + public function logSql($sql, array $params = null) + { + echo $sql . PHP_EOL; + if ($params) { + var_dump($params); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Logging/SqlLogger.php b/lib/Doctrine/DBAL/Logging/SqlLogger.php new file mode 100644 index 000000000..e06b4a259 --- /dev/null +++ b/lib/Doctrine/DBAL/Logging/SqlLogger.php @@ -0,0 +1,14 @@ + + * @since 2.0 + */ +interface SqlLogger +{ + public function logSql($sql, array $params = null); +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Types/Type.php b/lib/Doctrine/DBAL/Types/Type.php index 00144fc78..207b2f6c8 100644 --- a/lib/Doctrine/DBAL/Types/Type.php +++ b/lib/Doctrine/DBAL/Types/Type.php @@ -37,6 +37,9 @@ abstract class Type 'double' => 'Doctrine\DBAL\Types\DoubleType' ); + /* Prevent instantiation and force use of the factory method. */ + private function __construct() {} + public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { return $value; diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 6a41954f6..3d2bbee5a 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -170,11 +170,11 @@ class EntityManager } /** - * Starts a transaction on the underlying connection. + * Starts a transaction on the underlying database connection. */ public function beginTransaction() { - return $this->_conn->beginTransaction(); + $this->_conn->beginTransaction(); } /** @@ -182,15 +182,23 @@ class EntityManager * * This causes a flush() of the EntityManager if the flush mode is set to * AUTO or COMMIT. - * - * @return boolean */ public function commit() { if ($this->_flushMode == self::FLUSHMODE_AUTO || $this->_flushMode == self::FLUSHMODE_COMMIT) { $this->flush(); } - return $this->_conn->commitTransaction(); + $this->_conn->commitTransaction(); + } + + /** + * Performs a rollback on the underlying database connection and closes the + * EntityManager as it may now be in a corrupted state. + */ + public function rollback() + { + $this->_conn->rollback(); + $this->close(); } /** @@ -401,6 +409,7 @@ class EntityManager */ public function refresh($entity) { + $this->_errorIfClosed(); throw DoctrineException::notImplemented(); } diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index e424a4583..aa6e84e41 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -26,7 +26,7 @@ namespace Doctrine\ORM; * * This class cannot be instantiated. * - * @author robo + * @author Roman Borschel * @since 2.0 */ final class Events @@ -35,6 +35,9 @@ final class Events const preDelete = 'preDelete'; const postDelete = 'postDelete'; - const preSave = 'preSave'; - const postSave = 'postSave'; + const preInsert = 'preSave'; + const postInsert = 'postSave'; + const preUpdate = 'preUpdate'; + const postUpdate = 'postUpdate'; + const load = 'load'; } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 7aa9f9a5b..823700aff 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -185,7 +185,7 @@ abstract class AbstractHydrator $classMetadata = $this->_lookupDeclaringClass($classMetadata, $fieldName); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['isScalar'] = false; - $cache[$key]['type'] = Type::getType($classMetadata->getTypeOfField($fieldName)); + $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; } else { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 3746f13ce..8f1a0306e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -55,17 +55,12 @@ class ArrayHydrator extends AbstractHydrator /** @override */ protected function _hydrateAll() { - $s = microtime(true); - $result = array(); $cache = array(); while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->_hydrateRow($data, $cache, $result); } - $e = microtime(true); - echo 'Hydration took: ' . ($e - $s) . PHP_EOL; - return $result; } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index c2224558f..698ce163b 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -113,8 +113,6 @@ class ObjectHydrator extends AbstractHydrator */ protected function _hydrateAll() { - $s = microtime(true); - $result = $this->_rsm->isMixed ? array() : new Collection; $cache = array(); @@ -132,10 +130,6 @@ class ObjectHydrator extends AbstractHydrator $this->_collections = array(); $this->_initializedRelations = array(); - $e = microtime(true); - - echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records' . PHP_EOL; - return $result; } @@ -279,10 +273,10 @@ class ObjectHydrator extends AbstractHydrator */ private function setRelatedElement($entity1, $property, $entity2) { - $classMetadata1 = $this->_ce[get_class($entity1)]; - $classMetadata1->reflFields[$property]->setValue($entity1, $entity2); + $class = $this->_ce[get_class($entity1)]; + $class->reflFields[$property]->setValue($entity1, $entity2); $this->_uow->setOriginalEntityProperty(spl_object_hash($entity1), $property, $entity2); - $relation = $classMetadata1->associationMappings[$property]; + $relation = $class->associationMappings[$property]; if ($relation->isOneToOne()) { $targetClass = $this->_ce[$relation->targetEntityName]; if ($relation->isOwningSide) { @@ -290,7 +284,7 @@ class ObjectHydrator extends AbstractHydrator if (isset($targetClass->inverseMappings[$property])) { $sourceProp = $targetClass->inverseMappings[$property]->sourceFieldName; $targetClass->reflFields[$sourceProp]->setValue($entity2, $entity1); - } else if ($classMetadata1 === $targetClass) { + } else if ($class === $targetClass) { // Special case: self-referencing one-one on the same class $targetClass->reflFields[$property]->setValue($entity2, $entity1); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 184446e10..1c40bdb13 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -253,15 +253,6 @@ final class ClassMetadata * @var array */ public $columnNames = array(); - - /** - * Map that maps lowercased column names (keys) to field names (values). - * Mainly used during hydration because Doctrine enforces PDO_CASE_LOWER - * for portability. - * - * @var array - */ - public $lcColumnToFieldNames = array(); /** * Whether to automatically OUTER JOIN subtypes when a basetype is queried. @@ -724,29 +715,6 @@ final class ClassMetadata $this->fieldNames[$columnName] : $columnName; } - /** - * Gets the field name for a completely lowercased column name. - * Mainly used during hydration. - * - * @param string $lcColumnName The all-lowercase column name. - * @return string The field name. - */ - public function getFieldNameForLowerColumnName($lcColumnName) - { - return $this->lcColumnToFieldNames[$lcColumnName]; - } - - /** - * Checks whether a specified column name (all lowercase) exists in this class. - * - * @param string $lcColumnName - * @return boolean - */ - public function hasLowerColumn($lcColumnName) - { - return isset($this->lcColumnToFieldNames[$lcColumnName]); - } - /** * Validates & completes the given field mapping. * @@ -767,11 +735,9 @@ final class ClassMetadata if ( ! isset($mapping['columnName'])) { $mapping['columnName'] = $mapping['fieldName']; } - $lcColumnName = strtolower($mapping['columnName']); $this->columnNames[$mapping['fieldName']] = $mapping['columnName']; $this->fieldNames[$mapping['columnName']] = $mapping['fieldName']; - $this->lcColumnToFieldNames[$lcColumnName] = $mapping['fieldName']; // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 25cfed740..5d29a3d6a 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -246,6 +246,7 @@ class ClassMetadataFactory // Generate INSERT SQL $columns = $values = array(); if ($class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_JOINED) { + // Generate INSERT SQL for inheritance type JOINED foreach ($class->reflFields as $name => $field) { if (isset($class->fieldMappings[$name]['inherited']) && ! isset($class->fieldMappings[$name]['id']) || isset($class->inheritedAssociationFields[$name])) { @@ -266,6 +267,7 @@ class ClassMetadataFactory } } } else { + // Generate INSERT SQL for inheritance types NONE, SINGLE_TABLE, TABLE_PER_CLASS foreach ($class->reflFields as $name => $field) { if (isset($class->associationMappings[$name])) { $assoc = $class->associationMappings[$name]; @@ -281,6 +283,8 @@ class ClassMetadataFactory } } } + + // Add discriminator column to the INSERT SQL if necessary if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined() && $class->name == $class->rootEntityName) { $columns[] = $class->discriminatorColumn['name']; $values[] = '?'; diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 80b7b3de7..7c30fbecf 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -53,6 +53,7 @@ final class DoctrineColumn extends \Addendum\Annotation { public $length; public $unique = false; public $nullable = false; + public $quote = false; } final class DoctrineOneToOne extends \Addendum\Annotation { public $targetEntity; diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 8cec5bc60..fa9bd6c52 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -1,4 +1,4 @@ -_class->discriminatorColumn; - $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); - $result[$rootClass->primaryTable['name']][$discColumn['name']] = - $this->_class->discriminatorValue; - } - } + /** + * {@inheritdoc} + * + * @override + */ + protected function _prepareData($entity, array &$result, $isInsert = false) + { + 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; + } + } - /** - * {@inheritdoc} - * - * @override - */ - public function getOwningTable($fieldName) - { - if ( ! isset($this->_owningTableMap[$fieldName])) { - if (isset($this->_class->associationMappings[$fieldName])) { - if (isset($this->_class->inheritedAssociationFields[$fieldName])) { - $this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( - $this->_class->inheritedAssociationFields[$fieldName])->primaryTable['name']; - } else { - $this->_owningTableMap[$fieldName] = $this->_class->primaryTable['name']; - } - } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { - $this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( - $this->_class->fieldMappings[$fieldName]['inherited'])->primaryTable['name']; - } else { - $this->_owningTableMap[$fieldName] = $this->_class->primaryTable['name']; - } - } - return $this->_owningTableMap[$fieldName]; - } + /** + * {@inheritdoc} + * + * @override + */ + public function getOwningTable($fieldName) + { + if ( ! isset($this->_owningTableMap[$fieldName])) { + if (isset($this->_class->associationMappings[$fieldName])) { + if (isset($this->_class->inheritedAssociationFields[$fieldName])) { + $this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( + $this->_class->inheritedAssociationFields[$fieldName])->primaryTable['name']; + } else { + $this->_owningTableMap[$fieldName] = $this->_class->primaryTable['name']; + } + } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { + $this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( + $this->_class->fieldMappings[$fieldName]['inherited'])->primaryTable['name']; + } else { + $this->_owningTableMap[$fieldName] = $this->_class->primaryTable['name']; + } + } + return $this->_owningTableMap[$fieldName]; + } - /** - * {@inheritdoc} - * - * @override - */ - public function executeInserts() - { - if ( ! $this->_queuedInserts) { - return; - } + /** + * {@inheritdoc} + * + * @override + */ + public function executeInserts() + { + if ( ! $this->_queuedInserts) { + return; + } - $postInsertIds = array(); - $idGen = $this->_class->idGenerator; - $isPostInsertId = $idGen->isPostInsertGenerator(); + $postInsertIds = array(); + $idGen = $this->_class->idGenerator; + $isPostInsertId = $idGen->isPostInsertGenerator(); + $sqlLogger = $this->_conn->getConfiguration()->getSqlLogger(); - // Prepare statements for all tables - $stmts = $classes = array(); - $stmts[$this->_class->primaryTable['name']] = $this->_conn->prepare($this->_class->insertSql); - $classes[$this->_class->name] = $this->_class; - foreach ($this->_class->parentClasses as $parentClass) { - $classes[$parentClass] = $this->_em->getClassMetadata($parentClass); - $stmts[$classes[$parentClass]->primaryTable['name']] = $this->_conn->prepare($classes[$parentClass]->insertSql); - } - $rootTableName = $classes[$this->_class->rootEntityName]->primaryTable['name']; + // Prepare statements for all tables + $stmts = $classes = array(); + $stmts[$this->_class->primaryTable['name']] = $this->_conn->prepare($this->_class->insertSql); + $sql[$this->_class->primaryTable['name']] = $this->_class->insertSql; + foreach ($this->_class->parentClasses as $parentClass) { + $parentClass = $this->_em->getClassMetadata($parentClass); + $sql[$parentClass->primaryTable['name']] = $parentClass->insertSql; + $stmts[$parentClass->primaryTable['name']] = $this->_conn->prepare($parentClass->insertSql); + } + $rootTableName = $this->_em->getClassMetadata($this->_class->rootEntityName)->primaryTable['name']; - foreach ($this->_queuedInserts as $entity) { - $insertData = array(); - $this->_prepareData($entity, $insertData, true); - - // Execute insert on root table - $paramIndex = 1; - $stmt = $stmts[$rootTableName]; - foreach ($insertData[$rootTableName] as $columnName => $value) { - $stmt->bindValue($paramIndex++, $value/*, TODO: TYPE*/); - } - $stmt->execute(); - unset($insertData[$rootTableName]); + foreach ($this->_queuedInserts as $entity) { + $insertData = array(); + $this->_prepareData($entity, $insertData, true); - if ($isPostInsertId) { - $id = $idGen->generate($this->_em, $entity); - $postInsertIds[$id] = $entity; - } else { - $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); - } + // Execute insert on root table + $stmt = $stmts[$rootTableName]; + $paramIndex = 1; + if ($sqlLogger) { + $params = array(); + foreach ($insertData[$rootTableName] as $columnName => $value) { + $params[$paramIndex] = $value; + $stmt->bindValue($paramIndex++, $value/*, TODO: TYPE*/); + } + $sqlLogger->logSql($sql[$rootTableName], $params); + } else { + foreach ($insertData[$rootTableName] as $columnName => $value) { + $stmt->bindValue($paramIndex++, $value/*, TODO: TYPE*/); + } + } + $stmt->execute(); + unset($insertData[$rootTableName]); - // Execute inserts on subtables - foreach ($insertData as $tableName => $data) { - $stmt = $stmts[$tableName]; - $paramIndex = 1; - foreach ((array)$id as $idVal) { - $stmt->bindValue($paramIndex++, $idVal/*, TODO: TYPE*/); - } - foreach ($data as $columnName => $value) { - $stmt->bindValue($paramIndex++, $value/*, TODO: TYPE*/); - } - $stmt->execute(); - } - } + if ($isPostInsertId) { + $id = $idGen->generate($this->_em, $entity); + $postInsertIds[$id] = $entity; + } else { + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); + } - foreach ($stmts as $stmt) - $stmt->closeCursor(); + // Execute inserts on subtables + foreach ($insertData as $tableName => $data) { + $stmt = $stmts[$tableName]; + $paramIndex = 1; + if ($sqlLogger) { + //TODO: Log type + $params = array(); + foreach ((array)$id as $idVal) { + $params[$paramIndex] = $idVal; + $stmt->bindValue($paramIndex++, $idVal/*, TODO: TYPE*/); + } + foreach ($data as $columnName => $value) { + $params[$paramIndex] = $value; + $stmt->bindValue($paramIndex++, $value/*, TODO: TYPE*/); + } + $sqlLogger->logSql($sql[$tableName], $params); + } else { + foreach ((array)$id as $idVal) { + $stmt->bindValue($paramIndex++, $idVal/*, TODO: TYPE*/); + } + foreach ($data as $columnName => $value) { + $stmt->bindValue($paramIndex++, $value/*, TODO: TYPE*/); + } + } + $stmt->execute(); + } + } - $this->_queuedInserts = array(); + foreach ($stmts as $stmt) + $stmt->closeCursor(); - return $postInsertIds; - } + $this->_queuedInserts = array(); - /** - * Updates an entity. - * - * @param object $entity The entity to update. - * @override - */ - public function update($entity) - { - $updateData = array(); - $this->_prepareData($entity, $updateData); + return $postInsertIds; + } - $id = array_combine( - $this->_class->getIdentifierFieldNames(), - $this->_em->getUnitOfWork()->getEntityIdentifier($entity) - ); + /** + * Updates an entity. + * + * @param object $entity The entity to update. + * @override + */ + public function update($entity) + { + $updateData = array(); + $this->_prepareData($entity, $updateData); - foreach ($updateData as $tableName => $data) { - $this->_conn->update($tableName, $updateData[$tableName], $id); - } - } + $id = array_combine( + $this->_class->getIdentifierFieldNames(), + $this->_em->getUnitOfWork()->getEntityIdentifier($entity) + ); - /** - * Deletes an entity. - * - * @param object $entity The entity to delete. - * @override - */ - public function delete($entity) - { - $id = array_combine( - $this->_class->getIdentifierFieldNames(), - $this->_em->getUnitOfWork()->getEntityIdentifier($entity) - ); + foreach ($updateData as $tableName => $data) { + $this->_conn->update($tableName, $updateData[$tableName], $id); + } + } - // If the database platform supports FKs, just - // delete the row from the root table. Cascades do the rest. - if ($this->_conn->getDatabasePlatform()->supportsForeignKeyConstraints()) { - $this->_conn->delete($this->_em->getClassMetadata($this->_class->rootEntityName) - ->primaryTable['name'], $id); - } else { - // Delete the parent tables, starting from this class' table up to the root table - $this->_conn->delete($this->_class->primaryTable['name'], $id); - foreach ($this->_class->parentClasses as $parentClass) { - $this->_conn->delete($this->_em->getClassMetadata($parentClass)->primaryTable['name'], $id); - } - } - } - - /** - * Gets the SELECT SQL to select a single entity by a set of field criteria. - * - * @param array $criteria - * @return string The SQL. - * @todo Quote identifier. - * @override - */ - protected function _getSelectSingleEntitySql(array $criteria) - { - $tableAliases = array(); - $aliasIndex = 1; - $idColumns = $this->_class->getIdentifierColumnNames(); - $baseTableAlias = 't0'; - - foreach (array_merge($this->_class->subClasses, $this->_class->parentClasses) as $className) { - $tableAliases[$className] = 't' . $aliasIndex++; - } - - $columnList = ''; - foreach ($this->_class->fieldMappings as $fieldName => $mapping) { - $tableAlias = isset($mapping['inherited']) ? - $tableAliases[$mapping['inherited']] : $baseTableAlias; - if ($columnList != '') $columnList .= ', '; - $columnList .= $tableAlias . '.' . $this->_class->columnNames[$fieldName]; - } - - $sql = 'SELECT ' . $columnList . ' FROM ' . $this->_class->primaryTable['name']. ' ' . $baseTableAlias; - - // INNER JOIN parent tables - foreach ($this->_class->parentClasses as $parentClassName) { - $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $tableAliases[$parentClassName]; - $sql .= ' INNER JOIN ' . $parentClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; - $first = true; - foreach ($idColumns as $idColumn) { - if ($first) $first = false; else $sql .= ' AND '; - $sql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; - } - } - - // OUTER JOIN sub tables - foreach ($this->_class->subClasses as $subClassName) { - $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $tableAliases[$subClassName]; - $sql .= ' LEFT JOIN ' . $subClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; - $first = true; - foreach ($idColumns as $idColumn) { - if ($first) $first = false; else $sql .= ' AND '; - $sql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; - } - } - - $conditionSql = ''; - foreach ($criteria as $field => $value) { - if ($conditionSql != '') $conditionSql .= ' AND '; - $conditionSql .= $baseTableAlias . '.' . $this->_class->columnNames[$field] . ' = ?'; - } - - return $sql . ' WHERE ' . $conditionSql; - } + /** + * Deletes an entity. + * + * @param object $entity The entity to delete. + * @override + */ + public function delete($entity) + { + $id = array_combine( + $this->_class->getIdentifierFieldNames(), + $this->_em->getUnitOfWork()->getEntityIdentifier($entity) + ); + + // If the database platform supports FKs, just + // delete the row from the root table. Cascades do the rest. + if ($this->_conn->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->_conn->delete($this->_em->getClassMetadata($this->_class->rootEntityName) + ->primaryTable['name'], $id); + } else { + // Delete the parent tables, starting from this class' table up to the root table + $this->_conn->delete($this->_class->primaryTable['name'], $id); + foreach ($this->_class->parentClasses as $parentClass) { + $this->_conn->delete($this->_em->getClassMetadata($parentClass)->primaryTable['name'], $id); + } + } + } + + /** + * Gets the SELECT SQL to select a single entity by a set of field criteria. + * + * @param array $criteria + * @return string The SQL. + * @todo Quote identifier. + * @override + */ + protected function _getSelectSingleEntitySql(array $criteria) + { + $tableAliases = array(); + $aliasIndex = 1; + $idColumns = $this->_class->getIdentifierColumnNames(); + $baseTableAlias = 't0'; + + foreach (array_merge($this->_class->subClasses, $this->_class->parentClasses) as $className) { + $tableAliases[$className] = 't' . $aliasIndex++; + } + + $columnList = ''; + foreach ($this->_class->fieldMappings as $fieldName => $mapping) { + $tableAlias = isset($mapping['inherited']) ? + $tableAliases[$mapping['inherited']] : $baseTableAlias; + if ($columnList != '') $columnList .= ', '; + $columnList .= $tableAlias . '.' . $this->_class->columnNames[$fieldName]; + } + + $sql = 'SELECT ' . $columnList . ' FROM ' . $this->_class->primaryTable['name']. ' ' . $baseTableAlias; + + // INNER JOIN parent tables + foreach ($this->_class->parentClasses as $parentClassName) { + $parentClass = $this->_em->getClassMetadata($parentClassName); + $tableAlias = $tableAliases[$parentClassName]; + $sql .= ' INNER JOIN ' . $parentClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; + $first = true; + foreach ($idColumns as $idColumn) { + if ($first) $first = false; else $sql .= ' AND '; + $sql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + } + + // OUTER JOIN sub tables + foreach ($this->_class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + $tableAlias = $tableAliases[$subClassName]; + $sql .= ' LEFT JOIN ' . $subClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; + $first = true; + foreach ($idColumns as $idColumn) { + if ($first) $first = false; else $sql .= ' AND '; + $sql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + } + + $conditionSql = ''; + foreach ($criteria as $field => $value) { + if ($conditionSql != '') $conditionSql .= ' AND '; + $conditionSql .= $baseTableAlias . '.' . $this->_class->columnNames[$field] . ' = ?'; + } + + return $sql . ' WHERE ' . $conditionSql; + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 99a66bccd..a9a1a5cb9 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -1,4 +1,4 @@ -_em = $em; + $this->_entityName = $class->name; + $this->_conn = $em->getConnection(); + $this->_class = $class; + } + + /** + * Adds an entity to the queued inserts. + * + * @param object $entity + */ + public function addInsert($entity) + { + $this->_queuedInserts[spl_object_hash($entity)] = $entity; + } + + /** + * Executes all queued inserts. + * + * @return array An array of any generated post-insert IDs. + */ + public function executeInserts() + { + if ( ! $this->_queuedInserts) { + return; + } + + $postInsertIds = array(); + $idGen = $this->_class->idGenerator; + $isPostInsertId = $idGen->isPostInsertGenerator(); + + $stmt = $this->_conn->prepare($this->_class->insertSql); + $primaryTableName = $this->_class->primaryTable['name']; + $sqlLogger = $this->_conn->getConfiguration()->getSqlLogger(); + + foreach ($this->_queuedInserts as $entity) { + $insertData = array(); + $this->_prepareData($entity, $insertData, true); - /** - * The name of the entity the persister is used for. - * - * @var string - */ - protected $_entityName; + $paramIndex = 1; + if ($sqlLogger) { + //TODO: Log type + $params = array(); + foreach ($insertData[$primaryTableName] as $value) { + $params[$paramIndex] = $value; + $stmt->bindValue($paramIndex++, $value/*, Type::getType()*/); + } + $sqlLogger->logSql($this->_class->insertSql, $params); + } else { + foreach ($insertData[$primaryTableName] as $value) { + $stmt->bindValue($paramIndex++, $value/*, Type::getType()*/); + } + } - /** - * The Connection instance. - * - * @var Doctrine\DBAL\Connection $conn - */ - protected $_conn; - - /** - * The EntityManager instance. - * - * @var Doctrine\ORM\EntityManager - */ - protected $_em; + $stmt->execute(); - /** - * Queued inserts. - * - * @var array - */ - protected $_queuedInserts = array(); + if ($isPostInsertId) { + $postInsertIds[$idGen->generate($this->_em, $entity)] = $entity; + } + } - /** - * Initializes a new instance of a class derived from AbstractEntityPersister - * that uses the given EntityManager and persists instances of the class described - * by the given class metadata descriptor. - */ - public function __construct(EntityManager $em, ClassMetadata $class) - { - $this->_em = $em; - $this->_entityName = $class->name; - $this->_conn = $em->getConnection(); - $this->_class = $class; - } + $stmt->closeCursor(); + $this->_queuedInserts = array(); - /** - * Adds an entity to the queued inserts. - * - * @param object $entity - */ - public function addInsert($entity) - { - $this->_queuedInserts[spl_object_hash($entity)] = $entity; - } + return $postInsertIds; + } - /** - * Executes all queued inserts. - * - * @return array An array of any generated post-insert IDs. - */ - public function executeInserts() - { - if ( ! $this->_queuedInserts) { - return; - } - - $postInsertIds = array(); - $idGen = $this->_class->idGenerator; - $isPostInsertId = $idGen->isPostInsertGenerator(); + /** + * Updates an entity. + * + * @param object $entity The entity to update. + */ + public function update($entity) + { + $updateData = array(); + $this->_prepareData($entity, $updateData); + $id = array_combine($this->_class->getIdentifierFieldNames(), + $this->_em->getUnitOfWork()->getEntityIdentifier($entity)); + $tableName = $this->_class->primaryTable['name']; + $this->_conn->update($tableName, $updateData[$tableName], $id); + } - $stmt = $this->_conn->prepare($this->_class->insertSql); - $primaryTableName = $this->_class->primaryTable['name']; - foreach ($this->_queuedInserts as $entity) { - $insertData = array(); - $this->_prepareData($entity, $insertData, true); + /** + * Deletes an entity. + * + * @param object $entity The entity to delete. + */ + public function delete($entity) + { + $id = array_combine( + $this->_class->getIdentifierFieldNames(), + $this->_em->getUnitOfWork()->getEntityIdentifier($entity) + ); + $this->_conn->delete($this->_class->primaryTable['name'], $id); + } - $paramIndex = 1; - foreach ($insertData[$primaryTableName] as $value) { - $stmt->bindValue($paramIndex++, $value/*, Type::getType()*/); - } - $stmt->execute(); + /** + * Adds an entity to delete. + * + * @param object $entity + */ + public function addDelete($entity) + { - if ($isPostInsertId) { - $postInsertIds[$idGen->generate($this->_em, $entity)] = $entity; - } - } - $stmt->closeCursor(); - $this->_queuedInserts = array(); + } - return $postInsertIds; - } - - /** - * Updates an entity. - * - * @param object $entity The entity to update. - */ - public function update($entity) - { - $updateData = array(); - $this->_prepareData($entity, $updateData); - $id = array_combine($this->_class->getIdentifierFieldNames(), - $this->_em->getUnitOfWork()->getEntityIdentifier($entity)); - $tableName = $this->_class->primaryTable['name']; - $this->_conn->update($tableName, $updateData[$tableName], $id); - } - - /** - * Deletes an entity. - * - * @param object $entity The entity to delete. - */ - public function delete($entity) - { - $id = array_combine( - $this->_class->getIdentifierFieldNames(), - $this->_em->getUnitOfWork()->getEntityIdentifier($entity) - ); - $this->_conn->delete($this->_class->primaryTable['name'], $id); - } + /** + * Executes all pending entity deletions. + * + * @see addDelete() + */ + public function executeDeletions() + { - /** - * Adds an entity to delete. - * - * @param object $entity - */ - public function addDelete($entity) - { - - } + } - /** - * Executes all pending entity deletions. - * - * @see addDelete() - */ - public function executeDeletions() - { - - } + /** + * Gets the ClassMetadata instance of the entity class this persister is used for. + * + * @return Doctrine\ORM\Mapping\ClassMetadata + */ + public function getClassMetadata() + { + return $this->_class; + } - /** - * Gets the ClassMetadata instance of the entity class this persister is used for. - * - * @return Doctrine\ORM\Mapping\ClassMetadata - */ - public function getClassMetadata() - { - return $this->_class; - } + /** + * Gets the table name to use for temporary identifier tables. + */ + public function getTemporaryIdTableName() + { + //... + } - /** - * Gets the table name to use for temporary identifier tables. - */ - public function getTemporaryIdTableName() - { - //... - } - - /** - * Prepares the data changeset of an entity for database insertion. - * The array that is passed as the second parameter is filled with - * => pairs, grouped by table name, during this preparation. - * - * Example: - * - * array( - * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), - * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), - * ... - * ) - * - * - * Notes to inheritors: Be sure to call parent::_prepareData($entity, $result, $isInsert); - * - * @param object $entity - * @param array $result The reference to the data array. - * @param boolean $isInsert - */ - protected function _prepareData($entity, array &$result, $isInsert = false) - { - $platform = $this->_conn->getDatabasePlatform(); - $uow = $this->_em->getUnitOfWork(); - - foreach ($uow->getEntityChangeSet($entity) as $field => $change) { - $oldVal = $change[0]; - $newVal = $change[1]; + /** + * Prepares the data changeset of an entity for database insertion. + * The array that is passed as the second parameter is filled with + * => pairs, grouped by table name, during this preparation. + * + * Example: + * + * array( + * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), + * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), + * ... + * ) + * + * + * Notes to inheritors: Be sure to call parent::_prepareData($entity, $result, $isInsert); + * + * @param object $entity + * @param array $result The reference to the data array. + * @param boolean $isInsert + */ + protected function _prepareData($entity, array &$result, $isInsert = false) + { + $platform = $this->_conn->getDatabasePlatform(); + $uow = $this->_em->getUnitOfWork(); - $columnName = $this->_class->getColumnName($field); + foreach ($uow->getEntityChangeSet($entity) as $field => $change) { + $oldVal = $change[0]; + $newVal = $change[1]; - if (isset($this->_class->associationMappings[$field])) { - $assocMapping = $this->_class->associationMappings[$field]; - // Only owning side of x-1 associations can have a FK column. - if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { - continue; - } - - // Special case: One-one self-referencing of the same class. - if ($newVal !== null && $assocMapping->sourceEntityName == $assocMapping->targetEntityName) { - $oid = spl_object_hash($newVal); - $isScheduledForInsert = $uow->isRegisteredNew($newVal); - if (isset($this->_queuedInserts[$oid]) || $isScheduledForInsert) { - // The associated entity $newVal is not yet persisted, so we must - // set $newVal = null, in order to insert a null value and update later. - $newVal = null; - } else if ($isInsert && ! $isScheduledForInsert && $uow->getEntityState($newVal) == UnitOfWork::STATE_MANAGED) { - // $newVal is already fully persisted - // Clear changeset of $newVal, so that only the identifier is updated. - // Not sure this is really rock-solid here but it seems to work. - $uow->clearEntityChangeSet($oid); - $uow->propertyChanged($newVal, $field, $entity, $entity); - } - } - - foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { - $otherClass = $this->_em->getClassMetadata($assocMapping->targetEntityName); - if ($newVal === null) { - $result[$this->getOwningTable($field)][$sourceColumn] = null; - } else { - $result[$this->getOwningTable($field)][$sourceColumn] = - $otherClass->reflFields[$otherClass->fieldNames[$targetColumn]] - ->getValue($newVal); - } - } - } else if ($newVal === null) { - $result[$this->getOwningTable($field)][$columnName] = null; - } else { - $result[$this->getOwningTable($field)][$columnName] = Type::getType( - $this->_class->fieldMappings[$field]['type']) - ->convertToDatabaseValue($newVal, $platform); - } - } - } + $columnName = $this->_class->getColumnName($field); - /** - * Gets the name of the table that owns the column the given field is mapped to. - * - * @param string $fieldName - * @return string - */ - public function getOwningTable($fieldName) - { - return $this->_class->primaryTable['name']; - } + if (isset($this->_class->associationMappings[$field])) { + $assocMapping = $this->_class->associationMappings[$field]; + // Only owning side of x-1 associations can have a FK column. + if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { + continue; + } - /** - * Loads an entity by a list of field criteria. - * - * @param array $criteria The criteria by which to load the entity. - * @param object $entity The entity to load the data into. If not specified, - * a new entity is created. - */ - public function load(array $criteria, $entity = null) - { - $stmt = $this->_conn->prepare($this->_getSelectSingleEntitySql($criteria)); - $stmt->execute(array_values($criteria)); - $data = array(); - foreach ($stmt->fetch(\PDO::FETCH_ASSOC) as $column => $value) { - $fieldName = $this->_class->fieldNames[$column]; - $data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName)) - ->convertToPHPValue($value); - } - $stmt->closeCursor(); + // Special case: One-one self-referencing of the same class. + if ($newVal !== null && $assocMapping->sourceEntityName == $assocMapping->targetEntityName) { + $oid = spl_object_hash($newVal); + $isScheduledForInsert = $uow->isRegisteredNew($newVal); + if (isset($this->_queuedInserts[$oid]) || $isScheduledForInsert) { + // The associated entity $newVal is not yet persisted, so we must + // set $newVal = null, in order to insert a null value and update later. + $newVal = null; + } else if ($isInsert && ! $isScheduledForInsert && $uow->getEntityState($newVal) == UnitOfWork::STATE_MANAGED) { + // $newVal is already fully persisted + // Clear changeset of $newVal, so that only the identifier is updated. + // Not sure this is really rock-solid here but it seems to work. + $uow->clearEntityChangeSet($oid); + $uow->propertyChanged($newVal, $field, $entity, $entity); + } + } - if ($entity === null) { - $entity = $this->_em->getUnitOfWork()->createEntity($this->_entityName, $data); - } else { - foreach ($data as $field => $value) { - $this->_class->reflFields[$field]->setValue($entity, $value); - } - $id = array(); - if ($this->_class->isIdentifierComposite) { - foreach ($this->_class->identifier as $fieldName) { - $id[] = $data[$fieldName]; - } - } else { - $id = array($data[$this->_class->getSingleIdentifierFieldName()]); - } - $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); - } + foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { + $otherClass = $this->_em->getClassMetadata($assocMapping->targetEntityName); + if ($newVal === null) { + $result[$this->getOwningTable($field)][$sourceColumn] = null; + } else { + $result[$this->getOwningTable($field)][$sourceColumn] = + $otherClass->reflFields[$otherClass->fieldNames[$targetColumn]]->getValue($newVal); + } + } + } else if ($newVal === null) { + $result[$this->getOwningTable($field)][$columnName] = null; + } else { + $result[$this->getOwningTable($field)][$columnName] = Type::getType( + $this->_class->fieldMappings[$field]['type']) + ->convertToDatabaseValue($newVal, $platform); + } + } + } - if ( ! $this->_em->getConfiguration()->getAllowPartialObjects()) { - foreach ($this->_class->associationMappings as $field => $assoc) { - if ($assoc->isOneToOne()) { - if ($assoc->isLazilyFetched()) { - // Inject proxy - $proxy = $this->_em->getProxyGenerator()->getAssociationProxy($entity, $assoc); - $this->_class->reflFields[$field]->setValue($entity, $proxy); - } else { - //TODO: Eager fetch? - } - } else { - // Inject collection - $this->_class->reflFields[$field]->setValue( - $entity, new PersistentCollection($this->_em, - $this->_em->getClassMetadata($assoc->targetEntityName) - )); - } - } - } + /** + * Gets the name of the table that owns the column the given field is mapped to. + * + * @param string $fieldName + * @return string + */ + public function getOwningTable($fieldName) + { + return $this->_class->primaryTable['name']; + } - return $entity; - } + /** + * Loads an entity by a list of field criteria. + * + * @param array $criteria The criteria by which to load the entity. + * @param object $entity The entity to load the data into. If not specified, + * a new entity is created. + */ + public function load(array $criteria, $entity = null) + { + $stmt = $this->_conn->prepare($this->_getSelectSingleEntitySql($criteria)); + $stmt->execute(array_values($criteria)); + $data = array(); + foreach ($stmt->fetch(\PDO::FETCH_ASSOC) as $column => $value) { + $fieldName = $this->_class->fieldNames[$column]; + $data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName)) + ->convertToPHPValue($value); + } + $stmt->closeCursor(); - /** - * Gets the SELECT SQL to select a single entity by a set of field criteria. - * - * @param array $criteria - * @return string The SQL. - * @todo Quote identifier. - */ - protected function _getSelectSingleEntitySql(array $criteria) - { - $columnList = ''; - foreach ($this->_class->columnNames as $column) { - if ($columnList != '') $columnList .= ', '; - $columnList .= $column; - } + if ($entity === null) { + $entity = $this->_em->getUnitOfWork()->createEntity($this->_entityName, $data); + } else { + foreach ($data as $field => $value) { + $this->_class->reflFields[$field]->setValue($entity, $value); + } + $id = array(); + if ($this->_class->isIdentifierComposite) { + foreach ($this->_class->identifier as $fieldName) { + $id[] = $data[$fieldName]; + } + } else { + $id = array($data[$this->_class->getSingleIdentifierFieldName()]); + } + $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); + } - $conditionSql = ''; - foreach ($criteria as $field => $value) { - if ($conditionSql != '') $conditionSql .= ' AND '; - $conditionSql .= $this->_class->columnNames[$field] . ' = ?'; - } + if ( ! $this->_em->getConfiguration()->getAllowPartialObjects()) { + foreach ($this->_class->associationMappings as $field => $assoc) { + if ($assoc->isOneToOne()) { + if ($assoc->isLazilyFetched) { + // Inject proxy + $proxy = $this->_em->getProxyGenerator()->getAssociationProxy($entity, $assoc); + $this->_class->reflFields[$field]->setValue($entity, $proxy); + } else { + //TODO: Eager fetch? + } + } else { + // Inject collection + $this->_class->reflFields[$field]->setValue( + $entity, new PersistentCollection($this->_em, + $this->_em->getClassMetadata($assoc->targetEntityName) + )); + } + } + } - return 'SELECT ' . $columnList . ' FROM ' . $this->_class->getTableName() - . ' WHERE ' . $conditionSql; - } + return $entity; + } + + /** + * Gets the SELECT SQL to select a single entity by a set of field criteria. + * + * @param array $criteria + * @return string The SQL. + * @todo Quote identifier. + */ + protected function _getSelectSingleEntitySql(array $criteria) + { + $columnList = ''; + foreach ($this->_class->columnNames as $column) { + if ($columnList != '') $columnList .= ', '; + $columnList .= $column; + } + + $conditionSql = ''; + foreach ($criteria as $field => $value) { + if ($conditionSql != '') $conditionSql .= ' AND '; + $conditionSql .= $this->_class->columnNames[$field] . ' = ?'; + } + + return 'SELECT ' . $columnList . ' FROM ' . $this->_class->getTableName() + . ' WHERE ' . $conditionSql; + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index b2b2c434a..c925e0dd9 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -56,9 +56,8 @@ class ResultSetMapping /** * - * @param $class - * @param $alias The alias for this class. The alias must be unique within this ResultSetMapping. - * @param $discriminatorColumn + * @param string $class The class name. + * @param string $alias The alias for this class. The alias must be unique within this ResultSetMapping. */ public function addEntityResult($class, $alias) { @@ -67,9 +66,8 @@ class ResultSetMapping /** * - * @param $className - * @param $alias - * @param $discrColumn + * @param string $alias + * @param string $discrColumn */ public function setDiscriminatorColumn($alias, $discrColumn) { @@ -130,9 +128,9 @@ class ResultSetMapping /** * - * @param $alias - * @param $columnName - * @param $fieldName + * @param string $alias + * @param string $columnName + * @param string $fieldName */ public function addFieldResult($alias, $columnName, $fieldName) { @@ -145,10 +143,10 @@ class ResultSetMapping /** * - * @param $class - * @param $alias - * @param $parentAlias - * @param $relation + * @param string $class + * @param string $alias + * @param string $parentAlias + * @param object $relation */ public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) { @@ -156,12 +154,12 @@ class ResultSetMapping $this->parentAliasMap[$alias] = $parentAlias; $this->relationMap[$alias] = $relation; } - - /*public function isDiscriminatorColumn($columnName) - { - return isset($this->_discriminatorMap[$columnName]); - }*/ - + + /** + * + * @param string $columnName + * @param string $alias + */ public function addScalarResult($columnName, $alias) { $this->scalarMappings[$columnName] = $alias; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d0e280461..46e047ce6 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -483,7 +483,6 @@ class UnitOfWork implements PropertyChangedListener } if ( ! $assoc->isCascadeSave) { - //echo "NOT CASCADING INTO " . $assoc->getSourceFieldName() . PHP_EOL; return; // "Persistence by reachability" only if save cascade specified } @@ -498,7 +497,7 @@ class UnitOfWork implements PropertyChangedListener $oid = spl_object_hash($entry); if ($state == self::STATE_NEW) { // Get identifier, if possible (not post-insert) - $idGen = $targetClass->getIdGenerator(); + $idGen = $targetClass->idGenerator; if ( ! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->_em, $entry); $this->_entityStates[$oid] = self::STATE_MANAGED; @@ -803,35 +802,6 @@ class UnitOfWork implements PropertyChangedListener isset($this->_entityDeletions[$oid]); } - /** - * Detaches all currently managed entities. - * Alternatively, if an entity class name is given, all entities of that type - * (or subtypes) are detached. Don't forget that entities are registered in - * the identity map with the name of the root entity class. So calling detachAll() - * with a class name that is not the name of a root entity has no effect. - * - * @return integer The number of detached entities. - */ - public function detachAll($entityName = null) - { - $numDetached = 0; - if ($entityName !== null && isset($this->_identityMap[$entityName])) { - $numDetached = count($this->_identityMap[$entityName]); - foreach ($this->_identityMap[$entityName] as $entity) { - $this->detach($entity); - } - $this->_identityMap[$entityName] = array(); - } else { - $numDetached = count($this->_identityMap); - $this->_identityMap = array(); - $this->_entityInsertions = array(); - $this->_entityUpdates = array(); - $this->_entityDeletions = array(); - } - - return $numDetached; - } - /** * Registers an entity in the identity map. * Note that entities in a hierarchy are registered with the class name of @@ -960,8 +930,7 @@ class UnitOfWork implements PropertyChangedListener return isset($this->_identityMap [$classMetadata->rootEntityName] - [$idHash] - ); + [$idHash]); } /** @@ -1174,6 +1143,10 @@ class UnitOfWork implements PropertyChangedListener /** * Cascades a merge operation to associated entities. + * + * @param object $entity + * @param object $managedCopy + * @param array $visited */ private function _cascadeMerge($entity, $managedCopy, array &$visited) { @@ -1199,6 +1172,7 @@ class UnitOfWork implements PropertyChangedListener * * @param object $entity * @param array $visited + * @param array $insertNow */ private function _cascadeSave($entity, array &$visited, array &$insertNow) { @@ -1223,6 +1197,7 @@ class UnitOfWork implements PropertyChangedListener * Cascades the delete operation to associated entities. * * @param object $entity + * @param array $visited */ private function _cascadeDelete($entity, array &$visited) { diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php index dfda90539..a384e6b9e 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\DBAL\Functional\Schema; use Doctrine\Tests\TestUtil; use Doctrine\DBAL\Schema; +use Doctrine\DBAL\Types\Type; require_once __DIR__ . '/../../../TestInit.php'; @@ -59,13 +60,13 @@ class MysqlSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -86,13 +87,13 @@ class MysqlSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -114,13 +115,13 @@ class MysqlSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -158,13 +159,13 @@ class MysqlSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -197,13 +198,13 @@ class MysqlSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -245,7 +246,7 @@ class MysqlSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase $this->_sm->createView('test_create_view', 'SELECT * from mysql.user'); $views = $this->_sm->listViews(); - $this->assertEquals($views[0]['name'], 'test_create_view'); - $this->assertEquals($views[0]['sql'], '/* ALGORITHM=UNDEFINED */ select `mysql`.`user`.`Host` AS `Host`,`mysql`.`user`.`User` AS `User`,`mysql`.`user`.`Password` AS `Password`,`mysql`.`user`.`Select_priv` AS `Select_priv`,`mysql`.`user`.`Insert_priv` AS `Insert_priv`,`mysql`.`user`.`Update_priv` AS `Update_priv`,`mysql`.`user`.`Delete_priv` AS `Delete_priv`,`mysql`.`user`.`Create_priv` AS `Create_priv`,`mysql`.`user`.`Drop_priv` AS `Drop_priv`,`mysql`.`user`.`Reload_priv` AS `Reload_priv`,`mysql`.`user`.`Shutdown_priv` AS `Shutdown_priv`,`mysql`.`user`.`Process_priv` AS `Process_priv`,`mysql`.`user`.`File_priv` AS `File_priv`,`mysql`.`user`.`Grant_priv` AS `Grant_priv`,`mysql`.`user`.`References_priv` AS `References_priv`,`mysql`.`user`.`Index_priv` AS `Index_priv`,`mysql`.`user`.`Alter_priv` AS `Alter_priv`,`mysql`.`user`.`Show_db_priv` AS `Show_db_priv`,`mysql`.`user`.`Super_priv` AS `Super_priv`,`mysql`.`user`.`Create_tmp_table_priv` AS `Create_tmp_table_priv`,`mysql`.`user`.`Lock_tables_priv` AS `Lock_tables_priv`,`mysql`.`user`.`Execute_priv` AS `Execute_priv`,`mysql`.`user`.`Repl_slave_priv` AS `Repl_slave_priv`,`mysql`.`user`.`Repl_client_priv` AS `Repl_client_priv`,`mysql`.`user`.`Create_view_priv` AS `Create_view_priv`,`mysql`.`user`.`Show_view_priv` AS `Show_view_priv`,`mysql`.`user`.`Create_routine_priv` AS `Create_routine_priv`,`mysql`.`user`.`Alter_routine_priv` AS `Alter_routine_priv`,`mysql`.`user`.`Create_user_priv` AS `Create_user_priv`,`mysql`.`user`.`ssl_type` AS `ssl_type`,`mysql`.`user`.`ssl_cipher` AS `ssl_cipher`,`mysql`.`user`.`x509_issuer` AS `x509_issuer`,`mysql`.`user`.`x509_subject` AS `x509_subject`,`mysql`.`user`.`max_questions` AS `max_questions`,`mysql`.`user`.`max_updates` AS `max_updates`,`mysql`.`user`.`max_connections` AS `max_connections`,`mysql`.`user`.`max_user_connections` AS `max_user_connections` from `mysql`.`user`'); + $this->assertEquals('test_create_view', $views[0]['name']); + $this->assertEquals('/* ALGORITHM=UNDEFINED */ select `mysql`.`user`.`Host` AS `Host`,`mysql`.`user`.`User` AS `User`,`mysql`.`user`.`Password` AS `Password`,`mysql`.`user`.`Select_priv` AS `Select_priv`,`mysql`.`user`.`Insert_priv` AS `Insert_priv`,`mysql`.`user`.`Update_priv` AS `Update_priv`,`mysql`.`user`.`Delete_priv` AS `Delete_priv`,`mysql`.`user`.`Create_priv` AS `Create_priv`,`mysql`.`user`.`Drop_priv` AS `Drop_priv`,`mysql`.`user`.`Reload_priv` AS `Reload_priv`,`mysql`.`user`.`Shutdown_priv` AS `Shutdown_priv`,`mysql`.`user`.`Process_priv` AS `Process_priv`,`mysql`.`user`.`File_priv` AS `File_priv`,`mysql`.`user`.`Grant_priv` AS `Grant_priv`,`mysql`.`user`.`References_priv` AS `References_priv`,`mysql`.`user`.`Index_priv` AS `Index_priv`,`mysql`.`user`.`Alter_priv` AS `Alter_priv`,`mysql`.`user`.`Show_db_priv` AS `Show_db_priv`,`mysql`.`user`.`Super_priv` AS `Super_priv`,`mysql`.`user`.`Create_tmp_table_priv` AS `Create_tmp_table_priv`,`mysql`.`user`.`Lock_tables_priv` AS `Lock_tables_priv`,`mysql`.`user`.`Execute_priv` AS `Execute_priv`,`mysql`.`user`.`Repl_slave_priv` AS `Repl_slave_priv`,`mysql`.`user`.`Repl_client_priv` AS `Repl_client_priv`,`mysql`.`user`.`Create_view_priv` AS `Create_view_priv`,`mysql`.`user`.`Show_view_priv` AS `Show_view_priv`,`mysql`.`user`.`Create_routine_priv` AS `Create_routine_priv`,`mysql`.`user`.`Alter_routine_priv` AS `Alter_routine_priv`,`mysql`.`user`.`Create_user_priv` AS `Create_user_priv`,`mysql`.`user`.`ssl_type` AS `ssl_type`,`mysql`.`user`.`ssl_cipher` AS `ssl_cipher`,`mysql`.`user`.`x509_issuer` AS `x509_issuer`,`mysql`.`user`.`x509_subject` AS `x509_subject`,`mysql`.`user`.`max_questions` AS `max_questions`,`mysql`.`user`.`max_updates` AS `max_updates`,`mysql`.`user`.`max_connections` AS `max_connections`,`mysql`.`user`.`max_user_connections` AS `max_user_connections` from `mysql`.`user`', $views[0]['sql']); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php index d3b0d7366..82f7c0819 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\DBAL\Functional\Schema; use Doctrine\Tests\TestUtil; use Doctrine\DBAL\Schema; +use Doctrine\DBAL\Types\Type; require_once __DIR__ . '/../../../TestInit.php'; @@ -58,13 +59,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -91,13 +92,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -131,13 +132,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -164,13 +165,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -198,13 +199,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -250,13 +251,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); @@ -314,13 +315,13 @@ class SqliteSchemaManagerTest extends \Doctrine\Tests\DbalFunctionalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php index bb7ecbcf2..9d794869f 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php @@ -3,6 +3,7 @@ namespace Doctrine\Tests\DBAL\Platforms; use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Types\Type; require_once __DIR__ . '/../../TestInit.php'; @@ -19,13 +20,13 @@ class SqlitePlatformTest extends \Doctrine\Tests\DbalTestCase { $columns = array( 'id' => array( - 'type' => new \Doctrine\DBAL\Types\IntegerType, + 'type' => Type::getType('integer'), 'autoincrement' => true, 'primary' => true, 'notnull' => true ), 'test' => array( - 'type' => new \Doctrine\DBAL\Types\StringType, + 'type' => Type::getType('string'), 'length' => 255 ) ); diff --git a/tests/Doctrine/Tests/Models/Forum/ForumUser.php b/tests/Doctrine/Tests/Models/Forum/ForumUser.php index 0edc4b20f..b34f1105a 100644 --- a/tests/Doctrine/Tests/Models/Forum/ForumUser.php +++ b/tests/Doctrine/Tests/Models/Forum/ForumUser.php @@ -23,4 +23,20 @@ class ForumUser * @DoctrineJoinColumn(name="avatar_id", referencedColumnName="id") */ public $avatar; + + public function getId() { + return $this->id; + } + + public function getUsername() { + return $this->username; + } + + public function getAvatar() { + return $this->avatar; + } + + public function setAvatar(CmsAvatar $avatar) { + $this->avatar = $avatar; + } } \ 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 d08db2f69..f0eb3499a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -150,8 +150,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testManyToManyCollectionClearing() { - echo PHP_EOL . "MANY-MANY" . PHP_EOL; - $user = new CmsUser; $user->name = 'Guilherme'; $user->username = 'gblanco'; @@ -176,7 +174,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase //$user->groups->clear(); unset($user->groups); - echo PHP_EOL . "FINAL FLUSH" . PHP_EOL; $this->_em->flush(); // Check that the links in the association table have been deleted diff --git a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php index 23b7b61b5..f7bbeb230 100644 --- a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php @@ -24,7 +24,7 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase * * MAXIMUM TIME: 3 seconds */ - public function testNewHydrationSimpleQueryArrayHydrationPerformance() + public function testSimpleQueryArrayHydrationPerformance() { $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); @@ -69,7 +69,10 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $this->setMaxRunningTime(3); + $s = microtime(true); $result = $hydrator->hydrateAll($stmt, $rsm); + $e = microtime(true); + echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; } /** @@ -79,7 +82,7 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase * * MAXIMUM TIME: 4 seconds */ - public function testNewHydrationMixedQueryFetchJoinArrayHydrationPerformance() + public function testMixedQueryFetchJoinArrayHydrationPerformance() { $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); @@ -140,7 +143,10 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $this->setMaxRunningTime(4); + $s = microtime(true); $result = $hydrator->hydrateAll($stmt, $rsm); + $e = microtime(true); + echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; } /** @@ -193,8 +199,10 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); $this->setMaxRunningTime(5); + $s = microtime(true); $result = $hydrator->hydrateAll($stmt, $rsm); - echo count($result); + $e = microtime(true); + echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; } /** @@ -263,7 +271,10 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); $this->setMaxRunningTime(4); + $s = microtime(true); $result = $hydrator->hydrateAll($stmt, $rsm); + $e = microtime(true); + echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; } }