From 36b3e180a8acfa5206af56210d391eede2f8eed9 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 7 Apr 2010 20:35:33 +0200 Subject: [PATCH 01/56] Fix Testsuite to work with Git --- tests/.gitignore | 2 ++ .../ORM/Mapping/YamlMappingDriverTest.php | 4 ++++ .../ORM/Tools/ConvertDoctrine1SchemaTest.php | 4 ++++ .../Export/YamlClassMetadataExporterTest.php | 4 ++++ tests/Doctrine/Tests/TestInit.php | 21 +++++++++++++++++-- 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/.gitignore diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 000000000..aab720af4 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +Doctrine/Tests/Proxies/ +Doctrine/Tests/ORM/Proxy/generated/ \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php index b9193d581..2aad38645 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php @@ -12,6 +12,10 @@ class YamlMappingDriverTest extends AbstractMappingDriverTest { protected function _loadDriver() { + if (!class_exists('Symfony\Components\Yaml\Yaml', true)) { + $this->markTestSkipped('Please install Symfony YAML Component into the include path of your PHP installation.'); + } + return new YamlDriver(__DIR__ . DIRECTORY_SEPARATOR . 'yaml'); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php b/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php index 088c871f7..f76f7baa7 100644 --- a/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/ConvertDoctrine1SchemaTest.php @@ -40,6 +40,10 @@ class ConvertDoctrine1SchemaTest extends \Doctrine\Tests\OrmTestCase { public function testTest() { + if (!class_exists('Symfony\Components\Yaml\Yaml', true)) { + $this->markTestSkipped('Please install Symfony YAML Component into the include path of your PHP installation.'); + } + $cme = new ClassMetadataExporter(); $converter = new ConvertDoctrine1Schema(__DIR__ . '/doctrine1schema'); diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/YamlClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/YamlClassMetadataExporterTest.php index 03a246b40..d7258a561 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/YamlClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/YamlClassMetadataExporterTest.php @@ -37,6 +37,10 @@ class YamlClassMetadataExporterTest extends AbstractClassMetadataExporterTest { protected function _getType() { + if (!class_exists('Symfony\Components\Yaml\Yaml', true)) { + $this->markTestSkipped('Please install Symfony YAML Component into the include path of your PHP installation.'); + } + return 'yaml'; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/TestInit.php b/tests/Doctrine/Tests/TestInit.php index 0945ce50c..c6b0e29f8 100644 --- a/tests/Doctrine/Tests/TestInit.php +++ b/tests/Doctrine/Tests/TestInit.php @@ -13,8 +13,25 @@ require_once __DIR__ . '/../../../lib/Doctrine/Common/ClassLoader.php'; $classLoader = new \Doctrine\Common\ClassLoader('Doctrine'); $classLoader->register(); -$classLoader = new \Doctrine\Common\ClassLoader('Symfony', __DIR__ . '/../../../lib/vendor'); -$classLoader->register(); +if (!file_exists(__DIR__."/Proxies")) { + if (!mkdir(__DIR__."/Proxies")) { + throw new Exception("Could not create " . __DIR__."/Proxies Folder."); + } +} +if (!file_exists(__DIR__."/ORM/Proxy/generated")) { + if (!mkdir(__DIR__."/ORM/Proxy/generated")) { + throw new Exception("Could not create " . __DIR__."/ORM/Proxy/generated Folder."); + } +} + +spl_autoload_register(function($class) { + if (strpos($class, 'Symfony') === 0) { + $file = str_replace("\\", "/", $class); + if (@fopen($class, "r")) { + require_once ($file); + } + } +}); set_include_path( __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'lib' From ed9692187d0d43a546520a8f4b6edbfa386935ca Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 7 Apr 2010 20:38:13 +0200 Subject: [PATCH 02/56] Fix Testsuite to work with Git --- tests/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/.gitignore b/tests/.gitignore index aab720af4..721040526 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,3 @@ Doctrine/Tests/Proxies/ -Doctrine/Tests/ORM/Proxy/generated/ \ No newline at end of file +Doctrine/Tests/ORM/Proxy/generated/ +Doctrine/Tests/ORM/Tools/Export/export From 7d179aaf9531d51b1748d030affb2106b817ae12 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 7 Apr 2010 20:39:34 +0200 Subject: [PATCH 03/56] Current snapshot of the IBM Db2 Driver Implementation --- .../DBAL/Driver/IbmDb2/Db2Connection.php | 119 ++++++++ lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php | 113 +++++++ .../DBAL/Driver/IbmDb2/Db2Exception.php | 27 ++ .../DBAL/Driver/IbmDb2/Db2Statement.php | 285 ++++++++++++++++++ .../DBAL/Platforms/AbstractPlatform.php | 10 + .../DBAL/Platforms/IbmDb2Platform.php | 222 ++++++++++++++ .../DBAL/Schema/IbmDb2SchemaManager.php | 83 +++++ .../Tests/DBAL/Functional/DataAccessTest.php | 61 ++++ .../Schema/Db2SchemaManagerTest.php | 12 + .../Doctrine/Tests/DbalFunctionalTestCase.php | 4 + tests/Doctrine/Tests/TestUtil.php | 40 +-- 11 files changed, 959 insertions(+), 17 deletions(-) create mode 100644 lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php create mode 100644 lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php create mode 100644 lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php create mode 100644 lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php create mode 100644 lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php create mode 100644 lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php create mode 100644 tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php create mode 100644 tests/Doctrine/Tests/DBAL/Functional/Schema/Db2SchemaManagerTest.php diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php new file mode 100644 index 000000000..f6909a437 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php @@ -0,0 +1,119 @@ +. +*/ + +namespace Doctrine\DBAL\Driver\IbmDb2; + +class Db2Connection implements \Doctrine\DBAL\Driver\Connection +{ + private $_conn = null; + + public function __construct($dbname, $username, $password, $driverOptions = array(), $isPersistant = false) + { + if ($isPersistant) { + $this->_conn = db2_pconnect($dbname, $username, $password, $driverOptions); + } else { + $this->_conn = db2_connect($dbname, $username, $password, $driverOptions); + } + if (!$this->_conn) { + throw new Db2Exception(db2_conn_errormsg()); + } + } + + function prepare($sql) + { + $stmt = @db2_prepare($this->_conn, $sql); + if (!$stmt) { + throw new Db2Exception(db2_stmt_errormsg()); + } + return new Db2Statement($stmt); + } + + function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + return $stmt; + } + + function quote($input, $type=\PDO::PARAM_STR) + { + $input = db2_escape_string($input); + if ($type == \PDO::PARAM_INT ) { + return $input; + } else { + return "'".$input."'"; + } + } + + function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + return $stmt; + } + + function lastInsertId($name = null) + { + $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1'; + if ($stmt = $this->query($sql)) { + if ($col = $stmt->fetchColumn()) { + return $col; + } + } + return false; + } + + function beginTransaction() + { + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_OFF); + } + + function commit() + { + if (!db2_commit($this->_conn)) { + throw new Db2Exception(db2_conn_errormsg($this->_conn)); + } + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); + } + + function rollBack() + { + if (!db2_rollback($this->_conn)) { + throw new Db2Exception(db2_conn_errormsg($this->_conn)); + } + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); + } + + function errorCode() + { + return db2_conn_error($this->_conn); + } + + function errorInfo() + { + return array( + 0 => db2_conn_errormsg($this->_conn), + 1 => $this->errorCode(), + ); + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php new file mode 100644 index 000000000..5a88b488c --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php @@ -0,0 +1,113 @@ +. +*/ + +namespace Doctrine\DBAL\Driver\IbmDb2; + +use Doctrine\DBAL\Driver, + Doctrine\DBAL\Connection; + +/** + * IBM Db2 Driver + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class Db2Driver implements Driver +{ + /** + * Attempts to create a connection with the database. + * + * @param array $params All connection parameters passed by the user. + * @param string $username The username to use when connecting. + * @param string $password The password to use when connecting. + * @param array $driverOptions The driver options to use when connecting. + * @return Doctrine\DBAL\Driver\Connection The database connection. + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if ($params['host'] !== 'localhost' && $params['host'] != '127.0.0.1') { + // if the host isn't localhost, use extended connection params + $dbname = 'DRIVER={IBM DB2 ODBC DRIVER}' . + ';DATABASE=' . $params['dbname'] . + ';HOSTNAME=' . $params['host'] . + ';PORT=' . $params['port'] . + ';PROTOCOL=' . $params['protocol'] . + ';UID=' . $username . + ';PWD=' . $password .';'; + $username = null; + $password = null; + } else { + $dbname = $params['dbname']; + } + + $isPersistant = (isset($params['persistent']) && $params['persistent'] == true); + + return new Db2Connection($dbname, $username, $password, $driverOptions, $isPersistant); + } + + /** + * Gets the DatabasePlatform instance that provides all the metadata about + * the platform this driver connects to. + * + * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform. + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\IbmDb2Platform; + } + + /** + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @param Doctrine\DBAL\Connection $conn + * @return Doctrine\DBAL\SchemaManager + */ + public function getSchemaManager(Connection $conn) + { + return new \Doctrine\DBAL\Schema\IbmDb2SchemaManager($conn); + } + + /** + * Gets the name of the driver. + * + * @return string The name of the driver. + */ + public function getName() + { + return 'ibm_db2'; + } + + /** + * Get the name of the database connected to for this driver. + * + * @param Doctrine\DBAL\Connection $conn + * @return string $database + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + return $params['dbname']; + } +} diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php new file mode 100644 index 000000000..76d992b50 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php @@ -0,0 +1,27 @@ +. +*/ + +namespace Doctrine\DBAL\Driver\IbmDb2; + +class Db2Exception extends \Exception +{ + +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php new file mode 100644 index 000000000..c4594ff04 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php @@ -0,0 +1,285 @@ +. +*/ + +namespace Doctrine\DBAL\Driver\IbmDb2; + +class Db2Statement implements \Doctrine\DBAL\Driver\Statement +{ + private $_stmt = null; + + /** + * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG + * @var + */ + static private $_typeMap = array( + \PDO::PARAM_INT => DB2_LONG, + \PDO::PARAM_STR => DB2_CHAR, + ); + + public function __construct($stmt) + { + $this->_stmt = $stmt; + } + + /** + * Binds a value to a corresponding named or positional + * placeholder in the SQL statement that was used to prepare the statement. + * + * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement + * using question mark placeholders, this will be the 1-indexed position of the parameter + * + * @param mixed $value The value to bind to the parameter. + * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. + * + * @return boolean Returns TRUE on success or FALSE on failure. + */ + function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $variable, $type); + } + + /** + * Binds a PHP variable to a corresponding named or question mark placeholder in the + * SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(), + * the variable is bound as a reference and will only be evaluated at the time + * that PDOStatement->execute() is called. + * + * Most parameters are input parameters, that is, parameters that are + * used in a read-only fashion to build up the query. Some drivers support the invocation + * of stored procedures that return data as output parameters, and some also as input/output + * parameters that both send in data and are updated to receive it. + * + * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement + * using question mark placeholders, this will be the 1-indexed position of the parameter + * + * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. + * + * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return + * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the + * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. + * @return boolean Returns TRUE on success or FALSE on failure. + */ + function bindParam($column, &$variable, $type = null) + { + if (!$type && isset(self::$_typeMap[$type])) { + $type = self::$_typeMap[$type]; + } else { + $type = DB2_CHAR; + } + + if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) { + throw new Db2Exception(db2_stmt_errormsg()); + } + } + + /** + * Closes the cursor, enabling the statement to be executed again. + * + * @return boolean Returns TRUE on success or FALSE on failure. + */ + function closeCursor() + { + if (!$this->_stmt) { + return false; + } + + $ret = db2_free_stmt($this->_stmt); + $this->_stmt = false; + return $ret; + } + + /** + * columnCount + * Returns the number of columns in the result set + * + * @return integer Returns the number of columns in the result set represented + * by the PDOStatement object. If there is no result set, + * this method should return 0. + */ + function columnCount() + { + if (!$this->_stmt) { + return false; + } + return db2_num_fields($this->_stmt); + } + + /** + * errorCode + * Fetch the SQLSTATE associated with the last operation on the statement handle + * + * @see Doctrine_Adapter_Interface::errorCode() + * @return string error code string + */ + function errorCode() + { + return db2_stmt_error(); + } + + /** + * errorInfo + * Fetch extended error information associated with the last operation on the statement handle + * + * @see Doctrine_Adapter_Interface::errorInfo() + * @return array error info array + */ + function errorInfo() + { + return array( + 0 => db2_stmt_errormsg(), + 1 => db2_stmt_error(), + ); + } + + /** + * Executes a prepared statement + * + * If the prepared statement included parameter markers, you must either: + * call PDOStatement->bindParam() to bind PHP variables to the parameter markers: + * bound variables pass their value as input and receive the output value, + * if any, of their associated parameter markers or pass an array of input-only + * parameter values + * + * + * @param array $params An array of values with as many elements as there are + * bound parameters in the SQL statement being executed. + * @return boolean Returns TRUE on success or FALSE on failure. + */ + function execute($params = null) + { + if (!$this->_stmt) { + return false; + } + + $retval = true; + if ($params !== null) { + $retval = @db2_execute($this->_stmt, $params); + } else { + $retval = @db2_execute($this->_stmt); + } + + if ($retval === false) { + throw new Db2Exception(db2_stmt_errormsg()); + } + return $retval; + } + + /** + * fetch + * + * @see Query::HYDRATE_* constants + * @param integer $fetchStyle Controls how the next row will be returned to the caller. + * This value must be one of the Query::HYDRATE_* constants, + * defaulting to Query::HYDRATE_BOTH + * + * @param integer $cursorOrientation For a PDOStatement object representing a scrollable cursor, + * this value determines which row will be returned to the caller. + * This value must be one of the Query::HYDRATE_ORI_* constants, defaulting to + * Query::HYDRATE_ORI_NEXT. To request a scrollable cursor for your + * PDOStatement object, + * you must set the PDO::ATTR_CURSOR attribute to Doctrine::CURSOR_SCROLL when you + * prepare the SQL statement with Doctrine_Adapter_Interface->prepare(). + * + * @param integer $cursorOffset For a PDOStatement object representing a scrollable cursor for which the + * $cursorOrientation parameter is set to Query::HYDRATE_ORI_ABS, this value specifies + * the absolute number of the row in the result set that shall be fetched. + * + * For a PDOStatement object representing a scrollable cursor for + * which the $cursorOrientation parameter is set to Query::HYDRATE_ORI_REL, this value + * specifies the row to fetch relative to the cursor position before + * PDOStatement->fetch() was called. + * + * @return mixed + */ + function fetch($fetchStyle = \PDO::FETCH_BOTH) + { + switch ($fetchStyle) { + case \PDO::FETCH_BOTH: + return db2_fetch_both($this->_stmt); + case \PDO::FETCH_ASSOC: + return db2_fetch_assoc($this->_stmt); + case \PDO::FETCH_NUM: + return db2_fetch_array($this->_stmt); + default: + throw new Db2Exception("Given Fetch-Style " . $fetchStyle . " is not supported."); + } + } + + /** + * Returns an array containing all of the result set rows + * + * @param integer $fetchStyle Controls how the next row will be returned to the caller. + * This value must be one of the Query::HYDRATE_* constants, + * defaulting to Query::HYDRATE_BOTH + * + * @param integer $columnIndex Returns the indicated 0-indexed column when the value of $fetchStyle is + * Query::HYDRATE_COLUMN. Defaults to 0. + * + * @return array + */ + function fetchAll($fetchStyle = \PDO::FETCH_BOTH) + { + $rows = array(); + while ($row = $this->fetch($fetchStyle)) { + $rows[] = $row; + } + return $rows; + } + + /** + * fetchColumn + * Returns a single column from the next row of a + * result set or FALSE if there are no more rows. + * + * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. If no + * value is supplied, PDOStatement->fetchColumn() + * fetches the first column. + * + * @return string returns a single column in the next row of a result set. + */ + function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(\PDO::FETCH_NUM); + if (!$row && isset($row[$columnIndex])) { + return $row[$columnIndex]; + } + return false; + } + + /** + * rowCount + * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement + * executed by the corresponding object. + * + * If the last SQL statement executed by the associated Statement object was a SELECT statement, + * some databases may return the number of rows returned by that statement. However, + * this behaviour is not guaranteed for all databases and should not be + * relied on for portable applications. + * + * @return integer Returns the number of rows. + */ + function rowCount() + { + return (@db2_num_rows($this->_stmt))?:0; + } +} diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index 961ca0b7d..e2d416dda 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -1690,6 +1690,16 @@ abstract class AbstractPlatform return false; } + /** + * Some databases don't allow to create and drop databases at all or only with certain tools. + * + * @return bool + */ + public function supportsCreateDropDatabase() + { + return true; + } + /** * @return bool */ diff --git a/lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php b/lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php new file mode 100644 index 000000000..09de9c746 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php @@ -0,0 +1,222 @@ +. +*/ + +namespace Doctrine\DBAL\Platforms; + +class IbmDb2Platform extends AbstractPlatform +{ + /** + * Gets the SQL snippet used to declare a VARCHAR column type. + * + * @param array $field + */ + public function getVarcharTypeDeclarationSQL(array $field) + { + if ( ! isset($field['length'])) { + if (array_key_exists('default', $field)) { + $field['length'] = $this->getVarcharMaxLength(); + } else { + $field['length'] = false; + } + } + + $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; + $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; + + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * Gets the SQL snippet used to declare a CLOB column type. + * + * @param array $field + */ + public function getClobTypeDeclarationSQL(array $field) + { + // todo clob(n) with $field['length']; + return 'CLOB(1M)'; + } + + /** + * Gets the name of the platform. + * + * @return string + */ + public function getName() + { + return 'db2'; + } + + + /** + * Gets the SQL snippet that declares a boolean column. + * + * @param array $columnDef + * @return string + */ + public function getBooleanTypeDeclarationSQL(array $columnDef) + { + return 'SMALLINT'; + } + + /** + * Gets the SQL snippet that declares a 4 byte integer column. + * + * @param array $columnDef + * @return string + */ + public function getIntegerTypeDeclarationSQL(array $columnDef) + { + return 'INTEGER'; + } + + /** + * Gets the SQL snippet that declares an 8 byte integer column. + * + * @param array $columnDef + * @return string + */ + public function getBigIntTypeDeclarationSQL(array $columnDef) + { + return 'BIGINT'; + } + + /** + * Gets the SQL snippet that declares a 2 byte integer column. + * + * @param array $columnDef + * @return string + */ + public function getSmallIntTypeDeclarationSQL(array $columnDef) + { + return 'SMALLINT'; + } + + /** + * Gets the SQL snippet that declares common properties of an integer column. + * + * @param array $columnDef + * @return string + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + + } + + public function getListDatabasesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + public function getListSequencesSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + public function getListTableConstraintsSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + public function getListTableColumnsSQL($table) + { + return "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, + c.typename, c.default, c.nulls, c.length, c.scale, + c.identity, tc.type AS tabconsttype, k.colseq + FROM syscat.columns c + LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc + ON (k.tabschema = tc.tabschema + AND k.tabname = tc.tabname + AND tc.type = 'P')) + ON (c.tabschema = k.tabschema + AND c.tabname = k.tabname + AND c.colname = k.colname) + WHERE UPPER(c.tabname) = UPPER('" . $table . "') ORDER BY c.colno"; + } + + public function getListTablesSQL() + { + return "SELECT 'NAME' FROM SYSIBM.TABLES"; + } + + public function getListUsersSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Get the SQL to list all views of a database or user. + * + * @param string $database + * @return string + */ + public function getListViewsSQL($database) + { + return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS"; + } + + public function getListTableIndexesSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + public function getListTableForeignKeysSQL($table) + { + return "SELECT TBNAME, RELNAME, REFTBNAME, 'DELETE_RULE', 'UPDATE_RULE', FKCOLNAMES, PKCOLNAMES ". + "FROM SYSIBM.SYSRELS WHERE TBNAME = '".$table."'"; + } + + public function getCreateViewSQL($name, $sql) + { + return "CREATE VIEW ".$name." AS ".$sql; + } + + public function getDropViewSQL($name) + { + return "DROP VIEW ".$name; + } + + public function getDropSequenceSQL($sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + public function getSequenceNextValSQL($sequenceName) + { + throw DBALException::notSupported(__METHOD__); + } + + public function getCreateDatabaseSQL($database) + { + return "CREATE DATABASE ".$database; + } + + public function getDropDatabaseSQL($database) + { + return "DROP DATABASE ".$database.";"; + } + + public function supportsCreateDropDatabase() + { + return false; + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php b/lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php new file mode 100644 index 000000000..78c07cf33 --- /dev/null +++ b/lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php @@ -0,0 +1,83 @@ +. +*/ + +namespace Doctrine\DBAL\Schema; + +/** + * IBM Db2 Schema Manager + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class IbmDb2SchemaManager extends AbstractSchemaManager +{ + /** + * Get Table Column Definition + * + * @param array $tableColumn + * @return Column + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + + } + + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); + + $tableForeignKey['delete_rule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['delete_rule']); + $tableForeignKey['update_rule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['update_rule']); + + return new ForeignKeyConstraint( + (array)$tableForeignKey['pkcolnames'], + $tableForeignKey['referenced_table_name'], + (array)$tableForeignKey['fkcolnames'], + $tableForeignKey['relname'], + array( + 'onUpdate' => $tableForeignKey['update_rule'], + 'onDelete' => $tableForeignKey['delete_rule'], + ) + ); + } + + protected function _getPortableForeignKeyRuleDef($def) + { + if ($def == "C") { + return "CASCADE"; + } else if ($def == "N") { + return "SET NULL"; + } + return null; + } + + protected function _getPortableViewDefinition($view) + { + $view = array_change_key_case($view, \CASE_LOWER); + $pos = strpos($view['text'], ' AS '); + $sql = substr($view['text'], $pos+4); + + return new View($view['name'], $sql); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php b/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php new file mode 100644 index 000000000..4190025cd --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php @@ -0,0 +1,61 @@ +addColumn('test_int', 'integer'); + $table->addColumn('test_string', 'string'); + + $sm = $this->_conn->getSchemaManager(); + $sm->createTable($table); + + $this->_conn->insert('fetch_table', array('test_int' => 1, 'test_string' => 'foo')); + } catch(\Exception $e) { + + } + } + + public function testFetchAll() + { + $sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?"; + $data = $this->_conn->fetchAll($sql, array(1, 'foo')); + + $this->assertEquals(1, count($data)); + + $row = $data[0]; + $this->assertEquals(2, count($row)); + + $row = array_change_key_case($row, \CASE_LOWER); + $this->assertEquals(1, $row['test_int']); + $this->assertEquals('foo', $row['test_string']); + } + + public function testFetchRow() + { + $sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?"; + $row = $this->_conn->fetchRow($sql, array(1, 'foo')); + + $row = array_change_key_case($row, \CASE_LOWER); + + $this->assertEquals(1, $row['test_int']); + $this->assertEquals('foo', $row['test_string']); + } + + public function testFetchArray() + { + $sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?"; + $row = $this->_conn->fetchArray($sql, array(1, 'foo')); + + $this->assertEquals(1, $row[0]); + $this->assertEquals('foo', $row[1]); + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/Db2SchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/Db2SchemaManagerTest.php new file mode 100644 index 000000000..a567900c9 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/Db2SchemaManagerTest.php @@ -0,0 +1,12 @@ +real database connection using the following parameters * of the $GLOBALS array: - * + * * 'db_type' : The name of the Doctrine DBAL database driver to use. * 'db_username' : The username to use for connecting. * 'db_password' : The password to use for connecting. * 'db_host' : The hostname of the database to connect to. * 'db_name' : The name of the database to connect to. * 'db_port' : The port of the database to connect to. - * + * * Usually these variables of the $GLOBALS array are filled by PHPUnit based * on an XML configuration file. If no such parameters exist, an SQLite * in-memory database is used. - * + * * IMPORTANT: * 1) Each invocation of this method returns a NEW database connection. * 2) The database is dropped and recreated to ensure it's clean. - * + * * @return Doctrine\DBAL\Connection The database connection instance. */ public static function getConnection() @@ -52,18 +52,24 @@ class TestUtil 'dbname' => $GLOBALS['tmpdb_name'], 'port' => $GLOBALS['tmpdb_port'] ); - - // Connect to tmpdb in order to drop and create the real test db. - $tmpConn = \Doctrine\DBAL\DriverManager::getConnection($tmpDbParams); + $realConn = \Doctrine\DBAL\DriverManager::getConnection($realDbParams); - $dbname = $realConn->getDatabase(); - $realConn->close(); - - $tmpConn->getSchemaManager()->dropDatabase($dbname); - $tmpConn->getSchemaManager()->createDatabase($dbname); - - $tmpConn->close(); + $platform = $realConn->getDatabasePlatform(); + + if ($platform->supportsCreateDropDatabase()) { + $dbname = $realConn->getDatabase(); + // Connect to tmpdb in order to drop and create the real test db. + $tmpConn = \Doctrine\DBAL\DriverManager::getConnection($tmpDbParams); + $realConn->close(); + + $tmpConn->getSchemaManager()->dropDatabase($dbname); + $tmpConn->getSchemaManager()->createDatabase($dbname); + + $tmpConn->close(); + } else { + // wipe everything? + } $eventManager = null; if (isset($GLOBALS['db_event_subscribers'])) { @@ -73,9 +79,9 @@ class TestUtil $eventManager->addEventSubscriber($subscriberInstance); } } - + $conn = \Doctrine\DBAL\DriverManager::getConnection($realDbParams, null, $eventManager); - + } else { $params = array( 'driver' => 'pdo_sqlite', From 5fd6e687ce85020beaca0cd860557305553f3a1c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 8 Apr 2010 22:40:53 +0200 Subject: [PATCH 04/56] Commit current state of IBM DB2 driver, but it segfaults the hell out of the Doctrine Testsuite --- lib/Doctrine/DBAL/Connection.php | 2 +- .../DBAL/Driver/IbmDb2/Db2Connection.php | 8 +- lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php | 16 +- lib/Doctrine/DBAL/DriverManager.php | 3 +- .../DBAL/Platforms/AbstractPlatform.php | 2 +- .../{IbmDb2Platform.php => Db2Platform.php} | 188 ++++++++++++++++- lib/Doctrine/DBAL/Platforms/MySqlPlatform.php | 87 +------- lib/Doctrine/DBAL/Schema/Db2SchemaManager.php | 195 ++++++++++++++++++ .../DBAL/Schema/IbmDb2SchemaManager.php | 83 -------- .../SchemaManagerFunctionalTestCase.php | 26 ++- tests/Doctrine/Tests/TestUtil.php | 8 +- 11 files changed, 421 insertions(+), 197 deletions(-) rename lib/Doctrine/DBAL/Platforms/{IbmDb2Platform.php => Db2Platform.php} (52%) create mode 100644 lib/Doctrine/DBAL/Schema/Db2SchemaManager.php delete mode 100644 lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 92a3480d9..eb16a6aac 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -791,7 +791,7 @@ class Connection implements DriverConnection * Gets the SchemaManager that can be used to inspect or change the * database schema through the connection. * - * @return Doctrine\DBAL\Schema\SchemaManager + * @return Doctrine\DBAL\Schema\AbstractSchemaManager */ public function getSchemaManager() { diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php index f6909a437..bc180fac6 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php @@ -25,12 +25,14 @@ class Db2Connection implements \Doctrine\DBAL\Driver\Connection { private $_conn = null; - public function __construct($dbname, $username, $password, $driverOptions = array(), $isPersistant = false) + public function __construct(array $params, $username, $password, $driverOptions = array()) { + $isPersistant = (isset($params['persistent']) && $params['persistent'] == true); + if ($isPersistant) { - $this->_conn = db2_pconnect($dbname, $username, $password, $driverOptions); + $this->_conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions); } else { - $this->_conn = db2_connect($dbname, $username, $password, $driverOptions); + $this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions); } if (!$this->_conn) { throw new Db2Exception(db2_conn_errormsg()); diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php index 5a88b488c..3522d0e37 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php @@ -46,9 +46,13 @@ class Db2Driver implements Driver */ public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { + if ( !isset($params['schema']) ) { + + } + if ($params['host'] !== 'localhost' && $params['host'] != '127.0.0.1') { // if the host isn't localhost, use extended connection params - $dbname = 'DRIVER={IBM DB2 ODBC DRIVER}' . + $params['dbname'] = 'DRIVER={IBM DB2 ODBC DRIVER}' . ';DATABASE=' . $params['dbname'] . ';HOSTNAME=' . $params['host'] . ';PORT=' . $params['port'] . @@ -57,13 +61,9 @@ class Db2Driver implements Driver ';PWD=' . $password .';'; $username = null; $password = null; - } else { - $dbname = $params['dbname']; } - $isPersistant = (isset($params['persistent']) && $params['persistent'] == true); - - return new Db2Connection($dbname, $username, $password, $driverOptions, $isPersistant); + return new Db2Connection($params, $username, $password, $driverOptions); } /** @@ -74,7 +74,7 @@ class Db2Driver implements Driver */ public function getDatabasePlatform() { - return new \Doctrine\DBAL\Platforms\IbmDb2Platform; + return new \Doctrine\DBAL\Platforms\Db2Platform; } /** @@ -86,7 +86,7 @@ class Db2Driver implements Driver */ public function getSchemaManager(Connection $conn) { - return new \Doctrine\DBAL\Schema\IbmDb2SchemaManager($conn); + return new \Doctrine\DBAL\Schema\Db2SchemaManager($conn); } /** diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 3a99c0073..4ed2ac1f3 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -43,7 +43,8 @@ final class DriverManager 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', 'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver', - 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver' + 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', + 'ibm_db2' => 'Doctrine\DBAL\Driver\IbmDb2\Db2Driver', ); /** Private constructor. This class cannot be instantiated. */ diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index e2d416dda..d991c4b1e 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -1095,7 +1095,7 @@ abstract class AbstractPlatform throw \InvalidArgumentException("Incomplete definition. 'columns' required."); } - return 'CONSTRAINT' . $name . ' UNIQUE (' + return 'CONSTRAINT ' . $name . ' UNIQUE (' . $this->getIndexFieldDeclarationListSQL($index->getColumns()) . ')'; } diff --git a/lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php b/lib/Doctrine/DBAL/Platforms/Db2Platform.php similarity index 52% rename from lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php rename to lib/Doctrine/DBAL/Platforms/Db2Platform.php index 09de9c746..955440f43 100644 --- a/lib/Doctrine/DBAL/Platforms/IbmDb2Platform.php +++ b/lib/Doctrine/DBAL/Platforms/Db2Platform.php @@ -21,7 +21,11 @@ namespace Doctrine\DBAL\Platforms; -class IbmDb2Platform extends AbstractPlatform +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\TableDiff; + +class Db2Platform extends AbstractPlatform { /** * Gets the SQL snippet used to declare a VARCHAR column type. @@ -122,6 +126,42 @@ class IbmDb2Platform extends AbstractPlatform } + /** + * Obtain DBMS specific SQL to be used to create datetime fields in + * statements like CREATE TABLE + * + * @param array $fieldDeclaration + * @return string + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0)'; + } + + /** + * Obtain DBMS specific SQL to be used to create date fields in statements + * like CREATE TABLE. + * + * @param array $fieldDeclaration + * @return string + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * Obtain DBMS specific SQL to be used to create time fields in statements + * like CREATE TABLE. + * + * @param array $fieldDeclaration + * @return string + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + public function getListDatabasesSQL() { throw DBALException::notSupported(__METHOD__); @@ -137,6 +177,13 @@ class IbmDb2Platform extends AbstractPlatform throw DBALException::notSupported(__METHOD__); } + /** + * This code fragment is originally from the Zend_Db_Adapter_Db2 class. + * + * @license New BSD License + * @param string $table + * @return string + */ public function getListTableColumnsSQL($table) { return "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, @@ -155,7 +202,7 @@ class IbmDb2Platform extends AbstractPlatform public function getListTablesSQL() { - return "SELECT 'NAME' FROM SYSIBM.TABLES"; + return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'"; } public function getListUsersSQL() @@ -176,13 +223,13 @@ class IbmDb2Platform extends AbstractPlatform public function getListTableIndexesSQL($table) { - throw DBALException::notSupported(__METHOD__); + return "SELECT NAME, COLNAMES, UNIQUERULE FROM SYSIBM.SYSINDEXES WHERE TBNAME = UPPER('" . $table . "')"; } public function getListTableForeignKeysSQL($table) { - return "SELECT TBNAME, RELNAME, REFTBNAME, 'DELETE_RULE', 'UPDATE_RULE', FKCOLNAMES, PKCOLNAMES ". - "FROM SYSIBM.SYSRELS WHERE TBNAME = '".$table."'"; + return "SELECT TBNAME, RELNAME, REFTBNAME, DELETERULE, UPDATERULE, FKCOLNAMES, PKCOLNAMES ". + "FROM SYSIBM.SYSRELS WHERE TBNAME = UPPER('".$table."')"; } public function getCreateViewSQL($name, $sql) @@ -219,4 +266,135 @@ class IbmDb2Platform extends AbstractPlatform { return false; } + + /** + * Gets the SQL specific for the platform to get the current date. + * + * @return string + */ + public function getCurrentDateSQL() + { + return 'current date'; + } + + /** + * Gets the SQL specific for the platform to get the current time. + * + * @return string + */ + public function getCurrentTimeSQL() + { + return 'current time'; + } + + /** + * Gets the SQL specific for the platform to get the current timestamp + * + * @return string + */ + public function getCurrentTimestampSQL() + { + return 'current timestamp'; + } + + /** + * Obtain DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param string $name name of the index + * @param Index $index index definition + * @return string DBMS specific SQL code portion needed to set an index + */ + public function getIndexDeclarationSQL($name, Index $index) + { + return $this->getUniqueConstraintDeclarationSQL($name, $index); + } + + /** + * @param string $tableName + * @param array $columns + * @param array $options + * @return array + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $indexes = array(); + if (isset($options['indexes'])) { + $indexes = $options['indexes']; + } + $options['indexes'] = array(); + + $sqls = parent::_getCreateTableSQL($tableName, $columns, $options); + + foreach ($indexes as $index => $definition) { + $sqls[] = $this->getCreateIndexSQL($definition, $tableName); + } + return $sqls; + } + + /** + * Gets the SQL to alter an existing table. + * + * @param TableDiff $diff + * @return array + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + + $queryParts = array(); + foreach ($diff->addedColumns AS $fieldName => $column) { + $queryParts[] = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); + } + + foreach ($diff->removedColumns AS $column) { + $queryParts[] = 'DROP COLUMN ' . $column->getName(); + } + + foreach ($diff->changedColumns AS $columnDiff) { + /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $queryParts[] = 'ALTER ' . ($columnDiff->oldColumnName) . ' ' + . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); + } + + foreach ($diff->renamedColumns AS $oldColumnName => $column) { + $queryParts[] = 'RENAME ' . $oldColumnName . ' TO ' . $column->getName(); + } + + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(" ", $queryParts); + } + + $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); + + if ($diff->newName !== false) { + $sql[] = 'RENAME TABLE TO ' . $diff->newName; + } + + return $sql; + } + + public function getDefaultValueDeclarationSQL($field) + { + if (isset($field['notnull']) && $field['notnull'] && !isset($field['default'])) { + if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) { + $field['default'] = 0; + } else { + $field['default'] = ''; + } + } + + return parent::getDefaultValueDeclarationSQL($field); + } + + public function supportsIdentityColumns() + { + return true; + } + + public function prefersIdentityColumns() + { + return true; + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php b/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php index 5bf9b84c2..c7793de95 100644 --- a/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php @@ -412,91 +412,8 @@ class MySqlPlatform extends AbstractPlatform /** * Gets the SQL to alter an existing table. * - * @param string $name The name of the table that is intended to be changed. - * @param array $changes Associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the Metabase parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the Metabase parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @return boolean - * @override + * @param TableDiff $diff + * @return array */ public function getAlterTableSQL(TableDiff $diff) { diff --git a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php b/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php new file mode 100644 index 000000000..b3d802bac --- /dev/null +++ b/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php @@ -0,0 +1,195 @@ +. +*/ + +namespace Doctrine\DBAL\Schema; + +/** + * IBM Db2 Schema Manager + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @version $Revision$ + * @author Benjamin Eberlei + */ +class Db2SchemaManager extends AbstractSchemaManager +{ + /** + * Return a list of all tables in the current database + * + * Apparently creator is the schema not the user who created it: + * {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm} + * + * @return array + */ + public function listTableNames() + { + + $sql = $this->_platform->getListTablesSQL(); + $sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')"; + + $tables = $this->_conn->fetchAll($sql); + + return $this->_getPortableTablesList($tables); + } + + + /** + * Get Table Column Definition + * + * @param array $tableColumn + * @return Column + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, \CASE_LOWER); + + $length = null; + $fixed = null; + $unsigned = false; + $scale = false; + $precision = false; + + switch (strtolower($tableColumn['typename'])) { + case 'smallint': + case 'bigint': + case 'integer': + case 'time': + case 'date': + $type = strtolower($tableColumn['typename']); + break; + case 'varchar': + $type = 'string'; + $length = $tableColumn['length']; + $fixed = false; + break; + case 'character': + $type = 'string'; + $length = $tableColumn['length']; + $fixed = true; + break; + case 'clob': + $type = 'text'; + $length = $tableColumn['length']; + break; + case 'decimal': + case 'double': + case 'real': + $type = 'decimal'; + $scale = $tableColumn['scale']; + $precision = $tableColumn['length']; + break; + case 'timestamp': + $type = 'datetime'; + break; + default: + throw new \Doctrine\DBAL\DBALException("Unknown Type: ".$tableColumn['typename']); + } + + $options = array( + 'length' => $length, + 'unsigned' => (bool)$unsigned, + 'fixed' => (bool)$fixed, + 'default' => ($tableColumn['default'] == "NULL") ? null : $tableColumn['default'], + 'notnull' => (bool) ($tableColumn['nulls'] == 'N'), + 'scale' => null, + 'precision' => null, + 'platformOptions' => array(), + ); + + if ($scale !== null && $precision !== null) { + $options['scale'] = $scale; + $options['precision'] = $precision; + } + + return new Column($tableColumn['colname'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + protected function _getPortableTablesList($tables) + { + $tableNames = array(); + foreach ($tables AS $tableRow) { + $tableRow = array_change_key_case($tableRow, \CASE_LOWER); + $tableNames[] = $tableRow['name']; + } + return $tableNames; + } + + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $tableIndexRows = array(); + $indexes = array(); + foreach($tableIndexes AS $indexKey => $data) { + $data = array_change_key_case($data, \CASE_LOWER); + $unique = ($data['uniquerule'] == "D") ? false : true; + $primary = ($data['uniquerule'] == "P"); + + $indexName = strtolower($data['name']); + if ($primary) { + $keyName = 'primary'; + } else { + $keyName = $indexName; + } + + $indexes[$keyName] = new Index($indexName, explode("+", ltrim($data['colnames'], '+')), $unique, $primary); + } + + return $indexes; + } + + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); + + $tableForeignKey['deleterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['deleterule']); + $tableForeignKey['updaterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['updaterule']); + + return new ForeignKeyConstraint( + array_map('trim', (array)$tableForeignKey['fkcolnames']), + $tableForeignKey['reftbname'], + array_map('trim', (array)$tableForeignKey['pkcolnames']), + $tableForeignKey['relname'], + array( + 'onUpdate' => $tableForeignKey['updaterule'], + 'onDelete' => $tableForeignKey['deleterule'], + ) + ); + } + + protected function _getPortableForeignKeyRuleDef($def) + { + if ($def == "C") { + return "CASCADE"; + } else if ($def == "N") { + return "SET NULL"; + } + return null; + } + + protected function _getPortableViewDefinition($view) + { + $view = array_change_key_case($view, \CASE_LOWER); + $pos = strpos($view['text'], ' AS '); + $sql = substr($view['text'], $pos+4); + + return new View($view['name'], $sql); + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php b/lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php deleted file mode 100644 index 78c07cf33..000000000 --- a/lib/Doctrine/DBAL/Schema/IbmDb2SchemaManager.php +++ /dev/null @@ -1,83 +0,0 @@ -. -*/ - -namespace Doctrine\DBAL\Schema; - -/** - * IBM Db2 Schema Manager - * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com - * @since 1.0 - * @version $Revision$ - * @author Benjamin Eberlei - */ -class IbmDb2SchemaManager extends AbstractSchemaManager -{ - /** - * Get Table Column Definition - * - * @param array $tableColumn - * @return Column - */ - protected function _getPortableTableColumnDefinition($tableColumn) - { - - } - - protected function _getPortableTableForeignKeyDefinition($tableForeignKey) - { - $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); - - $tableForeignKey['delete_rule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['delete_rule']); - $tableForeignKey['update_rule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['update_rule']); - - return new ForeignKeyConstraint( - (array)$tableForeignKey['pkcolnames'], - $tableForeignKey['referenced_table_name'], - (array)$tableForeignKey['fkcolnames'], - $tableForeignKey['relname'], - array( - 'onUpdate' => $tableForeignKey['update_rule'], - 'onDelete' => $tableForeignKey['delete_rule'], - ) - ); - } - - protected function _getPortableForeignKeyRuleDef($def) - { - if ($def == "C") { - return "CASCADE"; - } else if ($def == "N") { - return "SET NULL"; - } - return null; - } - - protected function _getPortableViewDefinition($view) - { - $view = array_change_key_case($view, \CASE_LOWER); - $pos = strpos($view['text'], ' AS '); - $sql = substr($view['text'], $pos+4); - - return new View($view['name'], $sql); - } -} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php index ef770c756..167c9290b 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -28,6 +28,8 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->markTestSkipped('The ' . $testClass .' requires the use of ' . $dbms); } + #$this->_conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + $this->_sm = $this->_conn->getSchemaManager(); } @@ -59,6 +61,10 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest public function testListDatabases() { + if (!$this->_sm->getDatabasePlatform()->supportsCreateDropDatabase()) { + $this->markTestSkipped('Cannot drop Database client side with this Driver.'); + } + $this->_sm->dropAndCreateDatabase('test_create_database'); $databases = $this->_sm->listDatabases(); @@ -73,12 +79,12 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $tables = $this->_sm->listTables(); $this->assertType('array', $tables); - $this->assertTrue(count($tables) > 0); + $this->assertTrue(count($tables) > 0, "List Tables has to find at least one table named 'list_tables_test'."); $foundTable = false; foreach ($tables AS $table) { $this->assertType('Doctrine\DBAL\Schema\Table', $table); - if ($table->getName() == 'list_tables_test') { + if (strtolower($table->getName()) == 'list_tables_test') { $foundTable = true; $this->assertTrue($table->hasColumn('id')); @@ -86,6 +92,8 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->assertTrue($table->hasColumn('foreign_key_test')); } } + + $this->assertTrue( $foundTable , "The 'list_tables_test' table has to be found."); } public function testListTableColumns() @@ -122,7 +130,6 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->assertEquals('foo', strtolower($columns['foo']->getname())); $this->assertType('Doctrine\DBAL\Types\TextType', $columns['foo']->gettype()); - $this->assertEquals(null, $columns['foo']->getlength()); $this->assertEquals(false, $columns['foo']->getunsigned()); $this->assertEquals(false, $columns['foo']->getfixed()); $this->assertEquals(true, $columns['foo']->getnotnull()); @@ -171,6 +178,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->assertEquals(3, count($tableIndexes)); + $this->assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.'); $this->assertEquals(array('id'), array_map('strtolower', $tableIndexes['primary']->getColumns())); $this->assertTrue($tableIndexes['primary']->isUnique()); $this->assertTrue($tableIndexes['primary']->isPrimary()); @@ -218,7 +226,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->_sm->dropAndCreateTable($tableA); $fkConstraints = $this->_sm->listTableForeignKeys('test_create_fk'); - $this->assertEquals(1, count($fkConstraints)); + $this->assertEquals(1, count($fkConstraints), "Table 'test_create_fk1' has to have one foreign key."); $fkConstraint = current($fkConstraints); $this->assertType('\Doctrine\DBAL\Schema\ForeignKeyConstraint', $fkConstraint); @@ -237,22 +245,20 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->createTestTable('test_create_fk2'); $foreignKey = new \Doctrine\DBAL\Schema\ForeignKeyConstraint( - array('foreign_key_test'), 'test_create_fk2', array('id'), 'foreign_key_test_fk', array('onUpdate' => 'CASCADE', 'onDelete' => 'CASCADE') + array('foreign_key_test'), 'test_create_fk2', array('id'), 'foreign_key_test_fk', array('onDelete' => 'CASCADE') ); $this->_sm->createForeignKey($foreignKey, 'test_create_fk1'); $fkeys = $this->_sm->listTableForeignKeys('test_create_fk1'); - $this->assertEquals(1, count($fkeys)); + $this->assertEquals(1, count($fkeys), "Table 'test_create_fk1' has to have one foreign key."); + $this->assertType('Doctrine\DBAL\Schema\ForeignKeyConstraint', $fkeys[0]); $this->assertEquals(array('foreign_key_test'), array_map('strtolower', $fkeys[0]->getLocalColumns())); $this->assertEquals(array('id'), array_map('strtolower', $fkeys[0]->getForeignColumns())); $this->assertEquals('test_create_fk2', strtolower($fkeys[0]->getForeignTableName())); - if($fkeys[0]->hasOption('onUpdate')) { - $this->assertEquals('CASCADE', $fkeys[0]->getOption('onUpdate')); - } if($fkeys[0]->hasOption('onDelete')) { $this->assertEquals('CASCADE', $fkeys[0]->getOption('onDelete')); } @@ -278,6 +284,8 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->markTestSkipped('Alter Table is not supported by this platform.'); } + $this->_conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + $this->createTestTable('alter_table'); $this->createTestTable('alter_table_foreign'); diff --git a/tests/Doctrine/Tests/TestUtil.php b/tests/Doctrine/Tests/TestUtil.php index 2f266d6d9..185bb65a7 100644 --- a/tests/Doctrine/Tests/TestUtil.php +++ b/tests/Doctrine/Tests/TestUtil.php @@ -68,7 +68,13 @@ class TestUtil $tmpConn->close(); } else { - // wipe everything? + $sm = $realConn->getSchemaManager(); + + $tableNames = $sm->listTableNames(); + + foreach ($tableNames AS $tableName) { + $sm->dropTable($tableName); + } } $eventManager = null; From 889094709e2df7d5a40dd09a7ef24211f97bb958 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 00:04:44 -0300 Subject: [PATCH 05/56] [2.0] Added support to IdentificationVariable that was missing in ArithmeticPrimary (it was not correctly handling it). Uncommented a unit test that added coverage to it. --- .../Hydration/SingleScalarHydrator.php | 1 + lib/Doctrine/ORM/Query/AST/PathExpression.php | 1 + lib/Doctrine/ORM/Query/Parser.php | 32 +++++++++++-------- lib/Doctrine/ORM/Query/SqlWalker.php | 13 ++++++++ .../ORM/Query/SelectSqlGenerationTest.php | 5 +-- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php index a095c2a7d..21846b6dc 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php @@ -43,6 +43,7 @@ class SingleScalarHydrator extends AbstractHydrator } else if ($num > 1 || count($result[key($result)]) > 1) { throw new \Doctrine\ORM\NonUniqueResultException; } + $result = $this->_gatherScalarRowData($result[key($result)], $cache); return array_shift($result); diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 4f8d0e277..996d35c27 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -41,6 +41,7 @@ namespace Doctrine\ORM\Query\AST; */ class PathExpression extends Node { + const TYPE_IDENTIFICATION_VARIABLE = 1; const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_STATE_FIELD = 8; diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index d3c7e3808..6f9e73c26 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -565,7 +565,8 @@ class Parser $aliasIdentificationVariable = $pathExpression->identificationVariable; $parentField = $pathExpression->identificationVariable; $class = $qComp['metadata']; - $fieldType = null; + $fieldType = ($pathExpression->expectedType == AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) + ? AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE : null; $curIndex = 0; foreach ($parts as $field) { @@ -602,8 +603,8 @@ class Parser $class = $this->_em->getClassMetadata($assoc->targetEntityName); if ( - ($curIndex != $numParts - 1) && - ! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field]) + ($curIndex != $numParts - 1) && + ! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field]) ) { // Building queryComponent $joinQueryComponent = array( @@ -617,13 +618,13 @@ class Parser // Create AST node $joinVariableDeclaration = new AST\JoinVariableDeclaration( - new AST\Join( - AST\Join::JOIN_TYPE_INNER, - new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field), - $aliasIdentificationVariable . '.' . $field, - false - ), - null + new AST\Join( + AST\Join::JOIN_TYPE_INNER, + new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field), + $aliasIdentificationVariable . '.' . $field, + false + ), + null ); $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration; @@ -649,6 +650,11 @@ class Parser // We need to recognize which was expected type(s) $expectedStringTypes = array(); + // Validate state field type + if ($expectedType & AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) { + $expectedStringTypes[] = 'IdentificationVariable'; + } + // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; @@ -915,12 +921,12 @@ class Parser $identVariable = $this->IdentificationVariable(); $parts = array(); - do { + while ($this->_lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $parts[] = $this->_lexer->token['value']; - } while ($this->_lexer->isNextToken(Lexer::T_DOT)); + } // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts); @@ -2168,7 +2174,7 @@ class Parser return $this->SingleValuedPathExpression(); } - return $this->IdentificationVariable(); + return $this->PathExpression(AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index be583c625..7a0b362c1 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -446,6 +446,17 @@ class SqlWalker implements TreeWalker $sql = ''; switch ($pathExpr->type) { + case AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE: + $dqlAlias = $pathExpr->identificationVariable; + $class = $this->_queryComponents[$dqlAlias]['metadata']; + + if ($this->_useSqlTableAliases) { + $sql .= $this->walkIdentificationVariable($dqlAlias) . '.'; + } + + $sql .= $class->getQuotedColumnName($class->identifier[0], $this->_platform); + break; + case AST\PathExpression::TYPE_STATE_FIELD: $parts = $pathExpr->parts; $fieldName = array_pop($parts); @@ -458,6 +469,7 @@ class SqlWalker implements TreeWalker $sql .= $class->getQuotedColumnName($fieldName, $this->_platform); break; + case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: // 1- the owning side: // Just use the foreign key, i.e. u.group_id @@ -484,6 +496,7 @@ class SqlWalker implements TreeWalker throw QueryException::associationPathInverseSideNotSupported(); } break; + default: throw QueryException::invalidPathExpression($pathExpr); } diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 163bc594e..7ed9c6693 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -21,6 +21,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $query = $this->_em->createQuery($dqlToBeTested); $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) ->useQueryCache(false); + parent::assertEquals($sqlToBeConfirmed, $query->getSql()); $query->free(); } catch (\Exception $e) { @@ -385,7 +386,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('SELECT d0_.id AS id0 FROM date_time_model d0_ WHERE d0_.col_datetime > CURRENT_TIMESTAMP', $q->getSql()); } - /*public function testExistsExpressionInWhereCorrelatedSubqueryAssocCondition() + public function testExistsExpressionInWhereCorrelatedSubqueryAssocCondition() { $this->assertSqlGeneration( // DQL @@ -402,7 +403,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase . ')' ); - }*/ + } public function testLimitFromQueryClass() { From d150f8a6f734b5a0c7dc5d97a8c3eb6fe8f5a438 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 11:21:39 -0400 Subject: [PATCH 06/56] Fixing issue with 4 spaces being hardcoded and not replaced with the configured amount of spaces --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index c8957041a..b8c4d5002 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -98,7 +98,7 @@ class EntityGenerator */ public function () { - return $this->; +return $this->; }'; private static $_setMethodTemplate = @@ -109,7 +109,7 @@ public function () */ public function ($) { - $this-> = $; +$this-> = $; }'; private static $_addMethodTemplate = @@ -120,7 +120,7 @@ public function ($) */ public function ($) { - $this->[] = $; +$this->[] = $; }'; private static $_lifecycleCallbackMethodTemplate = @@ -129,7 +129,7 @@ public function ($) */ public function () { - // Add your code here +// Add your code here }'; /** @@ -192,7 +192,8 @@ public function () '', '', '', - '' + '', + '' ); $replacements = array( @@ -200,7 +201,8 @@ public function () $this->_generateEntityUse($metadata), $this->_generateEntityDocBlock($metadata), $this->_generateEntityClassName($metadata), - $this->_generateEntityBody($metadata) + $this->_generateEntityBody($metadata), + $this->_spaces ); return str_replace($placeHolders, $replacements, self::$_classTemplate); From 3afc8f794aeb5f9a7e53d73874521d4c824f23f9 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 14:17:09 -0400 Subject: [PATCH 07/56] Throw exception when entity generator is not set --- lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php index 1ba0d2e7f..254905eec 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php @@ -49,6 +49,9 @@ class AnnotationExporter extends AbstractExporter */ public function exportClassMetadata(ClassMetadataInfo $metadata) { + if ( ! $this->_entityGenerator) { + throw new \RuntimeException('For the AnnotationExporter you must set an EntityGenerator instance with the setEntityGenerator() method.'); + } $this->_entityGenerator->setGenerateAnnotations(true); $this->_entityGenerator->setGenerateStubMethods(false); $this->_entityGenerator->setRegenerateEntityIfExists(false); From ac4e33d0560166f40c75fc769ef8e493bfcafb3f Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 14:17:55 -0400 Subject: [PATCH 08/56] Fixing issue with EntityGenerator spaces --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index b8c4d5002..b8a47c64d 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -192,8 +192,7 @@ public function () '', '', '', - '', - '' + '' ); $replacements = array( @@ -201,11 +200,11 @@ public function () $this->_generateEntityUse($metadata), $this->_generateEntityDocBlock($metadata), $this->_generateEntityClassName($metadata), - $this->_generateEntityBody($metadata), - $this->_spaces + $this->_generateEntityBody($metadata) ); - return str_replace($placeHolders, $replacements, self::$_classTemplate); + $code = str_replace($placeHolders, $replacements, self::$_classTemplate); + return str_replace('', $this->_spaces, $code); } /** @@ -220,9 +219,10 @@ public function () $currentCode = file_get_contents($path); $body = $this->_generateEntityBody($metadata); + $body = str_replace('', $this->_spaces, $body); $last = strrpos($currentCode, '}'); - return substr($currentCode, 0, $last) . $body . '}'; + return substr($currentCode, 0, $last) . $body . "\n}"; } /** From 146b22a1a57f0da75068d430337d06a34e2f2d7e Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 14:18:10 -0400 Subject: [PATCH 09/56] Making 2nd argument of getExporter() optional again --- lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php b/lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php index c86b9113e..2b528a2c2 100644 --- a/lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php @@ -63,7 +63,7 @@ class ClassMetadataExporter * @param string $source The directory where the exporter will export to * @return AbstractExporter $exporter */ - public function getExporter($type, $dest) + public function getExporter($type, $dest = null) { if ( ! isset(self::$_exporterDrivers[$type])) { throw ExportException::invalidExporterDriverType($type); From c122953a7e928c1b8859ce45334f3c4a08b609e7 Mon Sep 17 00:00:00 2001 From: Steven Surowiec Date: Wed, 14 Apr 2010 14:20:44 -0400 Subject: [PATCH 10/56] Changed privates to protected and updated setUp to use late static binding. This allows projects using Doctrine2 to use its test cases for testing their own entities. --- .../Doctrine/Tests/OrmFunctionalTestCase.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 9e02cb285..9502f2b7a 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -15,7 +15,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase private static $_queryCacheImpl = null; /* Shared connection when a TestCase is run alone (outside of it's functional suite) */ - private static $_sharedConn; + protected static $_sharedConn; /** * @var \Doctrine\ORM\EntityManager @@ -33,13 +33,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase protected $_sqlLoggerStack; /** The names of the model sets used in this testcase. */ - private $_usedModelSets = array(); + protected $_usedModelSets = array(); /** Whether the database schema has already been created. */ - private static $_tablesCreated = array(); + protected static $_tablesCreated = array(); /** List of model sets and their classes. */ - private static $_modelSets = array( + protected static $_modelSets = array( 'cms' => array( 'Doctrine\Tests\Models\CMS\CmsUser', 'Doctrine\Tests\Models\CMS\CmsPhonenumber', @@ -170,11 +170,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $forceCreateTables = false; if ( ! isset($this->sharedFixture['conn'])) { - if ( ! isset(self::$_sharedConn)) { - self::$_sharedConn = TestUtil::getConnection(); + if ( ! isset(static::$_sharedConn)) { + static::$_sharedConn = TestUtil::getConnection(); } - $this->sharedFixture['conn'] = self::$_sharedConn; + $this->sharedFixture['conn'] = static::$_sharedConn; if ($this->sharedFixture['conn']->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) { $forceCreateTables = true; @@ -189,12 +189,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $classes = array(); foreach ($this->_usedModelSets as $setName => $bool) { - if ( ! isset(self::$_tablesCreated[$setName])/* || $forceCreateTables*/) { - foreach (self::$_modelSets[$setName] as $className) { + if ( ! isset(static::$_tablesCreated[$setName])/* || $forceCreateTables*/) { + foreach (static::$_modelSets[$setName] as $className) { $classes[] = $this->_em->getClassMetadata($className); } - self::$_tablesCreated[$setName] = true; + static::$_tablesCreated[$setName] = true; } } From 32a81f09d1b96f4286edbd7cfa2dbb05a4e7169b Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 14:32:47 -0400 Subject: [PATCH 11/56] Fixing generate-repositories command so it works again after migration to Symfony console --- .../Command/GenerateRepositoriesCommand.php | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php index 9c1477fcd..5410c08b4 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php @@ -40,12 +40,12 @@ use Symfony\Components\Console\Input\InputArgument, */ class GenerateRepositoriesCommand extends Console\Command\Command { - private static $_template = + protected static $_template = '; -use \Doctrine\ORM\EntityRepository; +use Doctrine\ORM\EntityRepository; /** * @@ -103,7 +103,7 @@ EOT ); } - if ( count($metadatas)) { + if (count($metadatas)) { $numRepositories = 0; foreach ($metadatas as $metadata) { @@ -112,7 +112,7 @@ EOT sprintf('Processing repository "%s"', $metadata->customRepositoryClassName) . PHP_EOL ); - $this->_generateRepositoryClass($metadata, $destPath); + $this->_writeRepositoryClass($metadata, $destPath); $numRepositories++; } @@ -129,9 +129,23 @@ EOT } } - private function _generateRepositoryClass($metadata, $destPath) + protected function _generateRepositoryClass($metadata) { - $code = $this->_generateRepositoryClass($metadata->customRepositoryClassName); + fullClassName = $metadata->customRepositoryClassName; + $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\')); + $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName)); + + $variables = array( + '' => $namespace, + '' => $className + ); + $code = str_replace(array_keys($variables), array_values($variables), self::$_template); + return $code; + } + + protected function _writeRepositoryClass($metadata, $destPath) + { + $code = $this->_generateRepositoryClass($metadata); $path = $destPath . DIRECTORY_SEPARATOR . str_replace('\\', \DIRECTORY_SEPARATOR, $metadata->customRepositoryClassName) . '.php'; From 4d758035cc28359529f8d40fd9c975218baec730 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 15:19:48 -0400 Subject: [PATCH 12/56] Extracting repository generation code to standalone class so it can be re-used --- .../Command/GenerateRepositoriesCommand.php | 52 +----------- .../ORM/Tools/EntityRepositoryGenerator.php | 81 +++++++++++++++++++ 2 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php diff --git a/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php index 5410c08b4..f417ee0f9 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php @@ -24,7 +24,8 @@ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Components\Console\Input\InputArgument, Symfony\Components\Console\Input\InputOption, Symfony\Components\Console, - Doctrine\ORM\Tools\Console\MetadataFilter; + Doctrine\ORM\Tools\Console\MetadataFilter, + Doctrine\ORM\Tools\EntityRepositoryGenerator; /** * Command to generate repository classes for mapping information. @@ -40,23 +41,6 @@ use Symfony\Components\Console\Input\InputArgument, */ class GenerateRepositoriesCommand extends Console\Command\Command { - protected static $_template = -'; - -use Doctrine\ORM\EntityRepository; - -/** - * - * - * This class was generated by the Doctrine ORM. Add your own custom - * repository methods below. - */ -class extends EntityRepository -{ -}'; - /** * @see Console\Command\Command */ @@ -105,6 +89,7 @@ EOT if (count($metadatas)) { $numRepositories = 0; + $generator = new EntityRepositoryGenerator(); foreach ($metadatas as $metadata) { if ($metadata->customRepositoryClassName) { @@ -112,7 +97,7 @@ EOT sprintf('Processing repository "%s"', $metadata->customRepositoryClassName) . PHP_EOL ); - $this->_writeRepositoryClass($metadata, $destPath); + $generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destPath); $numRepositories++; } @@ -128,33 +113,4 @@ EOT $output->write('No Metadata Classes to process.' . PHP_EOL); } } - - protected function _generateRepositoryClass($metadata) - { - fullClassName = $metadata->customRepositoryClassName; - $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\')); - $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName)); - - $variables = array( - '' => $namespace, - '' => $className - ); - $code = str_replace(array_keys($variables), array_values($variables), self::$_template); - return $code; - } - - protected function _writeRepositoryClass($metadata, $destPath) - { - $code = $this->_generateRepositoryClass($metadata); - - $path = $destPath . DIRECTORY_SEPARATOR - . str_replace('\\', \DIRECTORY_SEPARATOR, $metadata->customRepositoryClassName) . '.php'; - $dir = dirname($path); - - if ( ! is_dir($dir)) { - mkdir($dir, 0777, true); - } - - file_put_contents($path, $code); - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php b/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php new file mode 100644 index 000000000..82c18035e --- /dev/null +++ b/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\ORM\Tools; + +/** + * Class to generate entity repository classes + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EntityRepositoryGenerator +{ + protected static $_template = +'; + +use Doctrine\ORM\EntityRepository; + +/** + * + * + * This class was generated by the Doctrine ORM. Add your own custom + * repository methods below. + */ +class extends EntityRepository +{ +}'; + + public function generateEntityRepositoryClass($fullClassName) + { + $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\')); + $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName)); + + $variables = array( + '' => $namespace, + '' => $className + ); + return str_replace(array_keys($variables), array_values($variables), self::$_template); + } + + public function writeEntityRepositoryClass($fullClassName, $outputDirectory) + { + $code = $this->generateEntityRepositoryClass($fullClassName); + + $path = $outputDirectory . DIRECTORY_SEPARATOR + . str_replace('\\', \DIRECTORY_SEPARATOR, $fullClassName) . '.php'; + $dir = dirname($path); + + if ( ! is_dir($dir)) { + mkdir($dir, 0777, true); + } + + file_put_contents($path, $code); + } +} \ No newline at end of file From 89c71138e6d0dfd5e1cf2bd8addebbadcaf9c601 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 20:39:51 -0400 Subject: [PATCH 13/56] Fixing changed method names --- .../Console/Command/ConvertDoctrine1SchemaCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php index ae297c84f..2af1eb04b 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php @@ -134,16 +134,16 @@ EOT } $converter = new ConvertDoctrine1Schema($fromPaths); - $metadatas = $converter->getMetadatas(); + $metadata = $converter->getMetadata(); if ($metadatas) { $output->write(PHP_EOL); - foreach ($metadatas as $metadata) { - $output->write(sprintf('Processing entity "%s"', $metadata->name) . PHP_EOL); + foreach ($metadata as $class) { + $output->write(sprintf('Processing entity "%s"', $class->name) . PHP_EOL); } - $exporter->setMetadatas($metadatas); + $exporter->setMetadata($metadata); $exporter->export(); $output->write(PHP_EOL . sprintf( From 2f6f8587b5aa28facf432344bc6892740dcb69c1 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 20:40:12 -0400 Subject: [PATCH 14/56] Fixing changed method names --- .../Tools/Console/Command/ConvertMappingCommand.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php index c350ca045..6770e6360 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php @@ -97,8 +97,8 @@ EOT } $cmf = new DisconnectedClassMetadataFactory($em); - $metadatas = $cmf->getAllMetadata(); - $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); + $metadata = $cmf->getAllMetadata(); + $metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); // Process destination directory if ( ! is_dir($destPath = $input->getArgument('dest-path'))) { @@ -132,12 +132,12 @@ EOT } } - if (count($metadatas)) { - foreach ($metadatas as $metadata) { - $output->write(sprintf('Processing entity "%s"', $metadata->name) . PHP_EOL); + if (count($metadata)) { + foreach ($metadata as $class) { + $output->write(sprintf('Processing entity "%s"', $class->name) . PHP_EOL); } - $exporter->setMetadatas($metadatas); + $exporter->setMetadata($metadata); $exporter->export(); $output->write(PHP_EOL . sprintf( From b2eeac564026d97647b67d7af618f8a5354f030d Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 20:41:41 -0400 Subject: [PATCH 15/56] Fixing strict standards notice --- lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php b/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php index a1fd97bb1..d0eb5e9f0 100644 --- a/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php +++ b/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php @@ -41,7 +41,8 @@ class ConvertDoctrine1Schema private $_legacyTypeMap = array( // TODO: This list may need to be updated 'clob' => 'text', - 'timestamp' => 'datetime' + 'timestamp' => 'datetime', + 'enum' => 'string' ); /** @@ -238,6 +239,7 @@ class ConvertDoctrine1Schema if (isset($relation['refClass'])) { $type = 'many'; $foreignType = 'many'; + $joinColumns = array(); } else { $type = isset($relation['type']) ? $relation['type'] : 'one'; $foreignType = isset($relation['foreignType']) ? $relation['foreignType'] : 'many'; From c43740c08aa1c9b69bd1b2dd1529a6ecb7509c0e Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 20:42:17 -0400 Subject: [PATCH 16/56] Fixing EntityRepositoryGenerator to not generate the repository if it already exists --- lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php b/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php index 82c18035e..74740dbe4 100644 --- a/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php @@ -76,6 +76,8 @@ class extends EntityRepository mkdir($dir, 0777, true); } - file_put_contents($path, $code); + if ( ! file_exists($path)) { + file_put_contents($path, $code); + } } } \ No newline at end of file From c6d784abc56699141b3b381f0a3b6bab08b71625 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 14 Apr 2010 20:42:39 -0400 Subject: [PATCH 17/56] Fixing ORMException --- lib/Doctrine/ORM/Tools/ToolsException.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/ToolsException.php b/lib/Doctrine/ORM/Tools/ToolsException.php index db13d862a..f7ed87105 100644 --- a/lib/Doctrine/ORM/Tools/ToolsException.php +++ b/lib/Doctrine/ORM/Tools/ToolsException.php @@ -2,8 +2,12 @@ namespace Doctrine\ORM\Tools; -class ToolsException extends ORMException { - public static function couldNotMapDoctrine1Type($type) { +use Doctrine\ORM\ORMException; + +class ToolsException extends ORMException +{ + public static function couldNotMapDoctrine1Type($type) + { return new self("Could not map doctrine 1 type '$type'!"); } } \ No newline at end of file From 955dc09cb92a5e31e21dc780fa71c38396113832 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 22:03:29 -0300 Subject: [PATCH 18/56] [2.0] Optimized support to IdentificationVariable in ArithmeticPrimary --- lib/Doctrine/ORM/Query/AST/PathExpression.php | 1 - lib/Doctrine/ORM/Query/Parser.php | 31 +++++++++---------- lib/Doctrine/ORM/Query/SqlWalker.php | 11 ------- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 996d35c27..4f8d0e277 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -41,7 +41,6 @@ namespace Doctrine\ORM\Query\AST; */ class PathExpression extends Node { - const TYPE_IDENTIFICATION_VARIABLE = 1; const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_STATE_FIELD = 8; diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 6f9e73c26..5d9766262 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -557,40 +557,44 @@ class Parser { foreach ($this->_deferredPathExpressions as $deferredItem) { $pathExpression = $deferredItem['expression']; - $parts = $pathExpression->parts; - $numParts = count($parts); $qComp = $this->_queryComponents[$pathExpression->identificationVariable]; + $numParts = count($pathExpression->parts); + if ($numParts == 0) { + $pathExpression->parts = array($qComp['metadata']->identifier[0]); + $numParts++; + } + + $parts = $pathExpression->parts; $aliasIdentificationVariable = $pathExpression->identificationVariable; $parentField = $pathExpression->identificationVariable; $class = $qComp['metadata']; - $fieldType = ($pathExpression->expectedType == AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) - ? AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE : null; + $fieldType = null; $curIndex = 0; foreach ($parts as $field) { // Check if it is not in a state field if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) { $this->semanticalError( - 'Cannot navigate through state field named ' . $field . ' on ' . $parentField, - $deferredItem['token'] + 'Cannot navigate through state field named ' . $field . ' on ' . $parentField, + $deferredItem['token'] ); } // Check if it is not a collection field if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { $this->semanticalError( - 'Cannot navigate through collection field named ' . $field . ' on ' . $parentField, - $deferredItem['token'] + 'Cannot navigate through collection field named ' . $field . ' on ' . $parentField, + $deferredItem['token'] ); } // Check if field or association exists if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { $this->semanticalError( - 'Class ' . $class->name . ' has no field or association named ' . $field, - $deferredItem['token'] + 'Class ' . $class->name . ' has no field or association named ' . $field, + $deferredItem['token'] ); } @@ -650,11 +654,6 @@ class Parser // We need to recognize which was expected type(s) $expectedStringTypes = array(); - // Validate state field type - if ($expectedType & AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) { - $expectedStringTypes[] = 'IdentificationVariable'; - } - // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; @@ -2174,7 +2173,7 @@ class Parser return $this->SingleValuedPathExpression(); } - return $this->PathExpression(AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE); + return $this->SimpleStateFieldPathExpression(); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 25263dac5..f624798fd 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -446,17 +446,6 @@ class SqlWalker implements TreeWalker $sql = ''; switch ($pathExpr->type) { - case AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE: - $dqlAlias = $pathExpr->identificationVariable; - $class = $this->_queryComponents[$dqlAlias]['metadata']; - - if ($this->_useSqlTableAliases) { - $sql .= $this->walkIdentificationVariable($dqlAlias) . '.'; - } - - $sql .= $class->getQuotedColumnName($class->identifier[0], $this->_platform); - break; - case AST\PathExpression::TYPE_STATE_FIELD: $parts = $pathExpr->parts; $fieldName = array_pop($parts); From c4ffd04da094a474fe5607332cf7decd6331cd67 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 22:49:53 -0300 Subject: [PATCH 19/56] [2.0][DDC-430] Added coverage, fixing the ticket. --- .../Tests/ORM/Query/SelectSqlGenerationTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 7ed9c6693..dda612152 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -585,4 +585,15 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.name AS name0, (SELECT COUNT(c1_.phonenumber) AS dctrn__1 FROM cms_phonenumbers c1_ WHERE c1_.phonenumber = 1234) AS sclr1 FROM cms_users c0_ WHERE c0_.name = 'jon'" ); } + + /** + * DDC-430 + */ + public function testSupportSelectWithMoreThan10InputParameters() + { + $this->assertSqlGeneration( + "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1 OR u.id = ?2 OR u.id = ?3 OR u.id = ?4 OR u.id = ?5 OR u.id = ?6 OR u.id = ?7 OR u.id = ?8 OR u.id = ?9 OR u.id = ?10 OR u.id = ?11", + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ?" + ); + } } From 26ff265652bc4824ff585b9915c3a5bcb2d82b30 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 23:27:33 -0300 Subject: [PATCH 20/56] [2.0][DDC-431] Added coverage, fixing the ticket. --- lib/Doctrine/ORM/Configuration.php | 42 ++++++++++++++++- .../ORM/Query/SelectSqlGenerationTest.php | 47 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 609bbfcd1..229126635 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -50,7 +50,14 @@ class Configuration extends \Doctrine\DBAL\Configuration 'proxyDir' => null, 'useCExtension' => false, 'autoGenerateProxyClasses' => true, - 'proxyNamespace' => null + 'proxyNamespace' => null, + 'entityNamespaces' => array(), + 'namedNativeQueries' => array(), + 'namedQueries' => array(), + // Custom DQL Functions + 'customDatetimeFunctions' => array(), + 'customNumericFunctions' => array(), + 'customStringFunctions' => array() )); } @@ -366,10 +373,21 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomStringFunction($name) { + $name = strtolower($name); + return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } + /** + * Clean custom DQL functions that produces string values. + * + */ + public function clearCustomStringFunctions() + { + $this->_attributes['customStringFunctions'] = array(); + } + /** * Registers a custom DQL function that produces a numeric value. * Such a function can then be used in any DQL statement in any place where numeric @@ -391,10 +409,21 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomNumericFunction($name) { + $name = strtolower($name); + return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } + /** + * Clean custom DQL functions that produces numeric values. + * + */ + public function clearCustomNumericFunctions() + { + $this->_attributes['customNumericFunctions'] = array(); + } + /** * Registers a custom DQL function that produces a date/time value. * Such a function can then be used in any DQL statement in any place where date/time @@ -416,7 +445,18 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomDatetimeFunction($name) { + $name = strtolower($name); + return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } + + /** + * Clean custom DQL functions that produces date/time values. + * + */ + public function clearCustomDatetimeFunctions() + { + $this->_attributes['customDatetimeFunctions'] = array(); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index dda612152..1ef832571 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -596,4 +596,51 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ?" ); } + + /** + * DDC-431 + */ + public function testSupportToCustomDQLFunctions() + { + $config = $this->_em->getConfiguration(); + $config->addCustomNumericFunction('MYABS', 'Doctrine\Tests\ORM\Query\MyAbsFunction'); + + $this->assertSqlGeneration( + 'SELECT MYABS(p.phonenumber) FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p', + 'SELECT ABS(c0_.phonenumber) AS sclr0 FROM cms_phonenumbers c0_' + ); + + $config->clearCustomNumericFunctions(); + } } + + +class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode +{ + public $simpleArithmeticExpression; + + /** + * @override + */ + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + /** + * @override + */ + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(\Doctrine\ORM\Query\Lexer::T_IDENTIFIER); + $parser->match(\Doctrine\ORM\Query\Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(\Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS); + } +} \ No newline at end of file From e83bfeede34351e44eb18f43dbf1865918eb3b8c Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 11:55:03 +0200 Subject: [PATCH 21/56] Simplified and streamlined configuration classes. --- lib/Doctrine/DBAL/Configuration.php | 19 +--- lib/Doctrine/ORM/Configuration.php | 96 +++++++++---------- .../ORM/Query/SelectSqlGenerationTest.php | 2 +- 3 files changed, 50 insertions(+), 67 deletions(-) diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index 3ad9e36a0..dfda73240 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -1,7 +1,5 @@ _attributes = array( - 'sqlLogger' => null - ); - } - /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @param SQLLogger $logger */ - public function setSQLLogger($logger) + public function setSQLLogger(SqlLogger $logger) { $this->_attributes['sqlLogger'] = $logger; } @@ -73,6 +61,7 @@ class Configuration */ public function getSQLLogger() { - return $this->_attributes['sqlLogger']; + return isset($this->_attributes['sqlLogger']) ? + $this->_attributes['sqlLogger'] : null; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 229126635..abb8a15d8 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -27,8 +27,7 @@ use Doctrine\Common\Cache\Cache, * It combines all configuration options from DBAL & ORM. * * @since 2.0 - * @internal When adding a new configuration option just write a getter/setter - * pair and add the option to the _attributes array with a proper default value. + * @internal When adding a new configuration option just write a getter/setter pair. * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage @@ -36,31 +35,6 @@ use Doctrine\Common\Cache\Cache, */ class Configuration extends \Doctrine\DBAL\Configuration { - /** - * Creates a new configuration that can be used for Doctrine. - */ - public function __construct() - { - parent::__construct(); - $this->_attributes = array_merge($this->_attributes, array( - 'resultCacheImpl' => null, - 'queryCacheImpl' => null, - 'metadataCacheImpl' => null, - 'metadataDriverImpl' => null, - 'proxyDir' => null, - 'useCExtension' => false, - 'autoGenerateProxyClasses' => true, - 'proxyNamespace' => null, - 'entityNamespaces' => array(), - 'namedNativeQueries' => array(), - 'namedQueries' => array(), - // Custom DQL Functions - 'customDatetimeFunctions' => array(), - 'customNumericFunctions' => array(), - 'customStringFunctions' => array() - )); - } - /** * Sets the directory where Doctrine generates any necessary proxy class files. * @@ -78,7 +52,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getProxyDir() { - return $this->_attributes['proxyDir']; + return isset($this->_attributes['proxyDir']) ? + $this->_attributes['proxyDir'] : null; } /** @@ -89,7 +64,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getAutoGenerateProxyClasses() { - return $this->_attributes['autoGenerateProxyClasses']; + return isset($this->_attributes['autoGenerateProxyClasses']) ? + $this->_attributes['autoGenerateProxyClasses'] : true; } /** @@ -110,7 +86,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getProxyNamespace() { - return $this->_attributes['proxyNamespace']; + return isset($this->_attributes['proxyNamespace']) ? + $this->_attributes['proxyNamespace'] : null; } /** @@ -195,7 +172,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getMetadataDriverImpl() { - return $this->_attributes['metadataDriverImpl']; + return isset($this->_attributes['metadataDriverImpl']) ? + $this->_attributes['metadataDriverImpl'] : null; } /** @@ -205,7 +183,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getResultCacheImpl() { - return $this->_attributes['resultCacheImpl']; + return isset($this->_attributes['resultCacheImpl']) ? + $this->_attributes['resultCacheImpl'] : null; } /** @@ -225,7 +204,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getQueryCacheImpl() { - return $this->_attributes['queryCacheImpl']; + return isset($this->_attributes['queryCacheImpl']) ? + $this->_attributes['queryCacheImpl'] : null; } /** @@ -245,7 +225,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getMetadataCacheImpl() { - return $this->_attributes['metadataCacheImpl']; + return isset($this->_attributes['metadataCacheImpl']) ? + $this->_attributes['metadataCacheImpl'] : null; } /** @@ -266,7 +247,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getUseCExtension() { - return $this->_attributes['useCExtension']; + return isset($this->_attributes['useCExtension']) ? + $this->_attributes['useCExtension'] : false; } /** @@ -373,19 +355,23 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomStringFunction($name) { - $name = strtolower($name); - return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces string values. + * Sets a map of custom DQL string functions. * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added string functions are discarded. + * + * @param array $functions The map of custom DQL string functions. */ - public function clearCustomStringFunctions() + public function setCustomStringFunctions(array $functions) { - $this->_attributes['customStringFunctions'] = array(); + $this->_attributes['customStringFunctions'] = $functions; } /** @@ -409,19 +395,23 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomNumericFunction($name) { - $name = strtolower($name); - return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces numeric values. + * Sets a map of custom DQL numeric functions. * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added numeric functions are discarded. + * + * @param array $functions The map of custom DQL numeric functions. */ - public function clearCustomNumericFunctions() + public function setCustomNumericFunctions(array $functions) { - $this->_attributes['customNumericFunctions'] = array(); + $this->_attributes['customNumericFunctions'] = $functions; } /** @@ -445,18 +435,22 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomDatetimeFunction($name) { - $name = strtolower($name); - return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces date/time values. - * + * Sets a map of custom DQL date/time functions. + * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added date/time functions are discarded. + * + * @param array $functions The map of custom DQL date/time functions. */ - public function clearCustomDatetimeFunctions() + public function setCustomDatetimeFunctions(array $functions) { - $this->_attributes['customDatetimeFunctions'] = array(); + $this->_attributes['customDatetimeFunctions'] = $functions; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 1ef832571..a85873a92 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -610,7 +610,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT ABS(c0_.phonenumber) AS sclr0 FROM cms_phonenumbers c0_' ); - $config->clearCustomNumericFunctions(); + $config->setCustomNumericFunctions(array()); } } From 29e0863ffa13b5a6fe122b900ebebaecc9e67a92 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 12:41:34 +0200 Subject: [PATCH 22/56] Fixed casing. --- lib/Doctrine/DBAL/Configuration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index dfda73240..1ef539b90 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -19,7 +19,7 @@ namespace Doctrine\DBAL; -use Doctrine\DBAL\Logging\SqlLogger; +use Doctrine\DBAL\Logging\SQLLogger; /** * Configuration container for the Doctrine DBAL. @@ -49,7 +49,7 @@ class Configuration * * @param SQLLogger $logger */ - public function setSQLLogger(SqlLogger $logger) + public function setSQLLogger(SQLLogger $logger) { $this->_attributes['sqlLogger'] = $logger; } From 01c2c06bbf529d89c9741ea97702359509ea230a Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 18:36:17 +0200 Subject: [PATCH 23/56] [DDC-512] Fixed. --- lib/Doctrine/DBAL/Configuration.php | 3 - .../ORM/Persisters/SingleTablePersister.php | 1 - lib/Doctrine/ORM/Query/AST/PathExpression.php | 5 - lib/Doctrine/ORM/Query/Parser.php | 5 - lib/Doctrine/ORM/Query/SqlWalker.php | 16 ++-- .../Tests/Models/Company/CompanyEvent.php | 4 +- .../Functional/ClassTableInheritanceTest.php | 1 - .../ORM/Functional/Ticket/DDC512Test.php | 94 +++++++++++++++++++ 8 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index 1ef539b90..76ce1c4f0 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -24,10 +24,7 @@ use Doctrine\DBAL\Logging\SQLLogger; /** * Configuration container for the Doctrine DBAL. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index a0adbea67..5f5a8a4d7 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -26,7 +26,6 @@ use Doctrine\ORM\Mapping\ClassMetadata; * SINGLE_TABLE strategy. * * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @since 2.0 * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html */ diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 4f8d0e277..ed856f679 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 5d9766262..645cb5ae0 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index f624798fd..0b71b82bf 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1,7 +1,5 @@ getSqlTableAlias($class->table['name'], $dqlAlias); + $baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->getSqlTableAlias($parentClass->table['name'], $dqlAlias); - $sql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) + $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias); + // If this is a joined association we must use left joins to preserve the correct result. + $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; + $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; @@ -260,14 +259,13 @@ class SqlWalker implements TreeWalker } } - // LEFT JOIN subclass tables, if partial objects disallowed + // LEFT JOIN subclass tables, if partial objects disallowed. if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->getSqlTableAlias($subClass->table['name'], $dqlAlias); + $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - $first = true; foreach ($class->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; diff --git a/tests/Doctrine/Tests/Models/Company/CompanyEvent.php b/tests/Doctrine/Tests/Models/Company/CompanyEvent.php index f0bb730cf..2db4986ae 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyEvent.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyEvent.php @@ -6,9 +6,9 @@ namespace Doctrine\Tests\Models\Company; * @Entity @Table(name="company_events") * @InheritanceType("JOINED") * @DiscriminatorColumn(name="event_type", type="string") - * @DiscriminatorMap({"auction" = "CompanyAuction", "raffle" = "CompanyRaffle"}) + * @DiscriminatorMap({"auction"="CompanyAuction", "raffle"="CompanyRaffle"}) */ -class CompanyEvent { +abstract class CompanyEvent { /** * @Id @Column(type="integer") * @GeneratedValue diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php index 206a567b6..ea1ad15d8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php @@ -294,5 +294,4 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase ->getResult()) > 0); } - } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php new file mode 100644 index 000000000..72b11aaab --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php @@ -0,0 +1,94 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512Customer'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512OfferItem'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512Item'), + )); + } + + public function testIssue() + { + $customer1 = new DDC512Customer(); + $item = new DDC512OfferItem(); + $customer1->item = $item; + $this->_em->persist($customer1); + + $customer2 = new DDC512Customer(); + $this->_em->persist($customer2); + + $this->_em->flush(); + $this->_em->clear(); + + $q = $this->_em->createQuery("select u,i from ".__NAMESPACE__."\\DDC512Customer u left join u.item i"); + $result = $q->getResult(); + + $this->assertEquals(2, count($result)); + $this->assertTrue($result[0] instanceof DDC512Customer); + $this->assertTrue($result[1] instanceof DDC512Customer); + if ($result[0]->id == $customer1->id) { + $this->assertTrue($result[0]->item instanceof DDC512OfferItem); + $this->assertEquals($item->id, $result[0]->item->id); + $this->assertNull($result[1]->item); + } else { + $this->assertTrue($result[1]->item instanceof DDC512OfferItem); + $this->assertNull($result[0]->item); + } + } +} + +/** + * @Entity + */ +class DDC512Customer { + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; + + /** + * NOTE that we can currently not name the join column the same as the field + * (item = item), this currently confuses Doctrine. + * + * @OneToOne(targetEntity="DDC512OfferItem", cascade={"remove","persist"}) + * @JoinColumn(name="item_id", referencedColumnName="id") + */ + public $item; +} + +/** + * @Entity + */ +class DDC512OfferItem extends DDC512Item +{ +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"item" = "DDC512Item", "offerItem" = "DDC512OfferItem"}) + */ +class DDC512Item +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; +} + + + + From 4b39705cd460f8db7f5ad2ecc08eb6b38fda57ab Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 20:14:03 +0200 Subject: [PATCH 24/56] Fixed case-sensitivity of custom DQL functions. --- lib/Doctrine/ORM/Configuration.php | 15 ++++++++++++--- lib/Doctrine/ORM/Query/Parser.php | 21 ++++++++++++--------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index abb8a15d8..b6fa3a99e 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -339,6 +339,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where string * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -355,6 +357,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomStringFunction($name) { + $name = strtolower($name); return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } @@ -371,7 +374,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function setCustomStringFunctions(array $functions) { - $this->_attributes['customStringFunctions'] = $functions; + $this->_attributes['customStringFunctions'] = array_change_key_case($functions); } /** @@ -379,6 +382,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where numeric * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -395,6 +400,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomNumericFunction($name) { + $name = strtolower($name); return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } @@ -411,7 +417,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function setCustomNumericFunctions(array $functions) { - $this->_attributes['customNumericFunctions'] = $functions; + $this->_attributes['customNumericFunctions'] = array_change_key_case($functions); } /** @@ -419,6 +425,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where date/time * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -435,6 +443,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomDatetimeFunction($name) { + $name = strtolower($name); return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } @@ -451,6 +460,6 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function setCustomDatetimeFunctions(array $functions) { - $this->_attributes['customDatetimeFunctions'] = $functions; + $this->_attributes['customDatetimeFunctions'] = array_change_key_case($functions); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 645cb5ae0..960ae426d 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2619,9 +2619,10 @@ class Parser public function CustomFunctionsReturningNumerics() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = strtolower($this->_lexer->lookahead['value']); + // getCustomNumericFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; @@ -2642,9 +2643,10 @@ class Parser public function CustomFunctionsReturningDatetime() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = $this->_lexer->lookahead['value']; + // getCustomDatetimeFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; @@ -2670,9 +2672,10 @@ class Parser public function CustomFunctionsReturningStrings() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = $this->_lexer->lookahead['value']; + // getCustomStringFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; From 59f3fe3a40c3e556d17996210236c49da76c06ae Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 16 Apr 2010 13:20:46 -0400 Subject: [PATCH 25/56] Fixing code style issue --- .../ORM/Tools/Console/Command/GenerateEntitiesCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php index ccb29cf14..7565442ca 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php @@ -113,7 +113,7 @@ EOT ); } - if ( count($metadatas)) { + if (count($metadatas)) { // Create EntityGenerator $entityGenerator = new EntityGenerator(); From efb33a8365b064c8c2d687dd8b23b907af71aebd Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 16 Apr 2010 13:21:16 -0400 Subject: [PATCH 26/56] Merging some fixes to Symfony Console component --- .../Components/Console/Application.php | 2 +- .../Components/Console/Command/Command.php | 26 +++++++++++++++++-- .../Components/Console/Input/ArgvInput.php | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/vendor/Symfony/Components/Console/Application.php b/lib/vendor/Symfony/Components/Console/Application.php index e26319be7..929bc6bc0 100644 --- a/lib/vendor/Symfony/Components/Console/Application.php +++ b/lib/vendor/Symfony/Components/Console/Application.php @@ -488,7 +488,7 @@ class Application { // namespace $namespace = ''; - if (false !== $pos = strpos($name, ':')) + if (false !== $pos = strrpos($name, ':')) { $namespace = $this->findNamespace(substr($name, 0, $pos)); $name = substr($name, $pos + 1); diff --git a/lib/vendor/Symfony/Components/Console/Command/Command.php b/lib/vendor/Symfony/Components/Console/Command/Command.php index bbde5f94e..b0e3d863a 100644 --- a/lib/vendor/Symfony/Components/Console/Command/Command.php +++ b/lib/vendor/Symfony/Components/Console/Command/Command.php @@ -276,7 +276,7 @@ class Command */ public function setName($name) { - if (false !== $pos = strpos($name, ':')) + if (false !== $pos = strrpos($name, ':')) { $namespace = substr($name, 0, $pos); $name = substr($name, $pos + 1); @@ -375,6 +375,28 @@ class Command return $this->help; } + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->namespace.':'.$this->name; + + $placeholders = array( + '%command.name%', + '%command.full_name%' + ); + $replacements = array( + $name, + $_SERVER['PHP_SELF'].' '.$name + ); + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + /** * Sets the aliases for the command. * @@ -457,7 +479,7 @@ class Command $messages[] = $this->definition->asText(); - if ($help = $this->getHelp()) + if ($help = $this->getProcessedHelp()) { $messages[] = 'Help:'; $messages[] = ' '.implode("\n ", explode("\n", $help))."\n"; diff --git a/lib/vendor/Symfony/Components/Console/Input/ArgvInput.php b/lib/vendor/Symfony/Components/Console/Input/ArgvInput.php index a140219bf..7a78003f7 100644 --- a/lib/vendor/Symfony/Components/Console/Input/ArgvInput.php +++ b/lib/vendor/Symfony/Components/Console/Input/ArgvInput.php @@ -70,7 +70,7 @@ class ArgvInput extends Input protected function parse() { $this->parsed = $this->tokens; - while ($token = array_shift($this->parsed)) + while (null !== ($token = array_shift($this->parsed))) { if ('--' === substr($token, 0, 2)) { From aa70e6426dcc5e93bfcd20529a32fd211523b6cc Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 16 Apr 2010 13:23:07 -0400 Subject: [PATCH 27/56] Fixing Console helpers to not override the default helpers provided --- tools/sandbox/cli-config.php | 4 ++-- tools/sandbox/doctrine.php | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/sandbox/cli-config.php b/tools/sandbox/cli-config.php index 5c69499b0..afe1296e6 100644 --- a/tools/sandbox/cli-config.php +++ b/tools/sandbox/cli-config.php @@ -23,7 +23,7 @@ $connectionOptions = array( $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); -$helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( +$helpers = array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) -)); \ No newline at end of file +); \ No newline at end of file diff --git a/tools/sandbox/doctrine.php b/tools/sandbox/doctrine.php index 152a9932e..3b4a856de 100644 --- a/tools/sandbox/doctrine.php +++ b/tools/sandbox/doctrine.php @@ -13,7 +13,10 @@ require __DIR__ . '/cli-config.php'; $cli = new \Symfony\Components\Console\Application('Doctrine Command Line Interface', Doctrine\Common\Version::VERSION); $cli->setCatchExceptions(true); -$cli->setHelperSet($helperSet); +$helperSet = $cli->getHelperSet(); +foreach ($helpers as $name => $helper) { + $helperSet->set($helper, $name); +} $cli->addCommands(array( // DBAL Commands new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(), From fab5ef84298172ea5cca2251157363ae5f66f14d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 17 Apr 2010 08:47:40 +0200 Subject: [PATCH 28/56] DDC-515 - First version of a validate mapping command --- .../Console/Command/ValidateSchemaCommand.php | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php new file mode 100644 index 000000000..f3053e08a --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php @@ -0,0 +1,78 @@ +. +*/ + +namespace Doctrine\ORM\Tools\Console\Command; + +use Symfony\Components\Console\Input\InputArgument, + Symfony\Components\Console\Input\InputOption, + Symfony\Components\Console; + +/** + * Validate that the current mapping is valid + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ValidateSchemaCommand extends Console\Command\Command +{ + /** + * @see Console\Command\Command + */ + protected function configure() + { + $this->setName('orm:validate-schema') + ->setDescription('Validate that the current metadata schema is valid.'); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + */ + protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) + { + $emHelper = $this->getHelper('em'); + + /* @var $em \Doctrine\ORM\EntityManager */ + $em = $emHelper->getEntityManager(); + + $metadatas = $em->getMetadataFactory()->getAllMetadata(); + + if ( ! empty($metadatas)) { + // Create SchemaTool + $tool = new \Doctrine\ORM\Tools\SchemaTool($em); + $updateSql = $tool->getUpdateSchemaSql($metadatas, false); + + if (count($updateSql) == 0) { + $output->write("[Database] OK - Metadata schema exactly matches the database schema."); + } else { + $output->write("[Database] FAIL - There are differences between metadata and database schema."); + } + } else { + $output->write("No metadata mappings found"); + } + } +} \ No newline at end of file From 024b2bab91ccf0c6ce9a0729b07762b6f9ee2593 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 18 Apr 2010 19:12:38 +0200 Subject: [PATCH 29/56] DDC-496 Finished first versions of platform and schema manager for DB2, DDC-528 Added support for PDO_IBM driver, passing all but 3 tests that are related to CLOB fields --- lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php | 126 +++++++++++++++++ lib/Doctrine/DBAL/DriverManager.php | 1 + .../DBAL/Platforms/AbstractPlatform.php | 11 ++ lib/Doctrine/DBAL/Platforms/Db2Platform.php | 132 +++++++++++++++++- lib/Doctrine/DBAL/Schema/Db2SchemaManager.php | 10 +- lib/Doctrine/DBAL/Types/ArrayType.php | 1 + lib/Doctrine/DBAL/Types/ObjectType.php | 1 + lib/Doctrine/DBAL/Types/TextType.php | 13 ++ .../Query/Exec/MultiTableDeleteExecutor.php | 5 +- .../Query/Exec/MultiTableUpdateExecutor.php | 5 +- .../Tests/DBAL/Functional/AllTests.php | 1 + .../SchemaManagerFunctionalTestCase.php | 2 - .../Tests/ORM/Functional/NativeQueryTest.php | 33 +++-- .../Doctrine/Tests/OrmFunctionalTestCase.php | 2 +- 14 files changed, 309 insertions(+), 34 deletions(-) create mode 100644 lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php diff --git a/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php new file mode 100644 index 000000000..3f05f5c75 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php @@ -0,0 +1,126 @@ +. +*/ + +namespace Doctrine\DBAL\Driver\PDOIbm; + +use Doctrine\DBAL\Connection; + +/** + * Driver for the PDO IBM extension + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * Attempts to establish a connection with the underlying driver. + * + * @param array $params + * @param string $username + * @param string $password + * @param array $driverOptions + * @return Doctrine\DBAL\Driver\Connection + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + return $conn; + } + + /** + * Constructs the MySql PDO DSN. + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'ibm:'; + if (isset($params['host'])) { + $dsn .= 'HOSTNAME=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'PORT=' . $params['port'] . ';'; + } + $dsn .= 'PROTOCOL=TCPIP;'; + if (isset($params['dbname'])) { + $dsn .= 'DATABASE=' . $params['dbname'] . ';'; + } + + return $dsn; + } + + /** + * Gets the DatabasePlatform instance that provides all the metadata about + * the platform this driver connects to. + * + * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform. + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\Db2Platform; + } + + /** + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @param Doctrine\DBAL\Connection $conn + * @return Doctrine\DBAL\SchemaManager + */ + public function getSchemaManager(Connection $conn) + { + return new \Doctrine\DBAL\Schema\Db2SchemaManager($conn); + } + + /** + * Gets the name of the driver. + * + * @return string The name of the driver. + */ + public function getName() + { + return 'pdo_ibm'; + } + + /** + * Get the name of the database connected to for this driver. + * + * @param Doctrine\DBAL\Connection $conn + * @return string $database + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + return $params['dbname']; + } +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 4ed2ac1f3..21a5c09d4 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -45,6 +45,7 @@ final class DriverManager 'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver', 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', 'ibm_db2' => 'Doctrine\DBAL\Driver\IbmDb2\Db2Driver', + 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', ); /** Private constructor. This class cannot be instantiated. */ diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index d991c4b1e..702e6a8dc 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -1177,6 +1177,17 @@ abstract class AbstractPlatform return 'TEMPORARY'; } + /** + * Some vendors require temporary table names to be qualified specially. + * + * @param string $tableName + * @return string + */ + public function getTemporaryTableName($tableName) + { + return $tableName; + } + /** * Get sql query to show a list of database. * diff --git a/lib/Doctrine/DBAL/Platforms/Db2Platform.php b/lib/Doctrine/DBAL/Platforms/Db2Platform.php index 955440f43..b02d2eade 100644 --- a/lib/Doctrine/DBAL/Platforms/Db2Platform.php +++ b/lib/Doctrine/DBAL/Platforms/Db2Platform.php @@ -90,7 +90,7 @@ class Db2Platform extends AbstractPlatform */ public function getIntegerTypeDeclarationSQL(array $columnDef) { - return 'INTEGER'; + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** @@ -101,7 +101,7 @@ class Db2Platform extends AbstractPlatform */ public function getBigIntTypeDeclarationSQL(array $columnDef) { - return 'BIGINT'; + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** @@ -112,7 +112,7 @@ class Db2Platform extends AbstractPlatform */ public function getSmallIntTypeDeclarationSQL(array $columnDef) { - return 'SMALLINT'; + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** @@ -123,7 +123,11 @@ class Db2Platform extends AbstractPlatform */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { - + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; + } + return $autoinc; } /** @@ -135,6 +139,10 @@ class Db2Platform extends AbstractPlatform */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return "TIMESTAMP(0) WITH DEFAULT"; + } + return 'TIMESTAMP(0)'; } @@ -292,10 +300,10 @@ class Db2Platform extends AbstractPlatform * * @return string */ - public function getCurrentTimestampSQL() + /*public function getCurrentTimestampSQL() { return 'current timestamp'; - } + }*/ /** * Obtain DBMS specific SQL code portion needed to set an index @@ -380,14 +388,113 @@ class Db2Platform extends AbstractPlatform if (isset($field['notnull']) && $field['notnull'] && !isset($field['default'])) { if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) { $field['default'] = 0; + } else if((string)$field['type'] == "DateTime") { + $field['default'] = "00-00-00 00:00:00"; + } else if ((string)$field['type'] == "Date") { + $field['default'] = "00-00-00"; + } else if((string)$field['type'] == "Time") { + $field['default'] = "00:00:00"; } else { $field['default'] = ''; } } + unset($field['default']); // @todo this needs fixing + if (isset($field['version']) && $field['version']) { + if ((string)$field['type'] != "DateTime") { + $field['default'] = "1"; + } + } + return parent::getDefaultValueDeclarationSQL($field); } + /** + * Get the insert sql for an empty insert statement + * + * @param string $tableName + * @param string $identifierColumnName + * @return string $sql + */ + public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) + { + return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)'; + } + + public function getCreateTemporaryTableSnippetSQL() + { + return "DECLARE GLOBAL TEMPORARY TABLE"; + } + + /** + * DB2 automatically moves temporary tables into the SESSION. schema. + * + * @param string $tableName + * @return string + */ + public function getTemporaryTableName($tableName) + { + return "SESSION." . $tableName; + } + + public function getCurrentTimestampSQL() + { + return "VALUES CURRENT TIMESTAMP"; + } + + public function modifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null && $offset === null) { + return $query; + } + + $limit = (int)$limit; + $offset = (int)(($offset)?:0); + + // Todo OVER() needs ORDER BY data! + $sql = 'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* '. + 'FROM (' . $query . ') db21) db22 WHERE db22.DC_ROWNUM BETWEEN ' . ($offset+1) .' AND ' . ($offset+$limit); + return $sql; + } + + /** + * returns the position of the first occurrence of substring $substr in string $str + * + * @param string $substr literal string to find + * @param string $str literal string + * @param int $pos position to start at, beginning of string by default + * @return integer + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } else { + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + } + + /** + * return string to call a function to get a substring inside an SQL statement + * + * Note: Not SQL92, but common functionality. + * + * SQLite only supports the 2 parameter variant of this function + * + * @param string $value an sql string literal or column name/alias + * @param integer $from where to start the substring portion + * @param integer $len the substring portion length + * @return string + */ + public function getSubstringExpression($value, $from, $len = null) + { + if ($len === null) + return 'SUBSTR(' . $value . ', ' . $from . ')'; + else { + return 'SUBSTR(' . $value . ', ' . $from . ', ' . $len . ')'; + } + } + public function supportsIdentityColumns() { return true; @@ -397,4 +504,17 @@ class Db2Platform extends AbstractPlatform { return true; } + + /** + * Gets the character casing of a column in an SQL result set of this platform. + * + * DB2 returns all column names in SQL result sets in uppercase. + * + * @param string $column The column name for which to get the correct character casing. + * @return string The column name in the character casing used in SQL result sets. + */ + public function getSQLResultCasing($column) + { + return strtoupper($column); + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php b/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php index b3d802bac..656c9c00b 100644 --- a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php @@ -187,8 +187,14 @@ class Db2SchemaManager extends AbstractSchemaManager protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, \CASE_LOWER); - $pos = strpos($view['text'], ' AS '); - $sql = substr($view['text'], $pos+4); + // sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199 + //$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']); + if (!is_resource($view['text'])) { + $pos = strpos($view['text'], ' AS '); + $sql = substr($view['text'], $pos+4); + } else { + $sql = ''; + } return new View($view['name'], $sql); } diff --git a/lib/Doctrine/DBAL/Types/ArrayType.php b/lib/Doctrine/DBAL/Types/ArrayType.php index 4eb79af24..672710365 100644 --- a/lib/Doctrine/DBAL/Types/ArrayType.php +++ b/lib/Doctrine/DBAL/Types/ArrayType.php @@ -40,6 +40,7 @@ class ArrayType extends Type public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { + $value = (is_resource($value)) ? stream_get_contents($value) : $value; return unserialize($value); } diff --git a/lib/Doctrine/DBAL/Types/ObjectType.php b/lib/Doctrine/DBAL/Types/ObjectType.php index 6b59f5757..668746f36 100644 --- a/lib/Doctrine/DBAL/Types/ObjectType.php +++ b/lib/Doctrine/DBAL/Types/ObjectType.php @@ -21,6 +21,7 @@ class ObjectType extends Type public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { + $value = (is_resource($value)) ? stream_get_contents($value) : $value; return unserialize($value); } diff --git a/lib/Doctrine/DBAL/Types/TextType.php b/lib/Doctrine/DBAL/Types/TextType.php index 1ef008e04..4fcd87270 100644 --- a/lib/Doctrine/DBAL/Types/TextType.php +++ b/lib/Doctrine/DBAL/Types/TextType.php @@ -36,6 +36,19 @@ class TextType extends Type return $platform->getClobTypeDeclarationSQL($fieldDeclaration); } + /** + * Converts a value from its database representation to its PHP representation + * of this type. + * + * @param mixed $value The value to convert. + * @param AbstractPlatform $platform The currently used database platform. + * @return mixed The PHP representation of the value. + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (is_resource($value)) ? stream_get_contents($value) : $value; + } + public function getName() { return Type::TEXT; diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index 26453b2b9..8ddb78fff 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -58,7 +58,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); - $tempTable = $rootClass->getTemporaryIdTableName(); + $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); @@ -95,8 +95,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' - . $platform->getColumnDeclarationListSQL($columnDefinitions) - . ', PRIMARY KEY(' . $idColumnList . '))'; + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; } diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php index add39dc83..902cb0251 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php @@ -64,7 +64,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor $updateItems = $updateClause->updateItems; - $tempTable = $rootClass->getTemporaryIdTableName(); + $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); @@ -131,8 +131,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' - . $platform->getColumnDeclarationListSQL($columnDefinitions) - . ', PRIMARY KEY(' . $idColumnList . '))'; + . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; } diff --git a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php index 31719886b..2132fe5b4 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php +++ b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php @@ -25,6 +25,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\MySqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\PostgreSqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\OracleSchemaManagerTest'); + $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\Db2SchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\ConnectionTest'); return $suite; diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php index 167c9290b..9b8d21bc9 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -284,8 +284,6 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->markTestSkipped('Alter Table is not supported by this platform.'); } - $this->_conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); - $this->createTestTable('alter_table'); $this->createTestTable('alter_table_foreign'); diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 07aa2edf8..23dfa3a73 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -16,13 +16,12 @@ require_once __DIR__ . '/../../TestInit.php'; */ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase { + private $platform = null; + protected function setUp() { $this->useModelSet('cms'); parent::setUp(); - - if ($this->_em->getConnection()->getDatabasePlatform()->getName() == 'oracle') { - $this->markTestSkipped('The ' . __CLASS__ .' does not work with Oracle due to character casing.'); - } + $this->platform = $this->_em->getConnection()->getDatabasePlatform(); } public function testBasicNativeQuery() @@ -38,8 +37,8 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('name'), 'name'); $query = $this->_em->createNativeQuery('SELECT id, name FROM cms_users WHERE username = ?', $rsm); $query->setParameter(1, 'romanb'); @@ -70,11 +69,11 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addFieldResult('u', 'status', 'status'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('name'), 'name'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('status'), 'status'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'u', 'phonenumbers'); - $rsm->addFieldResult('p', 'phonenumber', 'phonenumber'); + $rsm->addFieldResult('p', $this->platform->getSQLResultCasing('phonenumber'), 'phonenumber'); $query = $this->_em->createNativeQuery('SELECT id, name, status, phonenumber FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', $rsm); $query->setParameter(1, 'romanb'); @@ -115,14 +114,14 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addFieldResult('u', 'id', 'id'); - $rsm->addFieldResult('u', 'name', 'name'); - $rsm->addFieldResult('u', 'status', 'status'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('name'), 'name'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('status'), 'status'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsAddress', 'a', 'u', 'address'); - $rsm->addFieldResult('a', 'a_id', 'id'); - $rsm->addFieldResult('a', 'country', 'country'); - $rsm->addFieldResult('a', 'zip', 'zip'); - $rsm->addFieldResult('a', 'city', 'city'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('a_id'), 'id'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('country'), 'country'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('zip'), 'zip'); + $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('city'), 'city'); $query = $this->_em->createNativeQuery('SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', $rsm); $query->setParameter(1, 'romanb'); diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 9e02cb285..2d1f370b0 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -251,7 +251,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $queries = ""; for($i = count($this->_sqlLoggerStack->queries)-1; $i > max(count($this->_sqlLoggerStack->queries)-25, 0); $i--) { $query = $this->_sqlLoggerStack->queries[$i]; - $params = array_map(function($p) { return "'".$p."'"; }, $query['params'] ?: array()); + $params = array_map(function($p) { if (is_object($p)) return get_class($p); else return "'".$p."'"; }, $query['params'] ?: array()); $queries .= ($i+1).". SQL: '".$query['sql']."' Params: ".implode(", ", $params).PHP_EOL; } From 4b71afe7c28c22dd3da37ac44d46ebff9b5a5a78 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Sun, 18 Apr 2010 22:47:03 +0200 Subject: [PATCH 30/56] Improving metadata caching performance by only serializing what is absolutely necessary. --- lib/Doctrine/Common/Annotations/Parser.php | 4 +- .../ORM/Mapping/AssociationMapping.php | 55 ++++++++++++++- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 69 +++++++++++++------ .../ORM/Mapping/ManyToManyMapping.php | 23 ++++++- lib/Doctrine/ORM/Mapping/OneToManyMapping.php | 23 ++++++- lib/Doctrine/ORM/Mapping/OneToOneMapping.php | 24 ++++++- .../Tests/ORM/Mapping/ClassMetadataTest.php | 2 + 7 files changed, 170 insertions(+), 30 deletions(-) diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index bde253d58..de373c3f8 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -1,7 +1,5 @@ _isNestedAnnotation && $this->_lexer->lookahead != null && + ( ! $this->_isNestedAnnotation && $this->_lexer->lookahead != null && ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && ! $this->_lexer->isNextToken(Lexer::T_AT)) || ! class_exists($name, false) diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index 18915ba3c..881f878fe 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -340,5 +340,58 @@ abstract class AssociationMapping ? $platform->quoteIdentifier($this->joinTable['name']) : $this->joinTable['name']; } - + + /** + * Determines which fields get serialized. + * + * It is only serialized what is necessary for best unserialization performance. + * That means any metadata properties that are not set or empty or simply have + * their default value are NOT serialized. + * + * @return array The names of all the fields that should be serialized. + */ + public function __sleep() + { + $serialized = array( + 'sourceEntityName', + 'targetEntityName', + 'sourceFieldName' + ); + + if ($this->isCascadeDetach) { + $serialized[] = 'isCascadeDetach'; + } + if ($this->isCascadeMerge) { + $serialized[] = 'isCascadeMerge'; + } + if ($this->isCascadePersist) { + $serialized[] = 'isCascadePersist'; + } + if ($this->isCascadeRefresh) { + $serialized[] = 'isCascadeRefresh'; + } + if ($this->isCascadeRemove) { + $serialized[] = 'isCascadeRemove'; + } + if ( ! $this->isOwningSide) { + $serialized[] = 'isOwningSide'; + } + if ($this->mappedBy) { + $serialized[] = 'mappedBy'; + } + if ($this->inversedBy) { + $serialized[] = 'inversedBy'; + } + if ($this->joinTable) { + $serialized[] = 'joinTable'; + } + if ($this->inherited) { + $serialized[] = 'inherited'; + } + if ($this->declared) { + $serialized[] = 'declared'; + } + + return $serialized; + } } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index c358cc007..a659e8235 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -264,8 +264,12 @@ class ClassMetadata extends ClassMetadataInfo /** * Determines which fields get serialized. + * + * It is only serialized what is necessary for best unserialization performance. + * That means any metadata properties that are not set or empty or simply have + * their default value are NOT serialized. * - * Parts that are NOT serialized because they can not be properly unserialized: + * Parts that are also NOT serialized because they can not be properly unserialized: * - reflClass (ReflectionClass) * - reflFields (ReflectionProperty array) * @@ -273,31 +277,56 @@ class ClassMetadata extends ClassMetadataInfo */ public function __sleep() { - return array( - 'associationMappings', // unserialization "bottleneck" with many associations - 'changeTrackingPolicy', + // This metadata is always serialized/cached. + $serialized = array( + 'associationMappings', 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] - 'customRepositoryClassName', - 'discriminatorColumn', - 'discriminatorValue', - 'discriminatorMap', - 'fieldMappings',//TODO: Not all of this stuff needs to be serialized. Only type, columnName and fieldName. - 'fieldNames', - 'generatorType', + 'fieldMappings', + 'fieldNames', 'identifier', - 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. - 'inheritanceType', - 'isIdentifierComposite', - 'isMappedSuperclass', - 'isVersioned', - 'lifecycleCallbacks', + 'isIdentifierComposite', // TODO: REMOVE 'name', - 'parentClasses', 'table', 'rootEntityName', - 'subClasses', - 'versionField' + 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. ); + + // The rest of the metadata is only serialized if necessary. + if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) { + $serialized[] = 'changeTrackingPolicy'; + } + + if ($this->customRepositoryClassName) { + $serialized[] = 'customRepositoryClassName'; + } + + if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) { + $serialized[] = 'inheritanceType'; + $serialized[] = 'discriminatorColumn'; + $serialized[] = 'discriminatorValue'; + $serialized[] = 'discriminatorMap'; + $serialized[] = 'parentClasses'; + $serialized[] = 'subClasses'; + } + + if ($this->generatorType != self::GENERATOR_TYPE_NONE) { + $serialized[] = 'generatorType'; + } + + if ($this->isMappedSuperclass) { + $serialized[] = 'isMappedSuperclass'; + } + + if ($this->isVersioned) { + $serialized[] = 'isVersioned'; + $serialized[] = 'versionField'; + } + + if ($this->lifecycleCallbacks) { + $serialized[] = 'lifecycleCallbacks'; + } + + return $serialized; } /** diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php index 95aad2b41..35f7e451f 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -1,7 +1,5 @@ orderBy) { + $serialized[] = 'orderBy'; + } + return $serialized; + } } diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php index 0c4a2bb4f..5860c625e 100644 --- a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -1,7 +1,5 @@ loadOneToManyCollection($this, $conditions, $targetCollection); } + + /** + * Determines which fields get serialized. + * + * It is only serialized what is necessary for best unserialization performance. + * That means any metadata properties that are not set or empty or simply have + * their default value are NOT serialized. + * + * @return array The names of all the fields that should be serialized. + */ + public function __sleep() + { + $serialized = parent::__sleep(); + if ($this->orderBy) { + $serialized[] = 'orderBy'; + } + if ($this->orphanRemoval) { + $serialized[] = 'orphanRemoval'; + } + return $serialized; + } } diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index c82558fb4..89a97449e 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -1,7 +1,5 @@ orphanRemoval) { + $serialized[] = 'orphanRemoval'; + } + return $serialized; + } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index b31f5077c..92231ae8e 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -20,8 +20,10 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $cm->rootEntityName); $this->assertEquals(array(), $cm->subClasses); $this->assertEquals(array(), $cm->parentClasses); + $this->assertEquals(ClassMetadata::INHERITANCE_TYPE_NONE, $cm->inheritanceType); // Customize state + $cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); $cm->setSubclasses(array("One", "Two", "Three")); $cm->setParentClasses(array("UserParent")); $cm->setCustomRepositoryClass("UserRepository"); From b7cac8c310676c700a7cf406e9ca1fda0a4025e0 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 20 Apr 2010 23:20:42 +0200 Subject: [PATCH 31/56] Fixed pecl/ibm_db2 Driver and Connection to run smoothly against the complete test-suite (depending on a c-patch to the extension though) --- .../DBAL/Driver/IbmDb2/Db2Connection.php | 10 ++-------- .../DBAL/Driver/IbmDb2/Db2Statement.php | 20 +++++++++++++++---- lib/Doctrine/DBAL/Platforms/Db2Platform.php | 16 ++++++--------- .../ORM/Functional/AdvancedDqlQueryTest.php | 2 +- .../ORM/Functional/DefaultValuesTest.php | 2 +- .../ManyToManyBasicAssociationTest.php | 6 +++++- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php index bc180fac6..2d13ce03f 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php @@ -71,18 +71,12 @@ class Db2Connection implements \Doctrine\DBAL\Driver\Connection { $stmt = $this->prepare($statement); $stmt->execute(); - return $stmt; + return $stmt->rowCount(); } function lastInsertId($name = null) { - $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1'; - if ($stmt = $this->query($sql)) { - if ($col = $stmt->fetchColumn()) { - return $col; - } - } - return false; + return db2_last_insert_id($this->_conn); } function beginTransaction() diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php index c4594ff04..d25269cc3 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php +++ b/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php @@ -25,6 +25,8 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement { private $_stmt = null; + private $_bindParam = array(); + /** * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG * @var @@ -54,7 +56,7 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement */ function bindValue($param, $value, $type = null) { - return $this->bindParam($param, $variable, $type); + return $this->bindParam($param, $value, $type); } /** @@ -81,7 +83,9 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement */ function bindParam($column, &$variable, $type = null) { - if (!$type && isset(self::$_typeMap[$type])) { + $this->_bindParam[$column] =& $variable; + + if ($type && isset(self::$_typeMap[$type])) { $type = self::$_typeMap[$type]; } else { $type = DB2_CHAR; @@ -90,6 +94,7 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) { throw new Db2Exception(db2_stmt_errormsg()); } + return true; } /** @@ -103,6 +108,8 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement return false; } + $this->_bindParam = array(); + db2_free_result($this->_stmt); $ret = db2_free_stmt($this->_stmt); $this->_stmt = false; return $ret; @@ -171,12 +178,17 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement return false; } - $retval = true; + /*$retval = true; if ($params !== null) { $retval = @db2_execute($this->_stmt, $params); } else { $retval = @db2_execute($this->_stmt); + }*/ + if ($params === null) { + ksort($this->_bindParam); + $params = array_values($this->_bindParam); } + $retval = @db2_execute($this->_stmt, $params); if ($retval === false) { throw new Db2Exception(db2_stmt_errormsg()); @@ -260,7 +272,7 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement function fetchColumn($columnIndex = 0) { $row = $this->fetch(\PDO::FETCH_NUM); - if (!$row && isset($row[$columnIndex])) { + if ($row && isset($row[$columnIndex])) { return $row[$columnIndex]; } return false; diff --git a/lib/Doctrine/DBAL/Platforms/Db2Platform.php b/lib/Doctrine/DBAL/Platforms/Db2Platform.php index b02d2eade..96cb18870 100644 --- a/lib/Doctrine/DBAL/Platforms/Db2Platform.php +++ b/lib/Doctrine/DBAL/Platforms/Db2Platform.php @@ -282,7 +282,7 @@ class Db2Platform extends AbstractPlatform */ public function getCurrentDateSQL() { - return 'current date'; + return 'VALUES CURRENT DATE'; } /** @@ -292,7 +292,7 @@ class Db2Platform extends AbstractPlatform */ public function getCurrentTimeSQL() { - return 'current time'; + return 'VALUES CURRENT TIME'; } /** @@ -300,10 +300,11 @@ class Db2Platform extends AbstractPlatform * * @return string */ - /*public function getCurrentTimestampSQL() + + public function getCurrentTimestampSQL() { - return 'current timestamp'; - }*/ + return "VALUES CURRENT TIMESTAMP"; + } /** * Obtain DBMS specific SQL code portion needed to set an index @@ -437,11 +438,6 @@ class Db2Platform extends AbstractPlatform return "SESSION." . $tableName; } - public function getCurrentTimestampSQL() - { - return "VALUES CURRENT TIMESTAMP"; - } - public function modifyLimitQuery($query, $limit, $offset = null) { if ($limit === null && $offset === null) { diff --git a/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php index be75c2c1e..6a994c944 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php @@ -122,7 +122,7 @@ class AdvancedDqlQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testUpdateAs() { $dql = 'UPDATE Doctrine\Tests\Models\Company\CompanyEmployee AS p SET p.salary = 1'; - $this->_em->createQuery($dql)->getResult(); + $this->_em->createQuery($dql)->execute(); $this->assertTrue(count($this->_em->createQuery( 'SELECT count(p.id) FROM Doctrine\Tests\Models\Company\CompanyEmployee p WHERE p.salary = 1')->getResult()) > 0); diff --git a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php index 71edf4dc2..873f0d938 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php @@ -28,7 +28,7 @@ class DefaultValuesTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->name = 'romanb'; $this->_em->persist($user); $this->_em->flush(); - $this->_em->clear(); + $this->_em->clear(); $userId = $user->id; // e.g. from $_REQUEST $user2 = $this->_em->getReference(get_class($user), $userId); diff --git a/tests/Doctrine/Tests/ORM/Functional/ManyToManyBasicAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/ManyToManyBasicAssociationTest.php index bdabf6206..0ccb8db45 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ManyToManyBasicAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ManyToManyBasicAssociationTest.php @@ -47,7 +47,9 @@ class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCa // Get user $user = $uRep->findOneById($user->getId()); - + + $this->assertNotNull($user, "Has to return exactly one entry."); + $this->assertFalse($user->getGroups()->isInitialized()); // Check groups @@ -89,6 +91,8 @@ class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCa // Association should not exist $user2 = $this->_em->find(get_class($user), $user->getId()); + + $this->assertNotNull($user2, "Has to return exactly one entry."); $this->assertEquals(0, $user2->getGroups()->count()); } From 1f656a16ac664210cdcc6d369cecdaac2f98e84b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 21 Apr 2010 20:23:58 +0200 Subject: [PATCH 32/56] Renamed Db2 to DB2 --- .../Db2Connection.php => IBMDB2/DB2Connection.php} | 14 +++++++------- .../{IbmDb2/Db2Driver.php => IBMDB2/DB2Driver.php} | 10 +++++----- .../Db2Exception.php => IBMDB2/DB2Exception.php} | 4 ++-- .../Db2Statement.php => IBMDB2/DB2Statement.php} | 10 +++++----- lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php | 4 ++-- lib/Doctrine/DBAL/DriverManager.php | 2 +- .../Platforms/{Db2Platform.php => DB2Platform.php} | 0 .../{Db2SchemaManager.php => DB2SchemaManager.php} | 2 +- 8 files changed, 23 insertions(+), 23 deletions(-) rename lib/Doctrine/DBAL/Driver/{IbmDb2/Db2Connection.php => IBMDB2/DB2Connection.php} (88%) rename lib/Doctrine/DBAL/Driver/{IbmDb2/Db2Driver.php => IBMDB2/DB2Driver.php} (93%) rename lib/Doctrine/DBAL/Driver/{IbmDb2/Db2Exception.php => IBMDB2/DB2Exception.php} (92%) rename lib/Doctrine/DBAL/Driver/{IbmDb2/Db2Statement.php => IBMDB2/DB2Statement.php} (97%) rename lib/Doctrine/DBAL/Platforms/{Db2Platform.php => DB2Platform.php} (100%) rename lib/Doctrine/DBAL/Schema/{Db2SchemaManager.php => DB2SchemaManager.php} (99%) diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php similarity index 88% rename from lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php rename to lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php index 2d13ce03f..5d706de7b 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Connection.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php @@ -19,9 +19,9 @@ * . */ -namespace Doctrine\DBAL\Driver\IbmDb2; +namespace Doctrine\DBAL\Driver\IBMDB2; -class Db2Connection implements \Doctrine\DBAL\Driver\Connection +class DB2Connection implements \Doctrine\DBAL\Driver\Connection { private $_conn = null; @@ -35,7 +35,7 @@ class Db2Connection implements \Doctrine\DBAL\Driver\Connection $this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions); } if (!$this->_conn) { - throw new Db2Exception(db2_conn_errormsg()); + throw new DB2Exception(db2_conn_errormsg()); } } @@ -43,9 +43,9 @@ class Db2Connection implements \Doctrine\DBAL\Driver\Connection { $stmt = @db2_prepare($this->_conn, $sql); if (!$stmt) { - throw new Db2Exception(db2_stmt_errormsg()); + throw new DB2Exception(db2_stmt_errormsg()); } - return new Db2Statement($stmt); + return new DB2Statement($stmt); } function query() @@ -87,7 +87,7 @@ class Db2Connection implements \Doctrine\DBAL\Driver\Connection function commit() { if (!db2_commit($this->_conn)) { - throw new Db2Exception(db2_conn_errormsg($this->_conn)); + throw new DB2Exception(db2_conn_errormsg($this->_conn)); } db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); } @@ -95,7 +95,7 @@ class Db2Connection implements \Doctrine\DBAL\Driver\Connection function rollBack() { if (!db2_rollback($this->_conn)) { - throw new Db2Exception(db2_conn_errormsg($this->_conn)); + throw new DB2Exception(db2_conn_errormsg($this->_conn)); } db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); } diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php similarity index 93% rename from lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php rename to lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php index 3522d0e37..6209d5ffd 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Driver.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php @@ -19,7 +19,7 @@ * . */ -namespace Doctrine\DBAL\Driver\IbmDb2; +namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver, Doctrine\DBAL\Connection; @@ -33,7 +33,7 @@ use Doctrine\DBAL\Driver, * @version $Revision$ * @author Benjamin Eberlei */ -class Db2Driver implements Driver +class DB2Driver implements Driver { /** * Attempts to create a connection with the database. @@ -63,7 +63,7 @@ class Db2Driver implements Driver $password = null; } - return new Db2Connection($params, $username, $password, $driverOptions); + return new DB2Connection($params, $username, $password, $driverOptions); } /** @@ -74,7 +74,7 @@ class Db2Driver implements Driver */ public function getDatabasePlatform() { - return new \Doctrine\DBAL\Platforms\Db2Platform; + return new \Doctrine\DBAL\Platforms\DB2Platform; } /** @@ -86,7 +86,7 @@ class Db2Driver implements Driver */ public function getSchemaManager(Connection $conn) { - return new \Doctrine\DBAL\Schema\Db2SchemaManager($conn); + return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn); } /** diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php similarity index 92% rename from lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php rename to lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php index 76d992b50..b2a8de63a 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Exception.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php @@ -19,9 +19,9 @@ * . */ -namespace Doctrine\DBAL\Driver\IbmDb2; +namespace Doctrine\DBAL\Driver\IBMDB2; -class Db2Exception extends \Exception +class DB2Exception extends \Exception { } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php similarity index 97% rename from lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php rename to lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php index d25269cc3..41bff920e 100644 --- a/lib/Doctrine/DBAL/Driver/IbmDb2/Db2Statement.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php @@ -19,9 +19,9 @@ * . */ -namespace Doctrine\DBAL\Driver\IbmDb2; +namespace Doctrine\DBAL\Driver\IBMDB2; -class Db2Statement implements \Doctrine\DBAL\Driver\Statement +class DB2Statement implements \Doctrine\DBAL\Driver\Statement { private $_stmt = null; @@ -92,7 +92,7 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement } if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) { - throw new Db2Exception(db2_stmt_errormsg()); + throw new DB2Exception(db2_stmt_errormsg()); } return true; } @@ -191,7 +191,7 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement $retval = @db2_execute($this->_stmt, $params); if ($retval === false) { - throw new Db2Exception(db2_stmt_errormsg()); + throw new DB2Exception(db2_stmt_errormsg()); } return $retval; } @@ -233,7 +233,7 @@ class Db2Statement implements \Doctrine\DBAL\Driver\Statement case \PDO::FETCH_NUM: return db2_fetch_array($this->_stmt); default: - throw new Db2Exception("Given Fetch-Style " . $fetchStyle . " is not supported."); + throw new DB2Exception("Given Fetch-Style " . $fetchStyle . " is not supported."); } } diff --git a/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php index 3f05f5c75..844f2ab3f 100644 --- a/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php @@ -87,7 +87,7 @@ class Driver implements \Doctrine\DBAL\Driver */ public function getDatabasePlatform() { - return new \Doctrine\DBAL\Platforms\Db2Platform; + return new \Doctrine\DBAL\Platforms\DB2Platform; } /** @@ -99,7 +99,7 @@ class Driver implements \Doctrine\DBAL\Driver */ public function getSchemaManager(Connection $conn) { - return new \Doctrine\DBAL\Schema\Db2SchemaManager($conn); + return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn); } /** diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 21a5c09d4..39c88f1b8 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -44,7 +44,7 @@ final class DriverManager 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', 'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver', 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', - 'ibm_db2' => 'Doctrine\DBAL\Driver\IbmDb2\Db2Driver', + 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', ); diff --git a/lib/Doctrine/DBAL/Platforms/Db2Platform.php b/lib/Doctrine/DBAL/Platforms/DB2Platform.php similarity index 100% rename from lib/Doctrine/DBAL/Platforms/Db2Platform.php rename to lib/Doctrine/DBAL/Platforms/DB2Platform.php diff --git a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php b/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php similarity index 99% rename from lib/Doctrine/DBAL/Schema/Db2SchemaManager.php rename to lib/Doctrine/DBAL/Schema/DB2SchemaManager.php index 656c9c00b..9c1466715 100644 --- a/lib/Doctrine/DBAL/Schema/Db2SchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php @@ -30,7 +30,7 @@ namespace Doctrine\DBAL\Schema; * @version $Revision$ * @author Benjamin Eberlei */ -class Db2SchemaManager extends AbstractSchemaManager +class DB2SchemaManager extends AbstractSchemaManager { /** * Return a list of all tables in the current database From 5ecca4f5e0817d0bc5769e20c8588529249cfae5 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 22 Apr 2010 11:32:01 -0300 Subject: [PATCH 33/56] [2.0][DDC-524] Fixed issue with UPDATE/DELETE statements generating wrong SQL when using Association Paths. --- lib/Doctrine/ORM/Query/SqlWalker.php | 8 ++++++-- .../Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index f624798fd..ece61bd50 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -478,8 +478,12 @@ class SqlWalker implements TreeWalker if (count($assoc->sourceToTargetKeyColumns) > 1) { throw QueryException::associationPathCompositeKeyNotSupported(); } - $sql .= $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.' - . reset($assoc->targetToSourceKeyColumns); + + if ($this->_useSqlTableAliases) { + $sql .= $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.'; + } + + $sql .= reset($assoc->targetToSourceKeyColumns); } else { // 2- Inverse side: NOT (YET?) SUPPORTED throw QueryException::associationPathInverseSideNotSupported(); diff --git a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php index 969ee35ca..506dacb8b 100644 --- a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php @@ -159,4 +159,12 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'UPDATE cms_users SET status = ? WHERE id BETWEEN ? AND ?' ); } + + public function testSingleValuedAssociationFieldInWhere() + { + $this->assertSqlGeneration( + "UPDATE Doctrine\Tests\Models\CMS\CmsPhonenumber p SET p.phonenumber = 1234 WHERE p.user = ?1", + "UPDATE cms_phonenumbers SET phonenumber = 1234 WHERE user_id = ?" + ); + } } From 825cd7f47822a55784cdcbc7747210439088e5b9 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 22 Apr 2010 12:17:58 -0300 Subject: [PATCH 34/56] [2.0][DDC-529] Fixed undeclared in dbal:run-sql command. Thanks for provided patch, Hannes. --- lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php index 0e6c05c82..a4fdaccd0 100644 --- a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php +++ b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php @@ -78,7 +78,7 @@ EOT if (preg_match('/^select/i', $sql)) { $resultSet = $conn->fetchAll($sql); } else { - $resultSet = $em->getConnection()->executeUpdate($sql); + $resultSet = $conn->executeUpdate($sql); } \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth); From 841008c46107e7ff68e2d49d9aff1a5b5af04e03 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 23 Apr 2010 00:51:32 -0300 Subject: [PATCH 35/56] [2.0] Coding Standards fixes, added missing docblocks, removed some dependencies from Common package (in Annotations component), etc. --- .../Common/Annotations/Annotation.php | 24 +++++++++-- .../Annotations/AnnotationException.php | 20 +++++++-- .../Common/Annotations/AnnotationReader.php | 18 ++++---- lib/Doctrine/Common/Annotations/Lexer.php | 42 ++++++++++++++----- lib/Doctrine/Common/Annotations/Parser.php | 10 +++-- lib/Doctrine/ORM/EntityManager.php | 3 +- lib/Doctrine/ORM/Query/Parser.php | 24 ++++++----- 7 files changed, 99 insertions(+), 42 deletions(-) diff --git a/lib/Doctrine/Common/Annotations/Annotation.php b/lib/Doctrine/Common/Annotations/Annotation.php index ba7704be0..e2bf42bb5 100644 --- a/lib/Doctrine/Common/Annotations/Annotation.php +++ b/lib/Doctrine/Common/Annotations/Annotation.php @@ -27,7 +27,8 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel @@ -52,14 +53,29 @@ class Annotation $this->$key = $value; } } - + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name + */ public function __get($name) { - throw new \BadMethodCallException("Unknown annotation property '$name' on annotation '".get_class($this)."'."); + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); } + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unkown property name + * @param mixed $value Property value + */ public function __set($name, $value) { - throw new \BadMethodCallException("Unknown annotation property '$name' on annotation '".get_class($this)."'."); + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); } } \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/AnnotationException.php b/lib/Doctrine/Common/Annotations/AnnotationException.php index 7610b16ef..bdee49420 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationException.php +++ b/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -27,20 +27,32 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ -class AnnotationException extends \Doctrine\Common\CommonException +class AnnotationException extends \Exception { + /** + * Creates a new AnnotationException describing a Syntax error. + * + * @param string $message Exception message + * @return AnnotationException + */ public static function syntaxError($message) { return new self('[Syntax Error] ' . $message); } - - public static function semanticalError($message) + /** + * Creates a new AnnotationException describing a Semantical error. + * + * @param string $message Exception message + * @return AnnotationException + */ + public static function semanticalError($message) { return new self('[Semantical Error] ' . $message); } diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php index bed578e35..dbbf8dff6 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -32,7 +32,7 @@ use \ReflectionClass, * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage @@ -46,7 +46,7 @@ class AnnotationReader * @var string * @static */ - private static $CACHE_SALT = "@"; + private static $CACHE_SALT = '@'; /** * Annotations Parser @@ -56,15 +56,14 @@ class AnnotationReader private $_parser; /** - * Cache machanism to store processed Annotations + * Cache mechanism to store processed Annotations * * @var Doctrine\Common\Cache\Cache */ private $_cache; /** - * Constructor. Initializes a new AnnotationReader that uses the given - * Cache provider. + * Constructor. Initializes a new AnnotationReader that uses the given Cache provider. * * @param Cache $cache The cache provider to use. If none is provided, ArrayCache is used. */ @@ -112,7 +111,7 @@ class AnnotationReader return $data; } - $annotations = $this->_parser->parse($class->getDocComment(), "class ".$class->getName()); + $annotations = $this->_parser->parse($class->getDocComment(), 'class ' . $class->getName()); $this->_cache->save($cacheKey, $annotations, null); return $annotations; @@ -128,6 +127,7 @@ class AnnotationReader public function getClassAnnotation(ReflectionClass $class, $annotation) { $annotations = $this->getClassAnnotations($class); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } @@ -148,7 +148,7 @@ class AnnotationReader return $data; } - $context = "property ".$property->getDeclaringClass()->getName()."::\$".$property->getName(); + $context = 'property ' . $property->getDeclaringClass()->getName() . "::\$" . $property->getName(); $annotations = $this->_parser->parse($property->getDocComment(), $context); $this->_cache->save($cacheKey, $annotations, null); @@ -165,6 +165,7 @@ class AnnotationReader public function getPropertyAnnotation(ReflectionProperty $property, $annotation) { $annotations = $this->getPropertyAnnotations($property); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } @@ -185,7 +186,7 @@ class AnnotationReader return $data; } - $context = "method ".$method->getDeclaringClass()->getName()."::".$method->getName()."()"; + $context = 'method ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()'; $annotations = $this->_parser->parse($method->getDocComment(), $context); $this->_cache->save($cacheKey, $annotations, null); @@ -202,6 +203,7 @@ class AnnotationReader public function getMethodAnnotation(ReflectionMethod $method, $annotation) { $annotations = $this->getMethodAnnotations($method); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } } \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/Lexer.php b/lib/Doctrine/Common/Annotations/Lexer.php index cb32f0cc2..a6b8c0675 100644 --- a/lib/Doctrine/Common/Annotations/Lexer.php +++ b/lib/Doctrine/Common/Annotations/Lexer.php @@ -27,7 +27,8 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel @@ -80,7 +81,7 @@ class Lexer extends \Doctrine\Common\Lexer $newVal = $this->_getNumeric($value); // Checking numeric value - if ($newVal !== false){ + if ($newVal !== false) { $value = $newVal; return (strpos($value, '.') !== false || stripos($value, 'e') !== false) @@ -93,16 +94,34 @@ class Lexer extends \Doctrine\Common\Lexer return self::T_STRING; } else { switch (strtolower($value)) { - case '@': return self::T_AT; - case ',': return self::T_COMMA; - case '(': return self::T_OPEN_PARENTHESIS; - case ')': return self::T_CLOSE_PARENTHESIS; - case '{': return self::T_OPEN_CURLY_BRACES; + case '@': + return self::T_AT; + + case ',': + return self::T_COMMA; + + case '(': + return self::T_OPEN_PARENTHESIS; + + case ')': + return self::T_CLOSE_PARENTHESIS; + + case '{': + return self::T_OPEN_CURLY_BRACES; + case '}': return self::T_CLOSE_CURLY_BRACES; - case '=': return self::T_EQUALS; - case '\\': return self::T_NAMESPACE_SEPARATOR; - case 'true': return self::T_TRUE; - case 'false': return self::T_FALSE; + case '=': + return self::T_EQUALS; + + case '\\': + return self::T_NAMESPACE_SEPARATOR; + + case 'true': + return self::T_TRUE; + + case 'false': + return self::T_FALSE; + default: if (ctype_alpha($value[0]) || $value[0] === '_') { return self::T_IDENTIFIER; @@ -126,6 +145,7 @@ class Lexer extends \Doctrine\Common\Lexer if ( ! is_scalar($value)) { return false; } + // Checking for valid numeric numbers: 1.234, -1.234e-2 if (is_numeric($value)) { return $value; diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index bde253d58..eba4ae42a 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -27,11 +27,11 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel - * @author Benjamin Eberlei */ class Parser { @@ -173,9 +173,10 @@ class Parser $message .= "'{$token['value']}' at position {$token['position']}"; } - if(strlen($this->_context)) { - $message .= ' in '.$this->_context; + if (strlen($this->_context)) { + $message .= ' in ' . $this->_context; } + $message .= '.'; throw AnnotationException::syntaxError($message); @@ -411,6 +412,7 @@ class Parser foreach ($values as $value) { list ($key, $val) = $value; + if ($key !== null) { $array[$key] = $val; } else { diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index e2db2b0d0..c4aa9bb55 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -440,7 +440,8 @@ class EntityManager * * @param object $entity The entity to copy. * @return object The new entity. - * @todo Implementation or remove. + * @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e: + * Fatal error: Maximum function nesting level of '100' reached, aborting! */ public function copy($entity, $deep = false) { diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 960ae426d..b15248689 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -203,12 +203,15 @@ class Parser // Process any deferred validations of some nodes in the AST. // This also allows post-processing of the AST for modification purposes. $this->_processDeferredIdentificationVariables(); + if ($this->_deferredPartialObjectExpressions) { $this->_processDeferredPartialObjectExpressions(); } + if ($this->_deferredPathExpressions) { $this->_processDeferredPathExpressions($AST); } + if ($this->_deferredResultVariables) { $this->_processDeferredResultVariables(); } @@ -456,7 +459,7 @@ class Parser // Check if IdentificationVariable exists in queryComponents if ( ! isset($this->_queryComponents[$identVariable])) { $this->semanticalError( - "'$identVariable' is not defined.", $deferredItem['token'] + "'$identVariable' is not defined.", $deferredItem['token'] ); } @@ -465,14 +468,14 @@ class Parser // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['metadata'])) { $this->semanticalError( - "'$identVariable' does not point to a Class.", $deferredItem['token'] + "'$identVariable' does not point to a Class.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( - "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] + "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } @@ -486,15 +489,15 @@ class Parser foreach ($expr->partialFieldSet as $field) { if ( ! isset($class->fieldMappings[$field])) { $this->semanticalError( - "There is no mapped field named '$field' on class " . $class->name . ".", - $deferredItem['token'] + "There is no mapped field named '$field' on class " . $class->name . ".", + $deferredItem['token'] ); } } if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) { $this->semanticalError( - "The partial field selection of class " . $class->name . " must contain the identifier.", - $deferredItem['token'] + "The partial field selection of class " . $class->name . " must contain the identifier.", + $deferredItem['token'] ); } } @@ -514,7 +517,7 @@ class Parser // Check if ResultVariable exists in queryComponents if ( ! isset($this->_queryComponents[$resultVariable])) { $this->semanticalError( - "'$resultVariable' is not defined.", $deferredItem['token'] + "'$resultVariable' is not defined.", $deferredItem['token'] ); } @@ -523,14 +526,14 @@ class Parser // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['resultVariable'])) { $this->semanticalError( - "'$identVariable' does not point to a ResultVariable.", $deferredItem['token'] + "'$identVariable' does not point to a ResultVariable.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( - "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token'] + "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } @@ -625,6 +628,7 @@ class Parser ), null ); + $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration; $this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent; From 6d29f05d3ade5081646f8733c08a8a44e6788eec Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 23 Apr 2010 14:37:29 -0400 Subject: [PATCH 36/56] Renaming PhpDriver to PHPDriver --- .../ORM/Mapping/Driver/{PhpDriver.php => PHPDriver.php} | 4 ++-- .../{PhpMappingDriverTest.php => PHPMappingDriverTest.php} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/Doctrine/ORM/Mapping/Driver/{PhpDriver.php => PHPDriver.php} (95%) rename tests/Doctrine/Tests/ORM/Mapping/{PhpMappingDriverTest.php => PHPMappingDriverTest.php} (91%) diff --git a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php similarity index 95% rename from lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php rename to lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php index 6b6954b83..99b8a145a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php @@ -30,7 +30,7 @@ use Doctrine\Common\Cache\ArrayCache, Doctrine\ORM\Mapping\Driver\AbstractFileDriver; /** - * The PhpDriver includes php files which just populate ClassMetadataInfo + * The PHPDriver includes php files which just populate ClassMetadataInfo * instances with plain php code * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL @@ -43,7 +43,7 @@ use Doctrine\Common\Cache\ArrayCache, * @author Roman Borschel * @todo Rename: PHPDriver */ -class PhpDriver extends AbstractFileDriver +class PHPDriver extends AbstractFileDriver { /** * {@inheritdoc} diff --git a/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php similarity index 91% rename from tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php rename to tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php index 50bfcb819..08ec30bcf 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata, - Doctrine\ORM\Mapping\Driver\PhpDriver, + Doctrine\ORM\Mapping\Driver\PHPDriver, Doctrine\ORM\Tools\Export\ClassMetadataExporter; require_once __DIR__ . '/../../TestInit.php'; @@ -26,6 +26,6 @@ class PhpMappingDriverTest extends AbstractMappingDriverTest $exporter->export(); */ - return new PhpDriver($path); + return new PHPDriver($path); } } \ No newline at end of file From 27a0058b89d5fc4b413f0b1a7542c57cec60e1c6 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 23 Apr 2010 16:16:16 -0400 Subject: [PATCH 37/56] Initial entry of StaticPHPDriver --- lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php | 1 - .../ORM/Mapping/Driver/StaticPHPDriver.php | 122 ++++++++++++++++++ .../ORM/Mapping/AbstractMappingDriverTest.php | 106 +++++++++++++++ tests/Doctrine/Tests/ORM/Mapping/AllTests.php | 3 +- .../ORM/Mapping/PHPMappingDriverTest.php | 2 +- .../Mapping/StaticPHPMappingDriverTest.php | 17 +++ 6 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/StaticPHPMappingDriverTest.php diff --git a/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php index 99b8a145a..fae3721fa 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php @@ -41,7 +41,6 @@ use Doctrine\Common\Cache\ArrayCache, * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel - * @todo Rename: PHPDriver */ class PHPDriver extends AbstractFileDriver { diff --git a/lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php b/lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php new file mode 100644 index 000000000..01edab71b --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php @@ -0,0 +1,122 @@ +. + */ + +namespace Doctrine\ORM\Mapping\Driver; + +use Doctrine\ORM\Mapping\ClassMetadataInfo, + Doctrine\ORM\Mapping\MappingException; + +/** + * The StaticPHPDriver calls a static loadMetadata() method on your entity + * classes where you can manually populate the ClassMetadata instance. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class StaticPHPDriver implements Driver +{ + private $_paths = array(); + + public function __construct($paths) + { + $this->addPaths((array) $paths); + } + + public function addPaths(array $paths) + { + $this->_paths = array_unique(array_merge($this->_paths, $paths)); + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadataInfo $metadata) + { + call_user_func_array(array($className, 'loadMetadata'), array($metadata)); + } + + /** + * {@inheritDoc} + * @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it? + */ + public function getAllClassNames() + { + if ($this->_classNames !== null) { + return $this->_classNames; + } + + if (!$this->_paths) { + throw MappingException::pathRequired(); + } + + $classes = array(); + $includedFiles = array(); + + foreach ($this->_paths as $path) { + if ( ! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath(); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) { + continue; + } + + $sourceFile = realpath($file->getPathName()); + require_once $sourceFile; + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new \ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) { + $classes[] = $className; + } + } + + $this->_classNames = $classes; + + return $classes; + } + + /** + * {@inheritdoc} + */ + public function isTransient($className) + { + return method_exists($className, 'loadMetadata') ? false : true; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 8b29c6bad..1fdf4a33c 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -3,6 +3,7 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\ORM\Mapping\Driver\XmlDriver, Doctrine\ORM\Mapping\Driver\YamlDriver; @@ -264,4 +265,109 @@ class User { } + + public static function loadMetadata(ClassMetadataInfo $metadata) + { + $metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE); + $metadata->setPrimaryTable(array( + 'name' => 'cms_users', + )); + $metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT); + $metadata->addLifecycleCallback('doStuffOnPrePersist', 'prePersist'); + $metadata->addLifecycleCallback('doOtherStuffOnPrePersistToo', 'prePersist'); + $metadata->addLifecycleCallback('doStuffOnPostPersist', 'postPersist'); + $metadata->mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer', + 'columnName' => 'id', + )); + $metadata->mapField(array( + 'fieldName' => 'name', + 'type' => 'string', + 'length' => 50, + 'unique' => true, + 'nullable' => true, + 'columnName' => 'name', + )); + $metadata->mapField(array( + 'fieldName' => 'email', + 'type' => 'string', + 'columnName' => 'user_email', + 'columnDefinition' => 'CHAR(32) NOT NULL', + )); + $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); + $metadata->mapOneToOne(array( + 'fieldName' => 'address', + 'targetEntity' => 'Doctrine\\Tests\\ORM\\Mapping\\Address', + 'cascade' => + array( + 0 => 'remove', + ), + 'mappedBy' => NULL, + 'inversedBy' => 'user', + 'joinColumns' => + array( + 0 => + array( + 'name' => 'address_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + 'onUpdate' => 'CASCADE' + ), + ), + 'orphanRemoval' => false, + )); + $metadata->mapOneToMany(array( + 'fieldName' => 'phonenumbers', + 'targetEntity' => 'Doctrine\\Tests\\ORM\\Mapping\\Phonenumber', + 'cascade' => + array( + 1 => 'persist', + ), + 'mappedBy' => 'user', + 'orphanRemoval' => false, + 'orderBy' => + array( + 'number' => 'ASC', + ), + )); + $metadata->mapManyToMany(array( + 'fieldName' => 'groups', + 'targetEntity' => 'Doctrine\\Tests\\ORM\\Mapping\\Group', + 'cascade' => + array( + 0 => 'remove', + 1 => 'persist', + 2 => 'refresh', + 3 => 'merge', + 4 => 'detach', + ), + 'mappedBy' => NULL, + 'joinTable' => + array( + 'name' => 'cms_users_groups', + 'joinColumns' => + array( + 0 => + array( + 'name' => 'user_id', + 'referencedColumnName' => 'id', + 'unique' => false, + 'nullable' => false, + ), + ), + 'inverseJoinColumns' => + array( + 0 => + array( + 'name' => 'group_id', + 'referencedColumnName' => 'id', + 'columnDefinition' => 'INT NULL', + ), + ), + ), + 'orderBy' => NULL, + )); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/AllTests.php b/tests/Doctrine/Tests/ORM/Mapping/AllTests.php index db9ceb0e2..dcd5c768c 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AllTests.php @@ -23,7 +23,8 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\XmlMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\YamlMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\AnnotationDriverTest'); - $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\PhpMappingDriverTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\PHPMappingDriverTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\StaticPHPMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataFactoryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataLoadEventTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\BasicInheritanceMappingTest'); diff --git a/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php index 08ec30bcf..b346973cf 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping\ClassMetadata, require_once __DIR__ . '/../../TestInit.php'; -class PhpMappingDriverTest extends AbstractMappingDriverTest +class PHPMappingDriverTest extends AbstractMappingDriverTest { protected function _loadDriver() { diff --git a/tests/Doctrine/Tests/ORM/Mapping/StaticPHPMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/StaticPHPMappingDriverTest.php new file mode 100644 index 000000000..1a5a2074e --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/StaticPHPMappingDriverTest.php @@ -0,0 +1,17 @@ + Date: Mon, 26 Apr 2010 13:02:30 +0200 Subject: [PATCH 38/56] [DDC-497][DDC-500][DDC-342] Fixed. Persister polishing and refactoring. More to come for that, but after beta1. --- .../Common/Annotations/AnnotationReader.php | 5 - lib/Doctrine/Common/Annotations/Parser.php | 5 +- lib/Doctrine/Common/Cache/AbstractCache.php | 5 - lib/Doctrine/Common/ClassLoader.php | 2 - .../Common/Collections/ArrayCollection.php | 19 +- .../Common/Collections/Collection.php | 5 - lib/Doctrine/Common/Lexer.php | 35 +- lib/Doctrine/Common/Version.php | 2 - lib/Doctrine/DBAL/Connection.php | 2 - lib/Doctrine/ORM/EntityNotFoundException.php | 19 +- lib/Doctrine/ORM/EntityRepository.php | 5 - lib/Doctrine/ORM/Event/LifecycleEventArgs.php | 11 +- lib/Doctrine/ORM/Id/AbstractIdGenerator.php | 17 + lib/Doctrine/ORM/Id/AssignedGenerator.php | 5 - lib/Doctrine/ORM/Id/IdentityGenerator.php | 17 + lib/Doctrine/ORM/Id/SequenceGenerator.php | 6 +- .../ORM/Id/SequenceIdentityGenerator.php | 17 + lib/Doctrine/ORM/Id/TableGenerator.php | 20 +- .../ORM/Internal/CommitOrderCalculator.php | 2 - .../Internal/Hydration/AbstractHydrator.php | 5 - .../ORM/Internal/Hydration/ArrayHydrator.php | 2 - .../ORM/Internal/Hydration/IterableResult.php | 2 - .../ORM/Internal/Hydration/ObjectHydrator.php | 6 +- .../ORM/Internal/Hydration/ScalarHydrator.php | 2 - .../Hydration/SingleScalarHydrator.php | 2 - .../ORM/Mapping/AssociationMapping.php | 3 +- lib/Doctrine/ORM/Mapping/Driver/Driver.php | 9 +- .../ORM/Mapping/Driver/DriverChain.php | 16 +- .../ORM/Mapping/ManyToManyMapping.php | 33 +- lib/Doctrine/ORM/Mapping/MappingException.php | 2 - lib/Doctrine/ORM/Mapping/OneToManyMapping.php | 20 +- lib/Doctrine/ORM/Mapping/OneToOneMapping.php | 51 +- lib/Doctrine/ORM/NoResultException.php | 17 + lib/Doctrine/ORM/NonUniqueResultException.php | 17 + lib/Doctrine/ORM/ORMException.php | 17 + lib/Doctrine/ORM/OptimisticLockException.php | 2 - lib/Doctrine/ORM/PersistentCollection.php | 2 - .../AbstractEntityInheritancePersister.php | 4 +- .../Persisters/JoinedSubclassPersister.php | 82 +-- .../ORM/Persisters/SingleTablePersister.php | 36 +- .../Persisters/StandardEntityPersister.php | 527 ++++++++++++------ lib/Doctrine/ORM/Proxy/ProxyFactory.php | 18 +- lib/Doctrine/ORM/Query/SqlWalker.php | 21 +- lib/Doctrine/ORM/UnitOfWork.php | 7 +- .../Functional/ClassTableInheritanceTest2.php | 52 +- .../Functional/SingleTableInheritanceTest.php | 17 +- 46 files changed, 671 insertions(+), 500 deletions(-) diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php index bed578e35..0863ea94f 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index de373c3f8..a90d1f361 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -22,10 +22,7 @@ namespace Doctrine\Common\Annotations; /** * A simple parser for docblock annotations. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel @@ -234,7 +231,7 @@ class Parser $nameParts[] = $this->_lexer->token['value']; } - // Effectively pick the name of class (append default NS if none, grab from NS alias, etc) + // Effectively pick the name of the class (append default NS if none, grab from NS alias, etc) if (count($nameParts) == 1) { if (strpos($nameParts[0], ':')) { list ($alias, $simpleName) = explode(':', $nameParts[0]); diff --git a/lib/Doctrine/Common/Cache/AbstractCache.php b/lib/Doctrine/Common/Cache/AbstractCache.php index ea5b5df5e..fa9e0a505 100644 --- a/lib/Doctrine/Common/Cache/AbstractCache.php +++ b/lib/Doctrine/Common/Cache/AbstractCache.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/Common/ClassLoader.php b/lib/Doctrine/Common/ClassLoader.php index b2c525595..d971fe9a6 100644 --- a/lib/Doctrine/Common/ClassLoader.php +++ b/lib/Doctrine/Common/ClassLoader.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel @@ -39,7 +33,6 @@ class ArrayCollection implements Collection { /** * An array containing the entries of this collection. - * This is the internal php array. * * @var array */ @@ -54,7 +47,7 @@ class ArrayCollection implements Collection { $this->_elements = $elements; } - + /** * Gets the PHP array representation of this collection. * @@ -121,7 +114,7 @@ class ArrayCollection implements Collection * Removes an element with a specific key/index from the collection. * * @param mixed $key - * @return mixed + * @return mixed The removed element or NULL, if no element exists for the given key. */ public function remove($key) { @@ -131,7 +124,7 @@ class ArrayCollection implements Collection return $removed; } - + return null; } @@ -413,6 +406,7 @@ class ArrayCollection implements Collection /** * Returns a string representation of this object. * + * @return string */ public function __toString() { @@ -421,7 +415,6 @@ class ArrayCollection implements Collection /** * Clears the collection. - * */ public function clear() { diff --git a/lib/Doctrine/Common/Collections/Collection.php b/lib/Doctrine/Common/Collections/Collection.php index 6b415baaf..41af440b5 100644 --- a/lib/Doctrine/Common/Collections/Collection.php +++ b/lib/Doctrine/Common/Collections/Collection.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/Common/Lexer.php b/lib/Doctrine/Common/Lexer.php index e4d39ce95..3949e11aa 100644 --- a/lib/Doctrine/Common/Lexer.php +++ b/lib/Doctrine/Common/Lexer.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel + * @todo Rename: AbstractLexer */ abstract class Lexer { @@ -50,7 +46,7 @@ abstract class Lexer private $_peek = 0; /** - * @var array The next token in the query string. + * @var array The next token in the input. */ public $lookahead; @@ -60,9 +56,12 @@ abstract class Lexer public $token; /** - * Inputs data to be tokenized + * Sets the input data to be tokenized. * - * @param string $input input to be tokenized + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. */ public function setInput($input) { @@ -72,20 +71,18 @@ abstract class Lexer } /** - * Resets the scanner - * + * Resets the lexer. */ public function reset() { $this->lookahead = null; - $this->token = null; - $this->_peek = 0; + $this->token = null; + $this->_peek = 0; $this->_position = 0; } - + /** - * Resets the peek pointer to 0 - * + * Resets the peek pointer to 0. */ public function resetPeek() { @@ -93,7 +90,7 @@ abstract class Lexer } /** - * Resets the lexer position on the input to the given position + * Resets the lexer position on the input to the given position. * * @param integer $position Position to place the lexical scanner */ @@ -235,14 +232,14 @@ abstract class Lexer } /** - * Lexical catchable patterns + * Lexical catchable patterns. * * @return array */ abstract protected function getCatchablePatterns(); /** - * Lexical non-catchable patterns + * Lexical non-catchable patterns. * * @return array */ diff --git a/lib/Doctrine/Common/Version.php b/lib/Doctrine/Common/Version.php index fd1b499f0..c01da049f 100644 --- a/lib/Doctrine/Common/Version.php +++ b/lib/Doctrine/Common/Version.php @@ -1,7 +1,5 @@ . + */ namespace Doctrine\ORM; @@ -12,6 +29,6 @@ class EntityNotFoundException extends ORMException { public function __construct() { - parent::__construct('Entity was found although one item was expected.'); + parent::__construct('Entity was not found.'); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 1382cb5e6..de919430d 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index 3e7cdee21..a5dd39cfd 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -1,7 +1,5 @@ - * @author Benjamin Eberlei + * @since 2.0 + * @author Roman Borschel + * @author Benjamin Eberlei */ class LifecycleEventArgs extends \Doctrine\Common\EventArgs { diff --git a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php index ffbad370e..cfe3b5daf 100644 --- a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php +++ b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php index 226d2b406..f4bd3d631 100644 --- a/lib/Doctrine/ORM/Id/AssignedGenerator.php +++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/ORM/Id/IdentityGenerator.php b/lib/Doctrine/ORM/Id/IdentityGenerator.php index e2c3bf423..96ad08add 100644 --- a/lib/Doctrine/ORM/Id/IdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/IdentityGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; diff --git a/lib/Doctrine/ORM/Id/SequenceGenerator.php b/lib/Doctrine/ORM/Id/SequenceGenerator.php index fc9737653..0d564ed32 100644 --- a/lib/Doctrine/ORM/Id/SequenceGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceGenerator.php @@ -1,7 +1,5 @@ */ -class SequenceGenerator extends AbstractIdGenerator implements \Serializable +class SequenceGenerator extends AbstractIdGenerator implements Serializable { private $_allocationSize; private $_sequenceName; diff --git a/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php b/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php index 758d73474..c7158bbed 100644 --- a/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; diff --git a/lib/Doctrine/ORM/Id/TableGenerator.php b/lib/Doctrine/ORM/Id/TableGenerator.php index 12ee0a2b3..5c46f8b5c 100644 --- a/lib/Doctrine/ORM/Id/TableGenerator.php +++ b/lib/Doctrine/ORM/Id/TableGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; @@ -7,10 +24,7 @@ use Doctrine\ORM\EntityManager; /** * Id generator that uses a single-row database table and a hi/lo algorithm. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php index 7af95e41e..8997b1ea5 100644 --- a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php +++ b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php @@ -1,7 +1,5 @@ * @author Roman Borschel */ diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 9e5dcf2e9..aaa2c6ece 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -1,7 +1,5 @@ * @since 2.0 + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ abstract class AssociationMapping { @@ -58,7 +59,7 @@ abstract class AssociationMapping public $isCascadeRemove; /** - * READ-ONLY: Whether the association cascades save() operations from the source entity + * READ-ONLY: Whether the association cascades persist() operations from the source entity * to the target entity/entities. * * @var boolean diff --git a/lib/Doctrine/ORM/Mapping/Driver/Driver.php b/lib/Doctrine/ORM/Mapping/Driver/Driver.php index 1faaac964..b6cfe36b4 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/Driver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/Driver.php @@ -26,12 +26,9 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * Contract for metadata drivers. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com - * @since 2.0 - * @version $Revision: 1393 $ - * @author Jonathan H. Wage - * @todo Rename: MetadataDriver + * @since 2.0 + * @author Jonathan H. Wage + * @todo Rename: MetadataDriver or MappingDriver */ interface Driver { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php b/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php index cd2a727fe..3abf3480c 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php @@ -1,7 +1,5 @@ - * @author Guilherme Blanco - * @author Jonathan H. Wage - * @author Roman Borschel + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + * @todo Rename: MappingDriverChain or MetadataDriverChain */ class DriverChain implements Driver { diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php index 35f7e451f..21bdafe20 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -37,6 +37,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.0 * @author Roman Borschel * @author Giorgio Sironi + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ class ManyToManyMapping extends AssociationMapping { @@ -140,39 +141,11 @@ class ManyToManyMapping extends AssociationMapping * @param object The owner of the collection. * @param object The collection to populate. * @param array + * @todo Remove */ public function load($sourceEntity, $targetCollection, $em, array $joinColumnValues = array()) { - $sourceClass = $em->getClassMetadata($this->sourceEntityName); - $joinTableConditions = array(); - if ($this->isOwningSide) { - foreach ($this->relationToSourceKeyColumns as $relationKeyColumn => $sourceKeyColumn) { - // getting id - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $joinTableConditions[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, $sourceKeyColumn - ); - } - } - } else { - $owningAssoc = $em->getClassMetadata($this->targetEntityName)->associationMappings[$this->mappedBy]; - // TRICKY: since the association is inverted source and target are flipped - foreach ($owningAssoc->relationToTargetKeyColumns as $relationKeyColumn => $sourceKeyColumn) { - // getting id - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $joinTableConditions[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, $sourceKeyColumn - ); - } - } - } - - $persister = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName); - $persister->loadManyToManyCollection($this, $joinTableConditions, $targetCollection); + $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadManyToManyCollection($this, $sourceEntity, $targetCollection); } /** {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 8881a3c71..561f66bbd 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -1,7 +1,5 @@ * @author Giorgio Sironi * @since 2.0 + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ class OneToManyMapping extends AssociationMapping { @@ -99,8 +100,6 @@ class OneToManyMapping extends AssociationMapping /** * {@inheritdoc} - * - * @override */ public function isOneToMany() { @@ -115,24 +114,11 @@ class OneToManyMapping extends AssociationMapping * @param $em The EntityManager to use. * @param $joinColumnValues * @return void + * @todo Remove */ public function load($sourceEntity, $targetCollection, $em, array $joinColumnValues = array()) { - $persister = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName); - // a one-to-many is always inverse (does not have foreign key) - $sourceClass = $em->getClassMetadata($this->sourceEntityName); - $owningAssoc = $em->getClassMetadata($this->targetEntityName)->associationMappings[$this->mappedBy]; - // TRICKY: since the association is specular source and target are flipped - foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - // getting id - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - $conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn]; - } - } - - $persister->loadOneToManyCollection($this, $conditions, $targetCollection); + $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadOneToManyCollection($this, $sourceEntity, $targetCollection); } /** diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 89a97449e..918228060 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -37,6 +37,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.0 * @author Roman Borschel * @author Giorgio Sironi + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ class OneToOneMapping extends AssociationMapping { @@ -133,57 +134,11 @@ class OneToOneMapping extends AssociationMapping * @param object $targetEntity the entity to load data in * @param EntityManager $em * @param array $joinColumnValues Values of the join columns of $sourceEntity. + * @todo Remove */ public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues = array()) { - $targetClass = $em->getClassMetadata($this->targetEntityName); - - if ($this->isOwningSide) { - // Mark inverse side as fetched in the hints, otherwise the UoW would - // try to load it in a separate query (remember: to-one inverse sides can not be lazy). - $hints = array(); - if ($this->inversedBy) { - $hints['fetched'][$targetClass->name][$this->inversedBy] = true; - if ($targetClass->subClasses) { - foreach ($targetClass->subClasses as $targetSubclassName) { - $hints['fetched'][$targetSubclassName][$this->inversedBy] = true; - } - } - } - /* cascade read-only status - if ($em->getUnitOfWork()->isReadOnly($sourceEntity)) { - $hints[Query::HINT_READ_ONLY] = true; - } - */ - - $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($joinColumnValues, $targetEntity, $this, $hints); - - if ($targetEntity !== null && $this->inversedBy && ! $targetClass->isCollectionValuedAssociation($this->inversedBy)) { - $targetClass->reflFields[$this->inversedBy]->setValue($targetEntity, $sourceEntity); - } - } else { - $conditions = array(); - $sourceClass = $em->getClassMetadata($this->sourceEntityName); - $owningAssoc = $targetClass->getAssociationMapping($this->mappedBy); - // TRICKY: since the association is specular source and target are flipped - foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, $sourceKeyColumn - ); - } - } - - $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this); - - if ($targetEntity !== null) { - $targetClass->setFieldValue($targetEntity, $this->mappedBy, $sourceEntity); - } - } - - return $targetEntity; + return $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadOneToOneEntity($this, $sourceEntity, $targetEntity, $joinColumnValues); } /** diff --git a/lib/Doctrine/ORM/NoResultException.php b/lib/Doctrine/ORM/NoResultException.php index 1312ea1e3..80f08bb93 100644 --- a/lib/Doctrine/ORM/NoResultException.php +++ b/lib/Doctrine/ORM/NoResultException.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM; diff --git a/lib/Doctrine/ORM/NonUniqueResultException.php b/lib/Doctrine/ORM/NonUniqueResultException.php index 601de023f..811df75c1 100644 --- a/lib/Doctrine/ORM/NonUniqueResultException.php +++ b/lib/Doctrine/ORM/NonUniqueResultException.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM; diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index 4d39d1e47..abea24363 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM; diff --git a/lib/Doctrine/ORM/OptimisticLockException.php b/lib/Doctrine/ORM/OptimisticLockException.php index 796a7588c..ad6cde1ea 100644 --- a/lib/Doctrine/ORM/OptimisticLockException.php +++ b/lib/Doctrine/ORM/OptimisticLockException.php @@ -1,7 +1,5 @@ columnNames[$field]; - $sql = $this->_getSQLTableAlias($class) . '.' . $class->getQuotedColumnName($field, $this->_platform); + $sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); if ( ! isset($this->_resultColumnNames[$columnAlias])) { $this->_resultColumnNames[$columnAlias] = $columnName; diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 9a68749b7..fd3f9097b 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -1,7 +1,5 @@ Class Table Inheritance strategy. * * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html */ @@ -116,19 +113,19 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $this->_class : $this->_em->getClassMetadata($this->_class->rootEntityName); $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name); $rootTableName = $rootClass->table['name']; - $rootTableStmt = $this->_conn->prepare($rootPersister->getInsertSQL()); + $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL()); // Prepare statements for sub tables. $subTableStmts = array(); if ($rootClass !== $this->_class) { - $subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->getInsertSQL()); + $subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->_getInsertSQL()); } foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $parentTableName = $parentClass->table['name']; if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); - $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->getInsertSQL()); + $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); } } @@ -231,27 +228,30 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * {@inheritdoc} */ - protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null) { $idColumns = $this->_class->getIdentifierColumnNames(); - $baseTableAlias = $this->_getSQLTableAlias($this->_class); + $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); + // Create the column list fragment only once if ($this->_selectColumnListSql === null) { // Add regular columns $columnList = ''; foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL($fieldName, - isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class); + isset($mapping['inherited']) ? + $this->_em->getClassMetadata($mapping['inherited']) : + $this->_class); } // Add foreign key columns - foreach ($this->_class->associationMappings as $assoc) { - if ($assoc->isOwningSide && $assoc->isOneToOne()) { - $tableAlias = $assoc->inherited ? - $this->_getSQLTableAlias($this->_em->getClassMetadata($assoc->inherited)) + foreach ($this->_class->associationMappings as $assoc2) { + if ($assoc2->isOwningSide && $assoc2->isOneToOne()) { + $tableAlias = $assoc2->inherited ? + $this->_getSQLTableAlias($assoc2->inherited) : $baseTableAlias; - foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { + foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $srcColumn . $this->_sqlAliasCounter++; $columnList .= ", $tableAlias.$srcColumn AS $columnAlias"; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); @@ -262,12 +262,12 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } } - // Add discriminator column (DO NOT ALIAS THIS COLUMN, see StandardEntityPersister#_processSQLResultInheritanceAware). + // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult). $discrColumn = $this->_class->discriminatorColumn['name']; if ($this->_class->rootEntityName == $this->_class->name) { $columnList .= ", $baseTableAlias.$discrColumn"; } else { - $columnList .= ', ' . $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->rootEntityName)) + $columnList .= ', ' . $this->_getSQLTableAlias($this->_class->rootEntityName) . ".$discrColumn"; } @@ -279,7 +279,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $joinSql = ''; foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->_getSQLTableAlias($parentClass); + $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { @@ -291,7 +291,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // OUTER JOIN sub tables foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->_getSQLTableAlias($subClass); + $tableAlias = $this->_getSQLTableAlias($subClassName); if ($this->_selectColumnListSql === null) { // Add subclass columns @@ -326,27 +326,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } } - $conditionSql = ''; - foreach ($criteria as $field => $value) { - if ($conditionSql != '') $conditionSql .= ' AND '; - if (isset($this->_class->fieldMappings[$field]['inherited'])) { - $conditionSql .= $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->fieldMappings[$field]['inherited'])) . '.'; - } else { - $conditionSql .= $baseTableAlias . '.'; - } - if (isset($this->_class->columnNames[$field])) { - $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); - } else if ($assoc !== null) { - $conditionSql .= $field; - } else { - throw ORMException::unrecognizedField($field); - } - $conditionSql .= ' = ?'; - } + $joinSql .= $assoc != null && $assoc->isManyToMany() ? + $this->_getSelectManyToManyJoinSQL($assoc) : ''; + + $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); $orderBySql = ''; - if ($orderBy !== null) { - $orderBySql = $this->_getCollectionOrderBySQL($orderBy, $baseTableAlias); + if ($assoc != null && isset($assoc->orderBy)) { + $orderBySql = $this->_getCollectionOrderBySQL($assoc->orderBy, $baseTableAlias); } if ($this->_selectColumnListSql === null) { @@ -359,10 +346,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql; } - /** Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ + /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { - throw new \BadMethodCallException("Illegal invocation of ".__METHOD__." on JoinedSubclassPersister."); + throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); } /** {@inheritdoc} */ @@ -398,17 +385,4 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister return $columns; } - - /** - * Gets the SQL to select a collection of entities in a many-many association. - * - * @param ManyToManyMapping $manyToMany - * @param array $criteria - * @return string - */ - protected function _getSelectManyToManyEntityCollectionSQL($manyToMany, array &$criteria) - { - // @todo - throw new \BadMethodCallException("Not yet implemented, see http://www.doctrine-project.org/jira/browse/DDC-342"); - } } diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index 5f5a8a4d7..3f7574cba 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -41,24 +41,25 @@ class SingleTablePersister extends AbstractEntityInheritancePersister protected function _getSelectColumnListSQL() { $columnList = parent::_getSelectColumnListSQL(); + // Append discriminator column $discrColumn = $this->_class->discriminatorColumn['name']; $columnList .= ", $discrColumn"; $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); - $tableAlias = $this->_getSQLTableAlias($rootClass); + $tableAlias = $this->_getSQLTableAlias($rootClass->name); $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $this->_resultColumnNames[$resultColumnName] = $discrColumn; + // Append subclass columns foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - // Append subclass columns + // Regular columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } } - - // Append subclass foreign keys + // Foreign key columns foreach ($subClass->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne() && ! $assoc->inherited) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { @@ -87,14 +88,27 @@ class SingleTablePersister extends AbstractEntityInheritancePersister } /** {@inheritdoc} */ - protected function _getSQLTableAlias(ClassMetadata $class) + protected function _getSQLTableAlias($className) { - if (isset($this->_sqlTableAliases[$class->rootEntityName])) { - return $this->_sqlTableAliases[$class->rootEntityName]; - } - $tableAlias = $this->_em->getClassMetadata($class->rootEntityName)->table['name'][0] . $this->_sqlAliasCounter++; - $this->_sqlTableAliases[$class->rootEntityName] = $tableAlias; + return parent::_getSQLTableAlias($this->_class->rootEntityName); + } - return $tableAlias; + /** {@inheritdoc} */ + protected function _getSelectConditionSQL(array $criteria, $assoc = null) + { + $conditionSql = parent::_getSelectConditionSQL($criteria, $assoc); + + // Append discriminator condition + if ($conditionSql) $conditionSql .= ' AND '; + $values = array($this->_conn->quote($this->_class->discriminatorValue)); + $discrValues = array_flip($this->_class->discriminatorMap); + foreach ($this->_class->subClasses as $subclassName) { + $values[] = $this->_conn->quote($discrValues[$subclassName]); + } + $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' + . $this->_class->discriminatorColumn['name'] + . ' IN (' . implode(', ', $values) . ')'; + + return $conditionSql; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index f599cc74b..29c04c09d 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -1,7 +1,5 @@ - * @author Giorgio Sironi - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @since 2.0 + * A persister is always responsible for a single entity type. + * + * EntityPersisters are used during a UnitOfWork to apply any changes to the persistent + * state of entities onto a relational database when the UnitOfWork is committed, + * as well as for basic querying of entities and their associations (not DQL). + * + * The persisting operations that are invoked during a commit of a UnitOfWork to + * persist the persistent entity state are: + * + * - {@link addInsert} : To schedule an entity for insertion. + * - {@link executeInserts} : To execute all scheduled insertions. + * - {@link update} : To update the persistent state of an entity. + * - {@link delete} : To delete the persistent state of an entity. + * + * As can be seen from the above list, insertions are batched and executed all at once + * for increased efficiency. + * + * The querying operations invoked during a UnitOfWork, either through direct find + * requests or lazy-loading, are the following: + * + * - {@link load} : Loads (the state of) a single, managed entity. + * - {@link loadAll} : Loads multiple, managed entities. + * - {@link loadOneToOneEntity} : Loads a one/many-to-one association (lazy-loading). + * - {@link loadOneToManyCollection} : Loads a one-to-many association (lazy-loading). + * - {@link loadManyToManyCollection} : Loads a many-to-many association (lazy-loading). + * + * The BasicEntityPersister implementation provides the default behavior for + * persisting and querying entities that are mapped to a single database table. + * + * Subclasses can be created to provide custom persisting and querying strategies, + * i.e. spanning multiple tables. + * + * @author Roman Borschel + * @author Giorgio Sironi + * @since 2.0 * @todo Rename: BasicEntityPersister */ class StandardEntityPersister @@ -50,16 +82,16 @@ class StandardEntityPersister protected $_class; /** - * The underlying Connection of the used EntityManager. + * The underlying DBAL Connection of the used EntityManager. * * @var Doctrine\DBAL\Connection $conn */ protected $_conn; - + /** * The database platform. * - * @var AbstractPlatform + * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; @@ -87,8 +119,8 @@ class StandardEntityPersister protected $_resultColumnNames = array(); /** - * The map of column names to DBAL mapping types of all prepared columns used when INSERTing - * or UPDATEing an entity. + * The map of column names to DBAL mapping types of all prepared columns used + * when INSERTing or UPDATEing an entity. * * @var array * @see _prepareInsertData($entity) @@ -143,9 +175,9 @@ class StandardEntityPersister /** * Adds an entity to the queued insertions. - * The entity remains queued until {@link executeInserts()} is invoked. + * The entity remains queued until {@link executeInserts} is invoked. * - * @param object $entity The entitiy to queue for insertion. + * @param object $entity The entity to queue for insertion. */ public function addInsert($entity) { @@ -171,7 +203,7 @@ class StandardEntityPersister $idGen = $this->_class->idGenerator; $isPostInsertId = $idGen->isPostInsertGenerator(); - $stmt = $this->_conn->prepare($this->getInsertSQL()); + $stmt = $this->_conn->prepare($this->_getInsertSQL()); $tableName = $this->_class->table['name']; foreach ($this->_queuedInserts as $entity) { @@ -209,9 +241,9 @@ class StandardEntityPersister * by the preceding INSERT statement and assigns it back in to the * entities version field. * - * @param $class - * @param $entity - * @param $id + * @param Doctrine\ORM\Mapping\ClassMetadata $class + * @param object $entity + * @param mixed $id */ protected function _assignDefaultVersionValue($class, $entity, $id) { @@ -226,7 +258,16 @@ class StandardEntityPersister } /** - * Updates an entity. + * Updates a managed entity. The entity is updated according to its current changeset + * in the running UnitOfWork. If there is no changeset, nothing is updated. + * + * The data to update is retrieved through {@link _prepareUpdateData}. + * Subclasses that override this method are supposed to obtain the update data + * in the same way, through {@link _prepareUpdateData}. + * + * Subclasses are also supposed to take care of versioning when overriding this method, + * if necessary. The {@link _updateTable} method can be used to apply the data retrieved + * from {@_prepareUpdateData} on the target tables, thereby optionally applying versioning. * * @param object $entity The entity to update. */ @@ -241,14 +282,14 @@ class StandardEntityPersister /** * Performs an UPDATE statement for an entity on a specific table. - * The UPDATE can be optionally versioned, which requires the entity to have a version field. + * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $tableName The name of the table to apply the UPDATE on. * @param array $updateData The map of columns to update (column => value). * @param boolean $versioned Whether the UPDATE should be versioned. */ - protected function _updateTable($entity, $tableName, $updateData, $versioned = false) + protected final function _updateTable($entity, $tableName, array $updateData, $versioned = false) { $set = $params = $types = array(); @@ -261,7 +302,7 @@ class StandardEntityPersister $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } - + $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->_class->identifier as $idField) { @@ -284,7 +325,7 @@ class StandardEntityPersister $types[] = $this->_class->fieldMappings[$versionField]['type']; } - $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set) + $sql = "UPDATE $tableName SET " . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->_conn->executeUpdate($sql, $params, $types); @@ -295,7 +336,12 @@ class StandardEntityPersister } /** - * Deletes an entity. + * Deletes a managed entity. + * + * The entity to delete must be managed and have a persistent identifier. + * The deletion happens instantaneously. + * + * Subclasses may override this method to customize the semantics of entity deletion. * * @param object $entity The entity to delete. */ @@ -319,7 +365,9 @@ class StandardEntityPersister } /** - * Prepares the data changeset of an entity for database insertion. + * Prepares the changeset of an entity for database insertion (UPDATE). + * + * The changeset is obtained from the currently running UnitOfWork. * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. @@ -333,8 +381,6 @@ class StandardEntityPersister * ) * * - * Notes to inheritors: Be sure to call parent::_prepareData($entity, $result, $isInsert); - * * @param object $entity The entity for which to prepare the data. * @return array The prepared data. */ @@ -348,7 +394,7 @@ class StandardEntityPersister } foreach ($uow->getEntityChangeSet($entity) as $field => $change) { - if ($versioned && $versionField == $field) { + if ($versioned && $versionField == $field) { //TODO: Needed? continue; } @@ -356,9 +402,9 @@ class StandardEntityPersister $newVal = $change[1]; if (isset($this->_class->associationMappings[$field])) { - $assocMapping = $this->_class->associationMappings[$field]; + $assoc = $this->_class->associationMappings[$field]; // Only owning side of x-1 associations can have a FK column. - if ( ! $assocMapping->isOwningSide || ! $assocMapping->isOneToOne()) { + if ( ! $assoc->isOwningSide || ! $assoc->isOneToOne()) { continue; } @@ -379,10 +425,10 @@ class StandardEntityPersister $newValId = $uow->getEntityIdentifier($newVal); } - $targetClass = $this->_em->getClassMetadata($assocMapping->targetEntityName); + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); $owningTable = $this->getOwningTable($field); - foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { + foreach ($assoc->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { if ($newVal === null) { $result[$owningTable][$sourceColumn] = null; } else { @@ -399,6 +445,16 @@ class StandardEntityPersister return $result; } + /** + * Prepares the data changeset of a managed entity for database insertion (initial INSERT). + * The changeset of the entity is obtained from the currently running UnitOfWork. + * + * The default insert data preparation is the same as for updates. + * + * @param object $entity The entity for which to prepare the data. + * @return array The prepared data for the tables to update. + * @see _prepareUpdateData + */ protected function _prepareInsertData($entity) { return $this->_prepareUpdateData($entity); @@ -407,8 +463,12 @@ class StandardEntityPersister /** * Gets the name of the table that owns the column the given field is mapped to. * - * @param string $fieldName - * @return string + * The default implementation in BasicEntityPersister always returns the name + * of the table the entity type of this persister is mapped to, since an entity + * is always persisted to a single table with a BasicEntityPersister. + * + * @param string $fieldName The field name. + * @return string The table name. */ public function getOwningTable($fieldName) { @@ -423,13 +483,13 @@ class StandardEntityPersister * a new entity is created. * @param $assoc The association that connects the entity to load to another entity, if any. * @param array $hints Hints for entity creation. - * @return The loaded entity instance or NULL if the entity/the data can not be found. + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array()) { $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); + $stmt = $this->_conn->executeQuery($sql, array_values($criteria)); $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -437,17 +497,80 @@ class StandardEntityPersister } /** - * Refreshes an entity. + * Loads an entity of this persister's mapped class as part of a single-valued + * association from another entity. + * + * @param OneToOneMapping $assoc The association to load. + * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). + * @param object $targetEntity The existing ghost entity (proxy) to load, if any. + * @param array $identifier The identifier of the entity to load. Must be provided if + * the association to load represents the owning side, otherwise + * the identifier is derived from the $sourceEntity. + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + */ + public function loadOneToOneEntity(OneToOneMapping $assoc, $sourceEntity, $targetEntity, array $identifier = array()) + { + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); + + if ($assoc->isOwningSide) { + // Mark inverse side as fetched in the hints, otherwise the UoW would + // try to load it in a separate query (remember: to-one inverse sides can not be lazy). + $hints = array(); + if ($assoc->inversedBy) { + $hints['fetched'][$targetClass->name][$assoc->inversedBy] = true; + if ($targetClass->subClasses) { + foreach ($targetClass->subClasses as $targetSubclassName) { + $hints['fetched'][$targetSubclassName][$assoc->inversedBy] = true; + } + } + } + /* cascade read-only status + if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { + $hints[Query::HINT_READ_ONLY] = true; + } + */ + + $targetEntity = $this->load($identifier, $targetEntity, $assoc, $hints); + + // Complete bidirectional association, if necessary + if ($targetEntity !== null && $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy)) { + $targetClass->reflFields[$assoc->inversedBy]->setValue($targetEntity, $sourceEntity); + } + } else { + $sourceClass = $this->_em->getClassMetadata($assoc->sourceEntityName); + $owningAssoc = $targetClass->getAssociationMapping($assoc->mappedBy); + // TRICKY: since the association is specular source and target are flipped + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { + if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + $identifier[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } else { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); + } + } + + $targetEntity = $this->load($identifier, $targetEntity, $assoc); + + if ($targetEntity !== null) { + $targetClass->setFieldValue($targetEntity, $assoc->mappedBy, $sourceEntity); + } + } + + return $targetEntity; + } + + /** + * Refreshes a managed entity. * - * @param array $id The identifier of the entity as an associative array from column names to values. + * @param array $id The identifier of the entity as an associative array from + * column or field names to values. * @param object $entity The entity to refresh. */ public function refresh(array $id, $entity) { $sql = $this->_getSelectEntitiesSQL($id); - $params = array_values($id); - - $stmt = $this->_conn->executeQuery($sql, $params); + $stmt = $this->_conn->executeQuery($sql, array_values($id)); $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -527,10 +650,8 @@ class StandardEntityPersister public function loadAll(array $criteria = array()) { $entities = array(); - $sql = $this->_getSelectEntitiesSQL($criteria); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); + $stmt = $this->_conn->executeQuery($sql, array_values($criteria)); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -541,37 +662,44 @@ class StandardEntityPersister return $entities; } - /** - * Loads a collection of entities in a one-to-many association. - * - * @param OneToManyMapping $assoc - * @param array $criteria The criteria by which to select the entities. - * @param PersistentCollection The collection to fill. - */ - public function loadOneToManyCollection($assoc, array $criteria, PersistentCollection $coll) - { - $owningAssoc = $this->_class->associationMappings[$coll->getMapping()->mappedBy]; - $sql = $this->_getSelectEntitiesSQL($criteria, $owningAssoc, $assoc->orderBy); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); - while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { - $coll->hydrateAdd($this->_createEntity($result)); - } - $stmt->closeCursor(); - } - /** * Loads a collection of entities of a many-to-many association. * - * @param ManyToManyMapping $assoc - * @param array $criteria + * @param ManyToManyMapping $assoc The association mapping of the association being loaded. + * @param object $sourceEntity The entity that owns the collection. * @param PersistentCollection $coll The collection to fill. */ - public function loadManyToManyCollection($assoc, array $criteria, PersistentCollection $coll) + public function loadManyToManyCollection(ManyToManyMapping $assoc, $sourceEntity, PersistentCollection $coll) { - $sql = $this->_getSelectManyToManyEntityCollectionSQL($assoc, $criteria); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); + $criteria = array(); + $sourceClass = $this->_em->getClassMetadata($assoc->sourceEntityName); + $joinTableConditions = array(); + if ($assoc->isOwningSide) { + foreach ($assoc->relationToSourceKeyColumns as $relationKeyColumn => $sourceKeyColumn) { + if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } else { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); + } + } + } else { + $owningAssoc = $this->_em->getClassMetadata($assoc->targetEntityName)->associationMappings[$assoc->mappedBy]; + // TRICKY: since the association is inverted source and target are flipped + foreach ($owningAssoc->relationToTargetKeyColumns as $relationKeyColumn => $sourceKeyColumn) { + if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } else { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); + } + } + } + + $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); + $stmt = $this->_conn->executeQuery($sql, array_values($criteria)); while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { $coll->hydrateAdd($this->_createEntity($result)); } @@ -613,10 +741,15 @@ class StandardEntityPersister /** * Processes an SQL result set row that contains data for an entity of the type * this persister is responsible for. + * + * Subclasses are supposed to override this method if they need to change the + * hydration procedure for entities loaded through basic find operations or + * lazy-loading (not DQL). * * @param array $sqlResult The SQL result set row to process. * @return array A tuple where the first value is the actual type of the entity and - * the second value the prepared data of the entity. + * the second value the prepared data of the entity (a map from field + * names to values). */ protected function _processSQLResult(array $sqlResult) { @@ -640,51 +773,37 @@ class StandardEntityPersister * * @param array $criteria * @param AssociationMapping $assoc - * @param string $orderBy * @return string + * @todo Refactor: _getSelectSQL(...) */ - protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null) { - // Construct WHERE conditions - $conditionSql = ''; - foreach ($criteria as $field => $value) { - if ($conditionSql != '') { - $conditionSql .= ' AND '; - } - - if (isset($this->_class->columnNames[$field])) { - $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); - } else if (isset($this->_class->fieldNames[$field])) { - $conditionSql .= $this->_class->getQuotedColumnName($this->_class->fieldNames[$field], $this->_platform); - } else if ($assoc !== null) { - $conditionSql .= $field; - } else { - throw ORMException::unrecognizedField($field); - } - $conditionSql .= ' = ?'; - } + $joinSql = $assoc != null && $assoc->isManyToMany() ? + $this->_getSelectManyToManyJoinSQL($assoc) : ''; - $orderBySql = ''; - if ($orderBy !== null) { - $orderBySql = $this->_getCollectionOrderBySQL( - $orderBy, $this->_getSQLTableAlias($this->_class) - ); - } + $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); + + $orderBySql = $assoc !== null && isset($assoc->orderBy) ? + $this->_getCollectionOrderBySQL($assoc->orderBy, $this->_getSQLTableAlias($this->_class->name)) + : ''; return 'SELECT ' . $this->_getSelectColumnListSQL() . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' - . $this->_getSQLTableAlias($this->_class) - . ($conditionSql ? ' WHERE ' . $conditionSql : '') . $orderBySql; + . $this->_getSQLTableAlias($this->_class->name) + . $joinSql + . ($conditionSql ? ' WHERE ' . $conditionSql : '') + . $orderBySql; } /** - * Generate ORDER BY SQL snippet for ordered collections. + * Gets the ORDER BY SQL snippet for ordered collections. * * @param array $orderBy * @param string $baseTableAlias * @return string + * @todo Rename: _getOrderBySQL */ - protected function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias) + protected final function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; foreach ($orderBy as $fieldName => $orientation) { @@ -693,27 +812,28 @@ class StandardEntityPersister } $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ? - $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited'])) + $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited']) : $baseTableAlias; $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); - if ($orderBySql != '') { - $orderBySql .= ', '; - } else { - $orderBySql = ' ORDER BY '; - } + $orderBySql .= $orderBySql ? ', ' : 'ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } return $orderBySql; } - + /** * Gets the SQL fragment with the list of columns to select when querying for - * an entity within this persister. + * an entity in this persister. + * + * Subclasses should override this method to alter or change the select column + * list SQL fragment. Note that in the implementation of StandardEntityPersister + * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. + * Subclasses may or may not do the same. * * @return string The SQL fragment. - * @todo Rename: _getSelectColumnListSQL() + * @todo Rename: _getSelectColumnsSQL() */ protected function _getSelectColumnListSQL() { @@ -725,7 +845,7 @@ class StandardEntityPersister // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { - if ($columnList != '') $columnList .= ', '; + if ($columnList) $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL($field, $this->_class); } @@ -733,15 +853,15 @@ class StandardEntityPersister return $this->_selectColumnListSql; } - + /** - * Gets the SQL to select a collection of entities in a many-many association. + * Gets the SQL join fragment used when selecting entities from a + * many-to-many association. * * @param ManyToManyMapping $manyToMany - * @param array $criteria * @return string */ - protected function _getSelectManyToManyEntityCollectionSQL($manyToMany, array &$criteria) + protected function _getSelectManyToManyJoinSQL(ManyToManyMapping $manyToMany) { if ($manyToMany->isOwningSide) { $owningAssoc = $manyToMany; @@ -756,32 +876,12 @@ class StandardEntityPersister $joinSql = ''; foreach ($joinClauses as $joinTableColumn => $sourceColumn) { if ($joinSql != '') $joinSql .= ' AND '; - $joinSql .= $this->_getSQLTableAlias($this->_class) . + $joinSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform) . ' = ' . $joinTableName . '.' . $joinTableColumn; } - $joinSql = ' INNER JOIN ' . $joinTableName . ' ON ' . $joinSql; - - $conditionSql = ''; - foreach ($criteria as $joinColumn => $value) { - if ($conditionSql != '') $conditionSql .= ' AND '; - $columnName = $joinTableName . '.' . $joinColumn; - $conditionSql .= $columnName . ' = ?'; - } - - $orderBySql = ''; - if ($manyToMany->orderBy !== null) { - $orderBySql = $this->_getCollectionOrderBySQL( - $manyToMany->orderBy, $this->_getSQLTableAlias($this->_class) - ); - } - - return 'SELECT ' . $this->_getSelectColumnListSQL() - . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' - . $this->_getSQLTableAlias($this->_class) - . $joinSql - . ' WHERE ' . $conditionSql . $orderBySql; + return " INNER JOIN $joinTableName ON $joinSql"; } /** @@ -789,19 +889,36 @@ class StandardEntityPersister * * @return string */ - public function getInsertSQL() + protected function _getInsertSQL() { if ($this->_insertSql === null) { - $this->_insertSql = $this->_generateInsertSQL(); + $insertSql = ''; + $columns = $this->_getInsertColumnList(); + if (empty($columns)) { + $insertSql = $this->_platform->getEmptyIdentityInsertSQL( + $this->_class->getQuotedTableName($this->_platform), + $this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform) + ); + } else { + $columns = array_unique($columns); + $values = array_fill(0, count($columns), '?'); + + $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) + . ' (' . implode(', ', $columns) . ') ' + . 'VALUES (' . implode(', ', $values) . ')'; + } + $this->_insertSql = $insertSql; } return $this->_insertSql; } /** * Gets the list of columns to put in the INSERT SQL statement. - * + * + * Subclasses should override this method to alter or change the list of + * columns placed in the INSERT statements used by the persister. + * * @return array The list of columns. - * @internal INSERT SQL is cached by getInsertSQL() per request. */ protected function _getInsertColumnList() { @@ -826,33 +943,6 @@ class StandardEntityPersister return $columns; } - /** - * Generates the INSERT SQL used by the persister to persist entities. - * - * @return string - * @internal Result is cached by getInsertSQL() per request. - */ - protected function _generateInsertSQL() - { - $insertSql = ''; - $columns = $this->_getInsertColumnList(); - if (empty($columns)) { - $insertSql = $this->_platform->getEmptyIdentityInsertSQL( - $this->_class->getQuotedTableName($this->_platform), - $this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform) - ); - } else { - $columns = array_unique($columns); - $values = array_fill(0, count($columns), '?'); - - $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) - . ' (' . implode(', ', $columns) . ') ' - . 'VALUES (' . implode(', ', $values) . ')'; - } - - return $insertSql; - } - /** * Gets the SQL snippet of a qualified column name for the given field name. * @@ -863,7 +953,7 @@ class StandardEntityPersister protected function _getSelectColumnSQL($field, ClassMetadata $class) { $columnName = $class->columnNames[$field]; - $sql = $this->_getSQLTableAlias($class) . '.' . $class->getQuotedColumnName($field, $this->_platform); + $sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); if ( ! isset($this->_resultColumnNames[$columnAlias])) { $this->_resultColumnNames[$columnAlias] = $columnName; @@ -875,17 +965,19 @@ class StandardEntityPersister /** * Gets the SQL snippet for all join columns of the given class that are to be * placed in an SQL SELECT statement. - * + * + * @param $class * @return string + * @todo Not reused... inline? */ - protected function _getSelectJoinColumnsSQL(ClassMetadata $class) + private function _getSelectJoinColumnsSQL(ClassMetadata $class) { $sql = ''; foreach ($class->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne()) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $srcColumn . $this->_sqlAliasCounter++; - $sql .= ', ' . $this->_getSQLTableAlias($this->_class) . ".$srcColumn AS $columnAlias"; + $sql .= ', ' . $this->_getSQLTableAlias($this->_class->name) . ".$srcColumn AS $columnAlias"; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); if ( ! isset($this->_resultColumnNames[$resultColumnName])) { $this->_resultColumnNames[$resultColumnName] = $srcColumn; @@ -898,19 +990,92 @@ class StandardEntityPersister } /** - * Gets the SQL table alias for the given class. + * Gets the SQL table alias for the given class name. * - * @param ClassMetadata $class + * @param string $className * @return string The SQL table alias. + * @todo Remove. Binding table aliases to class names is not such a good idea. */ - protected function _getSQLTableAlias(ClassMetadata $class) + protected function _getSQLTableAlias($className) { - if (isset($this->_sqlTableAliases[$class->name])) { - return $this->_sqlTableAliases[$class->name]; + if (isset($this->_sqlTableAliases[$className])) { + return $this->_sqlTableAliases[$className]; } - $tableAlias = $class->table['name'][0] . $this->_sqlAliasCounter++; - $this->_sqlTableAliases[$class->name] = $tableAlias; + $tableAlias = 't' . $this->_sqlAliasCounter++; + $this->_sqlTableAliases[$className] = $tableAlias; return $tableAlias; } + + /** + * Gets the conditional SQL fragment used in the WHERE clause when selecting + * entities in this persister. + * + * Subclasses are supposed to override this method if they intend to change + * or alter the criteria by which entities are selected. + * + * @param array $criteria + * @param AssociationMapping $assoc + * @return string + */ + protected function _getSelectConditionSQL(array $criteria, $assoc = null) + { + $conditionSql = ''; + foreach ($criteria as $field => $value) { + $conditionSql .= $conditionSql ? ' AND ' : ''; + + if (isset($this->_class->columnNames[$field])) { + if (isset($this->_class->fieldMappings[$field]['inherited'])) { + $conditionSql .= $this->_getSQLTableAlias($this->_class->fieldMappings[$field]['inherited']) . '.'; + } else { + $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; + } + $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); + } else if ($assoc !== null) { + if ($assoc->isManyToMany()) { + $owningAssoc = $assoc->isOwningSide ? $assoc : $this->_em->getClassMetadata($assoc->targetEntityName) + ->associationMappings[$assoc->mappedBy]; + $conditionSql .= $owningAssoc->getQuotedJoinTableName($this->_platform) . '.' . $field; + } else { + $conditionSql .= $field; + } + } else { + throw ORMException::unrecognizedField($field); + } + $conditionSql .= ' = ?'; + } + + return $conditionSql; + } + + /** + * Loads a collection of entities in a one-to-many association. + * + * @param OneToManyMapping $assoc + * @param array $criteria The criteria by which to select the entities. + * @param PersistentCollection The collection to load/fill. + */ + public function loadOneToManyCollection(OneToManyMapping $assoc, $sourceEntity, PersistentCollection $coll) + { + $criteria = array(); + $owningAssoc = $this->_class->associationMappings[$assoc->mappedBy]; + $sourceClass = $this->_em->getClassMetadata($assoc->sourceEntityName); + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { + $criteria[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } + + $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); + $params = array_values($criteria); + $stmt = $this->_conn->executeQuery($sql, $params); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $coll->hydrateAdd($this->_createEntity($result)); + } + $stmt->closeCursor(); + } + + //TODO + /*protected function _getOneToOneEagerFetchSQL() + { + + }*/ } diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index d4e255dcb..fceab4023 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -1,7 +1,5 @@ ProxyFactory class that is - * connected to the given EntityManager. - * - * @param EntityManager $em The EntityManager the new factory works for. - * @param string $proxyDir The directory to use for the proxy classes. It must exist. - * @param string $proxyNs The namespace to use for the proxy classes. - * @param boolean $autoGenerate Whether to automatically generate proxy classes. + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param EntityManager $em The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param boolean $autoGenerate Whether to automatically generate proxy classes. */ public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false) { @@ -240,7 +238,7 @@ class ProxyFactory return $sleepImpl; } - /** Reference Proxy class code template */ + /** Proxy class code template */ private static $_proxyClassTemplate = '_selectedClasses AS $dqlAlias => $class) { $qComp = $this->_queryComponents[$dqlAlias]; - if (isset($qComp['relation']) && ($qComp['relation']->isManyToMany() || $qComp['relation']->isOneToMany()) - && $qComp['relation']->orderBy != null) { - + if (isset($qComp['relation']->orderBy)) { foreach ($qComp['relation']->orderBy AS $fieldName => $orientation) { if ($qComp['metadata']->isInheritanceTypeJoined()) { $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); @@ -299,8 +297,8 @@ class SqlWalker implements TreeWalker if ($sql != '') { $sql .= ', '; } - $sql .= $this->getSqlTableAlias($tableName, $dqlAlias) . "." . - $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " ".$orientation; + $sql .= $this->getSqlTableAlias($tableName, $dqlAlias) . '.' . + $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation"; } } } @@ -313,7 +311,7 @@ class SqlWalker implements TreeWalker * @param string $dqlAlias * @return string */ - private function _generateDiscriminatorColumnConditionSql($dqlAlias) + private function _generateDiscriminatorColumnConditionSQL($dqlAlias) { $sql = ''; @@ -338,7 +336,6 @@ class SqlWalker implements TreeWalker return $sql; } - /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * @@ -351,7 +348,7 @@ class SqlWalker implements TreeWalker if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); - } else if (($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) !== '') { + } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias)) !== '') { $sql .= ' WHERE ' . $discSql; } @@ -385,7 +382,7 @@ class SqlWalker implements TreeWalker if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); - } else if (($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) !== '') { + } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias)) !== '') { $sql .= ' WHERE ' . $discSql; } @@ -405,7 +402,7 @@ class SqlWalker implements TreeWalker if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); - } else if (($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) !== '') { + } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias)) !== '') { $sql .= ' WHERE ' . $discSql; } @@ -780,7 +777,7 @@ class SqlWalker implements TreeWalker ). ')'; } - $discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias); + $discrSql = $this->_generateDiscriminatorColumnConditionSQL($joinedDqlAlias); if ($discrSql) { $sql .= ' AND ' . $discrSql; @@ -1217,7 +1214,7 @@ class SqlWalker implements TreeWalker ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) ); - $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias); + $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias); if ($discrSql) { $sql .= ' AND ' . $discrSql; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 46628c904..9cfbeda75 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel - * @internal This class contains performance-critical code. + * @internal This class contains highly performance-sensitive code. */ class UnitOfWork implements PropertyChangedListener { diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php index 7256d571e..d720f5d92 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php @@ -17,9 +17,10 @@ class ClassTableInheritanceTest2 extends \Doctrine\Tests\OrmFunctionalTestCase $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIParent'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIChild'), - $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIRelated') + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIRelated'), + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIRelated2') )); - } catch (\Exception $e) { + } catch (\Exception $ignored) { // Swallow all exceptions. We do not test the schema tool here. } } @@ -49,6 +50,27 @@ class ClassTableInheritanceTest2 extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($related2, $related2->getCTIParent()->getRelated()); } + + public function testManyToManyToCTIHierarchy() + { + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger()); + $mmrel = new CTIRelated2; + $child = new CTIChild; + $child->setData('child'); + $mmrel->addCTIChild($child); + + $this->_em->persist($mmrel); + $this->_em->persist($child); + + $this->_em->flush(); + $this->_em->clear(); + + $mmrel2 = $this->_em->find(get_class($mmrel), $mmrel->getId()); + $this->assertFalse($mmrel2->getCTIChildren()->isInitialized()); + $this->assertEquals(1, count($mmrel2->getCTIChildren())); + $this->assertTrue($mmrel2->getCTIChildren()->isInitialized()); + $this->assertTrue($mmrel2->getCTIChildren()->get(0) instanceof CTIChild); + } } /** @@ -126,3 +148,29 @@ class CTIRelated { $this->ctiParent = $ctiParent; } } + +/** @Entity */ +class CTIRelated2 +{ + /** @Id @Column(type="integer") @GeneratedValue */ + private $id; + /** @ManyToMany(targetEntity="CTIChild") */ + private $ctiChildren; + + + public function __construct() { + $this->ctiChildren = new \Doctrine\Common\Collections\ArrayCollection; + } + + public function getId() { + return $this->id; + } + + public function addCTIChild(CTIChild $child) { + $this->ctiChildren->add($child); + } + + public function getCTIChildren() { + return $this->ctiChildren; + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php index 01583d1e0..17f01f27c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php @@ -66,6 +66,7 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); + // READ by DQL on subtype $query = $this->_em->createQuery("select e from Doctrine\Tests\ORM\Functional\ChildEntity e"); $entities = $query->getResult(); $this->assertEquals(1, count($entities)); @@ -77,6 +78,18 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); + // READ by findAll() on subtype + $entities = $this->_em->getRepository('Doctrine\Tests\ORM\Functional\ChildEntity')->findAll(); + $this->assertEquals(1, count($entities)); + $this->assertTrue($entities[0] instanceof ChildEntity); + $this->assertTrue(is_numeric($entities[0]->getId())); + $this->assertEquals('thedata', $entities[0]->getData()); + $this->assertEquals(1234, $entities[0]->getNumber()); + $this->assertNull($entities[0]->getParentRelated()); + + $this->_em->clear(); + + // READ by joining into an STI hierarchy from outwards $query = $this->_em->createQuery("select r,o from Doctrine\Tests\ORM\Functional\RelatedEntity r join r.owner o"); $entities = $query->getResult(); @@ -194,7 +207,7 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase class ParentEntity { /** * @Id - * @Column(type="integer") + * @Column(name="parent_id", type="integer") * @GeneratedValue(strategy="AUTO") */ private $id; @@ -317,7 +330,7 @@ class ParentRelatedEntity { public function setData($data) {$this->data = $data;} /** * @OneToOne(targetEntity="ParentEntity") - * @JoinColumn(name="parent_id", referencedColumnName="id") + * @JoinColumn(name="parent_id", referencedColumnName="parent_id") */ private $parent; public function getParent() {return $this->parent;} From b505a27370a531acf0ffdd4a428e994de8d5aa80 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Mon, 26 Apr 2010 13:12:46 +0200 Subject: [PATCH 39/56] Renamed StandardEntityPersister to BasicEntityPersister --- .../ORM/Persisters/AbstractEntityInheritancePersister.php | 2 +- ...tandardEntityPersister.php => BasicEntityPersister.php} | 7 +++---- lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php | 2 +- lib/Doctrine/ORM/UnitOfWork.php | 2 +- tests/Doctrine/Tests/Mocks/EntityPersisterMock.php | 2 +- tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) rename lib/Doctrine/ORM/Persisters/{StandardEntityPersister.php => BasicEntityPersister.php} (99%) diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php index d75855c02..512f636e2 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php @@ -30,7 +30,7 @@ use Doctrine\ORM\Mapping\ClassMetadata, * @author Roman Borschel * @since 2.0 */ -abstract class AbstractEntityInheritancePersister extends StandardEntityPersister +abstract class AbstractEntityInheritancePersister extends BasicEntityPersister { /** * Map from column names to class names that declare the field the column is mapped to. diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php similarity index 99% rename from lib/Doctrine/ORM/Persisters/StandardEntityPersister.php rename to lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 29c04c09d..9b8609850 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -70,9 +70,8 @@ use PDO, * @author Roman Borschel * @author Giorgio Sironi * @since 2.0 - * @todo Rename: BasicEntityPersister */ -class StandardEntityPersister +class BasicEntityPersister { /** * Metadata object that describes the mapping of the mapped entity class. @@ -159,7 +158,7 @@ class StandardEntityPersister protected $_sqlTableAliases = array(); /** - * Initializes a new StandardEntityPersister that uses the given EntityManager + * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. * * @param Doctrine\ORM\EntityManager $em @@ -828,7 +827,7 @@ class StandardEntityPersister * an entity in this persister. * * Subclasses should override this method to alter or change the select column - * list SQL fragment. Note that in the implementation of StandardEntityPersister + * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. * Subclasses may or may not do the same. * diff --git a/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php b/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php index 6afc51b4b..b2e683a27 100644 --- a/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php @@ -2,7 +2,7 @@ namespace Doctrine\ORM\Persisters; -class UnionSubclassPersister extends StandardEntityPersister +class UnionSubclassPersister extends BasicEntityPersister { } \ No newline at end of file diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 9cfbeda75..4a0c5663f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1966,7 +1966,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! isset($this->_persisters[$entityName])) { $class = $this->_em->getClassMetadata($entityName); if ($class->isInheritanceTypeNone()) { - $persister = new Persisters\StandardEntityPersister($this->_em, $class); + $persister = new Persisters\BasicEntityPersister($this->_em, $class); } else if ($class->isInheritanceTypeSingleTable()) { $persister = new Persisters\SingleTablePersister($this->_em, $class); } else if ($class->isInheritanceTypeJoined()) { diff --git a/tests/Doctrine/Tests/Mocks/EntityPersisterMock.php b/tests/Doctrine/Tests/Mocks/EntityPersisterMock.php index 2bf42fe9b..aa43081a8 100644 --- a/tests/Doctrine/Tests/Mocks/EntityPersisterMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityPersisterMock.php @@ -5,7 +5,7 @@ namespace Doctrine\Tests\Mocks; /** * EntityPersister implementation used for mocking during tests. */ -class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersister +class EntityPersisterMock extends \Doctrine\ORM\Persisters\BasicEntityPersister { private $_inserts = array(); private $_updates = array(); diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php index 9445af133..fcf07cfa0 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php @@ -156,7 +156,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase protected function _getMockPersister() { - $persister = $this->getMock('Doctrine\ORM\Persisters\StandardEntityPersister', array('load'), array(), '', false); + $persister = $this->getMock('Doctrine\ORM\Persisters\BasicEntityPersister', array('load'), array(), '', false); return $persister; } } From d1d1cecc06e9403e7044e60506402d2514863c7b Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Mon, 26 Apr 2010 14:12:20 +0200 Subject: [PATCH 40/56] Restored missing files. --- lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php | 71 +++++++++++++++++++ .../AbstractEntityInheritancePersister.php | 2 +- tests/Doctrine/Tests/ORM/Mapping/AllTests.php | 2 +- .../ORM/Mapping/PhpMappingDriverTest.php | 31 ++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php diff --git a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php b/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php new file mode 100644 index 000000000..6b6954b83 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php @@ -0,0 +1,71 @@ +. + */ + +namespace Doctrine\ORM\Mapping\Driver; + +use Doctrine\Common\Cache\ArrayCache, + Doctrine\Common\Annotations\AnnotationReader, + Doctrine\DBAL\Schema\AbstractSchemaManager, + Doctrine\ORM\Mapping\ClassMetadataInfo, + Doctrine\ORM\Mapping\MappingException, + Doctrine\Common\Util\Inflector, + Doctrine\ORM\Mapping\Driver\AbstractFileDriver; + +/** + * The PhpDriver includes php files which just populate ClassMetadataInfo + * instances with plain php code + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + * @todo Rename: PHPDriver + */ +class PhpDriver extends AbstractFileDriver +{ + /** + * {@inheritdoc} + */ + protected $_fileExtension = '.php'; + protected $_metadata; + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadataInfo $metadata) + { + $this->_metadata = $metadata; + $this->_loadMappingFile($this->_findMappingFile($className)); + } + + /** + * {@inheritdoc} + */ + protected function _loadMappingFile($file) + { + $metadata = $this->_metadata; + include $file; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php index 512f636e2..0515e4dc0 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php @@ -37,7 +37,7 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister * * @var array */ - /*private*/protected $_declaringClassMap = array(); + private $_declaringClassMap = array(); /** * {@inheritdoc} diff --git a/tests/Doctrine/Tests/ORM/Mapping/AllTests.php b/tests/Doctrine/Tests/ORM/Mapping/AllTests.php index 06bfe498f..dcd5c768c 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AllTests.php @@ -23,7 +23,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\XmlMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\YamlMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\AnnotationDriverTest'); - //$suite->addTestSuite('Doctrine\Tests\ORM\Mapping\PHPMappingDriverTest'); FILE MISSING!!! + $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\PHPMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\StaticPHPMappingDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataFactoryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataLoadEventTest'); diff --git a/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php new file mode 100644 index 000000000..50bfcb819 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php @@ -0,0 +1,31 @@ +addMappingSource(__DIR__ . DIRECTORY_SEPARATOR . 'yaml'); + + $exporter = $cme->getExporter('php', $path); + $exporter->setMetadatas($cme->getMetadatas()); + $exporter->export(); + */ + + return new PhpDriver($path); + } +} \ No newline at end of file From 6bda2b44fedc832350e1153043b316e5dfe946d5 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 23 Apr 2010 14:37:29 -0400 Subject: [PATCH 41/56] Renaming PhpDriver to PHPDriver --- .../ORM/Mapping/Driver/{PhpDriver.php => PHPDriver.php} | 4 ++-- .../{PhpMappingDriverTest.php => PHPMappingDriverTest.php} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/Doctrine/ORM/Mapping/Driver/{PhpDriver.php => PHPDriver.php} (95%) rename tests/Doctrine/Tests/ORM/Mapping/{PhpMappingDriverTest.php => PHPMappingDriverTest.php} (91%) diff --git a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php similarity index 95% rename from lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php rename to lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php index 6b6954b83..99b8a145a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/PhpDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php @@ -30,7 +30,7 @@ use Doctrine\Common\Cache\ArrayCache, Doctrine\ORM\Mapping\Driver\AbstractFileDriver; /** - * The PhpDriver includes php files which just populate ClassMetadataInfo + * The PHPDriver includes php files which just populate ClassMetadataInfo * instances with plain php code * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL @@ -43,7 +43,7 @@ use Doctrine\Common\Cache\ArrayCache, * @author Roman Borschel * @todo Rename: PHPDriver */ -class PhpDriver extends AbstractFileDriver +class PHPDriver extends AbstractFileDriver { /** * {@inheritdoc} diff --git a/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php similarity index 91% rename from tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php rename to tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php index 50bfcb819..08ec30bcf 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/PhpMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata, - Doctrine\ORM\Mapping\Driver\PhpDriver, + Doctrine\ORM\Mapping\Driver\PHPDriver, Doctrine\ORM\Tools\Export\ClassMetadataExporter; require_once __DIR__ . '/../../TestInit.php'; @@ -26,6 +26,6 @@ class PhpMappingDriverTest extends AbstractMappingDriverTest $exporter->export(); */ - return new PhpDriver($path); + return new PHPDriver($path); } } \ No newline at end of file From 8396e72a2c3e8b35c0560eb0dc8c3ed14bf238a7 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Mon, 26 Apr 2010 14:25:23 +0200 Subject: [PATCH 42/56] Fixed casing. --- tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php index 08ec30bcf..b346973cf 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping\ClassMetadata, require_once __DIR__ . '/../../TestInit.php'; -class PhpMappingDriverTest extends AbstractMappingDriverTest +class PHPMappingDriverTest extends AbstractMappingDriverTest { protected function _loadDriver() { From 2f3e9fdcf89aea65084be03f3bcdb0ae78144e6e Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 27 Apr 2010 18:44:55 +0200 Subject: [PATCH 43/56] Updated UPGRADE file. --- UPGRADE_TO_2_0 | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/UPGRADE_TO_2_0 b/UPGRADE_TO_2_0 index edd11e965..24e7094bd 100644 --- a/UPGRADE_TO_2_0 +++ b/UPGRADE_TO_2_0 @@ -3,9 +3,9 @@ ## Console migrated to Symfony Console -The Doctrine Cli has been replaced by Symfony Console Configuration +The Doctrine CLI has been replaced by Symfony Console Configuration -Instead of having to specifiy: +Instead of having to specify: [php] $cliConfig = new CliConfiguration(); @@ -100,9 +100,22 @@ The "default" option for database column defaults has been removed. If desired, be implemented by using the columnDefinition attribute of the @Column annotation (or the approriate XML and YAML equivalents). Prefer PHP default values, if possible. -## Partial Objects +## Selecting Partial Objects -[TBD: New syntax, results, etc.] +Querying for partial objects now has a new syntax. The old syntax to query for partial objects +now has a different meaning. This is best illustrated by an example. If you previously +had a DQL query like this: + + [sql] + SELECT u.id, u.name FROM User u + +Since BETA1, simple state field path expressions in the select clause are used to select +object fields as plain scalar values (something that was not possible before). +To achieve the same result as previously (that is, a partial object with only id and name populated) +you need to use the following, explicit syntax: + + [sql] + SELECT PARTIAL u.{id,name} FROM User u ## XML Mapping Driver @@ -133,3 +146,9 @@ by using the API on the `PreUpdateEventArgs` instance passed to the preUpdate li to the state of the entitys properties won't affect the database UPDATE statement anymore. This gives drastic performance benefits for the preUpdate event. +## Collection API + +The Collection interface in the Common package has been updated with some missing methods +that were present only on the default implementation, ArrayCollection. Custom collection +implementations need to be updated to adhere to the updated interface. + From 025735e7308e619fa8258310b4b52e100e4b083a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 27 Apr 2010 19:37:27 +0200 Subject: [PATCH 44/56] DDC-536 - Make forwards compatible change in EntityRepository adding getters for the protected variables to allow a smooth change when they will be turned private in Beta2 --- UPGRADE_TO_2_0 | 13 +++++++++++++ lib/Doctrine/ORM/EntityRepository.php | 24 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/UPGRADE_TO_2_0 b/UPGRADE_TO_2_0 index 24e7094bd..d1be43bf2 100644 --- a/UPGRADE_TO_2_0 +++ b/UPGRADE_TO_2_0 @@ -1,6 +1,19 @@ # Upgrade from 2.0-ALPHA4 to 2.0-BETA1 +## EntityRepository deprecates access to protected variables + +Instead of accessing protected variables for the EntityManager in +a custom EntityRepository it is now required to use the getter methods +for all the three instance variables: + +* `$this->_em` now accessible through `$this->getEntityManager()` +* `$this->_class` now accessible through `$this->getClassMetadata()` +* `$this->_entityName` now accessible through `$this->getEntityName()` + +Important: For Beta 2 the protected visibility of these three properties will be +changed to private! + ## Console migrated to Symfony Console The Doctrine CLI has been replaced by Symfony Console Configuration diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index de919430d..67eda3a47 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -176,4 +176,28 @@ class EntityRepository throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by); } } + + /** + * @return string + */ + protected function getEntityName() + { + return $this->_entityName; + } + + /** + * @return EntityManager + */ + protected function getEntityManager() + { + return $this->_em; + } + + /** + * @return Mapping\ClassMetadata + */ + protected function getClassMetadata() + { + return $this->_class; + } } \ No newline at end of file From ae39a5d389965757876b022fb704b3bd849b3e08 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 27 Apr 2010 20:11:42 +0200 Subject: [PATCH 45/56] Fixed svn dependency in build file and some weird issues where the oci8 driver loses spaces in the sql while transforming positional to named parameters. --- build.xml | 8 ++++---- lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php | 1 + lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.xml b/build.xml index ca368eb5a..54693d2c2 100644 --- a/build.xml +++ b/build.xml @@ -142,16 +142,16 @@ - - + + - - + + diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php index 78b21796d..4ebdbec1c 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php @@ -60,6 +60,7 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement * placeholders and converted to a named parameter. * * @param string $statement The SQL statement to convert. + * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. */ private function _convertPositionalToNamedPlaceholders($statement) { diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 9b8609850..54c7e5195 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -815,7 +815,7 @@ class BasicEntityPersister : $baseTableAlias; $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); - $orderBySql .= $orderBySql ? ', ' : 'ORDER BY '; + $orderBySql .= $orderBySql ? ', ' : ' ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } From 47ebbb4312d53da54d066717bea1886369299528 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 27 Apr 2010 23:18:28 +0200 Subject: [PATCH 46/56] Updated build.xml with new pear channel URLs. --- build.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index 54693d2c2..7665bdb63 100644 --- a/build.xml +++ b/build.xml @@ -163,7 +163,7 @@ DoctrineCommon Common Doctrine code - pear.phpdoctrine.org + pear.doctrine-project.org The Doctrine Common package contains shared code between the other packages. @@ -182,7 +182,7 @@ DoctrineDBAL Doctrine Database Abstraction Layer - pear.phpdoctrine.org + pear.doctrine-project.org The Doctrine DBAL package is the database abstraction layer used to power the ORM package. @@ -201,7 +201,7 @@ DoctrineORM Doctrine Object Relationl Mapper - pear.phpdoctrine.org + pear.doctrine-project.org The Doctrine ORM package is the primary package containing the object relational mapper. From de72db2a7a1b9efe0f77a6746baf6c9fe789c2f2 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Wed, 28 Apr 2010 11:39:47 -0400 Subject: [PATCH 47/56] Fixing xml schema generation for code completion in ides --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 366e14de8..23466a1b9 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -53,7 +53,7 @@ class XmlExporter extends AbstractExporter $xml->addAttribute('xmlns', 'http://doctrine-project.org/schemas/orm/doctrine-mapping'); $xml->addAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); - $xml->addAttribute('xsi:schemaLocation', 'http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd'); + $xml->addAttribute('xsi:schemaLocation', 'http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd'); if ($metadata->isMappedSuperclass) { $root = $xml->addChild('mapped-superclass'); From f38584a51ef9bac77b1f91e968d9146d22ded11c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 28 Apr 2010 20:27:53 +0200 Subject: [PATCH 48/56] DDC-545 - Add unittest for all drivers uniqueConstraint capabilities, fix bug in XML Driver that disallowed using them --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 16 ++++++++------ .../ORM/Mapping/AbstractMappingDriverTest.php | 22 ++++++++++++++++++- .../php/Doctrine.Tests.ORM.Mapping.User.php | 5 ++++- .../Doctrine.Tests.ORM.Mapping.User.dcm.xml | 4 ++++ .../Doctrine.Tests.ORM.Mapping.User.dcm.yml | 5 ++++- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 40608d001..fd06dd07c 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -118,15 +118,17 @@ class XmlDriver extends AbstractFileDriver // Evaluate if (isset($xmlRoot->{'unique-constraints'})) { foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) { - if (is_string($unique['columns'])) { - $columns = explode(',', $unique['columns']); + $columns = explode(',', (string)$unique['columns']); + + if (isset($unique['name'])) { + $metadata->table['uniqueConstraints'][(string)$unique['name']] = array( + 'columns' => $columns + ); } else { - $columns = $unique['columns']; + $metadata->table['uniqueConstraints'][] = array( + 'columns' => $columns + ); } - - $metadata->table['uniqueConstraints'][$unique['name']] = array( - 'columns' => $columns - ); } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 1fdf4a33c..741b35a6c 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -36,6 +36,23 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase return $class; } + /** + * @depends testEntityTableNameAndInheritance + * @param ClassMetadata $class + */ + public function testEntityUniqueConstraints($class) + { + $this->assertArrayHasKey('uniqueConstraints', $class->table, + 'ClassMetadata should have uniqueConstraints key in table property when Unique Constraints are set.'); + + $this->assertEquals(array( + "search_idx" => array("columns" => array("name", "user_email")) + ), $class->table['uniqueConstraints']); + + return $class; + } + + /** * @depends testEntityTableNameAndInheritance * @param ClassMetadata $class @@ -206,7 +223,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase /** * @Entity * @HasLifecycleCallbacks - * @Table(name="cms_users") + * @Table(name="cms_users", uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "user_email"})}) */ class User { @@ -369,5 +386,8 @@ class User ), 'orderBy' => NULL, )); + $metadata->table['uniqueConstraints'] = array( + 'search_idx' => array('columns' => array('name', 'user_email')), + ); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php index e2a057f14..a7a58c60d 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php @@ -102,4 +102,7 @@ $metadata->mapManyToMany(array( ), ), 'orderBy' => NULL, - )); \ No newline at end of file + )); +$metadata->table['uniqueConstraints'] = array( + 'search_idx' => array('columns' => array('name', 'user_email')), +); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml index 65b71f04c..7372604bb 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml @@ -6,6 +6,10 @@ http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> + + + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml index 3cf687169..9017da012 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml @@ -51,4 +51,7 @@ Doctrine\Tests\ORM\Mapping\User: - all lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] - postPersist: [ doStuffOnPostPersist ] \ No newline at end of file + postPersist: [ doStuffOnPostPersist ] + uniqueConstraints: + search_idx: + columns: name,user_email \ No newline at end of file From 41e830ca68a8e3a46041a1a368fc6d408d58fa6a Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 29 Apr 2010 08:13:02 -0400 Subject: [PATCH 49/56] Fixing sequence-generate in XmlDriver --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 40608d001..3c45ad8a4 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -203,9 +203,9 @@ class XmlDriver extends AbstractFileDriver if (isset($idElement->{'sequence-generator'})) { $seqGenerator = $idElement->{'sequence-generator'}; $metadata->setSequenceGeneratorDefinition(array( - 'sequenceName' => $seqGenerator->{'sequence-name'}, - 'allocationSize' => $seqGenerator->{'allocation-size'}, - 'initialValue' => $seqGeneratorAnnot->{'initial-value'} + 'sequenceName' => (string)$seqGenerator['sequence-name'], + 'allocationSize' => (string)$seqGenerator['allocation-size'], + 'initialValue' => (string)$seqGeneratorAnnot['initial-value'] )); } else if (isset($idElement->{'table-generator'})) { throw MappingException::tableIdGeneratorNotImplemented($className); From db2be55e27c87fa513073b2bf44456f1d1423582 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 30 Apr 2010 17:30:27 +0200 Subject: [PATCH 50/56] [DDC-561] Fixed. --- lib/Doctrine/ORM/Mapping/AssociationMapping.php | 3 ++- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 1 + lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | 2 +- lib/Doctrine/ORM/PersistentCollection.php | 2 +- tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php | 2 ++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index cadda07e3..488bcd0ff 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -356,7 +356,8 @@ abstract class AssociationMapping $serialized = array( 'sourceEntityName', 'targetEntityName', - 'sourceFieldName' + 'sourceFieldName', + 'fetchMode' ); if ($this->isCascadeDetach) { diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index a659e8235..18558c01a 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -286,6 +286,7 @@ class ClassMetadata extends ClassMetadataInfo 'identifier', 'isIdentifierComposite', // TODO: REMOVE 'name', + 'namespace', // TODO: REMOVE 'table', 'rootEntityName', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index ec3d92f7b..e44af01cf 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -143,7 +143,7 @@ class AnnotationDriver implements Driver throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } - // Evaluate DoctrineTable annotation + // Evaluate Table annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) { $tableAnnot = $classAnnotations['Doctrine\ORM\Mapping\Table']; $primaryTable = array( diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 81d9a3e07..afbbf361f 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -359,7 +359,7 @@ final class PersistentCollection implements Collection $this->_em->getUnitOfWork()->scheduleOrphanRemoval($removed); } } - + return $removed; } diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index 92231ae8e..96fc026f7 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -37,6 +37,7 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase // Check state $this->assertTrue(count($cm->getReflectionProperties()) > 0); + $this->assertEquals('Doctrine\Tests\Models\CMS', $cm->namespace); $this->assertTrue($cm->reflClass instanceof \ReflectionClass); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $cm->name); $this->assertEquals('UserParent', $cm->rootEntityName); @@ -47,6 +48,7 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($cm->getAssociationMapping('phonenumbers') instanceof \Doctrine\ORM\Mapping\OneToOneMapping); $this->assertEquals(1, count($cm->associationMappings)); $oneOneMapping = $cm->getAssociationMapping('phonenumbers'); + $this->assertTrue($oneOneMapping->fetchMode == \Doctrine\ORM\Mapping\AssociationMapping::FETCH_LAZY); $this->assertEquals('phonenumbers', $oneOneMapping->sourceFieldName); $this->assertEquals('Doctrine\Tests\Models\CMS\Bar', $oneOneMapping->targetEntityName); } From 94928c5dd8e7c18ba7f09c6e650dc13b1d4a0733 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Fri, 30 Apr 2010 12:27:52 -0400 Subject: [PATCH 51/56] [DDC-552] Fixing issue with getReflectionClass() not existing on ClassMetadataInfo when it is required by AnnotationDriver --- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 17 -------------- .../ORM/Mapping/ClassMetadataInfo.php | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 18558c01a..48bbc996a 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -41,13 +41,6 @@ use ReflectionClass, ReflectionProperty; */ class ClassMetadata extends ClassMetadataInfo { - /** - * The ReflectionClass instance of the mapped class. - * - * @var ReflectionClass - */ - public $reflClass; - /** * The ReflectionProperty instances of the mapped class. * @@ -76,16 +69,6 @@ class ClassMetadata extends ClassMetadataInfo $this->table['name'] = $this->reflClass->getShortName(); } - /** - * Gets the ReflectionClass instance of the mapped class. - * - * @return ReflectionClass - */ - public function getReflectionClass() - { - return $this->reflClass; - } - /** * Gets the ReflectionPropertys of the mapped class. * diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index ac481c48f..d1f13f002 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -19,6 +19,8 @@ namespace Doctrine\ORM\Mapping; +use ReflectionClass; + /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and it's associations. @@ -366,6 +368,13 @@ class ClassMetadataInfo */ public $versionField; + /** + * The ReflectionClass instance of the mapped class. + * + * @var ReflectionClass + */ + public $reflClass; + /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. @@ -378,6 +387,19 @@ class ClassMetadataInfo $this->rootEntityName = $entityName; } + /** + * Gets the ReflectionClass instance of the mapped class. + * + * @return ReflectionClass + */ + public function getReflectionClass() + { + if ( ! $this->reflClass) { + $this->reflClass = new ReflectionClass($entityName); + } + return $this->reflClass; + } + /** * Sets the change tracking policy used by this class. * From 6c7aaa727c0b25f62078cb05f3bb861c57f962ed Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 03:28:18 +0200 Subject: [PATCH 52/56] Added tests for 41e830ca68a8e3a46041a1a368fc6d408d58fa6a, thereby finding two issues with XML and YAML Driver handling of Sequence-Generator --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 2 +- .../ORM/Mapping/Driver/YamlDriver.php | 16 ++++++---- .../ORM/Mapping/AbstractMappingDriverTest.php | 29 ++++++++++++++++++- .../php/Doctrine.Tests.ORM.Mapping.User.php | 7 ++++- .../Doctrine.Tests.ORM.Mapping.User.dcm.xml | 1 + .../Doctrine.Tests.ORM.Mapping.User.dcm.yml | 4 +++ 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 5eaa8897e..fe221c0d8 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -207,7 +207,7 @@ class XmlDriver extends AbstractFileDriver $metadata->setSequenceGeneratorDefinition(array( 'sequenceName' => (string)$seqGenerator['sequence-name'], 'allocationSize' => (string)$seqGenerator['allocation-size'], - 'initialValue' => (string)$seqGeneratorAnnot['initial-value'] + 'initialValue' => (string)$seqGenerator['initial-value'] )); } else if (isset($idElement->{'table-generator'})) { throw MappingException::tableIdGeneratorNotImplemented($className); diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index f5448ff1f..873f9b21f 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -135,6 +135,10 @@ class YamlDriver extends AbstractFileDriver if (isset($element['id'])) { // Evaluate identifier settings foreach ($element['id'] as $name => $idElement) { + if (!isset($idElement['type'])) { + throw MappingException::propertyTypeIsRequired($className, $name); + } + $mapping = array( 'id' => true, 'fieldName' => $name, @@ -151,6 +155,12 @@ class YamlDriver extends AbstractFileDriver $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($idElement['generator']['strategy']))); } + // Check for SequenceGenerator/TableGenerator definition + if (isset($idElement['sequenceGenerator'])) { + $metadata->setSequenceGeneratorDefinition($idElement['sequenceGenerator']); + } else if (isset($idElement['tableGenerator'])) { + throw MappingException::tableIdGeneratorNotImplemented($className); + } } } @@ -177,12 +187,6 @@ class YamlDriver extends AbstractFileDriver . strtoupper($fieldMapping['generator']['strategy']))); } } - // Check for SequenceGenerator/TableGenerator definition - if (isset($fieldMapping['sequenceGenerator'])) { - $metadata->setSequenceGeneratorDefinition($fieldMapping['sequenceGenerator']); - } else if (isset($fieldMapping['tableGenerator'])) { - throw MappingException::tableIdGeneratorNotImplemented($className); - } if (isset($fieldMapping['column'])) { $mapping['columnName'] = $fieldMapping['column']; } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 741b35a6c..270e16234 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -52,6 +52,23 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase return $class; } + /** + * @depends testEntityTableNameAndInheritance + * @param ClassMetadata $class + */ + public function testEntitySequence($class) + { + $this->assertType('array', $class->sequenceGeneratorDefinition, 'No Sequence Definition set on this driver.'); + $this->assertEquals( + array( + 'sequenceName' => 'tablename_seq', + 'allocationSize' => 100, + 'initialValue' => 1, + ), + $class->sequenceGeneratorDefinition + ); + } + /** * @depends testEntityTableNameAndInheritance @@ -227,7 +244,12 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ class User { - /** @Id @Column(type="integer") @generatedValue(strategy="AUTO") */ + /** + * @Id + * @Column(type="integer") + * @generatedValue(strategy="AUTO") + * @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100) + **/ public $id; /** @@ -389,5 +411,10 @@ class User $metadata->table['uniqueConstraints'] = array( 'search_idx' => array('columns' => array('name', 'user_email')), ); + $metadata->setSequenceGeneratorDefinition(array( + 'sequenceName' => 'tablename_seq', + 'allocationSize' => 100, + 'initialValue' => 1, + )); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php index a7a58c60d..819a01109 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php @@ -105,4 +105,9 @@ $metadata->mapManyToMany(array( )); $metadata->table['uniqueConstraints'] = array( 'search_idx' => array('columns' => array('name', 'user_email')), -); \ No newline at end of file +); +$metadata->setSequenceGeneratorDefinition(array( + 'sequenceName' => 'tablename_seq', + 'allocationSize' => 100, + 'initialValue' => 1, + )); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml index 7372604bb..793be0f06 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml @@ -18,6 +18,7 @@ + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml index 9017da012..7dd6bfaed 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml @@ -6,6 +6,10 @@ Doctrine\Tests\ORM\Mapping\User: type: integer generator: strategy: AUTO + sequenceGenerator: + sequenceName: tablename_seq + allocationSize: 100 + initialValue: 1 fields: name: type: string From 987fbee75862b9235292b0219fb9ffe7dfd0347a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 03:33:13 +0200 Subject: [PATCH 53/56] DDC-537 Add missing sequence-generator tag definition in doctrine-mapping.xsd --- doctrine-mapping.xsd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index f3bea4f8e..e74010a8e 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -173,12 +173,19 @@ + + + + + + + From 427d4eed29e9c8893e33770492fcaee0100e50e6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 03:57:58 +0200 Subject: [PATCH 54/56] DDC-541 - Schema Table now return Pk, then Fk, then normal columns in that order --- lib/Doctrine/DBAL/Schema/Table.php | 19 ++++++++++++++++++- .../SchemaTool/MySqlSchemaToolTest.php | 2 +- .../SchemaTool/PostgreSqlSchemaToolTest.php | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/DBAL/Schema/Table.php b/lib/Doctrine/DBAL/Schema/Table.php index c514f8f8f..7cf31fa8e 100644 --- a/lib/Doctrine/DBAL/Schema/Table.php +++ b/lib/Doctrine/DBAL/Schema/Table.php @@ -510,7 +510,24 @@ class Table extends AbstractAsset */ public function getColumns() { - return $this->_columns; + $columns = $this->_columns; + + $pkCols = array(); + $fkCols = array(); + + if ($this->hasIndex($this->_primaryKeyName)) { + $pkCols = $this->getPrimaryKey()->getColumns(); + } + foreach ($this->getForeignKeys() AS $fk) { + /* @var $fk ForeignKeyConstraint */ + $fkCols = array_merge($fkCols, $fk->getColumns()); + } + $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns))); + + uksort($columns, function($a, $b) use($colNames) { + return (array_search($a, $colNames) >= array_search($b, $colNames)); + }); + return $columns; } diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php index 78977205c..194f7109e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php @@ -27,7 +27,7 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $tool = new SchemaTool($this->_em); $sql = $tool->getCreateSchemaSql($classes); $this->assertEquals(8, count($sql)); - $this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); + $this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); $this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX cms_users_username_uniq (username), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id)) ENGINE = InnoDB", $sql[2]); $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber)) ENGINE = InnoDB", $sql[3]); diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php index 9210bbd3b..8646068f0 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php @@ -34,7 +34,7 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $sql = $tool->getCreateSchemaSql($classes); $this->assertEquals(count($sql), 11); - $this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))", $sql[0]); + $this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", $sql[0]); $this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", $sql[1]); $this->assertEquals("CREATE UNIQUE INDEX cms_users_username_uniq ON cms_users (username)", $sql[2]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", $sql[3]); From 9277dba383d1d584594f1f3f9d02494b62675102 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 04:31:10 +0200 Subject: [PATCH 55/56] Cleanup in DB2 Platform and SchemaManager --- lib/Doctrine/DBAL/Platforms/DB2Platform.php | 2 +- lib/Doctrine/DBAL/Schema/DB2SchemaManager.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Doctrine/DBAL/Platforms/DB2Platform.php b/lib/Doctrine/DBAL/Platforms/DB2Platform.php index 96cb18870..9cc04840d 100644 --- a/lib/Doctrine/DBAL/Platforms/DB2Platform.php +++ b/lib/Doctrine/DBAL/Platforms/DB2Platform.php @@ -25,7 +25,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; -class Db2Platform extends AbstractPlatform +class DB2Platform extends AbstractPlatform { /** * Gets the SQL snippet used to declare a VARCHAR column type. diff --git a/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php b/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php index 9c1466715..e5d87148b 100644 --- a/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php @@ -42,7 +42,6 @@ class DB2SchemaManager extends AbstractSchemaManager */ public function listTableNames() { - $sql = $this->_platform->getListTablesSQL(); $sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')"; From 302409dba49ebc59a47ddc87b325f56cf184e4cd Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 1 May 2010 13:51:29 +0200 Subject: [PATCH 56/56] Fixed Error due to merging tests/Doctrine/Tests/TestInit.php file --- tests/Doctrine/Tests/TestInit.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Doctrine/Tests/TestInit.php b/tests/Doctrine/Tests/TestInit.php index dd4a82080..1c7bea434 100644 --- a/tests/Doctrine/Tests/TestInit.php +++ b/tests/Doctrine/Tests/TestInit.php @@ -13,6 +13,9 @@ require_once __DIR__ . '/../../../lib/Doctrine/Common/ClassLoader.php'; $classLoader = new \Doctrine\Common\ClassLoader('Doctrine'); $classLoader->register(); +$classLoader = new \Doctrine\Common\ClassLoader('Symfony', __DIR__ . "/../../../lib/vendor"); +$classLoader->register(); + if (!file_exists(__DIR__."/Proxies")) { if (!mkdir(__DIR__."/Proxies")) { throw new Exception("Could not create " . __DIR__."/Proxies Folder.");