diff --git a/lib/Doctrine/Common/DoctrineException.php b/lib/Doctrine/Common/DoctrineException.php index 9102a6213..6c63b4c4e 100644 --- a/lib/Doctrine/Common/DoctrineException.php +++ b/lib/Doctrine/Common/DoctrineException.php @@ -100,12 +100,12 @@ class DoctrineException extends \Exception if (($message = self::getExceptionMessage($messageKey)) !== false) { $message = sprintf($message, $arguments); } else { - $dumper = function ($value) { return var_export($value, true); }; + //$dumper = function ($value) { return var_export($value, true); }; $message = strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $method)); $message = ucfirst(str_replace('_', ' ', $message)); - if ($arguments) { + /*if ($arguments) { $message .= ' (' . implode(', ', array_map($dumper, $arguments)) . ')'; - } + }*/ } return new $class($message, $innerException); diff --git a/lib/Doctrine/DBAL/DBALException.php b/lib/Doctrine/DBAL/DBALException.php new file mode 100644 index 000000000..5fb4eeaa6 --- /dev/null +++ b/lib/Doctrine/DBAL/DBALException.php @@ -0,0 +1,5 @@ +_stmt = $stmt; $this->_rsm = $resultSetMapping; + $this->_hints = $hints; $this->_prepare(); return new IterableResult($this); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php index 762933c53..da52c6d13 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php @@ -26,7 +26,7 @@ use Doctrine\DBAL\Connection; /** * Hydrator that produces flat, rectangular results of scalar data. * The created result is almost the same as a regular SQL result set, except - * that column names are mapped to field names and data type conversions. + * that column names are mapped to field names and data type conversions take place. * * @author Roman Borschel * @since 2.0 @@ -49,10 +49,4 @@ class ScalarHydrator extends AbstractHydrator { $result[] = $this->_gatherScalarRowData($data, $cache); } - - /** @override */ - protected function _getRowContainer() - { - return array(); - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index a46a0d33d..7bd52574d 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -169,6 +169,15 @@ final class ClassMetadata extends ClassMetadataInfo return $this->reflFields[$this->identifier[0]]->getValue($entity); } } + + public function getColumnValues($entity, array $columns) + { + $values = array(); + foreach ($columns as $column) { + $values[] = $this->reflFields[$this->fieldNames[$column]]->getValue($entity); + } + return $values; + } /** * Populates the entity identifier of an entity. diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 8b54d8071..a61d448df 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -221,7 +221,6 @@ class OneToOneMapping extends AssociationMapping if ($this->isOwningSide) { foreach ($this->sourceToTargetKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - // getting customer_id if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { @@ -240,7 +239,6 @@ class OneToOneMapping extends AssociationMapping $owningAssoc = $em->getClassMetadata($this->targetEntityName)->getAssociationMapping($this->mappedByFieldName); // TRICKY: since the association is specular source and target are flipped foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - // getting id if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { @@ -250,7 +248,12 @@ class OneToOneMapping extends AssociationMapping $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this); - $targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity); + if ($targetEntity !== null) { + $targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity); + } + } + + return $targetEntity; } } diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php new file mode 100644 index 000000000..87f809b8f --- /dev/null +++ b/lib/Doctrine/ORM/ORMException.php @@ -0,0 +1,11 @@ + * @author Giorgio Sironi @@ -410,6 +410,13 @@ class StandardEntityPersister public function load(array $criteria, $entity = null, $assoc = null) { $stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($criteria, $assoc)); + if ($stmt === null) { + try { + throw new \Exception(); + } catch (\Exception $e) { + var_dump($e->getTraceAsString()); + } + } $stmt->execute(array_values($criteria)); $result = $stmt->fetch(Connection::FETCH_ASSOC); $stmt->closeCursor(); @@ -417,6 +424,88 @@ class StandardEntityPersister return $this->_createEntity($result, $entity); } + /** + * Refreshes an entity. + * + * @param array $id The identifier of the entity as an associative array from column names to values. + * @param object $entity The entity to refresh. + */ + public function refresh(array $id, $entity) + { + $stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($id)); + $stmt->execute(array_values($id)); + $result = $stmt->fetch(Connection::FETCH_ASSOC); + $stmt->closeCursor(); + + $metaColumns = array(); + $newData = array(); + + // Refresh simple state + foreach ($result as $column => $value) { + $column = $this->_class->resultColumnNames[$column]; + if (isset($this->_class->fieldNames[$column])) { + $fieldName = $this->_class->fieldNames[$column]; + $type = Type::getType($this->_class->fieldMappings[$fieldName]['type']); + $newValue = $type->convertToPHPValue($value, $this->_platform); + $this->_class->reflFields[$fieldName]->setValue($entity, $newValue); + $newData[$fieldName] = $newValue; + } else { + $metaColumns[$column] = $value; + } + } + + // Refresh associations + foreach ($this->_class->associationMappings as $field => $assoc) { + $value = $this->_class->reflFields[$field]->getValue($entity); + if ($assoc->isOneToOne()) { + if ($value instanceof Proxy && ! $value->__isInitialized()) { + continue; // skip uninitialized proxies + } + + if ($assoc->isOwningSide) { + $joinColumnValues = array(); + $targetColumns = array(); + foreach ($assoc->targetToSourceKeyColumns as $targetColumn => $srcColumn) { + if ($metaColumns[$srcColumn] !== null) { + $joinColumnValues[] = $metaColumns[$srcColumn]; + } + $targetColumns[] = $targetColumn; + } + if ( ! $joinColumnValues && $value !== null) { + $this->_class->reflFields[$field]->setValue($entity, null); + $newData[$field] = null; + } else if ($value !== null) { + // Check identity map first, if the entity is not there, + // place a proxy in there instead. + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); + if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) { + $this->_class->reflFields[$field]->setValue($entity, $found); + // Complete inverse side, if necessary. + if (isset($targetClass->inverseMappings[$this->_class->name][$field])) { + $inverseAssoc = $targetClass->inverseMappings[$this->_class->name][$field]; + $targetClass->reflFields[$inverseAssoc->sourceFieldName]->setValue($found, $entity); + } + $newData[$field] = $found; + } else if ((array)$this->_class->getIdentifierValues($value) != $joinColumnValues) { + $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues); + $this->_class->reflFields[$field]->setValue($entity, $proxy); + $newData[$field] = $proxy; + } + } + } else { + // Inverse side of 1-1/1-x can never be lazy + $assoc->load($entity, null, $this->_em); + $newData[$field] = $this->_class->reflFields[$field]->getValue($entity); + } + } else if ($value instanceof PersistentCollection && $value->isInitialized()) { + $value->setInitialized(false); + $newData[$field] = $value; + } + } + + $this->_em->getUnitOfWork()->setOriginalEntityData($entity, $newData); + } + /** * Loads all entities by a list of field criteria. * diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1a23e495f..4b62cb1b1 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1443,16 +1443,13 @@ class UnitOfWork implements PropertyChangedListener $visited[$oid] = $entity; // mark visited $class = $this->_em->getClassMetadata(get_class($entity)); - switch ($this->getEntityState($entity)) { - case self::STATE_MANAGED: - $this->getEntityPersister($class->name)->load( - array_combine($class->identifier, $this->_entityIdentifiers[$oid]), - $entity - ); - //TODO: refresh (initialized) associations - break; - default: - throw new \InvalidArgumentException("Entity is not MANAGED."); + if ($this->getEntityState($entity) == self::STATE_MANAGED) { + $this->getEntityPersister($class->name)->refresh( + array_combine($class->getIdentifierColumnNames(), $this->_entityIdentifiers[$oid]), + $entity + ); + } else { + throw new \InvalidArgumentException("Entity is not MANAGED."); } $this->_cascadeRefresh($entity, $visited); @@ -1651,6 +1648,7 @@ class UnitOfWork implements PropertyChangedListener * @param array $data The data for the entity. * @return object The created entity instance. * @internal Highly performance-sensitive method. + * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = array()) { @@ -1698,25 +1696,31 @@ class UnitOfWork implements PropertyChangedListener // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { foreach ($class->associationMappings as $field => $assoc) { - // Check if the association is not among the fetch-joined associatons already. + // Check if the association is not among the fetch-joined associations already. if ( ! isset($hints['fetched'][$className][$field])) { if ($assoc->isOneToOne()) { - $joinColumns = array(); if ($assoc->isOwningSide) { + $joinColumns = array(); foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { - $joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]]; + $joinColumnValue = $data[$assoc->joinColumnFieldNames[$srcColumn]]; + if ($joinColumnValue !== null) { + $joinColumns[$srcColumn] = $joinColumnValue; + } + } + if ( ! $joinColumns) { + $class->reflFields[$field]->setValue($entity, null); + $this->_originalEntityData[$oid][$field] = null; + } else { + //TODO: If its in the identity map just get it from there if possible! + // Inject proxy + $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns); + $this->_originalEntityData[$oid][$field] = $proxy; + $class->reflFields[$field]->setValue($entity, $proxy); } - } - //TODO: If its in the identity map just get it from there if possible! - if ($assoc->isLazilyFetched() /*&& $assoc->isOwningSide)*/) { - // Inject proxy - $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns); - $this->_originalEntityData[$oid][$field] = $proxy; - $class->reflFields[$field]->setValue($entity, $proxy); } else { - // Eager load - //TODO: Allow more efficient and configurable batching of these loads - $assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumns); + // Inverse side can never be lazy. + $targetEntity = $assoc->load($entity, new $assoc->targetEntityName, $this->_em); + $class->reflFields[$field]->setValue($entity, $targetEntity); } } else { // Inject collection @@ -1778,6 +1782,14 @@ class UnitOfWork implements PropertyChangedListener } return array(); } + + /** + * @ignore + */ + public function setOriginalEntityData($entity, array $data) + { + $this->_originalEntityData[spl_object_hash($entity)] = $data; + } /** * INTERNAL: diff --git a/tests/Doctrine/Tests/Common/DoctrineExceptionTest.php b/tests/Doctrine/Tests/Common/DoctrineExceptionTest.php index 6787b4b95..9d24bccf0 100644 --- a/tests/Doctrine/Tests/Common/DoctrineExceptionTest.php +++ b/tests/Doctrine/Tests/Common/DoctrineExceptionTest.php @@ -6,12 +6,12 @@ require_once __DIR__ . '/../TestInit.php'; class DoctrineExceptionTest extends \Doctrine\Tests\DoctrineTestCase { - public function testStaticCall() + /*public function testStaticCall() { $e = \Doctrine\Common\DoctrineException::testingStaticCallBuildsErrorMessageWithParams('param1', 'param2'); $this->assertEquals($e->getMessage(), "Testing static call builds error message with params ('param1', 'param2')"); - } + }*/ public function testInnerException() { diff --git a/tests/Doctrine/Tests/ORM/Functional/IdentityMapTest.php b/tests/Doctrine/Tests/ORM/Functional/IdentityMapTest.php index 7a4f23808..7fe549fdd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/IdentityMapTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/IdentityMapTest.php @@ -23,8 +23,81 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->useModelSet('cms'); parent::setUp(); } + + public function testBasicIdentityManagement() + { + $user = new CmsUser; + $user->status = 'dev'; + $user->username = 'romanb'; + $user->name = 'Roman B.'; + + $address = new CmsAddress; + $address->country = 'de'; + $address->zip = 1234; + $address->city = 'Berlin'; + + $user->setAddress($address); + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user2 = $this->_em->find(get_class($user), $user->getId()); + $this->assertTrue($user2 !== $user); + $user3 = $this->_em->find(get_class($user), $user->getId()); + $this->assertTrue($user2 === $user3); + + $address2 = $this->_em->find(get_class($address), $address->getId()); + $this->assertTrue($address2 !== $address); + $address3 = $this->_em->find(get_class($address), $address->getId()); + $this->assertTrue($address2 === $address3); + + $this->assertTrue($user2->getAddress() === $address2); // !!! + } + + public function testSingleValuedAssociationIdentityMapBehaviorWithRefresh() + { + $address = new CmsAddress; + $address->country = 'de'; + $address->zip = '12345'; + $address->city = 'Berlin'; + + $user1 = new CmsUser; + $user1->status = 'dev'; + $user1->username = 'romanb'; + $user1->name = 'Roman B.'; - public function testSingleValuedAssociationIdentityMapBehavior() + $user2 = new CmsUser; + $user2->status = 'dev'; + $user2->username = 'gblanco'; + $user2->name = 'Guilherme Blanco'; + + $address->setUser($user1); + + $this->_em->persist($address); + $this->_em->persist($user1); + $this->_em->persist($user2); + $this->_em->flush(); + + $this->assertSame($user1, $address->user); + + //external update to CmsAddress + $this->_em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', array($user2->getId())); + + // But we want to have this external change! + // Solution 1: refresh(), broken atm! + $this->_em->refresh($address); + + // Now the association should be "correct", referencing $user2 + $this->assertSame($user2, $address->user); + $this->assertSame($user2->address, $address); // check back reference also + + // Attention! refreshes can result in broken bidirectional associations! this is currently expected! + // $user1 still points to $address! + $this->assertSame($user1->address, $address); + } + + public function testSingleValuedAssociationIdentityMapBehaviorWithRefreshQuery() { $address = new CmsAddress; $address->country = 'de'; @@ -65,8 +138,6 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($user2->address === null); // But we want to have this external change! - // Solution 1: refresh(), broken atm! - //$this->_em->refresh($address2); // Solution 2: Alternatively, a refresh query should work $q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u'); $q->setHint(Query::HINT_REFRESH, true); @@ -83,7 +154,7 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($user1->address, $address2); } - public function testCollectionValuedAssociationIdentityMapBehavior() + public function testCollectionValuedAssociationIdentityMapBehaviorWithRefreshQuery() { $user = new CmsUser; $user->status = 'dev'; @@ -133,5 +204,52 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase // Now the collection should be refreshed with correct count $this->assertEquals(4, count($user3->getPhonenumbers())); } + + public function testCollectionValuedAssociationIdentityMapBehaviorWithRefresh() + { + $user = new CmsUser; + $user->status = 'dev'; + $user->username = 'romanb'; + $user->name = 'Roman B.'; + + $phone1 = new CmsPhonenumber; + $phone1->phonenumber = 123; + + $phone2 = new CmsPhonenumber; + $phone2->phonenumber = 234; + + $phone3 = new CmsPhonenumber; + $phone3->phonenumber = 345; + + $user->addPhonenumber($phone1); + $user->addPhonenumber($phone2); + $user->addPhonenumber($phone3); + + $this->_em->persist($user); // cascaded to phone numbers + $this->_em->flush(); + + $this->assertEquals(3, count($user->getPhonenumbers())); + + //external update to CmsAddress + $this->_em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', array(999, $user->getId())); + + //select + $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p'); + $user2 = $q->getSingleResult(); + + $this->assertSame($user, $user2); + + // Should still be the same 3 phonenumbers + $this->assertEquals(3, count($user2->getPhonenumbers())); + + // But we want to have this external change! + // Solution 1: refresh(). + $this->_em->refresh($user2); + + $this->assertSame($user, $user2); // should still be the same, always from identity map + + // Now the collection should be refreshed with correct count + $this->assertEquals(4, count($user2->getPhonenumbers())); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php index fc0b97d6c..90ab33fa3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php @@ -37,7 +37,7 @@ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase $e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1); $this->assertEquals(1, $e2->getId()); $this->assertEquals('Roman', $e2->getName()); - $this->assertTrue($e2->getMappedRelated1() instanceof MappedSuperclassRelated1); + $this->assertNull($e2->getMappedRelated1()); $this->assertEquals(42, $e2->getMapped1()); $this->assertEquals('bar', $e2->getMapped2()); } diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php index ad44777d2..c479f4c52 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php @@ -78,7 +78,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional $this->assertEquals('Giorgio', $cart->getCustomer()->getName()); } - public function testLazyLoadsObjectsOnTheInverseSide() + public function testInverseSideIsNeverLazy() { $this->_createFixture(); $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer'); @@ -90,7 +90,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional $this->assertNull($customer->getMentor()); $this->assertTrue($customer->getCart() instanceof ECommerceCart); - $this->assertTrue($customer->getCart() instanceof \Doctrine\ORM\Proxy\Proxy); + $this->assertFalse($customer->getCart() instanceof \Doctrine\ORM\Proxy\Proxy); $this->assertEquals('paypal', $customer->getCart()->getPayment()); } diff --git a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php index 8a63e15bf..d9c206b3d 100644 --- a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php @@ -4,9 +4,9 @@ namespace Doctrine\Tests\ORM\Performance; require_once __DIR__ . '/../../TestInit.php'; -use Doctrine\Tests\Mocks\HydratorMockStatement; -use Doctrine\ORM\Query\ResultSetMapping; -use Doctrine\ORM\Query; +use Doctrine\Tests\Mocks\HydratorMockStatement, + Doctrine\ORM\Query\ResultSetMapping, + Doctrine\ORM\Query; /** * Tests to prevent serious performance regressions. @@ -18,6 +18,64 @@ use Doctrine\ORM\Query; */ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase { + /** + * Times for comparison: + * + * [romanb: 10000 rows => 0.7 seconds] + * + * MAXIMUM TIME: 1 second + */ + public function testSimpleQueryScalarHydrationPerformance10000Rows() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('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\ScalarHydrator($this->_em); + + $this->setMaxRunningTime(1); + $s = microtime(true); + $result = $hydrator->hydrateAll($stmt, $rsm); + $e = microtime(true); + echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; + } + /** * Times for comparison: * @@ -219,6 +277,16 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__username', 'username'); $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsAddress', + 'a', + 'u', + 'address' + ); + $rsm->addFieldResult('a', 'a__id', 'id'); + //$rsm->addFieldResult('a', 'a__country', 'country'); + //$rsm->addFieldResult('a', 'a__zip', 'zip'); + //$rsm->addFieldResult('a', 'a__city', 'city'); // Faked result set $resultSet = array( @@ -228,27 +296,17 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase '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', + 'a__id' => '1' ) ); - for ($i = 4; $i < 10000; ++$i) { + for ($i = 2; $i < 10000; ++$i) { $resultSet[] = array( 'u__id' => $i, 'u__status' => 'developer', 'u__username' => 'jwage', 'u__name' => 'Jonathan', + 'a__id' => $i ); } @@ -355,6 +413,13 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsAddress', + 'a', + 'u', + 'address' + ); + $rsm->addFieldResult('a', 'a__id', 'id'); // Faked result set $resultSet = array( @@ -366,33 +431,19 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase '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' + 'a__id' => '1' ) ); - for ($i = 4; $i < 2000; ++$i) { + for ($i = 2; $i < 2000; ++$i) { $resultSet[] = array( 'u__id' => $i, 'u__status' => 'developer', 'u__username' => 'jwage', 'u__name' => 'Jonathan', 'sclr0' => 'JWAGE' . $i, - 'p__phonenumber' => '91' + 'p__phonenumber' => '91', + 'a__id' => $i ); } @@ -405,63 +456,5 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase $e = microtime(true); echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; } - - /** - * Times for comparison: - * - * [romanb: 10000 rows => 0.7 seconds] - * - * MAXIMUM TIME: 1 second - */ - public function testSimpleQueryScalarHydrationPerformance10000Rows() - { - $rsm = new ResultSetMapping; - $rsm->addEntityResult('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\ScalarHydrator($this->_em); - - $this->setMaxRunningTime(1); - $s = microtime(true); - $result = $hydrator->hydrateAll($stmt, $rsm); - $e = microtime(true); - echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; - } }