Commit a758b565 authored by romanb's avatar romanb

[2.0] Introduced SQL logging facilities. Made Type constructor private to...

[2.0] Introduced SQL logging facilities. Made Type constructor private to prevent instantiation and force use of the factory method getType().
parent 0ed8e7a3
......@@ -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()
{
......
......@@ -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;
/**
* 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);
}
/**
* 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;
/**
* 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();
}
/**
* 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();
/**
* 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);
}
/**
* 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);
}
++$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;
}
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
<?php
namespace Doctrine\DBAL\Logging;
/**
* A SQL logger that logs to the standard output using echo/var_dump.
*
* @author Roman Borschel <roman@code-factory.org>
* @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
<?php
namespace Doctrine\DBAL\Logging;
/**
* Interface for SQL loggers.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
interface SqlLogger
{
public function logSql($sql, array $params = null);
}
\ No newline at end of file
......@@ -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;
......
......@@ -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();
}
......
......@@ -26,7 +26,7 @@ namespace Doctrine\ORM;
*
* This class cannot be instantiated.
*
* @author robo
* @author Roman Borschel <roman@code-factory.org>
* @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
......@@ -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 {
......
......@@ -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;
}
......
......@@ -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);
}
......
......@@ -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) {
......
......@@ -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[] = '?';
......
......@@ -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;
......
<?php
<?php
/*
* $Id$
*
......@@ -36,227 +36,252 @@ use Doctrine\Common\DoctrineException;
*/
class JoinedSubclassPersister extends StandardEntityPersister
{
/** Map that maps column names to the table names that own them.
* This is mainly a temporary cache, used during a single request.
*/
private $_owningTableMap = array();
/** Map that maps column names to the table names that own them.
* This is mainly a temporary cache, used during a single request.
*/
private $_owningTableMap = array();
/**
* {@inheritdoc}
*
* @override
*/
protected function _prepareData($entity, array &$result, $isInsert = false)
{
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
*/
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
<?php
<?php
/*
* $Id$
*
......@@ -37,336 +37,349 @@ use Doctrine\ORM\Mapping\ClassMetadata;
* @since 2.0
*/
class StandardEntityPersister
{
/**
* Metadata object that describes the mapping of the mapped entity class.
*
* @var Doctrine\ORM\Mapping\ClassMetadata
*/
protected $_class;
/**
* The name of the entity the persister is used for.
*
* @var string
*/
protected $_entityName;
/**
* The Connection instance.
*
* @var Doctrine\DBAL\Connection $conn
*/
protected $_conn;
/**
* The EntityManager instance.
*
* @var Doctrine\ORM\EntityManager
*/
protected $_em;
/**
* Queued inserts.
*
* @var array
*/
protected $_queuedInserts = array();
/**
* 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;
}
/**
* 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'];
foreach ($this->_queuedInserts as $entity) {
$insertData = array();
$this->_prepareData($entity, $insertData, true);
$paramIndex = 1;
foreach ($insertData[$primaryTableName] as $value) {
$stmt->bindValue($paramIndex++, $value/*, Type::getType()*/);
}
$stmt->execute();
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);
}
/**
* 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 table name to use for temporary identifier tables.
*/
public function getTemporaryIdTableName()
{
//...
}
{
/**
* Metadata object that describes the mapping of the mapped entity class.
*
* @var Doctrine\ORM\Mapping\ClassMetadata
*/
protected $_class;
/**
* The name of the entity the persister is used for.
*
* @var string
*/
protected $_entityName;
/**
* The Connection instance.
*
* @var Doctrine\DBAL\Connection $conn
*/
protected $_conn;
/**
* The EntityManager instance.
*
* @var Doctrine\ORM\EntityManager
*/
protected $_em;
/**
* Queued inserts.
*
* @var array
*/
protected $_queuedInserts = array();
/**
* 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;
}
/**
* 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);
/**
* Prepares the data changeset of an entity for database insertion.
* The array that is passed as the second parameter is filled with
* <columnName> => <value> pairs, grouped by table name, during this preparation.
*
* Example:
* <code>
* array(
* 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...),
* 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...),
* ...
* )
* </code>
*
* Notes to inheritors: Be sure to call <code>parent::_prepareData($entity, $result, $isInsert);</code>
*
* @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];
$columnName = $this->_class->getColumnName($field);
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);
}
}
}
/**
* 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'];
}
/**
* 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();
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);
}
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 $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;
}
$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()*/);
}
}
$stmt->execute();
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);
}
/**
* 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 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
* <columnName> => <value> pairs, grouped by table name, during this preparation.
*
* Example:
* <code>
* array(
* 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...),
* 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...),
* ...
* )
* </code>
*
* Notes to inheritors: Be sure to call <code>parent::_prepareData($entity, $result, $isInsert);</code>
*
* @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];
$columnName = $this->_class->getColumnName($field);
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);
}
}
}
/**
* 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'];
}
/**
* 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();
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);
}
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 $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
......@@ -56,9 +56,8 @@ class ResultSetMapping
/**
*
* @param <type> $class
* @param <type> $alias The alias for this class. The alias must be unique within this ResultSetMapping.
* @param <type> $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 <type> $className
* @param <type> $alias
* @param <type> $discrColumn
* @param string $alias
* @param string $discrColumn
*/
public function setDiscriminatorColumn($alias, $discrColumn)
{
......@@ -130,9 +128,9 @@ class ResultSetMapping
/**
*
* @param <type> $alias
* @param <type> $columnName
* @param <type> $fieldName
* @param string $alias
* @param string $columnName
* @param string $fieldName
*/
public function addFieldResult($alias, $columnName, $fieldName)
{
......@@ -145,10 +143,10 @@ class ResultSetMapping
/**
*
* @param <type> $class
* @param <type> $alias
* @param <type> $parentAlias
* @param <type> $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;
......
......@@ -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)
{
......
......@@ -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
......@@ -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
)
);
......
......@@ -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
)
);
......
......@@ -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
......@@ -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
......
......@@ -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;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment