diff --git a/bin/doctrine.php b/bin/doctrine.php index b1a29efeb..c307cb800 100644 --- a/bin/doctrine.php +++ b/bin/doctrine.php @@ -52,6 +52,7 @@ $cli->addCommands(array( new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(), new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(), new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(), + new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(), )); $cli->run(); \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php index 966078e8c..5b58fb72f 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -179,7 +179,7 @@ class AnnotationReader // Attempt to grab data from cache if (($data = $this->_cache->fetch($cacheKey)) !== false) { return $data; - } + } $context = 'method ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()'; $annotations = $this->_parser->parse($method->getDocComment(), $context); diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 38e368c13..3ad6e6e0b 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -310,9 +310,8 @@ class Connection implements DriverConnection * @param string $statement The SQL query. * @param array $params The query parameters. * @return array - * @todo Rename: fetchAssoc */ - public function fetchRow($statement, array $params = array()) + public function fetchAssoc($statement, array $params = array()) { return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC); } @@ -583,10 +582,10 @@ class Connection implements DriverConnection * represents a row of the result set. * @return mixed The projected result of the query. */ - public function project($query, array $params = array(), Closure $function) + public function project($query, array $params, Closure $function) { $result = array(); - $stmt = $this->executeQuery($query, $params); + $stmt = $this->executeQuery($query, $params ?: array()); while ($row = $stmt->fetch()) { $result[] = $function($row); @@ -789,7 +788,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\AbstractSchemaManager + * @return Doctrine\DBAL\Schema\SchemaManager */ public function getSchemaManager() { @@ -820,7 +819,7 @@ class Connection implements DriverConnection * @return boolean * @throws ConnectionException If no transaction is active. */ - public function getRollbackOnly() + public function isRollbackOnly() { if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); @@ -911,4 +910,4 @@ class Connection implements DriverConnection } } } -} +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver.php b/lib/Doctrine/DBAL/Driver.php index 535601e0f..4933b96d3 100644 --- a/lib/Doctrine/DBAL/Driver.php +++ b/lib/Doctrine/DBAL/Driver.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\DBAL; @@ -51,5 +68,5 @@ interface Driver * @param Doctrine\DBAL\Connection $conn * @return string $database */ - public function getDatabase(\Doctrine\DBAL\Connection $conn); + public function getDatabase(Connection $conn); } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/Connection.php b/lib/Doctrine/DBAL/Driver/Connection.php index cee11f31a..4cc5776a6 100644 --- a/lib/Doctrine/DBAL/Driver/Connection.php +++ b/lib/Doctrine/DBAL/Driver/Connection.php @@ -1,7 +1,5 @@ + * @since 2.0 + * @author Benjamin Eberlei */ class DB2Driver implements Driver { diff --git a/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php b/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php index 1b5fb4efc..b9ff38899 100644 --- a/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php +++ b/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php @@ -1,7 +1,5 @@ exec('BEGIN TRANSACTION'); } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + $stmt = $this->query('SELECT SCOPE_IDENTITY()'); + $id = $stmt->fetchColumn(); + $stmt->closeCursor(); + return $id; + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php index 37beb01e4..71a7f9f27 100644 --- a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage @@ -154,11 +149,12 @@ class EntityManager * * Example: * - * [php] + * * $qb = $em->createQueryBuilder(); * $expr = $em->getExpressionBuilder(); * $qb->select('u')->from('User', 'u') * ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2))); + * * * @return ExpressionBuilder */ @@ -172,6 +168,8 @@ class EntityManager /** * Starts a transaction on the underlying database connection. + * + * @deprecated Use {@link getConnection}.beginTransaction(). */ public function beginTransaction() { @@ -180,6 +178,8 @@ class EntityManager /** * Commits a transaction on the underlying database connection. + * + * @deprecated Use {@link getConnection}.commit(). */ public function commit() { @@ -187,13 +187,13 @@ class EntityManager } /** - * Performs a rollback on the underlying database connection and closes the - * EntityManager as it may now be in a corrupted state. + * Performs a rollback on the underlying database connection. + * + * @deprecated Use {@link getConnection}.rollback(). */ public function rollback() { $this->_conn->rollback(); - $this->close(); } /** @@ -274,6 +274,9 @@ class EntityManager * Flushes all changes to objects that have been queued up to now to the database. * This effectively synchronizes the in-memory state of managed objects with the * database. + * + * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that + * makes use of optimistic locking fails. */ public function flush() { diff --git a/lib/Doctrine/ORM/Id/IdentityGenerator.php b/lib/Doctrine/ORM/Id/IdentityGenerator.php index 96ad08add..75da2733d 100644 --- a/lib/Doctrine/ORM/Id/IdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/IdentityGenerator.php @@ -21,23 +21,36 @@ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; +/** + * Id generator that obtains IDs from special "identity" columns. These are columns + * that automatically get a database-generated, auto-incremented identifier on INSERT. + * This generator obtains the last insert id after such an insert. + */ class IdentityGenerator extends AbstractIdGenerator { + /** @var string The name of the sequence to pass to lastInsertId(), if any. */ + private $_seqName; + /** - * Generates an ID for the given entity. - * - * @param object $entity - * @return integer|float - * @override + * @param string $seqName The name of the sequence to pass to lastInsertId() + * to obtain the last generated identifier within the current + * database session/connection, if any. */ - public function generate(EntityManager $em, $entity) + public function __construct($seqName = null) { - return $em->getConnection()->lastInsertId(); + $this->_seqName = $seqName; } /** - * @return boolean - * @override + * {@inheritdoc} + */ + public function generate(EntityManager $em, $entity) + { + return $em->getConnection()->lastInsertId($this->_seqName); + } + + /** + * {@inheritdoc} */ public function isPostInsertGenerator() { diff --git a/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php b/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php deleted file mode 100644 index c7158bbed..000000000 --- a/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php +++ /dev/null @@ -1,46 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Id; - -use Doctrine\ORM\EntityManager; - -class SequenceIdentityGenerator extends IdentityGenerator -{ - private $_sequenceName; - - public function __construct($sequenceName) - { - $this->_sequenceName = $sequenceName; - } - - public function generate(EntityManager $em, $entity) - { - return $em->getConnection()->lastInsertId($this->_sequenceName); - } - - /** - * @return boolean - * @override - */ - public function isPostInsertGenerator() - { - return true; - } -} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 1dc812cd7..6429989cd 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -19,8 +19,10 @@ namespace Doctrine\ORM\Mapping; -use Doctrine\ORM\ORMException, - Doctrine\DBAL\Platforms\AbstractPlatform, +use ReflectionException, + Doctrine\ORM\ORMException, + Doctrine\ORM\EntityManager, + Doctrine\DBAL\Platforms, Doctrine\ORM\Events; /** @@ -53,7 +55,7 @@ class ClassMetadataFactory * * @param $driver The metadata driver to use. */ - public function __construct(\Doctrine\ORM\EntityManager $em) + public function __construct(EntityManager $em) { $this->_em = $em; } @@ -94,15 +96,15 @@ class ClassMetadataFactory if ( ! $this->_initialized) { $this->_initialize(); } - + $metadata = array(); foreach ($this->_driver->getAllClassNames() as $className) { $metadata[] = $this->getMetadataFor($className); } - + return $metadata; } - + /** * Lazy initialization of this stuff, especially the metadata driver, * since these are not needed at all when a metadata cache is active. @@ -252,7 +254,7 @@ class ClassMetadataFactory // Invoke driver try { $this->_driver->loadMetadataForClass($className, $class); - } catch(\ReflectionException $e) { + } catch(ReflectionException $e) { throw MappingException::reflectionFailure($className, $e); } @@ -275,9 +277,9 @@ class ClassMetadataFactory } else { $this->_completeIdGeneratorMapping($class); } - + if ($parent && $parent->isInheritanceTypeSingleTable()) { - $class->setTableName($parent->getTableName()); + $class->setPrimaryTable($parent->table); } $class->setParentClasses($visited); @@ -376,7 +378,13 @@ class ClassMetadataFactory // Create & assign an appropriate ID generator instance switch ($class->generatorType) { case ClassMetadata::GENERATOR_TYPE_IDENTITY: - $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator()); + // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to + // __seq in PostgreSQL for SERIAL columns. + // Not pretty but necessary and the simplest solution that currently works. + $seqName = $this->_targetPlatform instanceof Platforms\PostgreSQLPlatform ? + $class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' : + null; + $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName)); break; case ClassMetadata::GENERATOR_TYPE_SEQUENCE: // If there is no sequence definition yet, create a default definition diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index a6100856c..8a016fd47 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -317,7 +317,7 @@ class ClassMetadataInfo * READ-ONLY: The ID generator used for generating IDs for this class. * * @var AbstractIdGenerator - * @todo Remove + * @todo Remove! */ public $idGenerator; @@ -335,6 +335,7 @@ class ClassMetadataInfo * * * @var array + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $sequenceGeneratorDefinition; @@ -343,6 +344,7 @@ class ClassMetadataInfo * TABLE generation strategy. * * @var array + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $tableGeneratorDefinition; @@ -901,8 +903,8 @@ class ClassMetadataInfo /** * Sets the name of the primary table the class is mapped to. * - * @param string $tableName The table name. - * @deprecated + * @param string $tableName The table name. + * @deprecated Use {@link setPrimaryTable}. */ public function setTableName($tableName) { @@ -910,18 +912,22 @@ class ClassMetadataInfo } /** - * Sets the primary table definition. The provided array must have the + * Sets the primary table definition. The provided array supports the * following structure: * - * name => - * schema => - * catalog => + * name => (optional, defaults to class name) + * indexes => array of indexes (optional) + * uniqueConstraints => array of constraints (optional) * - * @param array $primaryTableDefinition + * @param array $table */ - public function setPrimaryTable(array $primaryTableDefinition) + public function setPrimaryTable(array $table) { - $this->table = $primaryTableDefinition; + if (isset($table['name']) && $table['name'][0] == '`') { + $table['name'] = trim($table['name'], '`'); + $table['quoted'] = true; + } + $this->table = $table; } /** diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index e44af01cf..6a61772a7 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.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 */ class AnnotationDriver implements Driver { diff --git a/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php index 99b8a145a..ff86597f6 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php @@ -1,7 +1,5 @@ attributes + $table = array(); if (isset($xmlRoot['table'])) { - $metadata->table['name'] = (string)$xmlRoot['table']; + $table['name'] = (string)$xmlRoot['table']; } + $metadata->setPrimaryTable($table); + + /* not implemented specially anyway. use table = schema.table if (isset($xmlRoot['schema'])) { $metadata->table['schema'] = (string)$xmlRoot['schema']; - } - + }*/ + if (isset($xmlRoot['inheritance-type'])) { $inheritanceType = (string)$xmlRoot['inheritance-type']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); @@ -479,4 +483,4 @@ class XmlDriver extends AbstractFileDriver return $result; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 873f9b21f..05a3fe125 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.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 */ class YamlDriver extends AbstractFileDriver { @@ -61,13 +56,16 @@ class YamlDriver extends AbstractFileDriver } // Evaluate root level properties + $table = array(); if (isset($element['table'])) { - $metadata->table['name'] = $element['table']; + $table['name'] = $element['table']; } + $metadata->setPrimaryTable($table); + /* not implemented specially anyway. use table = schema.table if (isset($element['schema'])) { $metadata->table['schema'] = $element['schema']; - } + }*/ if (isset($element['inheritanceType'])) { $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType']))); diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index abea24363..f03f8503c 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -19,13 +19,15 @@ namespace Doctrine\ORM; +use Exception; + /** * Base exception class for all ORM exceptions. * * @author Roman Borschel * @since 2.0 */ -class ORMException extends \Exception +class ORMException extends Exception { public static function missingMappingDriverImpl() { diff --git a/lib/Doctrine/ORM/OptimisticLockException.php b/lib/Doctrine/ORM/OptimisticLockException.php index ad6cde1ea..15dd61c44 100644 --- a/lib/Doctrine/ORM/OptimisticLockException.php +++ b/lib/Doctrine/ORM/OptimisticLockException.php @@ -20,15 +20,33 @@ namespace Doctrine\ORM; /** - * OptimisticLockException + * An OptimisticLockException is thrown when a version check on an object + * that uses optimistic locking through a version field fails. * * @author Roman Borschel * @since 2.0 */ class OptimisticLockException extends ORMException { - public static function lockFailed() + private $entity; + + public function __construct($msg, $entity) { - return new self("The optimistic lock failed."); + $this->entity = $entity; + } + + /** + * Gets the entity that caused the exception. + * + * @return object + */ + public function getEntity() + { + return $this->entity; + } + + public static function lockFailed($entity) + { + return new self("The optimistic lock on an entity failed.", $entity); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 54c7e5195..376572b30 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -330,7 +330,7 @@ class BasicEntityPersister $result = $this->_conn->executeUpdate($sql, $params, $types); if ($this->_class->isVersioned && ! $result) { - throw OptimisticLockException::lockFailed(); + throw OptimisticLockException::lockFailed($entity); } } diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index b15248689..241a28751 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -267,11 +267,11 @@ class Parser { $AST = $this->getAST(); - if ($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) { + if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { $this->_customTreeWalkers = $customWalkers; } - if ($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) { + if (($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) { $this->_customOutputWalker = $customOutputWalker; } @@ -1786,6 +1786,12 @@ class Parser $conditionalTerms[] = $this->ConditionalTerm(); } + // Phase 1 AST optimization: Prevent AST\ConditionalExpression + // if only one AST\ConditionalTerm is defined + if (count($conditionalTerms) == 1) { + return $conditionalTerms[0]; + } + return new AST\ConditionalExpression($conditionalTerms); } @@ -1804,6 +1810,12 @@ class Parser $conditionalFactors[] = $this->ConditionalFactor(); } + // Phase 1 AST optimization: Prevent AST\ConditionalTerm + // if only one AST\ConditionalFactor is defined + if (count($conditionalFactors) == 1) { + return $conditionalFactors[0]; + } + return new AST\ConditionalTerm($conditionalFactors); } @@ -1820,11 +1832,19 @@ class Parser $this->match(Lexer::T_NOT); $not = true; } + + $conditionalPrimary = $this->ConditionalPrimary(); - $condFactor = new AST\ConditionalFactor($this->ConditionalPrimary()); - $condFactor->not = $not; + // Phase 1 AST optimization: Prevent AST\ConditionalFactor + // if only one AST\ConditionalPrimary is defined + if ( ! $not) { + return $conditionalPrimary; + } - return $condFactor; + $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary); + $conditionalFactor->not = $not; + + return $conditionalFactor; } /** @@ -2104,6 +2124,12 @@ class Parser $terms[] = $this->ArithmeticTerm(); } + // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression + // if only one AST\ArithmeticTerm is defined + if (count($terms) == 1) { + return $terms[0]; + } + return new AST\SimpleArithmeticExpression($terms); } @@ -2124,6 +2150,12 @@ class Parser $factors[] = $this->ArithmeticFactor(); } + // Phase 1 AST optimization: Prevent AST\ArithmeticTerm + // if only one AST\ArithmeticFactor is defined + if (count($factors) == 1) { + return $factors[0]; + } + return new AST\ArithmeticTerm($factors); } @@ -2134,14 +2166,22 @@ class Parser */ public function ArithmeticFactor() { - $sign = null; + $sign = null; - if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) { - $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); - $sign = $isPlus; - } + if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) { + $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); + $sign = $isPlus; + } + + $primary = $this->ArithmeticPrimary(); - return new AST\ArithmeticFactor($this->ArithmeticPrimary(), $sign); + // Phase 1 AST optimization: Prevent AST\ArithmeticFactor + // if only one AST\ArithmeticPrimary is defined + if ($sign === null) { + return $primary; + } + + return new AST\ArithmeticFactor($primary, $sign); } /** diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index af9b81afb..52e73585e 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -665,9 +665,7 @@ class SqlWalker implements TreeWalker */ public function walkHavingClause($havingClause) { - return ' HAVING ' . implode( - ' OR ', array_map(array($this, 'walkConditionalTerm'), $havingClause->conditionalExpression->conditionalTerms) - ); + return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); } /** @@ -775,10 +773,10 @@ class SqlWalker implements TreeWalker } // Handle WITH clause - if ($join->conditionalExpression !== null) { - $sql .= ' AND (' . implode(' OR ', - array_map(array($this, 'walkConditionalTerm'), $join->conditionalExpression->conditionalTerms) - ). ')'; + if (($condExpr = $join->conditionalExpression) !== null) { + // Phase 2 AST optimization: Skip processment of ConditionalExpression + // if only one ConditionalTerm is defined + $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')'; } $discrSql = $this->_generateDiscriminatorColumnConditionSQL($joinedDqlAlias); @@ -787,7 +785,7 @@ class SqlWalker implements TreeWalker $sql .= ' AND ' . $discrSql; } - //FIXME: these should either be nested or all forced to be left joins (DDC-XXX) + // FIXME: these should either be nested or all forced to be left joins (DDC-XXX) if ($targetClass->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); } @@ -879,7 +877,12 @@ class SqlWalker implements TreeWalker $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias); } - else if ($expr instanceof AST\SimpleArithmeticExpression) { + else if ( + $expr instanceof AST\SimpleArithmeticExpression || + $expr instanceof AST\ArithmeticTerm || + $expr instanceof AST\ArithmeticFactor || + $expr instanceof AST\ArithmeticPrimary + ) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; } else { @@ -1211,20 +1214,27 @@ class SqlWalker implements TreeWalker */ public function walkWhereClause($whereClause) { - $sql = ' WHERE '; - $condExpr = $whereClause->conditionalExpression; + $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias); + $condSql = $this->walkConditionalExpression($whereClause->conditionalExpression); - $sql .= implode( - ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) - ); + return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); + } - $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias); - - if ($discrSql) { - $sql .= ' AND ' . $discrSql; - } - - return $sql; + /** + * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. + * + * @param ConditionalExpression + * @return string The SQL. + */ + public function walkConditionalExpression($condExpr) + { + // Phase 2 AST optimization: Skip processment of ConditionalExpression + // if only one ConditionalTerm is defined + return ( ! ($condExpr instanceof AST\ConditionalExpression)) + ? $this->walkConditionalTerm($condExpr) + : implode( + ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) + ); } /** @@ -1235,9 +1245,13 @@ class SqlWalker implements TreeWalker */ public function walkConditionalTerm($condTerm) { - return implode( - ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors) - ); + // Phase 2 AST optimization: Skip processment of ConditionalTerm + // if only one ConditionalFactor is defined + return ( ! ($condTerm instanceof AST\ConditionalTerm)) + ? $this->walkConditionalFactor($condTerm) + : implode( + ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors) + ); } /** @@ -1248,21 +1262,28 @@ class SqlWalker implements TreeWalker */ public function walkConditionalFactor($factor) { - $sql = ($factor->not) ? 'NOT ' : ''; - - $primary = $factor->conditionalPrimary; + // Phase 2 AST optimization: Skip processment of ConditionalFactor + // if only one ConditionalPrimary is defined + return ( ! ($factor instanceof AST\ConditionalFactor)) + ? $this->walkConditionalPrimary($factor) + : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); + } + /** + * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. + * + * @param ConditionalPrimary + * @return string The SQL. + */ + public function walkConditionalPrimary($primary) + { if ($primary->isSimpleConditionalExpression()) { - $sql .= $primary->simpleConditionalExpression->dispatch($this); + return $primary->simpleConditionalExpression->dispatch($this); } else if ($primary->isConditionalExpression()) { $condExpr = $primary->conditionalExpression; - $sql .= '(' . implode( - ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) - ) . ')'; + return '(' . $this->walkConditionalExpression($condExpr) . ')'; } - - return $sql; } /** @@ -1602,6 +1623,21 @@ class SqlWalker implements TreeWalker : $this->walkSubselect($arithmeticExpr->subselect); } + /** + * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. + * + * @param SimpleArithmeticExpression + * @return string The SQL. + */ + public function walkSimpleArithmeticExpression($simpleArithmeticExpr) + { + return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) + ? $this->walkArithmeticTerm($simpleArithmeticExpr) + : implode( + ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms) + ); + } + /** * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. * @@ -1610,11 +1646,55 @@ class SqlWalker implements TreeWalker */ public function walkArithmeticTerm($term) { - if (is_string($term)) return $term; + if (is_string($term)) { + return $term; + } - return implode( - ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors) - ); + // Phase 2 AST optimization: Skip processment of ArithmeticTerm + // if only one ArithmeticFactor is defined + return ( ! ($term instanceof AST\ArithmeticTerm)) + ? $this->walkArithmeticFactor($term) + : implode( + ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors) + ); + } + + /** + * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. + * + * @param mixed + * @return string The SQL. + */ + public function walkArithmeticFactor($factor) + { + if (is_string($factor)) { + return $factor; + } + + // Phase 2 AST optimization: Skip processment of ArithmeticFactor + // if only one ArithmeticPrimary is defined + return ( ! ($factor instanceof AST\ArithmeticFactor)) + ? $this->walkArithmeticPrimary($factor) + : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) + . $this->walkArithmeticPrimary($factor->arithmeticPrimary); + } + + /** + * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. + * + * @param mixed + * @return string The SQL. + */ + public function walkArithmeticPrimary($primary) + { + if ($primary instanceof AST\SimpleArithmeticExpression) { + return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; + } else if ($primary instanceof AST\Node) { + return $primary->dispatch($this); + } + + // We need to deal with IdentificationVariable here + return ''; } /** @@ -1629,41 +1709,4 @@ class SqlWalker implements TreeWalker ? $this->_conn->quote($stringPrimary) : $stringPrimary->dispatch($this); } - - /** - * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. - * - * @param mixed - * @return string The SQL. - */ - public function walkArithmeticFactor($factor) - { - if (is_string($factor)) return $factor; - - $sql = ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')); - $primary = $factor->arithmeticPrimary; - - if ($primary instanceof AST\SimpleArithmeticExpression) { - $sql .= '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; - } else if ($primary instanceof AST\Node) { - $sql .= $primary->dispatch($this); - } else if (is_string($primary)) { - // We need to deal with IdentificationVariable here - } - - return $sql; - } - - /** - * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. - * - * @param SimpleArithmeticExpression - * @return string The SQL. - */ - public function walkSimpleArithmeticExpression($simpleArithmeticExpr) - { - return implode( - ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms) - ); - } } diff --git a/lib/Doctrine/ORM/Query/TreeWalker.php b/lib/Doctrine/ORM/Query/TreeWalker.php index b76b77258..1654f2a1c 100644 --- a/lib/Doctrine/ORM/Query/TreeWalker.php +++ b/lib/Doctrine/ORM/Query/TreeWalker.php @@ -218,6 +218,14 @@ interface TreeWalker */ function walkWhereClause($whereClause); + /** + * Walks down a ConditionalExpression AST node, thereby generating the appropriate SQL. + * + * @param ConditionalExpression + * @return string The SQL. + */ + function walkConditionalExpression($condExpr); + /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * @@ -234,6 +242,14 @@ interface TreeWalker */ function walkConditionalFactor($factor); + /** + * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. + * + * @param ConditionalPrimary + * @return string The SQL. + */ + function walkConditionalPrimary($primary); + /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * diff --git a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php index 669409630..7f5f33f3c 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php @@ -252,6 +252,14 @@ abstract class TreeWalkerAdapter implements TreeWalker */ public function walkWhereClause($whereClause) {} + /** + * Walks down a ConditionalExpression AST node, thereby generating the appropriate SQL. + * + * @param ConditionalExpression + * @return string The SQL. + */ + public function walkConditionalExpression($condExpr) {} + /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * @@ -268,6 +276,14 @@ abstract class TreeWalkerAdapter implements TreeWalker */ public function walkConditionalFactor($factor) {} + /** + * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. + * + * @param ConditionalPrimary + * @return string The SQL. + */ + public function walkConditionalPrimary($primary) {} + /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * diff --git a/lib/Doctrine/ORM/Query/TreeWalkerChain.php b/lib/Doctrine/ORM/Query/TreeWalkerChain.php index 0a83939cf..28ff54b95 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerChain.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerChain.php @@ -355,6 +355,19 @@ class TreeWalkerChain implements TreeWalker } } + /** + * Walks down a ConditionalExpression AST node, thereby generating the appropriate SQL. + * + * @param ConditionalExpression + * @return string The SQL. + */ + public function walkConditionalExpression($condExpr) + { + foreach ($this->_walkers as $walker) { + $walker->walkConditionalExpression($condExpr); + } + } + /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * @@ -381,6 +394,19 @@ class TreeWalkerChain implements TreeWalker } } + /** + * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. + * + * @param ConditionalPrimary + * @return string The SQL. + */ + public function walkConditionalPrimary($condPrimary) + { + foreach ($this->_walkers as $walker) { + $walker->walkConditionalPrimary($condPrimary); + } + } + /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index fcfe873d8..e547814ff 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -1,7 +1,5 @@ - * @author Jonathan Wage - * @author Roman Borschel + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel */ class QueryBuilder { + /* The query types. */ const SELECT = 0; const DELETE = 1; const UPDATE = 2; + /** The builder states. */ const STATE_DIRTY = 0; const STATE_CLEAN = 1; /** - * @var EntityManager $em The EntityManager used by this QueryBuilder. + * @var EntityManager The EntityManager used by this QueryBuilder. */ private $_em; /** - * @var array $dqlParts The array of DQL parts collected. + * @var array The array of DQL parts collected. */ private $_dqlParts = array( 'select' => array(), @@ -105,13 +102,17 @@ class QueryBuilder /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. - * Intended for convenient inline usage. Example: + * This producer method is intended for convenient inline usage. Example: * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. * * @return Expr */ @@ -141,16 +142,9 @@ class QueryBuilder } /** - * Get the state of this query builder instance + * Get the state of this query builder instance. * - * [php] - * if ($qb->getState() == QueryBuilder::STATE_DIRTY) { - * echo 'Query builder is dirty'; - * } else { - * echo 'Query builder is clean'; - * } - * - * @return integer + * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. */ public function getState() { @@ -158,15 +152,16 @@ class QueryBuilder } /** - * Get the complete DQL string for this query builder instance + * Get the complete DQL string formed by the current specifications of this QueryBuilder. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * echo $qb->getDql(); // SELECT u FROM User u + * * - * @return string The DQL string + * @return string The DQL query string. */ public function getDQL() { @@ -198,14 +193,15 @@ class QueryBuilder } /** - * Constructs a Query instance from the current configuration of the builder. + * Constructs a Query instance from the current specifications of the builder. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * $q = $qb->getQuery(); * $results = $q->execute(); + * * * @return Query */ @@ -218,17 +214,19 @@ class QueryBuilder } /** - * Get the root alias for the query. This is the first entity alias involved - * in the construction of the query + * Gets the root alias of the query. This is the first entity alias involved + * in the construction of the query. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * echo $qb->getRootAlias(); // u + * * * @return string $rootAlias + * @todo Rename/Refactor: getRootAliases(), there can be multiple roots! */ public function getRootAlias() { @@ -236,14 +234,15 @@ class QueryBuilder } /** - * Sets a query parameter. + * Sets a query parameter for the query being constructed. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id') * ->setParameter(':user_id', 1); + * * * @param string|integer $key The parameter position or name. * @param mixed $value The parameter value. @@ -256,9 +255,9 @@ class QueryBuilder } /** - * Sets a collection of query parameters. + * Sets a collection of query parameters for the query being constructed. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') @@ -267,8 +266,9 @@ class QueryBuilder * ':user_id1' => 1, * ':user_id2' => 2 * )); + * * - * @param array $params + * @param array $params The query parameters to set. * @return QueryBuilder This QueryBuilder instance. */ public function setParameters(array $params) @@ -278,17 +278,17 @@ class QueryBuilder } /** - * Get all defined parameters + * Gets all defined query parameters for the query being constructed. * - * @return array Defined parameters + * @return array The currently defined query parameters. */ - public function getParameters($params = array()) + public function getParameters() { return $this->_params; } - + /** - * Gets a query parameter. + * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. * @return mixed The value of the bound parameter. @@ -297,7 +297,7 @@ class QueryBuilder { return isset($this->_params[$key]) ? $this->_params[$key] : null; } - + /** * Sets the position of the first result to retrieve (the "offset"). * @@ -309,10 +309,10 @@ class QueryBuilder $this->_firstResult = $firstResult; return $this; } - + /** * Gets the position of the first result the query object was set to retrieve (the "offset"). - * Returns NULL if {@link setFirstResult} was not applied to this query builder. + * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. * * @return integer The position of the first result. */ @@ -324,7 +324,7 @@ class QueryBuilder /** * Sets the maximum number of results to retrieve (the "limit"). * - * @param integer $maxResults + * @param integer $maxResults The maximum number of results to retrieve. * @return QueryBuilder This QueryBuilder instance. */ public function setMaxResults($maxResults) @@ -345,7 +345,10 @@ class QueryBuilder } /** - * Add a single DQL query part to the array of parts + * Either appends to or replaces a single, generic query part. + * + * The available parts are: 'select', 'from', 'join', 'set', 'where', + * 'groupBy', 'having' and 'orderBy'. * * @param string $dqlPartName * @param string $dqlPart @@ -368,15 +371,17 @@ class QueryBuilder } /** - * Set the SELECT statement + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u', 'p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); + * * - * @param mixed $select String SELECT statement or SELECT Expr instance + * @param mixed $select The selection expressions. * @return QueryBuilder This QueryBuilder instance. */ public function select($select = null) @@ -393,16 +398,17 @@ class QueryBuilder } /** - * Add to the SELECT statement + * Adds an item that is to be returned in the query result. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->addSelect('p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); + * * - * @param mixed $select String SELECT statement or SELECT Expr instance + * @param mixed $select The selection expression. * @return QueryBuilder This QueryBuilder instance. */ public function addSelect($select = null) @@ -419,16 +425,18 @@ class QueryBuilder } /** - * Construct a DQL DELETE query + * Turns the query being built into a bulk delete query that ranges over + * a certain entity type. * - * [php] + * * $qb = $em->createQueryBuilder() * ->delete('User', 'u') * ->where('u.id = :user_id'); * ->setParameter(':user_id', 1); + * * - * @param string $delete The model to delete - * @param string $alias The alias of the model + * @param string $delete The class/type whose instances are subject to the deletion. + * @param string $alias The class/type alias used in the constructed query. * @return QueryBuilder This QueryBuilder instance. */ public function delete($delete = null, $alias = null) @@ -443,16 +451,18 @@ class QueryBuilder } /** - * Construct a DQL UPDATE query + * Turns the query being built into a bulk update query that ranges over + * a certain entity type. * - * [php] + * * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', md5('password')) * ->where('u.id = ?'); + * * - * @param string $update The model to update - * @param string $alias The alias of the model + * @param string $update The class/type whose instances are subject to the update. + * @param string $alias The class/type alias used in the constructed query. * @return QueryBuilder This QueryBuilder instance. */ public function update($update = null, $alias = null) @@ -467,12 +477,14 @@ class QueryBuilder } /** - * Specify the FROM part when constructing a SELECT DQL query + * Create and add a query root corresponding to the entity identified by the given alias, + * forming a cartesian product with any existing query roots. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') + * * * @param string $from The class name. * @param string $alias The alias of the class. @@ -482,20 +494,25 @@ class QueryBuilder { return $this->add('from', new Expr\From($from, $alias), true); } - + /** - * Add a INNER JOIN to an associated class. + * Creates and adds a join over an entity association to the query. * - * [php] + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') - * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * * - * @param string $join The relationship to join - * @param string $alias The alias of the join - * @param string $conditionType The condition type constant. Either ON or WITH. - * @param string $condition The condition for the join + * @param string $join The relationship to join + * @param string $alias The alias of the join + * @param string $conditionType The condition type constant. Either ON or WITH. + * @param string $condition The condition for the join * @return QueryBuilder This QueryBuilder instance. */ public function join($join, $alias, $conditionType = null, $condition = null) @@ -504,7 +521,11 @@ class QueryBuilder } /** - * Add an INNER JOIN to an associated class. + * Creates and adds a join over an entity association to the query. + * + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. * * [php] * $qb = $em->createQueryBuilder() @@ -512,10 +533,10 @@ class QueryBuilder * ->from('User', 'u') * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * - * @param string $join The relationship to join - * @param string $alias The alias of the join - * @param string $conditionType The condition type constant. Either ON or WITH. - * @param string $condition The condition for the join + * @param string $join The relationship to join + * @param string $alias The alias of the join + * @param string $conditionType The condition type constant. Either ON or WITH. + * @param string $condition The condition for the join * @return QueryBuilder This QueryBuilder instance. */ public function innerJoin($join, $alias, $conditionType = null, $condition = null) @@ -526,19 +547,24 @@ class QueryBuilder } /** - * Add a LEFT JOIN + * Creates and adds a left join over an entity association to the query. * - * [php] + * The entities in the joined association will be fetched as part of the query + * result if the alias used for the joined association is placed in the select + * expressions. + * + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); + * * - * @param string $join The relationship to join - * @param string $alias The alias of the join - * @param string $conditionType The condition type constant. Either ON or WITH. - * @param string $condition The condition for the join - * @return QueryBuilder $qb + * @param string $join The relationship to join + * @param string $alias The alias of the join + * @param string $conditionType The condition type constant. Either ON or WITH. + * @param string $condition The condition for the join + * @return QueryBuilder This QueryBuilder instance. */ public function leftJoin($join, $alias, $conditionType = null, $condition = null) { @@ -548,17 +574,18 @@ class QueryBuilder } /** - * Add a SET statement for a DQL UPDATE query + * Sets a new value for a field in a bulk update query. * - * [php] + * * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', md5('password')) * ->where('u.id = ?'); + * * - * @param string $key The key/field to set - * @param string $value The value, expression, placeholder, etc. to use in the SET - * @return QueryBuilder $qb + * @param string $key The key/field to set. + * @param string $value The value, expression, placeholder, etc. + * @return QueryBuilder This QueryBuilder instance. */ public function set($key, $value) { @@ -566,9 +593,10 @@ class QueryBuilder } /** - * Set and override any existing WHERE statements + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') @@ -584,9 +612,10 @@ class QueryBuilder * $qb->update('User', 'u') * ->set('u.password', md5('password')) * ->where($or); + * * - * @param mixed $predicates The predicates. - * @return QueryBuilder + * @param mixed $predicates The restriction predicates. + * @return QueryBuilder This QueryBuilder instance. */ public function where($predicates) { @@ -598,17 +627,19 @@ class QueryBuilder } /** - * Add a new WHERE statement with an AND + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); + * * - * @param mixed $where The WHERE statement - * @return QueryBuilder $qb + * @param mixed $where The query restrictions. + * @return QueryBuilder This QueryBuilder instance. * @see where() */ public function andWhere($where) @@ -627,14 +658,16 @@ class QueryBuilder } /** - * Add a new WHERE statement with an OR + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); + * * * @param mixed $where The WHERE statement * @return QueryBuilder $qb @@ -656,16 +689,18 @@ class QueryBuilder } /** - * Set the GROUP BY clause + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.id'); + * * - * @param string $groupBy The GROUP BY clause - * @return QueryBuilder $qb + * @param string $groupBy The grouping expression. + * @return QueryBuilder This QueryBuilder instance. */ public function groupBy($groupBy) { @@ -674,17 +709,18 @@ class QueryBuilder /** - * Add to the existing GROUP BY clause + * Adds a grouping expression to the query. * - * [php] + * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') - * ->groupBy('u.last_login'); - * ->addGroupBy('u.created_at') + * ->groupBy('u.lastLogin'); + * ->addGroupBy('u.createdAt') + * * - * @param string $groupBy The GROUP BY clause - * @return QueryBuilder $qb + * @param string $groupBy The grouping expression. + * @return QueryBuilder This QueryBuilder instance. */ public function addGroupBy($groupBy) { @@ -692,10 +728,11 @@ class QueryBuilder } /** - * Set the HAVING clause + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. * - * @param mixed $having - * @return QueryBuilder $qb + * @param mixed $having The restriction over the groups. + * @return QueryBuilder This QueryBuilder instance. */ public function having($having) { @@ -707,10 +744,11 @@ class QueryBuilder } /** - * Add to the existing HAVING clause with an AND + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. * - * @param mixed $having - * @return QueryBuilder $qb + * @param mixed $having The restriction to append. + * @return QueryBuilder This QueryBuilder instance. */ public function andHaving($having) { @@ -728,10 +766,11 @@ class QueryBuilder } /** - * Add to the existing HAVING clause with an OR + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. * - * @param mixed $having - * @return QueryBuilder $qb + * @param mixed $having The restriction to add. + * @return QueryBuilder This QueryBuilder instance. */ public function orHaving($having) { @@ -749,11 +788,12 @@ class QueryBuilder } /** - * Set the ORDER BY clause + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. * - * @param string $sort What to sort on - * @param string $order Optional: The order to sort the results. - * @return QueryBuilder $qb + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * @return QueryBuilder This QueryBuilder instance. */ public function orderBy($sort, $order = null) { @@ -762,11 +802,11 @@ class QueryBuilder } /** - * Add to the existing ORDER BY clause + * Adds an ordering to the query results. * - * @param string $sort What to sort on - * @param string $order Optional: The order to sort the results. - * @return QueryBuilder $qb + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * @return QueryBuilder This QueryBuilder instance. */ public function addOrderBy($sort, $order = null) { @@ -774,10 +814,11 @@ class QueryBuilder } /** - * Get a DQL part or parts by the part name + * Get a query part by its name. * * @param string $queryPartName * @return mixed $queryPart + * @todo Rename: getQueryPart (or remove?) */ public function getDQLPart($queryPartName) { @@ -785,9 +826,10 @@ class QueryBuilder } /** - * Get the full DQL parts array + * Get all query parts. * * @return array $dqlParts + * @todo Rename: getQueryParts (or remove?) */ public function getDQLParts() { @@ -836,6 +878,12 @@ class QueryBuilder . (isset($options['post']) ? $options['post'] : ''); } + /** + * Gets a string representation of this QueryBuilder which corresponds to + * the final DQL query being constructed. + * + * @return string The string representation of this QueryBuilder. + */ public function __toString() { return $this->getDQL(); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaValidatorCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaValidatorCommand.php new file mode 100644 index 000000000..61d07fbc2 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaValidatorCommand.php @@ -0,0 +1,80 @@ +. +*/ + +namespace Doctrine\ORM\Tools\Console\Command; + +use Symfony\Components\Console\Input\InputArgument, + Symfony\Components\Console\Input\InputOption, + Symfony\Components\Console; + +/** + * Schema Validator Command + * + * @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 SchemaValidatorCommand extends Console\Command\Command +{ + /** + * @see Console\Command\Command + */ + protected function configure() + { + $this + ->setName('orm:validate-schema') + ->setDescription('Validate that the mapping files.') + ->setHelp(<<getHelper('em')->getEntityManager(); + + $validator = new \Doctrine\ORM\Tools\SchemaValidator($em); + $errors = $validator->validateMapping(); + + if ($errors) { + foreach ($errors AS $className => $errorMessages) { + $output->write("The entity-class '" . $className . "' is invalid:\n"); + foreach ($errorMessages AS $errorMessage) { + $output->write('* ' . $errorMessage . "\n"); + } + $output->write("\n"); + } + } + + if (!$validator->schemaInSyncWithMetadata()) { + $output->write('The database schema is not in sync with the current mapping file.'); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php index f3053e08a..6b391e2b8 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php @@ -44,35 +44,44 @@ class ValidateSchemaCommand extends Console\Command\Command */ protected function configure() { - $this->setName('orm:validate-schema') - ->setDescription('Validate that the current metadata schema is valid.'); + $this + ->setName('orm:validate-schema') + ->setDescription('Validate that the mapping files.') + ->setHelp(<<getHelper('em'); + $em = $this->getHelper('em')->getEntityManager(); - /* @var $em \Doctrine\ORM\EntityManager */ - $em = $emHelper->getEntityManager(); + $validator = new \Doctrine\ORM\Tools\SchemaValidator($em); + $errors = $validator->validateMapping(); - $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."); + $exit = 0; + if ($errors) { + foreach ($errors AS $className => $errorMessages) { + $output->write("[Mapping] FAIL - The entity-class '" . $className . "' mapping is invalid:\n"); + foreach ($errorMessages AS $errorMessage) { + $output->write('* ' . $errorMessage . "\n"); + } + $output->write("\n"); } - } else { - $output->write("No metadata mappings found"); + $exit += 1; } + + if (!$validator->schemaInSyncWithMetadata()) { + $output->write('[Database] FAIL - The database schema is not in sync with the current mapping file.' . "\n"); + $exit += 2; + } else { + $output->write('[Database] OK - The database schema is in sync with the mapping files.' . "\n"); + } + + exit($exit); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php new file mode 100644 index 000000000..aae4d37d8 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -0,0 +1,197 @@ +. +*/ + +namespace Doctrine\ORM\Tools; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ManyToManyMapping; +use Doctrine\ORM\Mapping\OneToOneMapping; + +/** + * Performs strict validation of the mapping schema + * + * @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 SchemaValidator +{ + /** + * @var EntityManager + */ + private $em; + + /** + * @param EntityManager $em + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + /** + * Checks the internal consistency of mapping files. + * + * There are several checks that can't be done at runtime or are to expensive, which can be verified + * with this command. For example: + * + * 1. Check if a relation with "mappedBy" is actually connected to that specified field. + * 2. Check if "mappedBy" and "inversedBy" are consistent to each other. + * 3. Check if "referencedColumnName" attributes are really pointing to primary key columns. + * + * @return array + */ + public function validateMapping() + { + $errors = array(); + $cmf = $this->em->getMetadataFactory(); + $classes = $cmf->getAllMetadata(); + + foreach ($classes AS $class) { + /* @var $class ClassMetadata */ + foreach ($class->associationMappings AS $fieldName => $assoc) { + $ce = array(); + if (!$cmf->hasMetadataFor($assoc->targetEntityName)) { + $ce[] = "The target entity '" . $assoc->targetEntityName . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; + } + + if ($assoc->mappedBy && $assoc->inversedBy) { + $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; + } + + $targetMetadata = $cmf->getMetadataFor($assoc->targetEntityName); + + /* @var $assoc AssociationMapping */ + if ($assoc->mappedBy) { + if ($targetMetadata->hasField($assoc->mappedBy)) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". + "field " . $assoc->targetEntityName . "#" . $assoc->mappedBy . " which is not defined as association."; + } + if (!$targetMetadata->hasAssociation($assoc->mappedBy)) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". + "field " . $assoc->targetEntityName . "#" . $assoc->mappedBy . " which does not exist."; + } + + if ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy == null) { + $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". + "bi-directional relationship, but the specified mappedBy association on the target-entity ". + $assoc->targetEntityName . "#" . $assoc->mappedBy . " does not contain the required ". + "'inversedBy' attribute."; + } else if ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy != $fieldName) { + $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . + $assoc->targetEntityName . "#" . $assoc->mappedBy . " are ". + "incosistent with each other."; + } + } + + if ($assoc->inversedBy) { + if ($targetMetadata->hasField($assoc->inversedBy)) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". + "field " . $assoc->targetEntityName . "#" . $assoc->inversedBy . " which is not defined as association."; + } + if (!$targetMetadata->hasAssociation($assoc->inversedBy)) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". + "field " . $assoc->targetEntityName . "#" . $assoc->inversedBy . " which does not exist."; + } + + if (isset($targetMetadata->associationMappings[$assoc->mappedBy]) && + $targetMetadata->associationMappings[$assoc->mappedBy]->mappedBy == null) { + $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". + "bi-directional relationship, but the specified mappedBy association on the target-entity ". + $assoc->targetEntityName . "#" . $assoc->mappedBy . " does not contain the required ". + "'inversedBy' attribute."; + } else if ($targetMetadata->associationMappings[$assoc->inversedBy]->mappedBy != $fieldName) { + $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . + $assoc->targetEntityName . "#" . $assoc->inversedBy . " are ". + "incosistent with each other."; + } + } + + if ($assoc instanceof ManyToManyMapping && $assoc->isOwningSide) { + foreach ($assoc->joinTable['joinColumns'] AS $joinColumn) { + if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $class->name . "'."; + break; + } + + $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; + if (!in_array($fieldName, $class->identifier)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + foreach ($assoc->joinTable['inverseJoinColumns'] AS $inverseJoinColumn) { + if (!isset($class->fieldNames[$inverseJoinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $class->name . "'."; + break; + } + + $fieldName = $class->fieldNames[$inverseJoinColumn['referencedColumnName']]; + if (!in_array($fieldName, $class->identifier)) { + $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + } else if ($assoc instanceof OneToOneMapping) { + foreach ($assoc->joinColumns AS $joinColumn) { + if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $class->name . "'."; + break; + } + + $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; + if (!in_array($fieldName, $class->identifier)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + } + + if ($ce) { + $errors[$class->name] = $ce; + } + } + } + + return $errors; + } + + /** + * Check if the Database Schema is in sync with the current metadata state. + * + * @return bool + */ + public function schemaInSyncWithMetadata() + { + $schemaTool = new SchemaTool($this->em); + + $allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); + return (count($schemaTool->getUpdateSchemaSql($allMetadata, false)) == 0); + } +} diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 2f5167d08..7336150e7 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -19,7 +19,8 @@ namespace Doctrine\ORM; -use Doctrine\Common\Collections\ArrayCollection, +use Exception, + Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection, Doctrine\Common\NotifyPropertyChanged, Doctrine\Common\PropertyChangedListener, @@ -280,13 +281,13 @@ class UnitOfWork implements PropertyChangedListener $conn = $this->_em->getConnection(); $conn->beginTransaction(); - try { + try { if ($this->_entityInsertions) { foreach ($commitOrder as $class) { $this->_executeInserts($class); } } - + if ($this->_entityUpdates) { foreach ($commitOrder as $class) { $this->_executeUpdates($class); @@ -317,10 +318,9 @@ class UnitOfWork implements PropertyChangedListener } $conn->commit(); - } catch (\Exception $e) { - $conn->setRollbackOnly(); - $conn->rollback(); + } catch (Exception $e) { $this->_em->close(); + $conn->rollback(); throw $e; } @@ -1288,6 +1288,8 @@ class UnitOfWork implements PropertyChangedListener * * @param object $entity * @return object The managed copy of the entity. + * @throws OptimisticLockException If the entity uses optimistic locking through a version + * attribute and the version check against the managed copy fails. */ public function merge($entity) { @@ -1314,7 +1316,7 @@ class UnitOfWork implements PropertyChangedListener throw new \InvalidArgumentException('New entity detected during merge.' . ' Persist the new entity before merging.'); } - + // MANAGED entities are ignored by the merge operation if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) { $managedCopy = $entity; @@ -1342,7 +1344,7 @@ class UnitOfWork implements PropertyChangedListener $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); // Throw exception if versions dont match. if ($managedCopyVersion != $entityVersion) { - throw OptimisticLockException::lockFailed(); + throw OptimisticLockException::lockFailed($entity); } } diff --git a/tests/Doctrine/Tests/DBAL/DriverManagerTest.php b/tests/Doctrine/Tests/DBAL/DriverManagerTest.php index d7d0fb94f..a95c9ad67 100644 --- a/tests/Doctrine/Tests/DBAL/DriverManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/DriverManagerTest.php @@ -56,12 +56,11 @@ class DriverManagerTest extends \Doctrine\Tests\DbalTestCase public function testCustomWrapper() { - $wrapperMock = $this->getMock('\Doctrine\DBAL\Connection', array(), array(), '', false); - $wrapperClass = get_class($wrapperMock); + $wrapperClass = 'Doctrine\Tests\Mocks\ConnectionMock'; $options = array( 'pdo' => new \PDO('sqlite::memory:'), - 'wrapperClass' => $wrapperClass + 'wrapperClass' => $wrapperClass, ); $conn = \Doctrine\DBAL\DriverManager::getConnection($options); diff --git a/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php index 8ed7a57d4..fc231f2f3 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php @@ -25,7 +25,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); //no rethrow } - $this->assertTrue($this->_conn->getRollbackOnly()); + $this->assertTrue($this->_conn->isRollbackOnly()); $this->_conn->commit(); // should throw exception $this->fail('Transaction commit after failed nested transaction should fail.'); @@ -44,7 +44,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase throw new \Exception; - $this->_conn->commit(); // never reached + $this->_connx->commit(); // never reached } catch (\Exception $e) { $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); $this->_conn->rollback(); diff --git a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php index 92596b021..38681b3e1 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php @@ -4,7 +4,6 @@ namespace Doctrine\Tests\DBAL\Platforms; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Types\Type; -use Doctrine\DBAL\Connection; require_once __DIR__ . '/../../TestInit.php'; @@ -74,19 +73,19 @@ class PostgreSqlPlatformTest extends AbstractPlatformTestCase { $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_UNCOMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED) ); $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_COMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED) ); $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_REPEATABLE_READ) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) ); $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_SERIALIZABLE) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) ); } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php index 4d09a2e0d..fae5538e3 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php @@ -36,10 +36,22 @@ class SqlitePlatformTest extends AbstractPlatformTestCase public function testGeneratesTransactionCommands() { - $this->assertEquals('PRAGMA read_uncommitted = 0', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED)); - $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED)); - $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ)); - $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE)); + $this->assertEquals( + 'PRAGMA read_uncommitted = 0', + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED) + ); + $this->assertEquals( + 'PRAGMA read_uncommitted = 1', + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED) + ); + $this->assertEquals( + 'PRAGMA read_uncommitted = 1', + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) + ); + $this->assertEquals( + 'PRAGMA read_uncommitted = 1', + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) + ); } public function testPrefersIdentityColumns() diff --git a/tests/Doctrine/Tests/Mocks/ConnectionMock.php b/tests/Doctrine/Tests/Mocks/ConnectionMock.php index 41f5ebab0..fabecf87a 100644 --- a/tests/Doctrine/Tests/Mocks/ConnectionMock.php +++ b/tests/Doctrine/Tests/Mocks/ConnectionMock.php @@ -11,8 +11,11 @@ class ConnectionMock extends \Doctrine\DBAL\Connection public function __construct(array $params, $driver, $config = null, $eventManager = null) { - parent::__construct($params, $driver, $config, $eventManager); $this->_platformMock = new DatabasePlatformMock(); + + parent::__construct($params, $driver, $config, $eventManager); + + // Override possible assignment of platform to database platform mock $this->_platform = $this->_platformMock; } diff --git a/tests/Doctrine/Tests/ORM/Functional/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/AllTests.php index fa301beb9..c0827bcfb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Functional/AllTests.php @@ -54,6 +54,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Functional\EntityRepositoryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\IdentityMapTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\DatabaseDriverTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Functional\PostgreSQLIdentityStrategyTest'); $suite->addTest(Locking\AllTests::suite()); $suite->addTest(SchemaTool\AllTests::suite()); diff --git a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php index 817a79eb3..d7c7a4259 100644 --- a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php @@ -39,20 +39,43 @@ class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->useModelSet('cms'); parent::setUp(); } - + + public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) + { + try { + $query = $this->_em->createQuery($dqlToBeTested); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Functional\CustomTreeWalker')) + ->useQueryCache(false); + + parent::assertEquals($sqlToBeConfirmed, $query->getSql()); + $query->free(); + } catch (\Exception $e) { + $this->fail($e->getMessage()); + } + } + public function testSupportsQueriesWithoutWhere() { - - $q = $this->_em->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName'); - $q->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Functional\CustomTreeWalker')); - - $this->assertEquals("SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1", $q->getSql()); - - $q->setDql('select u from Doctrine\Tests\Models\CMS\CmsUser u'); - $this->assertEquals("SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = 1", $q->getSql()); - - $q->setDql('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name'); - $this->assertEquals("SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.name = ? AND c0_.id = 1", $q->getSql()); + $this->assertSqlGeneration( + 'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName', + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1" + ); + } + + public function testSupportsQueriesWithSimpleConditionalExpressions() + { + $this->assertSqlGeneration( + 'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name', + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.name = ? AND c0_.id = 1" + ); + } + + public function testSupportsQueriesWithMultipleConditionalExpressions() + { + $this->assertSqlGeneration( + 'select u from Doctrine\Tests\Models\CMS\CmsUser u', + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = 1" + ); } } @@ -62,6 +85,7 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter { // Get the DQL aliases of all the classes we want to modify $dqlAliases = array(); + foreach ($this->_getQueryComponents() as $dqlAlias => $comp) { // Hard-coded check just for demonstration: We want to modify the query if // it involves the CmsUser class. @@ -84,10 +108,19 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $factors[] = $factor; } - if ($selectStatement->whereClause !== null) { + if (($whereClause = $selectStatement->whereClause) !== null) { // There is already a WHERE clause, so append the conditions - - $existingTerms = $selectStatement->whereClause->conditionalExpression->conditionalTerms; + $condExpr = $whereClause->conditionalExpression; + + // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression + if ( ! ($condExpr instanceof Query\AST\ConditionalExpression)) { + $condExpr = new Query\AST\ConditionalExpression(array($condExpr)); + + $whereClause->conditionalExpression = $condExpr; + } + + $existingTerms = $whereClause->conditionalExpression->conditionalTerms; + if (count($existingTerms) > 1) { // More than one term, so we need to wrap all these terms in a single root term // i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND " @@ -100,8 +133,15 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $selectStatement->whereClause->conditionalExpression->conditionalTerms = array($term); } else { // Just one term so we can simply append our factors to that term - $singleTerm = $selectStatement->whereClause->conditionalExpression->conditionalTerms[0]; + + // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression + if ( ! ($singleTerm instanceof Query\AST\ConditionalTerm)) { + $singleTerm = new Query\AST\ConditionalTerm(array($singleTerm)); + + $selectStatement->whereClause->conditionalExpression->conditionalTerms[0] = $singleTerm; + } + $singleTerm->conditionalFactors = array_merge($singleTerm->conditionalFactors, $factors); $selectStatement->whereClause->conditionalExpression->conditionalTerms = array($singleTerm); } diff --git a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php index 22f1d2072..ddb3c7fec 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php @@ -84,6 +84,44 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase $phonenumbers = $user->getPhonenumbers(); $this->assertTrue($this->_em->contains($phonenumbers[0])); $this->assertTrue($this->_em->contains($phonenumbers[1])); - } + } + + /** + * @group DDC-518 + */ + /*public function testMergeDetachedEntityWithNewlyPersistentOneToOneAssoc() + { + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + // Create a detached user + $user = new CmsUser; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'dev'; + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + //$address = new CmsAddress; + //$address->city = 'Berlin'; + //$address->country = 'Germany'; + //$address->street = 'Sesamestreet'; + //$address->zip = 12345; + //$address->setUser($user); + + $phone = new CmsPhonenumber(); + $phone->phonenumber = '12345'; + + $user2 = $this->_em->merge($user); + + $user2->addPhonenumber($phone); + $this->_em->persist($phone); + + //$address->setUser($user2); + //$this->_em->persist($address); + + $this->_em->flush(); + + $this->assertEquals(1,1); + }*/ } diff --git a/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php b/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php index 947afdc68..73c5cdcf1 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php @@ -3,6 +3,7 @@ namespace Doctrine\Tests\ORM\Functional\Locking; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\OptimisticLockException; use Doctrine\Common\EventManager; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\Tests\TestUtil; @@ -37,15 +38,17 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->assertEquals(1, $test->version); + + return $test; } /** - * @expectedException Doctrine\ORM\OptimisticLockException + * @depends testJoinedChildInsertSetsInitialVersionValue */ - public function testJoinedChildFailureThrowsException() + public function testJoinedChildFailureThrowsException(OptimisticJoinedChild $child) { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedChild t WHERE t.name = :name'); - $q->setParameter('name', 'child'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedChild t WHERE t.id = :id'); + $q->setParameter('id', $child->id); $test = $q->getSingleResult(); // Manually update/increment the version so we can try and save the same @@ -55,7 +58,11 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase // Now lets change a property and try and save it again $test->whatever = 'ok'; - $this->_em->flush(); + try { + $this->_em->flush(); + } catch (OptimisticLockException $e) { + $this->assertSame($test, $e->getEntity()); + } } public function testJoinedParentInsertSetsInitialVersionValue() @@ -66,15 +73,17 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->assertEquals(1, $test->version); + + return $test; } /** - * @expectedException Doctrine\ORM\OptimisticLockException + * @depends testJoinedParentInsertSetsInitialVersionValue */ - public function testJoinedParentFailureThrowsException() + public function testJoinedParentFailureThrowsException(OptimisticJoinedParent $parent) { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedParent t WHERE t.name = :name'); - $q->setParameter('name', 'parent'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedParent t WHERE t.id = :id'); + $q->setParameter('id', $parent->id); $test = $q->getSingleResult(); // Manually update/increment the version so we can try and save the same @@ -84,7 +93,11 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase // Now lets change a property and try and save it again $test->name = 'WHATT???'; - $this->_em->flush(); + try { + $this->_em->flush(); + } catch (OptimisticLockException $e) { + $this->assertSame($test, $e->getEntity()); + } } public function testStandardInsertSetsInitialVersionValue() @@ -95,15 +108,17 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->assertEquals(1, $test->getVersion()); + + return $test; } /** - * @expectedException Doctrine\ORM\OptimisticLockException + * @depends testStandardInsertSetsInitialVersionValue */ - public function testStandardFailureThrowsException() + public function testStandardFailureThrowsException(OptimisticStandard $entity) { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticStandard t WHERE t.name = :name'); - $q->setParameter('name', 'test'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticStandard t WHERE t.id = :id'); + $q->setParameter('id', $entity->id); $test = $q->getSingleResult(); // Manually update/increment the version so we can try and save the same @@ -113,7 +128,11 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase // Now lets change a property and try and save it again $test->name = 'WHATT???'; - $this->_em->flush(); + try { + $this->_em->flush(); + } catch (OptimisticLockException $e) { + $this->assertSame($test, $e->getEntity()); + } } public function testOptimisticTimestampSetsDefaultValue() @@ -124,15 +143,17 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->assertTrue(strtotime($test->version) > 0); + + return $test; } /** - * @expectedException Doctrine\ORM\OptimisticLockException + * @depends testOptimisticTimestampSetsDefaultValue */ - public function testOptimisticTimestampFailureThrowsException() + public function testOptimisticTimestampFailureThrowsException(OptimisticTimestamp $entity) { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticTimestamp t WHERE t.name = :name'); - $q->setParameter('name', 'Testing'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticTimestamp t WHERE t.id = :id'); + $q->setParameter('id', $entity->id); $test = $q->getSingleResult(); $this->assertType('DateTime', $test->version); @@ -143,7 +164,11 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase // Try and update the record and it should throw an exception $test->name = 'Testing again'; - $this->_em->flush(); + try { + $this->_em->flush(); + } catch (OptimisticLockException $e) { + $this->assertSame($test, $e->getEntity()); + } } } diff --git a/tests/Doctrine/Tests/ORM/Functional/PostgreSQLIdentityStrategyTest.php b/tests/Doctrine/Tests/ORM/Functional/PostgreSQLIdentityStrategyTest.php new file mode 100644 index 000000000..74b6ed213 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/PostgreSQLIdentityStrategyTest.php @@ -0,0 +1,53 @@ +_em->getConnection()->getDatabasePlatform()->getName() != 'postgresql') { + $this->markTestSkipped('This test is special to the PostgreSQL IDENTITY key generation strategy.'); + } else { + try { + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\PostgreSQLIdentityEntity'), + )); + } catch (\Exception $e) { + // Swallow all exceptions. We do not test the schema tool here. + } + } + } + + protected function tearDown() { + parent::tearDown(); + // drop sequence manually due to dependency + $this->_em->getConnection()->exec('DROP SEQUENCE postgresqlidentityentity_id_seq CASCADE'); + } + + public function testPreSavePostSaveCallbacksAreInvoked() + { + $entity = new PostgreSQLIdentityEntity(); + $entity->setValue('hello'); + $this->_em->persist($entity); + $this->_em->flush(); + $this->assertTrue(is_numeric($entity->getId())); + $this->assertTrue($entity->getId() > 0); + $this->assertTrue($this->_em->contains($entity)); + } +} + +/** @Entity */ +class PostgreSQLIdentityEntity { + /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */ + private $id; + /** @Column(type="string") */ + private $value; + public function getId() {return $this->id;} + public function getValue() {return $this->value;} + public function setValue($value) {$this->value = $value;} +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index fcce4dff8..5236bdf91 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -26,6 +26,7 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase // Self-made metadata $cm1 = new ClassMetadata('Doctrine\Tests\ORM\Mapping\TestEntity1'); + $cm1->setPrimaryTable(array('name' => '`group`')); // Add a mapped field $cm1->mapField(array('fieldName' => 'name', 'type' => 'varchar')); // Add a mapped field @@ -54,6 +55,8 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase // Go $cm1 = $cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\TestEntity1'); + $this->assertEquals('group', $cm1->table['name']); + $this->assertTrue($cm1->table['quoted']); $this->assertEquals(array(), $cm1->parentClasses); $this->assertTrue($cm1->hasField('name')); $this->assertEquals(ClassMetadata::GENERATOR_TYPE_SEQUENCE, $cm1->generatorType); diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index a85873a92..716182ac1 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -177,7 +177,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000', - 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE ((c0_.id + 5000) * c0_.id + 3) < 10000000' + 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.id + 5000) * c0_.id + 3 < 10000000' ); } @@ -456,7 +456,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( "select u from Doctrine\Tests\Models\CMS\CmsUser u where u.id > 10 and u.id < 42 and ((u.id * 2) > 5)", - "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id > 10 AND c0_.id < 42 AND ((c0_.id * 2) > 5)" + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id > 10 AND c0_.id < 42 AND (c0_.id * 2 > 5)" ); } @@ -464,7 +464,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( "select u from Doctrine\Tests\Models\CMS\CmsUser u where (u.id > 10) and (u.id < 42 and ((u.id * 2) > 5)) or u.id <> 42", - "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.id > 10) AND (c0_.id < 42 AND ((c0_.id * 2) > 5)) OR c0_.id <> 42" + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.id > 10) AND (c0_.id < 42 AND (c0_.id * 2 > 5)) OR c0_.id <> 42" ); } diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index d4a592e65..468e0e20c 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -1,7 +1,5 @@ * @author Roman Borschel assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) OR (u.id = :uid2)'); } + public function testComplexAndWhereOrWhereNesting() + { + $qb = $this->_em->createQueryBuilder(); + $qb->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->orWhere('u.id = :uid2') + ->andWhere('u.id = :uid3') + ->orWhere('u.name = :name1', 'u.name = :name2') + ->andWhere('u.name <> :noname'); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((((u.id = :uid) OR (u.id = :uid2)) AND (u.id = :uid3)) OR (u.name = :name1) OR (u.name = :name2)) AND (u.name <> :noname)'); + } + public function testAndWhereIn() { $qb = $this->_em->createQueryBuilder(); diff --git a/tests/Doctrine/Tests/ORM/Tools/AllTests.php b/tests/Doctrine/Tests/ORM/Tools/AllTests.php index a729044df..faeeb1dd0 100644 --- a/tests/Doctrine/Tests/ORM/Tools/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Tools/AllTests.php @@ -27,6 +27,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Tools\ConvertDoctrine1SchemaTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Tools\SchemaToolTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Tools\EntityGeneratorTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Tools\SchemaValidatorTest'); return $suite; } diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index 3e6d90df5..f1ba8ba92 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -67,7 +67,16 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest protected function _createMetadataDriver($type, $path) { - $class = 'Doctrine\ORM\Mapping\Driver\\' . ucfirst($type) . 'Driver'; + $mappingDriver = array( + 'php' => 'PHPDriver', + 'annotation' => 'AnnotationDriver', + 'xml' => 'XmlDriver', + 'yaml' => 'YamlDriver', + ); + $this->assertArrayHasKey($type, $mappingDriver, "There is no metadata driver for the type '" . $type . "'."); + $driverName = $mappingDriver[$type]; + + $class = 'Doctrine\ORM\Mapping\Driver\\' . $driverName; if ($type === 'annotation') { $driver = $class::create($path); } else { diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php new file mode 100644 index 000000000..64bb03f36 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php @@ -0,0 +1,74 @@ +em = $this->_getTestEntityManager(); + $this->validator = new SchemaValidator($this->em); + } + + public function testCmsModelSet() + { + $this->em->getConfiguration()->getMetadataDriverImpl()->addPaths(array( + __DIR__ . "/../../Models/CMS" + )); + $this->validator->validateMapping(); + } + + public function testCompanyModelSet() + { + $this->em->getConfiguration()->getMetadataDriverImpl()->addPaths(array( + __DIR__ . "/../../Models/Company" + )); + $this->validator->validateMapping(); + } + + public function testECommerceModelSet() + { + $this->em->getConfiguration()->getMetadataDriverImpl()->addPaths(array( + __DIR__ . "/../../Models/ECommerce" + )); + $this->validator->validateMapping(); + } + + public function testForumModelSet() + { + $this->em->getConfiguration()->getMetadataDriverImpl()->addPaths(array( + __DIR__ . "/../../Models/Forum" + )); + $this->validator->validateMapping(); + } + + public function testNavigationModelSet() + { + $this->em->getConfiguration()->getMetadataDriverImpl()->addPaths(array( + __DIR__ . "/../../Models/Navigation" + )); + $this->validator->validateMapping(); + } + + public function testRoutingModelSet() + { + $this->em->getConfiguration()->getMetadataDriverImpl()->addPaths(array( + __DIR__ . "/../../Models/Routing" + )); + $this->validator->validateMapping(); + } +} \ No newline at end of file diff --git a/tools/sandbox/doctrine.php b/tools/sandbox/doctrine.php index 3b4a856de..f325e143a 100644 --- a/tools/sandbox/doctrine.php +++ b/tools/sandbox/doctrine.php @@ -36,6 +36,7 @@ $cli->addCommands(array( new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(), new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(), new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(), + new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(), )); $cli->run(); \ No newline at end of file