Commit bb6e54fb authored by romanb's avatar romanb

[2.0][DDC-21] Fixed.

parent 204b6d71
...@@ -31,6 +31,7 @@ use Doctrine\ORM\PersistentCollection, ...@@ -31,6 +31,7 @@ use Doctrine\ORM\PersistentCollection,
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @since 2.0
* @internal Highly performance-sensitive code.
*/ */
class ObjectHydrator extends AbstractHydrator class ObjectHydrator extends AbstractHydrator
{ {
...@@ -49,7 +50,10 @@ class ObjectHydrator extends AbstractHydrator ...@@ -49,7 +50,10 @@ class ObjectHydrator extends AbstractHydrator
private $_fetchedAssociations; private $_fetchedAssociations;
private $_rootAliases = array(); private $_rootAliases = array();
private $_initializedCollections = array(); private $_initializedCollections = array();
private $_existingCollections = array();
private $_proxyFactory; private $_proxyFactory;
//private $_createdEntities;
/** @override */ /** @override */
protected function _prepare() protected function _prepare()
...@@ -57,9 +61,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -57,9 +61,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_allowPartialObjects = $this->_em->getConfiguration()->getAllowPartialObjects() $this->_allowPartialObjects = $this->_em->getConfiguration()->getAllowPartialObjects()
|| isset($this->_hints[Query::HINT_FORCE_PARTIAL_LOAD]); || isset($this->_hints[Query::HINT_FORCE_PARTIAL_LOAD]);
if ( ! $this->_allowPartialObjects) { $this->_proxyFactory = $this->_em->getProxyFactory();
$this->_proxyFactory = $this->_em->getProxyFactory();
}
$this->_identifierMap = $this->_identifierMap =
$this->_resultPointers = $this->_resultPointers =
...@@ -81,7 +83,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -81,7 +83,7 @@ class ObjectHydrator extends AbstractHydrator
// collection stubs or proxies and where not. // collection stubs or proxies and where not.
if (isset($this->_rsm->relationMap[$dqlAlias])) { if (isset($this->_rsm->relationMap[$dqlAlias])) {
$targetClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $targetClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
$targetClass = $this->_em->getClassMetadata($targetClassName); $targetClass = $this->_getClassMetadata($targetClassName);
$this->_ce[$targetClassName] = $targetClass; $this->_ce[$targetClassName] = $targetClass;
$assoc = $targetClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; $assoc = $targetClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true; $this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true;
...@@ -99,22 +101,20 @@ class ObjectHydrator extends AbstractHydrator ...@@ -99,22 +101,20 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @override
*/ */
protected function _cleanup() /*@override*/ protected function _cleanup()
{ {
parent::_cleanup(); parent::_cleanup();
$this->_identifierMap = $this->_identifierMap =
$this->_initializedCollections =
$this->_existingCollections =
$this->_resultPointers = array(); $this->_resultPointers = array();
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @override
*/ */
protected function _hydrateAll() /*@override*/ protected function _hydrateAll()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
...@@ -122,11 +122,10 @@ class ObjectHydrator extends AbstractHydrator ...@@ -122,11 +122,10 @@ class ObjectHydrator extends AbstractHydrator
$this->_hydrateRow($data, $cache, $result); $this->_hydrateRow($data, $cache, $result);
} }
// Take snapshots from all initialized collections // Take snapshots from all newly initialized collections
foreach ($this->_initializedCollections as $coll) { foreach ($this->_initializedCollections as $coll) {
$coll->takeSnapshot(); $coll->takeSnapshot();
} }
$this->_initializedCollections = array();
return $result; return $result;
} }
...@@ -155,17 +154,20 @@ class ObjectHydrator extends AbstractHydrator ...@@ -155,17 +154,20 @@ class ObjectHydrator extends AbstractHydrator
$value $value
); );
$value->setOwner($entity, $relation); $value->setOwner($entity, $relation);
} else { $class->reflFields[$name]->setValue($entity, $value);
// Is already PersistentCollection. $this->_uow->setOriginalEntityProperty($oid, $name, $value);
$this->_initializedCollections[$oid . $name] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH])) {
// Is already PersistentCollection, but REFRESH
$value->clear(); $value->clear();
$value->setDirty(false); $value->setDirty(false);
$value->setInitialized(true); $value->setInitialized(true);
$this->_initializedCollections[$oid . $name] = $value;
} else {
// Is already PersistentCollection, and DONT REFRESH
$this->_existingCollections[$oid . $name] = $value;
} }
$class->reflFields[$name]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $name, $value);
$this->_initializedCollections[$oid . $name] = $value;
return $value; return $value;
} }
...@@ -187,6 +189,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -187,6 +189,7 @@ class ObjectHydrator extends AbstractHydrator
$entity = $this->_uow->createEntity($className, $data, $this->_hints); $entity = $this->_uow->createEntity($className, $data, $this->_hints);
//FIXME: If $entity comes from the identity map there is no need to do this!
// Properly initialize any unfetched associations, if partial objects are not allowed. // Properly initialize any unfetched associations, if partial objects are not allowed.
if ( ! $this->_allowPartialObjects) { if ( ! $this->_allowPartialObjects) {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
...@@ -232,6 +235,23 @@ class ObjectHydrator extends AbstractHydrator ...@@ -232,6 +235,23 @@ class ObjectHydrator extends AbstractHydrator
return $entity; return $entity;
} }
private function _getEntityFromIdentityMap($className, array $data)
{
$class = $this->_ce[$className];
if ($class->isIdentifierComposite) {
$idHash = '';
foreach ($class->identifier as $fieldName) {
$idHash .= $data[$fieldName] . ' ';
}
$idHash = rtrim($idHash);
} else {
$idHash = $data[$class->identifier[0]];
}
return $this->_uow->tryGetByIdHash($idHash, $class->rootEntityName);
}
/** /**
* Gets a ClassMetadata instance from the local cache. * Gets a ClassMetadata instance from the local cache.
* If the instance is not yet in the local cache, it is loaded into the * If the instance is not yet in the local cache, it is loaded into the
...@@ -250,10 +270,8 @@ class ObjectHydrator extends AbstractHydrator ...@@ -250,10 +270,8 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @override
*/ */
protected function _hydrateRow(array &$data, array &$cache, array &$result) /*@override*/ protected function _hydrateRow(array &$data, array &$cache, array &$result)
{ {
// Initialize // Initialize
$id = $this->_idTemplate; // initialize the id-memory $id = $this->_idTemplate; // initialize the id-memory
...@@ -266,7 +284,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -266,7 +284,7 @@ class ObjectHydrator extends AbstractHydrator
unset($rowData['scalars']); unset($rowData['scalars']);
} }
// Hydrate the entity data found in the current row. // Hydrate the data found in the current row.
foreach ($rowData as $dqlAlias => $data) { foreach ($rowData as $dqlAlias => $data) {
$index = false; $index = false;
$entityName = $this->_rsm->aliasMap[$dqlAlias]; $entityName = $this->_rsm->aliasMap[$dqlAlias];
...@@ -274,31 +292,34 @@ class ObjectHydrator extends AbstractHydrator ...@@ -274,31 +292,34 @@ class ObjectHydrator extends AbstractHydrator
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
// It's a joined result // It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias]; $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
// Get a reference to the right object to which the joined result belongs. // Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
$first = reset($this->_resultPointers); $first = reset($this->_resultPointers);
$baseElement = $this->_resultPointers[$parent][key($first)]; $parentObject = $this->_resultPointers[$parentAlias][key($first)];
} else if (isset($this->_resultPointers[$parent])) { } else if (isset($this->_resultPointers[$parentAlias])) {
$baseElement = $this->_resultPointers[$parent]; $parentObject = $this->_resultPointers[$parentAlias];
} else { } else {
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent); // Parent object of relation not found, so skip it.
continue;
} }
$parentClass = get_class($baseElement); $parentClass = get_class($parentObject);
$oid = spl_object_hash($baseElement); $oid = spl_object_hash($parentObject);
$relationField = $this->_rsm->relationMap[$dqlAlias]; $relationField = $this->_rsm->relationMap[$dqlAlias];
$relation = $this->_ce[$parentClass]->associationMappings[$relationField]; $relation = $this->_ce[$parentClass]->associationMappings[$relationField];
$reflField = $this->_ce[$parentClass]->reflFields[$relationField]; $reflField = $this->_ce[$parentClass]->reflFields[$relationField];
$reflFieldValue = $reflField->getValue($baseElement);
// Check the type of the relation (many or single-valued) // Check the type of the relation (many or single-valued)
if ( ! $relation->isOneToOne()) { if ( ! $relation->isOneToOne()) {
// Collection-valued association // Collection-valued association
if (isset($nonemptyComponents[$dqlAlias])) { if (isset($nonemptyComponents[$dqlAlias])) {
if ( ! isset($this->_initializedCollections[$oid . $relationField])) { $collKey = $oid . $relationField;
$reflFieldValue = $this->_initRelatedCollection($baseElement, $relationField); if (isset($this->_initializedCollections[$collKey])) {
$reflFieldValue = $this->_initializedCollections[$collKey];
} else if ( ! isset($this->_existingCollections[$collKey])) {
$reflFieldValue = $this->_initRelatedCollection($parentObject, $relationField);
} }
$indexExists = isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]]);
...@@ -306,66 +327,78 @@ class ObjectHydrator extends AbstractHydrator ...@@ -306,66 +327,78 @@ class ObjectHydrator extends AbstractHydrator
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
$element = $this->_getEntity($data, $dqlAlias); if (isset($this->_existingCollections[$collKey])) {
// Collection exists, only look for $element in identity map.
// If it's a bi-directional many-to-many, also initialize the reverse collection. if ($element = $this->_getEntityFromIdentityMap($entityName, $data)) {
if ($relation->isManyToMany()) { $this->_resultPointers[$dqlAlias] = $element;
if ($relation->isOwningSide && isset($this->_ce[$entityName]->inverseMappings[$relation->sourceEntityName][$relationField])) { } else {
$inverseFieldName = $this->_ce[$entityName]->inverseMappings[$relation->sourceEntityName][$relationField]->sourceFieldName; unset($this->_resultPointers[$dqlAlias]);
// Only initialize reverse collection if it is not yet initialized.
if ( ! isset($this->_initializedCollections[spl_object_hash($element) . $inverseFieldName])) {
$this->_initRelatedCollection($element, $inverseFieldName);
}
} else if ($relation->mappedByFieldName) {
// Only initialize reverse collection if it is not yet initialized.
if ( ! isset($this->_initializedCollections[spl_object_hash($element) . $relation->mappedByFieldName])) {
$this->_initRelatedCollection($element, $relation->mappedByFieldName);
}
} }
}
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $indexValue;
} else { } else {
$reflFieldValue->hydrateAdd($element); $element = $this->_getEntity($data, $dqlAlias);
$reflFieldValue->last();
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $reflFieldValue->key(); // If it's a bi-directional many-to-many, also initialize the reverse collection.
if ($relation->isManyToMany()) {
if ($relation->isOwningSide && isset($this->_ce[$entityName]->inverseMappings[$relation->sourceEntityName][$relationField])) {
$inverseFieldName = $this->_ce[$entityName]->inverseMappings[$relation->sourceEntityName][$relationField]->sourceFieldName;
// Only initialize reverse collection if it is not yet initialized.
if ( ! isset($this->_initializedCollections[spl_object_hash($element) . $inverseFieldName])) {
$this->_initRelatedCollection($element, $inverseFieldName);
}
} else if ($relation->mappedByFieldName) {
// Only initialize reverse collection if it is not yet initialized.
if ( ! isset($this->_initializedCollections[spl_object_hash($element) . $relation->mappedByFieldName])) {
$this->_initRelatedCollection($element, $relation->mappedByFieldName);
}
}
}
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $indexValue;
} else {
$reflFieldValue->hydrateAdd($element);
$reflFieldValue->last();
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $reflFieldValue->key();
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
} }
} else {
// Update result pointer
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
} }
} else if ( ! $reflField->getValue($parentObject)) {
// Update result pointer
$this->_resultPointers[$dqlAlias] = $index === false ? $element : $reflFieldValue[$index];
} else if ( ! $reflFieldValue) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); $coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$reflField->setValue($baseElement, $coll); $reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll); $this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
} }
} else { } else {
// Single-valued association // Single-valued association
$reflFieldValue = $reflField->getValue($baseElement); $reflFieldValue = $reflField->getValue($parentObject);
if ( ! $reflFieldValue /* || doctrine.refresh hint set */) { if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) {
if (isset($nonemptyComponents[$dqlAlias])) { if (isset($nonemptyComponents[$dqlAlias])) {
$element = $this->_getEntity($data, $dqlAlias); $element = $this->_getEntity($data, $dqlAlias);
$reflField->setValue($baseElement, $element); $reflField->setValue($parentObject, $element);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
$targetClass = $this->_ce[$relation->targetEntityName]; $targetClass = $this->_ce[$relation->targetEntityName];
if ($relation->isOwningSide) { if ($relation->isOwningSide) {
// If there is an inverse mapping on the target class its bidirectional // If there is an inverse mapping on the target class its bidirectional
if (isset($targetClass->inverseMappings[$relation->sourceEntityName][$relationField])) { if (isset($targetClass->inverseMappings[$relation->sourceEntityName][$relationField])) {
$sourceProp = $targetClass->inverseMappings[$relation->sourceEntityName][$relationField]->sourceFieldName; $sourceProp = $targetClass->inverseMappings[$relation->sourceEntityName][$relationField]->sourceFieldName;
$targetClass->reflFields[$sourceProp]->setValue($element, $baseElement); $targetClass->reflFields[$sourceProp]->setValue($element, $parentObject);
} else if ($this->_ce[$parentClass] === $targetClass && $relation->mappedByFieldName) { } else if ($this->_ce[$parentClass] === $targetClass && $relation->mappedByFieldName) {
// Special case: bi-directional self-referencing one-one on the same class // Special case: bi-directional self-referencing one-one on the same class
$targetClass->reflFields[$relationField]->setValue($element, $baseElement); $targetClass->reflFields[$relationField]->setValue($element, $parentObject);
} }
} else { } else {
// For sure bidirectional, as there is no inverse side in unidirectional mappings // For sure bidirectional, as there is no inverse side in unidirectional mappings
$targetClass->reflFields[$relation->mappedByFieldName]->setValue($element, $baseElement); $targetClass->reflFields[$relation->mappedByFieldName]->setValue($element, $parentObject);
} }
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
} }
// else leave $reflFieldValue null for single-valued associations // else leave $reflFieldValue null for single-valued associations
} else { } else {
......
...@@ -273,7 +273,7 @@ class AnnotationDriver implements Driver ...@@ -273,7 +273,7 @@ class AnnotationDriver implements Driver
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy; $mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['cascade'] = $manyToManyAnnot->cascade;
$metadata->mapManyToMany($mapping); $metadata->mapManyToMany($mapping);
} }
} }
// Evaluate HasLifecycleCallbacks annotation // Evaluate HasLifecycleCallbacks annotation
......
...@@ -1080,10 +1080,8 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1080,10 +1080,8 @@ class UnitOfWork implements PropertyChangedListener
*/ */
public function tryGetByIdHash($idHash, $rootClassName) public function tryGetByIdHash($idHash, $rootClassName)
{ {
if ($this->containsIdHash($idHash, $rootClassName)) { return isset($this->_identityMap[$rootClassName][$idHash]) ?
return $this->getByIdHash($idHash, $rootClassName); $this->_identityMap[$rootClassName][$idHash] : false;
}
return false;
} }
/** /**
...@@ -1656,8 +1654,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1656,8 +1654,7 @@ class UnitOfWork implements PropertyChangedListener
* @param string $className The name of the entity class. * @param string $className The name of the entity class.
* @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. Run the performance test suites when * @internal Highly performance-sensitive method.
* making modifications.
*/ */
public function createEntity($className, array $data, $hints = array()) public function createEntity($className, array $data, $hints = array())
{ {
...@@ -1703,6 +1700,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1703,6 +1700,7 @@ class UnitOfWork implements PropertyChangedListener
} }
} }
//TODO: These should be invoked later, because associations are not yet loaded here.
if (isset($class->lifecycleCallbacks[Events::postLoad])) { if (isset($class->lifecycleCallbacks[Events::postLoad])) {
$class->invokeLifecycleCallbacks(Events::postLoad, $entity); $class->invokeLifecycleCallbacks(Events::postLoad, $entity);
} }
......
...@@ -40,6 +40,7 @@ class AllTests ...@@ -40,6 +40,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\StandardEntityPersisterTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\StandardEntityPersisterTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\MappedSuperclassTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\MappedSuperclassTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\EntityRepositoryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\EntityRepositoryTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\IdentityMapTest');
$suite->addTest(Locking\AllTests::suite()); $suite->addTest(Locking\AllTests::suite());
$suite->addTest(SchemaTool\AllTests::suite()); $suite->addTest(SchemaTool\AllTests::suite());
......
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\Tests\Models\CMS\CmsAddress,
Doctrine\Tests\Models\CMS\CmsPhonenumber,
Doctrine\ORM\Query;
require_once __DIR__ . '/../../TestInit.php';
/**
* IdentityMapTest
*
* Tests correct behavior and usage of the identity map. Local values and associations
* that are already fetched always prevail, unless explicitly refreshed.
*
* @author Roman Borschel <roman@code-factory.org>
*/
class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() {
$this->useModelSet('cms');
parent::setUp();
}
public function testSingleValuedAssociationIdentityMapBehavior()
{
$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()));
//select
$q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
$address2 = $q->getSingleResult();
$this->assertSame($address, $address2);
// Should still be $user1
$this->assertSame($user1, $address2->user);
$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);
$address3 = $q->getSingleResult();
$this->assertSame($address, $address3); // should still be the same, always from identity map
// Now the association should be "correct", referencing $user2
$this->assertSame($user2, $address2->user);
$this->assertSame($user2->address, $address2); // check back reference also
// Attention! refreshes can result in broken bidirectional associations! this is currently expected!
// $user1 still points to $address2!
$this->assertSame($user1->address, $address2);
}
public function testCollectionValuedAssociationIdentityMapBehavior()
{
$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); broken atm!
// Solution 2: Alternatively, a refresh query should work
$q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
$q->setHint(Query::HINT_REFRESH, true);
$user3 = $q->getSingleResult();
$this->assertSame($user, $user3); // should still be the same, always from identity map
// Now the collection should be refreshed with correct count
$this->assertEquals(4, count($user3->getPhonenumbers()));
}
}
...@@ -88,7 +88,7 @@ class StandardEntityPersisterTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -88,7 +88,7 @@ class StandardEntityPersisterTest extends \Doctrine\Tests\OrmFunctionalTestCase
// (change from ArrayCollection to PersistentCollection) // (change from ArrayCollection to PersistentCollection)
$f3 = new ECommerceFeature(); $f3 = new ECommerceFeature();
$f3->setDescription('XVID'); $f3->setDescription('XVID');
$p->addfeature($f3); $p->addFeature($f3);
// Now we persist the Feature #3 // Now we persist the Feature #3
$this->_em->persist($p); $this->_em->persist($p);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment