1
0
mirror of synced 2025-02-02 05:21:44 +03:00

[2.0][DDC-22] Fixed. Also cleaned up association handling. More to come: exception refactorings, proxy simplifications (single proxy class, not two).

This commit is contained in:
romanb 2009-10-28 10:31:47 +00:00
parent e832d1723a
commit 4fc1781d78
15 changed files with 376 additions and 141 deletions

View File

@ -100,12 +100,12 @@ class DoctrineException extends \Exception
if (($message = self::getExceptionMessage($messageKey)) !== false) { if (($message = self::getExceptionMessage($messageKey)) !== false) {
$message = sprintf($message, $arguments); $message = sprintf($message, $arguments);
} else { } 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 = strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $method));
$message = ucfirst(str_replace('_', ' ', $message)); $message = ucfirst(str_replace('_', ' ', $message));
if ($arguments) { /*if ($arguments) {
$message .= ' (' . implode(', ', array_map($dumper, $arguments)) . ')'; $message .= ' (' . implode(', ', array_map($dumper, $arguments)) . ')';
} }*/
} }
return new $class($message, $innerException); return new $class($message, $innerException);

View File

@ -0,0 +1,5 @@
<?php
namespace Doctrine\DBAL;
class DBALException extends \Exception {}

View File

@ -22,7 +22,7 @@
namespace Doctrine\ORM\Id; namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Doctrine\Common\DoctrineException; use Doctrine\ORM\ORMException;
/** /**
* Special generator for application-assigned identifiers (doesnt really generate anything). * Special generator for application-assigned identifiers (doesnt really generate anything).
@ -62,7 +62,7 @@ class Assigned extends AbstractIdGenerator
} }
if ( ! $identifier) { if ( ! $identifier) {
throw DoctrineException::entityMissingAssignedId($entity); throw ORMException::entityMissingAssignedId($entity);
} }
return $identifier; return $identifier;

View File

@ -78,10 +78,11 @@ abstract class AbstractHydrator
* @param object $resultSetMapping * @param object $resultSetMapping
* @return IterableResult * @return IterableResult
*/ */
public function iterate($stmt, $resultSetMapping) public function iterate($stmt, $resultSetMapping, array $hints = array())
{ {
$this->_stmt = $stmt; $this->_stmt = $stmt;
$this->_rsm = $resultSetMapping; $this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare(); $this->_prepare();
return new IterableResult($this); return new IterableResult($this);
} }

View File

@ -26,7 +26,7 @@ use Doctrine\DBAL\Connection;
/** /**
* Hydrator that produces flat, rectangular results of scalar data. * Hydrator that produces flat, rectangular results of scalar data.
* The created result is almost the same as a regular SQL result set, except * 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 <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @since 2.0
@ -49,10 +49,4 @@ class ScalarHydrator extends AbstractHydrator
{ {
$result[] = $this->_gatherScalarRowData($data, $cache); $result[] = $this->_gatherScalarRowData($data, $cache);
} }
/** @override */
protected function _getRowContainer()
{
return array();
}
} }

View File

@ -170,6 +170,15 @@ final class ClassMetadata extends ClassMetadataInfo
} }
} }
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. * Populates the entity identifier of an entity.
* *

View File

@ -221,7 +221,6 @@ class OneToOneMapping extends AssociationMapping
if ($this->isOwningSide) { if ($this->isOwningSide) {
foreach ($this->sourceToTargetKeyColumns as $sourceKeyColumn => $targetKeyColumn) { foreach ($this->sourceToTargetKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
// getting customer_id
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
$conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
} else { } else {
@ -240,7 +239,6 @@ class OneToOneMapping extends AssociationMapping
$owningAssoc = $em->getClassMetadata($this->targetEntityName)->getAssociationMapping($this->mappedByFieldName); $owningAssoc = $em->getClassMetadata($this->targetEntityName)->getAssociationMapping($this->mappedByFieldName);
// TRICKY: since the association is specular source and target are flipped // TRICKY: since the association is specular source and target are flipped
foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
// getting id
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
$conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
} else { } else {
@ -250,7 +248,12 @@ class OneToOneMapping extends AssociationMapping
$targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this); $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;
} }
} }

View File

@ -0,0 +1,11 @@
<?php
namespace Doctrine\ORM;
class ORMException extends \Exception
{
public static function entityMissingAssignedId($entity)
{
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
}
}

View File

@ -34,7 +34,7 @@ use Doctrine\Common\DoctrineException,
/** /**
* Base class for all EntityPersisters. An EntityPersister is a class that knows * Base class for all EntityPersisters. An EntityPersister is a class that knows
* how to persist (and to some extent how to load) entities of a specific type. * how to persist and load entities of a specific type.
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com> * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
@ -410,6 +410,13 @@ class StandardEntityPersister
public function load(array $criteria, $entity = null, $assoc = null) public function load(array $criteria, $entity = null, $assoc = null)
{ {
$stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($criteria, $assoc)); $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)); $stmt->execute(array_values($criteria));
$result = $stmt->fetch(Connection::FETCH_ASSOC); $result = $stmt->fetch(Connection::FETCH_ASSOC);
$stmt->closeCursor(); $stmt->closeCursor();
@ -417,6 +424,88 @@ class StandardEntityPersister
return $this->_createEntity($result, $entity); 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. * Loads all entities by a list of field criteria.
* *

View File

@ -1443,16 +1443,13 @@ class UnitOfWork implements PropertyChangedListener
$visited[$oid] = $entity; // mark visited $visited[$oid] = $entity; // mark visited
$class = $this->_em->getClassMetadata(get_class($entity)); $class = $this->_em->getClassMetadata(get_class($entity));
switch ($this->getEntityState($entity)) { if ($this->getEntityState($entity) == self::STATE_MANAGED) {
case self::STATE_MANAGED: $this->getEntityPersister($class->name)->refresh(
$this->getEntityPersister($class->name)->load( array_combine($class->getIdentifierColumnNames(), $this->_entityIdentifiers[$oid]),
array_combine($class->identifier, $this->_entityIdentifiers[$oid]), $entity
$entity );
); } else {
//TODO: refresh (initialized) associations throw new \InvalidArgumentException("Entity is not MANAGED.");
break;
default:
throw new \InvalidArgumentException("Entity is not MANAGED.");
} }
$this->_cascadeRefresh($entity, $visited); $this->_cascadeRefresh($entity, $visited);
@ -1651,6 +1648,7 @@ class UnitOfWork implements PropertyChangedListener
* @param array $data The data for the entity. * @param array $data The data for the entity.
* @return object The created entity instance. * @return object The created entity instance.
* @internal Highly performance-sensitive method. * @internal Highly performance-sensitive method.
* @todo Rename: getOrCreateEntity
*/ */
public function createEntity($className, array $data, &$hints = array()) 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. // Properly initialize any unfetched associations, if partial objects are not allowed.
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
foreach ($class->associationMappings as $field => $assoc) { 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 ( ! isset($hints['fetched'][$className][$field])) {
if ($assoc->isOneToOne()) { if ($assoc->isOneToOne()) {
$joinColumns = array();
if ($assoc->isOwningSide) { if ($assoc->isOwningSide) {
$joinColumns = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { 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 { } else {
// Eager load // Inverse side can never be lazy.
//TODO: Allow more efficient and configurable batching of these loads $targetEntity = $assoc->load($entity, new $assoc->targetEntityName, $this->_em);
$assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumns); $class->reflFields[$field]->setValue($entity, $targetEntity);
} }
} else { } else {
// Inject collection // Inject collection
@ -1779,6 +1783,14 @@ class UnitOfWork implements PropertyChangedListener
return array(); return array();
} }
/**
* @ignore
*/
public function setOriginalEntityData($entity, array $data)
{
$this->_originalEntityData[spl_object_hash($entity)] = $data;
}
/** /**
* INTERNAL: * INTERNAL:
* Sets a property value of the original data array of an entity. * Sets a property value of the original data array of an entity.

View File

@ -6,12 +6,12 @@ require_once __DIR__ . '/../TestInit.php';
class DoctrineExceptionTest extends \Doctrine\Tests\DoctrineTestCase class DoctrineExceptionTest extends \Doctrine\Tests\DoctrineTestCase
{ {
public function testStaticCall() /*public function testStaticCall()
{ {
$e = \Doctrine\Common\DoctrineException::testingStaticCallBuildsErrorMessageWithParams('param1', 'param2'); $e = \Doctrine\Common\DoctrineException::testingStaticCallBuildsErrorMessageWithParams('param1', 'param2');
$this->assertEquals($e->getMessage(), "Testing static call builds error message with params ('param1', 'param2')"); $this->assertEquals($e->getMessage(), "Testing static call builds error message with params ('param1', 'param2')");
} }*/
public function testInnerException() public function testInnerException()
{ {

View File

@ -24,7 +24,80 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
parent::setUp(); parent::setUp();
} }
public function testSingleValuedAssociationIdentityMapBehavior() 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.';
$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 = new CmsAddress;
$address->country = 'de'; $address->country = 'de';
@ -65,8 +138,6 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($user2->address === null); $this->assertTrue($user2->address === null);
// But we want to have this external change! // 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 // 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 = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
$q->setHint(Query::HINT_REFRESH, true); $q->setHint(Query::HINT_REFRESH, true);
@ -83,7 +154,7 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertSame($user1->address, $address2); $this->assertSame($user1->address, $address2);
} }
public function testCollectionValuedAssociationIdentityMapBehavior() public function testCollectionValuedAssociationIdentityMapBehaviorWithRefreshQuery()
{ {
$user = new CmsUser; $user = new CmsUser;
$user->status = 'dev'; $user->status = 'dev';
@ -133,5 +204,52 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
// Now the collection should be refreshed with correct count // Now the collection should be refreshed with correct count
$this->assertEquals(4, count($user3->getPhonenumbers())); $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()));
}
} }

View File

@ -37,7 +37,7 @@ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
$e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1); $e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1);
$this->assertEquals(1, $e2->getId()); $this->assertEquals(1, $e2->getId());
$this->assertEquals('Roman', $e2->getName()); $this->assertEquals('Roman', $e2->getName());
$this->assertTrue($e2->getMappedRelated1() instanceof MappedSuperclassRelated1); $this->assertNull($e2->getMappedRelated1());
$this->assertEquals(42, $e2->getMapped1()); $this->assertEquals(42, $e2->getMapped1());
$this->assertEquals('bar', $e2->getMapped2()); $this->assertEquals('bar', $e2->getMapped2());
} }

View File

@ -78,7 +78,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional
$this->assertEquals('Giorgio', $cart->getCustomer()->getName()); $this->assertEquals('Giorgio', $cart->getCustomer()->getName());
} }
public function testLazyLoadsObjectsOnTheInverseSide() public function testInverseSideIsNeverLazy()
{ {
$this->_createFixture(); $this->_createFixture();
$metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer'); $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer');
@ -90,7 +90,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional
$this->assertNull($customer->getMentor()); $this->assertNull($customer->getMentor());
$this->assertTrue($customer->getCart() instanceof ECommerceCart); $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()); $this->assertEquals('paypal', $customer->getCart()->getPayment());
} }

View File

@ -4,9 +4,9 @@ namespace Doctrine\Tests\ORM\Performance;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
use Doctrine\Tests\Mocks\HydratorMockStatement; use Doctrine\Tests\Mocks\HydratorMockStatement,
use Doctrine\ORM\Query\ResultSetMapping; Doctrine\ORM\Query\ResultSetMapping,
use Doctrine\ORM\Query; Doctrine\ORM\Query;
/** /**
* Tests to prevent serious performance regressions. * Tests to prevent serious performance regressions.
@ -18,6 +18,64 @@ use Doctrine\ORM\Query;
*/ */
class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase 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: * Times for comparison:
* *
@ -219,6 +277,16 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addFieldResult('u', 'u__username', 'username'); $rsm->addFieldResult('u', 'u__username', 'username');
$rsm->addFieldResult('u', 'u__name', 'name'); $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 // Faked result set
$resultSet = array( $resultSet = array(
@ -228,27 +296,17 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
'u__status' => 'developer', 'u__status' => 'developer',
'u__username' => 'romanb', 'u__username' => 'romanb',
'u__name' => 'Roman', 'u__name' => 'Roman',
), 'a__id' => '1'
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) { for ($i = 2; $i < 10000; ++$i) {
$resultSet[] = array( $resultSet[] = array(
'u__id' => $i, 'u__id' => $i,
'u__status' => 'developer', 'u__status' => 'developer',
'u__username' => 'jwage', 'u__username' => 'jwage',
'u__name' => 'Jonathan', 'u__name' => 'Jonathan',
'a__id' => $i
); );
} }
@ -355,6 +413,13 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
$rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('u', 'u__name', 'name');
$rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addScalarResult('sclr0', 'nameUpper');
$rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); $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 // Faked result set
$resultSet = array( $resultSet = array(
@ -366,33 +431,19 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
'u__name' => 'Roman', 'u__name' => 'Roman',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '42', 'p__phonenumber' => '42',
), 'a__id' => '1'
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) { for ($i = 2; $i < 2000; ++$i) {
$resultSet[] = array( $resultSet[] = array(
'u__id' => $i, 'u__id' => $i,
'u__status' => 'developer', 'u__status' => 'developer',
'u__username' => 'jwage', 'u__username' => 'jwage',
'u__name' => 'Jonathan', 'u__name' => 'Jonathan',
'sclr0' => 'JWAGE' . $i, '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); $e = microtime(true);
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; 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;
}
} }