diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index b52921644..787770a34 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -272,7 +272,7 @@ abstract class AbstractQuery * @param boolean $bool * @param integer $timeToLive * @param string $resultCacheId - * @return This query instance. + * @return Doctrine\ORM\AbstractQuery This query instance. */ public function useResultCache($bool, $timeToLive = null, $resultCacheId = null) { diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 7bc54c847..de2689db2 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -160,11 +160,14 @@ class EntityRepository implements ObjectRepository * Finds entities by a set of criteria. * * @param array $criteria - * @return array + * @param array|null $orderBy + * @param int|null $limit + * @param int|null $offset + * @return array The objects. */ - public function findBy(array $criteria) + public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) { - return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria); + return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria, $orderBy, $limit, $offset); } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index a820832f5..381fd4ab1 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -39,9 +39,9 @@ class ObjectHydrator extends AbstractHydrator * This local cache is maintained between hydration runs and not cleared. */ private $_ce = array(); - + /* The following parts are reinitialized on every hydration run. */ - + private $_identifierMap; private $_resultPointers; private $_idTemplate; @@ -50,7 +50,7 @@ class ObjectHydrator extends AbstractHydrator private $_initializedCollections = array(); private $_existingCollections = array(); //private $_createdEntities; - + /** @override */ protected function _prepare() @@ -71,10 +71,14 @@ class ObjectHydrator extends AbstractHydrator if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $class; } - + // Remember which associations are "fetch joined", so that we know where to inject // collection stubs or proxies and where not. if (isset($this->_rsm->relationMap[$dqlAlias])) { + if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { + throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); + } + $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $sourceClass = $this->_getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; @@ -185,7 +189,7 @@ class ObjectHydrator extends AbstractHydrator return $value; } - + /** * Gets an entity instance. * @@ -195,7 +199,7 @@ class ObjectHydrator extends AbstractHydrator */ private function _getEntity(array $data, $dqlAlias) { - $className = $this->_rsm->aliasMap[$dqlAlias]; + $className = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; @@ -203,7 +207,7 @@ class ObjectHydrator extends AbstractHydrator } return $this->_uow->createEntity($className, $data, $this->_hints); } - + private function _getEntityFromIdentityMap($className, array $data) { $class = $this->_ce[$className]; @@ -217,7 +221,7 @@ class ObjectHydrator extends AbstractHydrator return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName); } } - + /** * Gets a ClassMetadata instance from the local cache. * If the instance is not yet in the local cache, it is loaded into the @@ -275,7 +279,7 @@ class ObjectHydrator extends AbstractHydrator // Hydrate the data chunks foreach ($rowData as $dqlAlias => $data) { $entityName = $this->_rsm->aliasMap[$dqlAlias]; - + if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { // It's a joined result @@ -286,7 +290,7 @@ class ObjectHydrator extends AbstractHydrator // Get a reference to the parent object to which the joined element belongs. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { - $first = reset($this->_resultPointers); + $first = reset($this->_resultPointers); $parentObject = $this->_resultPointers[$parentAlias][key($first)]; } else if (isset($this->_resultPointers[$parentAlias])) { $parentObject = $this->_resultPointers[$parentAlias]; @@ -311,11 +315,11 @@ class ObjectHydrator extends AbstractHydrator } else if ( ! isset($this->_existingCollections[$collKey])) { $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); } - + $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); $index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; - + if ( ! $indexExists || ! $indexIsValid) { if (isset($this->_existingCollections[$collKey])) { // Collection exists, only look for the element in the identity map. diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index a10d8c866..f556257f9 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1628,6 +1628,7 @@ class ClassMetadataInfo implements ClassMetadata if (strpos($className, '\\') === false && strlen($this->namespace)) { $className = $this->namespace . '\\' . $className; } + $className = ltrim($className, '\\'); $this->discriminatorMap[$value] = $className; if ($this->name == $className) { $this->discriminatorValue = $value; diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index b74fc860e..22e6d4d7e 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -67,10 +67,10 @@ class AnnotationDriver implements Driver * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading * docblock annotations. * - * @param $reader The AnnotationReader to use. + * @param AnnotationReader $reader The AnnotationReader to use, duck-typed. * @param string|array $paths One or multiple paths where mapping classes can be found. */ - public function __construct(AnnotationReader $reader, $paths = null) + public function __construct($reader, $paths = null) { $this->_reader = $reader; if ($paths) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php index 0dc6a05e0..7f92df162 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php @@ -67,6 +67,26 @@ class DatabaseDriver implements Driver $this->_sm = $schemaManager; } + /** + * Set tables manually instead of relying on the reverse engeneering capabilities of SchemaManager. + * + * @param array $entityTables + * @param array $manyToManyTables + * @return void + */ + public function setTables($entityTables, $manyToManyTables) + { + $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); + foreach ($entityTables AS $table) { + $className = Inflector::classify(strtolower($table->getName())); + $this->classToTableNames[$className] = $table->getName(); + $this->tables[$table->getName()] = $table; + } + foreach ($manyToManyTables AS $table) { + $this->manyToManyTables[$table->getName()] = $table; + } + } + private function reverseEngineerMappingFromDatabase() { if ($this->tables !== null) { @@ -77,7 +97,7 @@ class DatabaseDriver implements Driver $tables[$tableName] = $this->_sm->listTableDetails($tableName); } - $this->tables = array(); + $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($tables AS $tableName => $table) { /* @var $table Table */ if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) { @@ -95,11 +115,7 @@ class DatabaseDriver implements Driver sort($pkColumns); sort($allForeignKeyColumns); - if ($pkColumns == $allForeignKeyColumns) { - if (count($table->getForeignKeys()) > 2) { - throw new \InvalidArgumentException("ManyToMany table '" . $tableName . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver."); - } - + if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) { $this->manyToManyTables[$tableName] = $table; } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, @@ -191,8 +207,10 @@ class DatabaseDriver implements Driver foreach ($this->manyToManyTables AS $manyTable) { foreach ($manyTable->getForeignKeys() AS $foreignKey) { + // foreign key maps to the table of the current entity, many to many association probably exists if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) { $myFk = $foreignKey; + $otherFk = null; foreach ($manyTable->getForeignKeys() AS $foreignKey) { if ($foreignKey != $myFk) { $otherFk = $foreignKey; @@ -200,6 +218,12 @@ class DatabaseDriver implements Driver } } + if (!$otherFk) { + // the definition of this many to many table does not contain + // enough foreign key information to continue reverse engeneering. + continue; + } + $localColumn = current($myFk->getColumns()); $associationMapping = array(); $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns())))); diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 39bb967e6..454078dfe 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -680,12 +680,15 @@ class BasicEntityPersister * Loads a list of entities by a list of field criteria. * * @param array $criteria + * @param array $orderBy + * @param int $limit + * @param int $offset * @return array */ - public function loadAll(array $criteria = array()) + public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) { $entities = array(); - $sql = $this->_getSelectEntitiesSQL($criteria); + $sql = $this->_getSelectEntitiesSQL($criteria, null, 0, $limit, $offset, $orderBy); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); @@ -831,19 +834,21 @@ class BasicEntityPersister * @param AssociationMapping $assoc * @param string $orderBy * @param int $lockMode + * @param int $limit + * @param int $offset + * @param array $orderBy * @return string * @todo Refactor: _getSelectSQL(...) */ - protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $joinSql = $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ? $this->_getSelectManyToManyJoinSQL($assoc) : ''; $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); - $orderBySql = $assoc !== null && isset($assoc['orderBy']) ? - $this->_getCollectionOrderBySQL($assoc['orderBy'], $this->_getSQLTableAlias($this->_class->name)) - : ''; + $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy; + $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : ''; $lockSql = ''; if ($lockMode == LockMode::PESSIMISTIC_READ) { @@ -869,12 +874,12 @@ class BasicEntityPersister * @return string * @todo Rename: _getOrderBySQL */ - protected final function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias) + protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; foreach ($orderBy as $fieldName => $orientation) { if ( ! isset($this->_class->fieldMappings[$fieldName])) { - ORMException::unrecognizedField($fieldName); + throw ORMException::unrecognizedField($fieldName); } $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ? diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 2490085cf..1cb73a9d6 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -238,7 +238,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * {@inheritdoc} */ - protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $idColumns = $this->_class->getIdentifierColumnNames(); $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); @@ -343,10 +343,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); - $orderBySql = ''; - if ($assoc != null && isset($assoc['orderBy'])) { - $orderBySql = $this->_getCollectionOrderBySQL($assoc['orderBy'], $baseTableAlias); - } + $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy; + $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : ''; if ($this->_selectColumnListSql === null) { $this->_selectColumnListSql = $columnList; diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index 673ab0205..82355faec 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -126,7 +126,7 @@ class Lexer extends \Doctrine\Common\Lexer '[a-z_\\\][a-z0-9_\:\\\]*[a-z0-9_]{1}', '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', "'(?:[^']|'')*'", - '\?[1-9][0-9]*|:[a-z][a-z0-9_]+' + '\?[1-9][0-9]*|:[a-z]{1}[a-z0-9_]{0,}' ); } diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 2ba869f22..791f765e9 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -234,7 +234,7 @@ class Parser * If they match, updates the lookahead token; otherwise raises a syntax * error. * - * @param int|string token type or value + * @param int token type * @return void * @throws QueryException If the tokens dont match. */ @@ -2308,7 +2308,8 @@ class Parser if ($peek['value'] == '.') { return $this->StateFieldPathExpression(); } else if ($peek['value'] == '(') { - return $this->FunctionsReturningStrings(); + // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions. + return $this->FunctionDeclaration(); } else { $this->syntaxError("'.' or '('"); } diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 7066405df..b10b0d15d 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -1,7 +1,5 @@ metaMappings[$columnName] = $fieldName; $this->columnOwnerMap[$columnName] = $alias; } -} - +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php new file mode 100644 index 000000000..ceacc9638 --- /dev/null +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -0,0 +1,100 @@ +. + */ + +namespace Doctrine\ORM\Query; + +use Doctrine\ORM\EntityManager; + +/** + * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields + * + * @author Michael Ridgway + * @since 2.1 + */ +class ResultSetMappingBuilder extends ResultSetMapping +{ + /** + * @var EntityManager + */ + private $em; + + /** + * @param EntityManager + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + /** + * Adds a root entity and all of its fields to the result set. + * + * @param string $class The class name of the root entity. + * @param string $alias The unique alias to use for the root entity. + * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName) + */ + public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array()) + { + $this->addEntityResult($class, $alias); + $classMetadata = $this->em->getClassMetadata($class); + if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) { + throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.'); + } + $platform = $this->em->getConnection()->getDatabasePlatform(); + foreach ($classMetadata->getColumnNames() AS $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + if (isset($renamedColumns[$columnName])) { + $columnName = $renamedColumns[$columnName]; + } + if (isset($this->fieldMappings[$columnName])) { + throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); + } + $this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName); + } + } + + /** + * Adds a joined entity and all of its fields to the result set. + * + * @param string $class The class name of the joined entity. + * @param string $alias The unique alias to use for the joined entity. + * @param string $parentAlias The alias of the entity result that is the parent of this joined result. + * @param object $relation The association field that connects the parent entity result with the joined entity result. + * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName) + */ + public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array()) + { + $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); + $classMetadata = $this->em->getClassMetadata($class); + if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) { + throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.'); + } + $platform = $this->em->getConnection()->getDatabasePlatform(); + foreach ($classMetadata->getColumnNames() AS $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + if (isset($renamedColumns[$columnName])) { + $columnName = $renamedColumns[$columnName]; + } + if (isset($this->fieldMappings[$columnName])) { + throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); + } + $this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php index e18a9c56a..835835d28 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php @@ -65,7 +65,7 @@ EOT protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) { - $output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL); + $output->write('ATTENTION: This operation should not be executed in a production enviroment.' . PHP_EOL . PHP_EOL); if ($input->getOption('dump-sql') === true) { $sqls = $schemaTool->getCreateSchemaSql($metadatas); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php index 82d91f4c6..f6f16cb79 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php @@ -92,7 +92,7 @@ EOT } $output->write('Database schema dropped successfully!' . PHP_EOL); } else { - $output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL); + $output->write('ATTENTION: This operation should not be executed in a production enviroment.' . PHP_EOL . PHP_EOL); if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php index f1a3a73cf..ed12358cd 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php @@ -86,7 +86,7 @@ EOT $schemaTool->updateSchema($metadatas, $saveMode); $output->write('Database schema updated successfully!' . PHP_EOL); } else { - $output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL); + $output->write('ATTENTION: This operation should not be executed in a production enviroment.' . PHP_EOL); $output->write('Use the incremental update to detect changes during development and use' . PHP_EOL); $output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL); diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 182dd441b..9eb1f4d2a 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -179,7 +179,7 @@ public function () $this->_isNew = !file_exists($path) || (file_exists($path) && $this->_regenerateEntityIfExists); if ( ! $this->_isNew) { - $this->_parseTokensInEntityFile($path); + $this->_parseTokensInEntityFile(file_get_contents($path)); } if ($this->_backupExisting && file_exists($path)) { @@ -400,24 +400,42 @@ public function () /** * @todo this won't work if there is a namespace in brackets and a class outside of it. - * @param string $path + * @param string $src */ - private function _parseTokensInEntityFile($path) + private function _parseTokensInEntityFile($src) { - $tokens = token_get_all(file_get_contents($path)); + $tokens = token_get_all($src); $lastSeenNamespace = ""; $lastSeenClass = false; + $inNamespace = false; + $inClass = false; for ($i = 0; $i < count($tokens); $i++) { $token = $tokens[$i]; - if ($token[0] == T_NAMESPACE) { - $lastSeenNamespace = $tokens[$i+2][1] . "\\"; - } else if ($token[0] == T_NS_SEPARATOR) { - $lastSeenNamespace .= $tokens[$i+1][1] . "\\"; - } else if ($token[0] == T_CLASS) { - $lastSeenClass = $lastSeenNamespace . $tokens[$i+2][1]; + if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) { + continue; + } + + if ($inNamespace) { + if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { + $lastSeenNamespace .= $token[1]; + } else if (is_string($token) && in_array($token, array(';', '{'))) { + $inNamespace = false; + } + } + + if ($inClass) { + $inClass = false; + $lastSeenClass = $lastSeenNamespace . '\\' . $token[1]; $this->_staticReflection[$lastSeenClass]['properties'] = array(); $this->_staticReflection[$lastSeenClass]['methods'] = array(); + } + + if ($token[0] == T_NAMESPACE) { + $lastSeenNamespace = ""; + $inNamespace = true; + } else if ($token[0] == T_CLASS) { + $inClass = true; } else if ($token[0] == T_FUNCTION) { if ($tokens[$i+2][0] == T_STRING) { $this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1]; @@ -502,7 +520,7 @@ public function () } if ($metadata->isMappedSuperclass) { - $lines[] = ' * @' . $this->_annotationsPrefix . 'MappedSupperClass'; + $lines[] = ' * @' . $this->_annotationsPrefix . 'MappedSuperClass'; } else { $lines[] = ' * @' . $this->_annotationsPrefix . 'Entity'; } diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index f1358708b..0ece01500 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -592,74 +592,44 @@ class SchemaTool } /** - * + * Get SQL to drop the tables defined by the passed classes. + * * @param array $classes * @return array */ public function getDropSchemaSQL(array $classes) { - /* @var $conn \Doctrine\DBAL\Connection */ - $conn = $this->_em->getConnection(); - - /* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */ - $sm = $conn->getSchemaManager(); - - $sql = array(); - $orderedTables = array(); + $visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->_platform); + $schema = $this->getSchemaFromMetadata($classes); - foreach ($classes AS $class) { - if ($class->isIdGeneratorSequence() && !$class->isMappedSuperclass && $class->name == $class->rootEntityName && $this->_platform->supportsSequences()) { - $sql[] = $this->_platform->getDropSequenceSQL($class->sequenceGeneratorDefinition['sequenceName']); - } - } - - $commitOrder = $this->_getCommitOrder($classes); - $associationTables = $this->_getAssociationTables($commitOrder); - - // Drop association tables first - foreach ($associationTables as $associationTable) { - if (!in_array($associationTable, $orderedTables)) { - $orderedTables[] = $associationTable; - } - } - - // Drop tables in reverse commit order - for ($i = count($commitOrder) - 1; $i >= 0; --$i) { - $class = $commitOrder[$i]; - - if (($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) - || $class->isMappedSuperclass) { - continue; - } - - if (!in_array($class->getTableName(), $orderedTables)) { - $orderedTables[] = $class->getTableName(); - } - } - - $supportsForeignKeyConstraints = $conn->getDatabasePlatform()->supportsForeignKeyConstraints(); - $dropTablesSql = array(); - - foreach ($orderedTables AS $tableName) { - if ($supportsForeignKeyConstraints) { - $foreignKeys = $sm->listTableForeignKeys($tableName); - - foreach ($foreignKeys AS $foreignKey) { - $sql[] = $this->_platform->getDropForeignKeySQL($foreignKey, $tableName); + $sm = $this->_em->getConnection()->getSchemaManager(); + $fullSchema = $sm->createSchema(); + foreach ($fullSchema->getTables() AS $table) { + if (!$schema->hasTable($table->getName())) { + foreach ($table->getForeignKeys() AS $foreignKey) { + /* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */ + if ($schema->hasTable($foreignKey->getForeignTableName())) { + $visitor->acceptForeignKey($table, $foreignKey); + } + } + } else { + $visitor->acceptTable($table); + foreach ($table->getForeignKeys() AS $foreignKey) { + $visitor->acceptForeignKey($table, $foreignKey); } } - - $dropTablesSql[] = $this->_platform->getDropTableSQL($tableName); } - return array_merge($sql, $dropTablesSql); + return $visitor->getQueries(); } /** * Updates the database schema of the given classes by comparing the ClassMetadata - * ins$tableNametances to the current database schema that is inspected. + * instances to the current database schema that is inspected. If $saveMode is set + * to true the command is executed in the Database, else SQL is returned. * * @param array $classes + * @param boolean $saveMode * @return void */ public function updateSchema(array $classes, $saveMode=false) @@ -675,8 +645,11 @@ class SchemaTool /** * Gets the sequence of SQL statements that need to be performed in order * to bring the given class mappings in-synch with the relational schema. + * If $saveMode is set to true the command is executed in the Database, + * else SQL is returned. * * @param array $classes The classes to consider. + * @param boolean $saveMode True for writing to DB, false for SQL string * @return array The sequence of SQL statements. */ public function getUpdateSchemaSql(array $classes, $saveMode=false) @@ -695,44 +668,4 @@ class SchemaTool return $schemaDiff->toSql($this->_platform); } } - - private function _getCommitOrder(array $classes) - { - $calc = new CommitOrderCalculator; - - // Calculate dependencies - foreach ($classes as $class) { - $calc->addClass($class); - - foreach ($class->associationMappings as $assoc) { - if ($assoc['isOwningSide']) { - $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); - - if ( ! $calc->hasClass($targetClass->name)) { - $calc->addClass($targetClass); - } - - // add dependency ($targetClass before $class) - $calc->addDependency($targetClass, $class); - } - } - } - - return $calc->getCommitOrder(); - } - - private function _getAssociationTables(array $classes) - { - $associationTables = array(); - - foreach ($classes as $class) { - foreach ($class->associationMappings as $assoc) { - if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { - $associationTables[] = $assoc['joinTable']['name']; - } - } - } - - return $associationTables; - } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 945815a69..90d3117e3 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -406,8 +406,11 @@ class UnitOfWork implements PropertyChangedListener $actualData = array(); foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); - if ($class->isCollectionValuedAssociation($name) && $value !== null + if (isset($class->associationMappings[$name]) + && ($class->associationMappings[$name]['type'] & ClassMetadata::TO_MANY) + && $value !== null && ! ($value instanceof PersistentCollection)) { + // If $value is not a Collection then use an ArrayCollection. if ( ! $value instanceof Collection) { $value = new ArrayCollection($value); @@ -426,7 +429,7 @@ class UnitOfWork implements PropertyChangedListener $coll->setDirty( ! $coll->isEmpty()); $class->reflFields[$name]->setValue($entity, $coll); $actualData[$name] = $coll; - } else if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) { + } else if ( (! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField) ) { $actualData[$name] = $value; } } @@ -474,9 +477,7 @@ class UnitOfWork implements PropertyChangedListener } } else if ($isChangeTrackingNotify) { continue; - } else if (is_object($orgValue) && $orgValue !== $actualValue) { - $changeSet[$propName] = array($orgValue, $actualValue); - } else if ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { + } else if ($orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } } diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common index ba63ae0f0..076a03f8f 160000 --- a/lib/vendor/doctrine-common +++ b/lib/vendor/doctrine-common @@ -1 +1 @@ -Subproject commit ba63ae0f0b6b62a2a8617f01386698730ff2b713 +Subproject commit 076a03f8f40b6e08f0ae2f4ee2678474e64b6f59 diff --git a/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php b/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php index 964c57119..37ce0b6ea 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php @@ -104,9 +104,42 @@ class DatabaseDriverTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertArrayHasKey('user', $metadatas['CmsGroups']->associationMappings); } + public function testIgnoreManyToManyTableWithoutFurtherForeignKeyDetails() + { + $tableB = new \Doctrine\DBAL\Schema\Table("dbdriver_bar"); + $tableB->addColumn('id', 'integer'); + $tableB->setPrimaryKey(array('id')); + + $tableA = new \Doctrine\DBAL\Schema\Table("dbdriver_baz"); + $tableA->addColumn('id', 'integer'); + $tableA->setPrimaryKey(array('id')); + + $tableMany = new \Doctrine\DBAL\Schema\Table("dbdriver_bar_baz"); + $tableMany->addColumn('bar_id', 'integer'); + $tableMany->addColumn('baz_id', 'integer'); + $tableMany->addForeignKeyConstraint('dbdriver_bar', array('bar_id'), array('id')); + + $metadatas = $this->convertToClassMetadata(array($tableA, $tableB), array($tableMany)); + + $this->assertEquals(0, count($metadatas['DbdriverBaz']->associationMappings), "no association mappings should be detected."); + } + + protected function convertToClassMetadata(array $entityTables, array $manyTables = array()) + { + $driver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver($this->_sm); + $driver->setTables($entityTables, $manyTables); + + $metadatas = array(); + foreach ($driver->getAllClassNames() AS $className) { + $class = new ClassMetadataInfo($className); + $driver->loadMetadataForClass($className, $class); + $metadatas[$className] = $class; + } + + return $metadatas; + } /** - * * @param string $className * @return ClassMetadata */ diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index 0e38fe209..6c620db38 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -320,5 +320,40 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(1, count($params), "Should only execute with one parameter."); $this->assertEquals(array('romanb'), $params); } + + /** + * @group DDC-1094 + */ + public function testFindByLimitOffset() + { + $this->loadFixture(); + + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + + $users1 = $repos->findBy(array(), null, 1, 0); + $users2 = $repos->findBy(array(), null, 1, 1); + + $this->assertEquals(2, count($repos->findBy(array()))); + $this->assertEquals(1, count($users1)); + $this->assertEquals(1, count($users2)); + $this->assertNotSame($users1[0], $users2[0]); + } + + /** + * @group DDC-1094 + */ + public function testFindByOrderBy() + { + $this->loadFixture(); + + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + $usersAsc = $repos->findBy(array(), array("username" => "ASC")); + $usersDesc = $repos->findBy(array(), array("username" => "DESC")); + + $this->assertEquals(2, count($usersAsc), "Pre-condition: only two users in fixture"); + $this->assertEquals(2, count($usersDesc), "Pre-condition: only two users in fixture"); + $this->assertSame($usersAsc[0], $usersDesc[1]); + $this->assertSame($usersAsc[1], $usersDesc[0]); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 23dfa3a73..f997c6c01 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -3,9 +3,12 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsAddress; +use Doctrine\Tests\Models\Company\CompanyFixContract; +use Doctrine\Tests\Models\Company\CompanyEmployee; require_once __DIR__ . '/../../TestInit.php'; @@ -156,5 +159,111 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($q, $q2); } + + public function testJoinedOneToManyNativeQueryWithRSMBuilder() + { + $user = new CmsUser; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'dev'; + + $phone = new CmsPhonenumber; + $phone->phonenumber = 424242; + + $user->addPhonenumber($phone); + + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + $rsm = new ResultSetMappingBuilder($this->_em); + $rsm->addRootEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'u', 'phonenumbers'); + $query = $this->_em->createNativeQuery('SELECT u.*, p.* FROM cms_users u LEFT JOIN cms_phonenumbers p ON u.id = p.user_id WHERE username = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + $this->assertEquals(1, count($users)); + $this->assertTrue($users[0] instanceof CmsUser); + $this->assertEquals('Roman', $users[0]->name); + $this->assertTrue($users[0]->getPhonenumbers() instanceof \Doctrine\ORM\PersistentCollection); + $this->assertTrue($users[0]->getPhonenumbers()->isInitialized()); + $this->assertEquals(1, count($users[0]->getPhonenumbers())); + $phones = $users[0]->getPhonenumbers(); + $this->assertEquals(424242, $phones[0]->phonenumber); + $this->assertTrue($phones[0]->getUser() === $users[0]); + } + + public function testJoinedOneToOneNativeQueryWithRSMBuilder() + { + $user = new CmsUser; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'dev'; + + $addr = new CmsAddress; + $addr->country = 'germany'; + $addr->zip = 10827; + $addr->city = 'Berlin'; + + + $user->setAddress($addr); + + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + + $rsm = new ResultSetMappingBuilder($this->_em); + $rsm->addRootEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress', 'a', 'u', 'address', array('id' => 'a_id')); + + $query = $this->_em->createNativeQuery('SELECT u.*, a.*, a.id AS a_id FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + + $this->assertEquals(1, count($users)); + $this->assertTrue($users[0] instanceof CmsUser); + $this->assertEquals('Roman', $users[0]->name); + $this->assertTrue($users[0]->getPhonenumbers() instanceof \Doctrine\ORM\PersistentCollection); + $this->assertFalse($users[0]->getPhonenumbers()->isInitialized()); + $this->assertTrue($users[0]->getAddress() instanceof CmsAddress); + $this->assertTrue($users[0]->getAddress()->getUser() == $users[0]); + $this->assertEquals('germany', $users[0]->getAddress()->getCountry()); + $this->assertEquals(10827, $users[0]->getAddress()->getZipCode()); + $this->assertEquals('Berlin', $users[0]->getAddress()->getCity()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRSMBuilderThrowsExceptionOnColumnConflict() + { + $rsm = new ResultSetMappingBuilder($this->_em); + $rsm->addRootEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress', 'a', 'u', 'address'); + } + + /** + * @group PR-39 + */ + public function testUnknownParentAliasThrowsException() + { + $rsm = new ResultSetMappingBuilder($this->_em); + $rsm->addRootEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityFromClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress', 'a', 'un', 'address', array('id' => 'a_id')); + + $query = $this->_em->createNativeQuery('SELECT u.*, a.*, a.id AS a_id FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $this->setExpectedException( + "Doctrine\ORM\Internal\Hydration\HydrationException", + "The parent object of entity result with alias 'a' was not found. The parent alias is 'un'." + ); + $users = $query->getResult(); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/CompanySchemaTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/CompanySchemaTest.php index 3a14056eb..797c202f6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/CompanySchemaTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/CompanySchemaTest.php @@ -50,4 +50,19 @@ class CompanySchemaTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($table->getColumn('pricePerHour')->getNotnull()); $this->assertFalse($table->getColumn('maxPrice')->getNotnull()); } + + /** + * @group DBAL-115 + */ + public function testDropPartSchemaWithForeignKeys() + { + if (!$this->_em->getConnection()->getDatabasePlatform()->supportsForeignKeyConstraints()) { + $this->markTestSkipped("Foreign Key test"); + } + + $sql = $this->_schemaTool->getDropSchemaSQL(array( + $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyManager'), + )); + $this->assertEquals(3, count($sql)); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1043Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1043Test.php new file mode 100644 index 000000000..31bd8350f --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1043Test.php @@ -0,0 +1,36 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testChangeSetPlusWeirdPHPCastingIntCastingRule() + { + $user = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user->name = "John Galt"; + $user->username = "jgalt"; + $user->status = "+44"; + + $this->_em->persist($user); + $this->_em->flush(); + + $user->status = "44"; + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->find("Doctrine\Tests\Models\CMS\CmsUser", $user->id); + $this->assertSame("44", $user->status); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1129Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1129Test.php new file mode 100644 index 000000000..c481aa395 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1129Test.php @@ -0,0 +1,46 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testVersionFieldIgnoredInChangesetComputation() + { + $article = new \Doctrine\Tests\Models\CMS\CmsArticle(); + $article->text = "I don't know."; + $article->topic = "Who is John Galt?"; + + $this->_em->persist($article); + $this->_em->flush(); + + $this->assertEquals(1, $article->version); + + $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsArticle'); + $uow = $this->_em->getUnitOfWork(); + + $uow->computeChangeSet($class, $article); + $changeSet = $uow->getEntityChangeSet($article); + $this->assertEquals(0, count($changeSet), "No changesets should be computed."); + + $article->text = "This is John Galt speaking."; + $this->_em->flush(); + + $this->assertEquals(2, $article->version); + + $uow->computeChangeSet($class, $article); + $changeSet = $uow->getEntityChangeSet($article); + $this->assertEquals(0, count($changeSet), "No changesets should be computed."); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Performance/UnitOfWorkPerformanceTest.php b/tests/Doctrine/Tests/ORM/Performance/UnitOfWorkPerformanceTest.php new file mode 100644 index 000000000..28d9ea6d3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Performance/UnitOfWorkPerformanceTest.php @@ -0,0 +1,50 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testComputeChanges() + { + $n = 100; + + $users = array(); + for ($i=1; $i<=$n; ++$i) { + $user = new CmsUser; + $user->status = 'user'; + $user->username = 'user' . $i; + $user->name = 'Mr.Smith-' . $i; + $this->_em->persist($user); + $users[] = $user; + } + $this->_em->flush(); + + + foreach ($users AS $user) { + $user->status = 'other'; + $user->username = $user->username . '++'; + $user->name = str_replace('Mr.', 'Mrs.', $user->name); + } + + $s = microtime(true); + $this->_em->flush(); + $e = microtime(true); + + echo ' Compute ChangeSet '.$n.' objects in ' . ($e - $s) . ' seconds' . PHP_EOL; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index 1ac05df90..2733d80f7 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -482,6 +482,16 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase $this->assertValidDQL('SELECT u, u.id + ?1 AS someNumber FROM Doctrine\Tests\Models\CMS\CmsUser u'); } + /** + * @group DDC-1091 + */ + public function testCustomFunctionsReturningStringInStringPrimary() + { + $this->_em->getConfiguration()->addCustomStringFunction('CC', 'Doctrine\ORM\Query\AST\Functions\ConcatFunction'); + + $this->assertValidDQL("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE CC('%', u.name) LIKE '%foo%'", true); + } + /** * @group DDC-505 */ @@ -513,6 +523,14 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase $this->assertInvalidDQL('SELECT g FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g'); } + /** + * @group DDC-1108 + */ + public function testInputParameterSingleChar() + { + $this->assertValidDQL('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = :q'); + } + /** * @group DDC-1053 */ diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index c8960f6a7..5a9c5d7eb 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -200,6 +200,54 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals($cm->idGenerator, $metadata->idGenerator); $this->assertEquals($cm->customRepositoryClassName, $metadata->customRepositoryClassName); } + + /** + * @dataProvider getParseTokensInEntityFileData + */ + public function testParseTokensInEntityFile($php, $classes) + { + $r = new \ReflectionObject($this->_generator); + $m = $r->getMethod('_parseTokensInEntityFile'); + $m->setAccessible(true); + + $p = $r->getProperty('_staticReflection'); + $p->setAccessible(true); + + $ret = $m->invoke($this->_generator, $php); + $this->assertEquals($classes, array_keys($p->getValue($this->_generator))); + } + + public function getParseTokensInEntityFileData() + { + return array( + array( + '