diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 1cbed0229..d9b1e201c 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -174,26 +174,26 @@ abstract class AbstractHydrator foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { - if ($this->_resultSetMapping->isIgnoredColumn($key)) { + if (isset($this->_resultSetMapping->ignoredColumns[$key])) { $cache[$key] = false; - } else if ($this->_resultSetMapping->isScalarResult($key)) { + } else if (isset($this->_resultSetMapping->scalarMappings[$key])) { $cache[$key]['fieldName'] = $this->_resultSetMapping->getScalarAlias($key); $cache[$key]['isScalar'] = true; - } else if ($this->_resultSetMapping->isFieldResult($key)) { + } else if (isset($this->_resultSetMapping->fieldMappings[$key])) { $classMetadata = $this->_resultSetMapping->getOwningClass($key); - $fieldName = $this->_resultSetMapping->getFieldName($key); + $fieldName = $this->_resultSetMapping->fieldMappings[$key]; $classMetadata = $this->_lookupDeclaringClass($classMetadata, $fieldName); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['isScalar'] = false; $cache[$key]['type'] = Type::getType($classMetadata->getTypeOfField($fieldName)); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); - $cache[$key]['dqlAlias'] = $this->_resultSetMapping->getEntityAlias($key); + $cache[$key]['dqlAlias'] = $this->_resultSetMapping->columnOwnerMap[$key]; } else { // Discriminator column $cache[$key]['isDiscriminator'] = true; $cache[$key]['isScalar'] = false; $cache[$key]['fieldName'] = $key; - $cache[$key]['dqlAlias'] = $this->_resultSetMapping->getEntityAlias($key); + $cache[$key]['dqlAlias'] = $this->_resultSetMapping->columnOwnerMap[$key]; } } @@ -245,21 +245,20 @@ abstract class AbstractHydrator foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { - if ($this->_resultSetMapping->isIgnoredColumn($key)) { + if (isset($this->_resultSetMapping->ignoredColumns[$key])) { $cache[$key] = false; continue; - } else if ($this->_resultSetMapping->isScalarResult($key)) { - $cache[$key]['fieldName'] = $this->_resultSetMapping->getScalarAlias($key); + } else if (isset($this->_resultSetMapping->scalarMappings[$key])) { + $cache[$key]['fieldName'] = $this->_resultSetMapping->scalarMappings[$key]; $cache[$key]['isScalar'] = true; } else { $classMetadata = $this->_resultSetMapping->getOwningClass($key); - $fieldName = $this->_resultSetMapping->getFieldName($key); + $fieldName = $this->_resultSetMapping->fieldMappings[$key]; $classMetadata = $this->_lookupDeclaringClass($classMetadata, $fieldName); - //$fieldName = $classMetadata->getFieldNameForLowerColumnName($columnName); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['isScalar'] = false; $cache[$key]['type'] = Type::getType($classMetadata->getTypeOfField($fieldName)); - $cache[$key]['dqlAlias'] = $this->_resultSetMapping->getEntityAlias($key); + $cache[$key]['dqlAlias'] = $this->_resultSetMapping->columnOwnerMap[$key]; } } @@ -284,8 +283,8 @@ abstract class AbstractHydrator */ protected function _getCustomIndexField($alias) { - return $this->_resultSetMapping->hasIndexBy($alias) ? - $this->_resultSetMapping->getIndexByField($alias) : null; + return isset($this->_resultSetMapping->indexByMap[$alias]) ? + $this->_resultSetMapping->indexByMap[$alias] : null; } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 19f5fef7a..16469973a 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -88,16 +88,17 @@ class ArrayHydrator extends AbstractHydrator foreach ($rowData as $dqlAlias => $data) { $index = false; - if ($this->_resultSetMapping->hasParentAlias($dqlAlias)) { + if (isset($this->_resultSetMapping->parentAliasMap[$dqlAlias])) { + // It's a joined result - $parent = $this->_resultSetMapping->getParentAlias($dqlAlias); - $relation = $this->_resultSetMapping->getRelation($dqlAlias); + $parent = $this->_resultSetMapping->parentAliasMap[$dqlAlias]; + $relation = $this->_resultSetMapping->relationMap[$dqlAlias]; $relationAlias = $relation->getSourceFieldName(); $path = $parent . '.' . $dqlAlias; // Get a reference to the right element in the result tree. // This element will get the associated element attached. - if ($this->_resultSetMapping->isMixedResult() && isset($this->_rootAliases[$parent])) { + if ($this->_resultSetMapping->isMixed && isset($this->_rootAliases[$parent])) { $key = key(reset($this->_resultPointers)); // TODO: Exception if $key === null ? $baseElement =& $this->_resultPointers[$parent][$key]; @@ -154,14 +155,14 @@ class ArrayHydrator extends AbstractHydrator if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $rowData[$dqlAlias]; if ($field = $this->_getCustomIndexField($dqlAlias)) { - if ($this->_resultSetMapping->isMixedResult()) { + if ($this->_resultSetMapping->isMixed) { $result[] = array($element[$field] => $element); ++$this->_resultCounter; } else { $result[$element[$field]] = $element; } } else { - if ($this->_resultSetMapping->isMixedResult()) { + if ($this->_resultSetMapping->isMixed) { $result[] = array($element); ++$this->_resultCounter; } else { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index ae76fb4df..e8561931f 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -53,13 +53,13 @@ class ObjectHydrator extends AbstractHydrator /** @override */ protected function _prepare() { - $this->_isSimpleQuery = $this->_resultSetMapping->getEntityResultCount() <= 1; + $this->_isSimpleQuery = count($this->_resultSetMapping->aliasMap) <= 1; $this->_allowPartialObjects = $this->_em->getConfiguration()->getAllowPartialObjects(); $this->_identifierMap = array(); $this->_resultPointers = array(); $this->_idTemplate = array(); $this->_resultCounter = 0; - foreach ($this->_resultSetMapping->getAliasMap() as $dqlAlias => $class) { + foreach ($this->_resultSetMapping->aliasMap as $dqlAlias => $class) { $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; @@ -73,8 +73,8 @@ class ObjectHydrator extends AbstractHydrator $this->_discriminatorMap[$class->name][$value] = $className; } } - if ($this->_resultSetMapping->isRelation($dqlAlias)) { - $assoc = $this->_resultSetMapping->getRelation($dqlAlias); + if (isset($this->_resultSetMapping->relationMap[$dqlAlias])) { + $assoc = $this->_resultSetMapping->relationMap[$dqlAlias]; $this->_fetchedAssociations[$assoc->getSourceEntityName()][$assoc->getSourceFieldName()] = true; if ($mappedByField = $assoc->getMappedByFieldName()) { $this->_fetchedAssociations[$assoc->getTargetEntityName()][$mappedByField] = true; @@ -97,7 +97,7 @@ class ObjectHydrator extends AbstractHydrator { $s = microtime(true); - if ($this->_resultSetMapping->isMixedResult()) { + if ($this->_resultSetMapping->isMixed) { $result = array(); } else { $result = new Collection; @@ -226,7 +226,8 @@ class ObjectHydrator extends AbstractHydrator private function getEntity(array $data, $className) { - if ($discrColumn = $this->_resultSetMapping->getDiscriminatorColumn($className)) { + if (isset($this->_resultSetMapping->discriminatorColumns[$className])) { + $discrColumn = $this->_resultSetMapping->discriminatorColumns[$className]; $className = $this->_discriminatorMap[$className][$data[$discrColumn]]; unset($data[$discrColumn]); } @@ -321,18 +322,18 @@ class ObjectHydrator extends AbstractHydrator // Hydrate the entity data found in the current row. foreach ($rowData as $dqlAlias => $data) { $index = false; - $entityName = $this->_resultSetMapping->getClass($dqlAlias)->name; + $entityName = $this->_resultSetMapping->aliasMap[$dqlAlias]->name; - if ($this->_resultSetMapping->hasParentAlias($dqlAlias)) { + if (isset($this->_resultSetMapping->parentAliasMap[$dqlAlias])) { // It's a joined result - $parent = $this->_resultSetMapping->getParentAlias($dqlAlias); - $relation = $this->_resultSetMapping->getRelation($dqlAlias); + $parent = $this->_resultSetMapping->parentAliasMap[$dqlAlias]; + $relation = $this->_resultSetMapping->relationMap[$dqlAlias]; $relationAlias = $relation->getSourceFieldName(); // Get a reference to the right element in the result tree. // This element will get the associated element attached. - if ($this->_resultSetMapping->isMixedResult() && isset($this->_rootAliases[$parent])) { + if ($this->_resultSetMapping->isMixed && isset($this->_rootAliases[$parent])) { $key = key(reset($this->_resultPointers)); // TODO: Exception if $key === null ? $baseElement =& $this->_resultPointers[$parent][$key]; @@ -420,7 +421,7 @@ class ObjectHydrator extends AbstractHydrator if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->getEntity($rowData[$dqlAlias], $entityName); if ($field = $this->_getCustomIndexField($dqlAlias)) { - if ($this->_resultSetMapping->isMixedResult()) { + if ($this->_resultSetMapping->isMixed) { $result[] = array( $this->_classMetadatas[$entityName] ->reflFields[$field] @@ -433,7 +434,7 @@ class ObjectHydrator extends AbstractHydrator ->getValue($element)); } } else { - if ($this->_resultSetMapping->isMixedResult()) { + if ($this->_resultSetMapping->isMixed) { $result[] = array($element); ++$this->_resultCounter; } else { diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 446fdd5e8..d1312d306 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -24,31 +24,35 @@ namespace Doctrine\ORM\Query; /** * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result. * + * IMPORTANT NOTE: + * The properties of this class are only public for fast internal READ access. + * Users should use the public methods. + * * @author Roman Borschel * @since 2.0 */ class ResultSetMapping { /** Whether the result is mixed (contains scalar values together with field values). */ - private $_isMixed = false; + public $isMixed = false; /** Maps alias names to ClassMetadata descriptors. */ - private $_aliasMap = array(); + public $aliasMap = array(); /** Maps alias names to related association mappings. */ - private $_relationMap = array(); + public $relationMap = array(); /** Maps alias names to parent alias names. */ - private $_parentAliasMap = array(); + public $parentAliasMap = array(); /** Maps column names in the result set to field names for each class. */ - private $_fieldMappings = array(); + public $fieldMappings = array(); /** Maps column names in the result set to the alias to use in the mapped result. */ - private $_scalarMappings = array(); + public $scalarMappings = array(); /** Maps column names in the result set to the alias they belong to. */ - private $_columnOwnerMap = array(); + public $columnOwnerMap = array(); /** List of columns in the result set that are used as discriminator columns. */ - private $_discriminatorColumns = array(); + public $discriminatorColumns = array(); /** Maps alias names to field names that should be used for indexing. */ - private $_indexByMap = array(); + public $indexByMap = array(); /** A list of columns that should be ignored/skipped during hydration. */ - private $_ignoredColumns = array(); + public $ignoredColumns = array(); /** * @@ -58,55 +62,55 @@ class ResultSetMapping */ public function addEntityResult($class, $alias) { - $this->_aliasMap[$alias] = $class; + $this->aliasMap[$alias] = $class; } public function setDiscriminatorColumn($className, $alias, $discrColumn) { - $this->_discriminatorColumns[$className] = $discrColumn; - $this->_columnOwnerMap[$discrColumn] = $alias; + $this->discriminatorColumns[$className] = $discrColumn; + $this->columnOwnerMap[$discrColumn] = $alias; } public function getDiscriminatorColumn($className) { - return isset($this->_discriminatorColumns[$className]) ? - $this->_discriminatorColumns[$className] : null; + return isset($this->discriminatorColumns[$className]) ? + $this->discriminatorColumns[$className] : null; } public function addIndexBy($alias, $fieldName) { - $this->_indexByMap[$alias] = $fieldName; + $this->indexByMap[$alias] = $fieldName; } public function hasIndexBy($alias) { - return isset($this->_indexByMap[$alias]); + return isset($this->indexByMap[$alias]); } public function getIndexByField($alias) { - return $this->_indexByMap[$alias]; + return $this->indexByMap[$alias]; } public function isFieldResult($columnName) { - return isset($this->_fieldMappings[$columnName]); + return isset($this->fieldMappings[$columnName]); } public function addFieldResult($alias, $columnName, $fieldName) { - $this->_fieldMappings[$columnName] = $fieldName; - $this->_columnOwnerMap[$columnName] = $alias; - if ( ! $this->_isMixed && $this->_scalarMappings) { - $this->_isMixed = true; + $this->fieldMappings[$columnName] = $fieldName; + $this->columnOwnerMap[$columnName] = $alias; + if ( ! $this->isMixed && $this->scalarMappings) { + $this->isMixed = true; } } public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) { - $this->_aliasMap[$alias] = $class; - $this->_parentAliasMap[$alias] = $parentAlias; - $this->_relationMap[$alias] = $relation; + $this->aliasMap[$alias] = $class; + $this->parentAliasMap[$alias] = $parentAlias; + $this->relationMap[$alias] = $relation; } /*public function isDiscriminatorColumn($columnName) @@ -116,9 +120,9 @@ class ResultSetMapping public function addScalarResult($columnName, $alias) { - $this->_scalarMappings[$columnName] = $alias; - if ( ! $this->_isMixed && $this->_fieldMappings) { - $this->_isMixed = true; + $this->scalarMappings[$columnName] = $alias; + if ( ! $this->isMixed && $this->fieldMappings) { + $this->isMixed = true; } } @@ -127,7 +131,7 @@ class ResultSetMapping */ public function isScalarResult($columnName) { - return isset($this->_scalarMappings[$columnName]); + return isset($this->scalarMappings[$columnName]); } /** @@ -136,7 +140,7 @@ class ResultSetMapping */ public function getClass($alias) { - return $this->_aliasMap[$alias]; + return $this->aliasMap[$alias]; } /** @@ -147,7 +151,7 @@ class ResultSetMapping */ public function getScalarAlias($columnName) { - return $this->_scalarMappings[$columnName]; + return $this->scalarMappings[$columnName]; } /** @@ -157,17 +161,17 @@ class ResultSetMapping */ public function getOwningClass($columnName) { - return $this->_aliasMap[$this->_columnOwnerMap[$columnName]]; + return $this->aliasMap[$this->columnOwnerMap[$columnName]]; } public function getRelation($alias) { - return $this->_relationMap[$alias]; + return $this->relationMap[$alias]; } public function isRelation($alias) { - return isset($this->_relationMap[$alias]); + return isset($this->relationMap[$alias]); } /** @@ -177,7 +181,7 @@ class ResultSetMapping */ public function getEntityAlias($columnName) { - return $this->_columnOwnerMap[$columnName]; + return $this->columnOwnerMap[$columnName]; } /** @@ -187,12 +191,12 @@ class ResultSetMapping */ public function getParentAlias($alias) { - return $this->_parentAliasMap[$alias]; + return $this->parentAliasMap[$alias]; } public function hasParentAlias($alias) { - return isset($this->_parentAliasMap[$alias]); + return isset($this->parentAliasMap[$alias]); } /** @@ -203,32 +207,32 @@ class ResultSetMapping */ public function getFieldName($columnName) { - return $this->_fieldMappings[$columnName]; + return $this->fieldMappings[$columnName]; } public function getAliasMap() { - return $this->_aliasMap; + return $this->aliasMap; } public function getEntityResultCount() { - return count($this->_aliasMap); + return count($this->aliasMap); } public function isMixedResult() { - return $this->_isMixed; + return $this->isMixed; } public function addIgnoredColumn($columnName) { - $this->_ignoredColumns[$columnName] = true; + $this->ignoredColumns[$columnName] = true; } public function isIgnoredColumn($columnName) { - return isset($this->_ignoredColumns[$columnName]); + return isset($this->ignoredColumns[$columnName]); } } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index f0598c00d..be5976164 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -679,76 +679,4 @@ class ObjectHydratorTest extends HydrationTest ++$rowNum; } } - - /** - * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper from User u - * join u.phonenumbers p - * = - * select u.id, u.status, p.phonenumber, upper(u.name) as u__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id - * - * @dataProvider hydrationModeProvider - */ - public function testNewHydrationMixedQueryFetchJoinPerformance() - { - $rsm = new ResultSetMapping; - $rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u'); - /*$rsm->addJoinedEntityResult( - $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'), - 'p', - 'u', - $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser')->getAssociationMapping('phonenumbers') - );*/ - $rsm->addFieldResult('u', 'u__id', 'id'); - $rsm->addFieldResult('u', 'u__status', 'status'); - $rsm->addFieldResult('u', 'u__username', 'username'); - $rsm->addFieldResult('u', 'u__name', 'name'); - //$rsm->addScalarResult('sclr0', 'nameUpper'); - //$rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); - - // Faked result set - $resultSet = array( - //row1 - array( - 'u__id' => '1', - 'u__status' => 'developer', - 'u__username' => 'romanb', - 'u__name' => 'Roman', - //'sclr0' => 'ROMANB', - //'p__phonenumber' => '42', - ), - array( - 'u__id' => '1', - 'u__status' => 'developer', - 'u__username' => 'romanb', - 'u__name' => 'Roman', - //'sclr0' => 'ROMANB', - //'p__phonenumber' => '43', - ), - array( - 'u__id' => '2', - 'u__status' => 'developer', - 'u__username' => 'romanb', - 'u__name' => 'Roman', - //'sclr0' => 'JWAGE', - //'p__phonenumber' => '91' - ) - ); - - for ($i = 4; $i < 10000; ++$i) { - $resultSet[] = array( - 'u__id' => $i, - 'u__status' => 'developer', - 'u__username' => 'jwage', - 'u__name' => 'Jonathan', - //'sclr0' => 'JWAGE' . $i, - //'p__phonenumber' => '91' - ); - } - - $stmt = new HydratorMockStatement($resultSet); - $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); - } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Performance/AllTests.php b/tests/Doctrine/Tests/ORM/Performance/AllTests.php new file mode 100644 index 000000000..5acea3e0e --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Performance/AllTests.php @@ -0,0 +1,31 @@ +addTestSuite('Doctrine\Tests\ORM\Performance\HydrationPerformanceTest'); + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'Orm_Performance_AllTests::main') { + AllTests::main(); +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php new file mode 100644 index 000000000..8bd60fdcf --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php @@ -0,0 +1,268 @@ + 1.8 seconds] + * + * MAXIMUM TIME: 3 seconds + */ + public function testNewHydrationSimpleQueryArrayHydrationPerformance() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addFieldResult('u', 'u__username', 'username'); + $rsm->addFieldResult('u', 'u__name', 'name'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + ), + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + ) + ); + + for ($i = 4; $i < 10000; ++$i) { + $resultSet[] = array( + 'u__id' => $i, + 'u__status' => 'developer', + 'u__username' => 'jwage', + 'u__name' => 'Jonathan', + ); + } + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + + $this->setMaxRunningTime(3); + $result = $hydrator->hydrateAll($stmt, $rsm); + } + + /** + * Times for comparison: + * + * [romanb: 10000 rows => 3.0 seconds] + * + * MAXIMUM TIME: 4 seconds + */ + public function testNewHydrationMixedQueryFetchJoinArrayHydrationPerformance() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u'); + $rsm->addJoinedEntityResult( + $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'), + 'p', + 'u', + $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser')->getAssociationMapping('phonenumbers') + ); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addFieldResult('u', 'u__username', 'username'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + 'sclr0' => 'ROMANB', + 'p__phonenumber' => '42', + ), + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + 'sclr0' => 'ROMANB', + 'p__phonenumber' => '43', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + 'sclr0' => 'JWAGE', + 'p__phonenumber' => '91' + ) + ); + + for ($i = 4; $i < 10000; ++$i) { + $resultSet[] = array( + 'u__id' => $i, + 'u__status' => 'developer', + 'u__username' => 'jwage', + 'u__name' => 'Jonathan', + 'sclr0' => 'JWAGE' . $i, + 'p__phonenumber' => '91' + ); + } + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + + $this->setMaxRunningTime(4); + $result = $hydrator->hydrateAll($stmt, $rsm); + } + + /** + * [romanb: 10000 rows => 3.4 seconds] + * + * MAXIMUM TIME: 4 seconds + */ + public function testSimpleQueryObjectHydrationPerformance() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addFieldResult('u', 'u__username', 'username'); + $rsm->addFieldResult('u', 'u__name', 'name'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + ), + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + ) + ); + + for ($i = 4; $i < 10000; ++$i) { + $resultSet[] = array( + 'u__id' => $i, + 'u__status' => 'developer', + 'u__username' => 'jwage', + 'u__name' => 'Jonathan', + ); + } + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $this->setMaxRunningTime(4); + $result = $hydrator->hydrateAll($stmt, $rsm); + } + + /** + * [romanb: 2000 rows => 3.1 seconds] + * + * MAXIMUM TIME: 4 seconds + */ + public function testMixedQueryFetchJoinObjectHydrationPerformance() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u'); + $rsm->addJoinedEntityResult( + $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'), + 'p', + 'u', + $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser')->getAssociationMapping('phonenumbers') + ); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addFieldResult('u', 'u__username', 'username'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + 'sclr0' => 'ROMANB', + 'p__phonenumber' => '42', + ), + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + 'sclr0' => 'ROMANB', + 'p__phonenumber' => '43', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'u__username' => 'romanb', + 'u__name' => 'Roman', + 'sclr0' => 'JWAGE', + 'p__phonenumber' => '91' + ) + ); + + for ($i = 4; $i < 2000; ++$i) { + $resultSet[] = array( + 'u__id' => $i, + 'u__status' => 'developer', + 'u__username' => 'jwage', + 'u__name' => 'Jonathan', + 'sclr0' => 'JWAGE' . $i, + 'p__phonenumber' => '91' + ); + } + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $this->setMaxRunningTime(4); + $result = $hydrator->hydrateAll($stmt, $rsm); + } +} + diff --git a/tests/Doctrine/Tests/OrmPerformanceTestCase.php b/tests/Doctrine/Tests/OrmPerformanceTestCase.php new file mode 100644 index 000000000..5350e211f --- /dev/null +++ b/tests/Doctrine/Tests/OrmPerformanceTestCase.php @@ -0,0 +1,68 @@ +_em = $this->_getTestEntityManager(); + } + + /** + * @var integer + */ + protected $maxRunningTime = 0; + + /** + */ + protected function runTest() + { + $s = microtime(true); + parent::runTest(); + $time = microtime(true) - $s; + + if ($this->maxRunningTime != 0 && $time > $this->maxRunningTime) { + $this->fail( + sprintf( + 'expected running time: <= %s but was: %s', + + $this->maxRunningTime, + $time + ) + ); + } + } + + /** + * @param integer $maxRunningTime + * @throws InvalidArgumentException + * @since Method available since Release 2.3.0 + */ + public function setMaxRunningTime($maxRunningTime) + { + if (is_integer($maxRunningTime) && $maxRunningTime >= 0) { + $this->maxRunningTime = $maxRunningTime; + } else { + throw new \InvalidArgumentException; + } + } + + /** + * @return integer + * @since Method available since Release 2.3.0 + */ + public function getMaxRunningTime() + { + return $this->maxRunningTime; + } +} +