1
0
mirror of synced 2025-02-02 21:41:45 +03:00

Merge pull request #6159 from nicolas-cajelli/backport-fix-relation-cache-#1551-to-2.5

#5821 Backport #1551 - Fixed support for inverse side second level cache
This commit is contained in:
Marco Pivetta 2016-12-12 08:34:27 +01:00 committed by GitHub
commit 20cb50451d
9 changed files with 79 additions and 34 deletions

View File

@ -32,6 +32,7 @@ use Doctrine\Common\Util\ClassUtils;
/** /**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com> * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5 * @since 2.5
*/ */
abstract class AbstractCollectionPersister implements CachedCollectionPersister abstract class AbstractCollectionPersister implements CachedCollectionPersister
@ -164,10 +165,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
public function storeCollectionCache(CollectionCacheKey $key, $elements) public function storeCollectionCache(CollectionCacheKey $key, $elements)
{ {
/* @var $targetPersister CachedEntityPersister */ /* @var $targetPersister CachedEntityPersister */
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
$targetRegion = $targetPersister->getCacheRegion(); $targetRegion = $targetPersister->getCacheRegion();
$targetHydrator = $targetPersister->getEntityHydrator(); $targetHydrator = $targetPersister->getEntityHydrator();
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
// Only preserve ordering if association configured it
if ( ! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
// Elements may be an array or a Collection
$elements = array_values(is_array($elements) ? $elements : $elements->getValues());
}
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
foreach ($entry->identifiers as $index => $entityKey) { foreach ($entry->identifiers as $index => $entityKey) {
if ($targetRegion->contains($entityKey)) { if ($targetRegion->contains($entityKey)) {

View File

@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
* Specific non-strict read/write cached entity persister * Specific non-strict read/write cached entity persister
* *
* @author Fabio B. Silva <fabio.bat.silva@gmail.com> * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5 * @since 2.5
*/ */
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
@ -78,13 +79,16 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
*/ */
public function delete($entity) public function delete($entity)
{ {
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$deleted = $this->persister->delete($entity);
if ($this->persister->delete($entity)) { if ($deleted) {
$this->region->evict($key); $this->region->evict($key);
} }
$this->queuedCache['delete'][] = $key; $this->queuedCache['delete'][] = $key;
return $deleted;
} }
/** /**

View File

@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
* Specific read-write entity persister * Specific read-write entity persister
* *
* @author Fabio B. Silva <fabio.bat.silva@gmail.com> * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5 * @since 2.5
*/ */
class ReadWriteCachedEntityPersister extends AbstractEntityPersister class ReadWriteCachedEntityPersister extends AbstractEntityPersister
@ -100,21 +101,24 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
*/ */
public function delete($entity) public function delete($entity)
{ {
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$lock = $this->region->lock($key); $lock = $this->region->lock($key);
$deleted = $this->persister->delete($entity);
if ($this->persister->delete($entity)) { if ($deleted) {
$this->region->evict($key); $this->region->evict($key);
} }
if ($lock === null) { if ($lock === null) {
return; return $deleted;
} }
$this->queuedCache['delete'][] = array( $this->queuedCache['delete'][] = array(
'lock' => $lock, 'lock' => $lock,
'key' => $key 'key' => $key
); );
return $deleted;
} }
/** /**

View File

@ -90,10 +90,6 @@ abstract class AbstractCollectionPersister implements CollectionPersister
// If Entity is scheduled for inclusion, it is not in this collection. // If Entity is scheduled for inclusion, it is not in this collection.
// We can assure that because it would have return true before on array check // We can assure that because it would have return true before on array check
if ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity)) { return ! ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity));
return false;
}
return true;
} }
} }

View File

@ -733,7 +733,6 @@ class UnitOfWork implements PropertyChangedListener
// Look for changes in associations of the entity // Look for changes in associations of the entity
foreach ($class->associationMappings as $field => $assoc) { foreach ($class->associationMappings as $field => $assoc) {
if (($val = $class->reflFields[$field]->getValue($entity)) === null) { if (($val = $class->reflFields[$field]->getValue($entity)) === null) {
continue; continue;
} }
@ -799,7 +798,7 @@ class UnitOfWork implements PropertyChangedListener
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here. // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) { if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
$this->computeChangeSet($class, $entity); $this->computeChangeSet($class, $entity);
} }
} }
@ -826,10 +825,7 @@ class UnitOfWork implements PropertyChangedListener
if ($value instanceof PersistentCollection && $value->isDirty()) { if ($value instanceof PersistentCollection && $value->isDirty()) {
$coid = spl_object_hash($value); $coid = spl_object_hash($value);
if ($assoc['isOwningSide']) { $this->collectionUpdates[$coid] = $value;
$this->collectionUpdates[$coid] = $value;
}
$this->visitedCollections[$coid] = $value; $this->visitedCollections[$coid] = $value;
} }

View File

@ -33,7 +33,7 @@ class State
protected $country; protected $country;
/** /**
* @Cache * @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state") * @OneToMany(targetEntity="City", mappedBy="state")
*/ */
protected $cities; protected $cities;

View File

@ -26,7 +26,7 @@ class Traveler
protected $name; protected $name;
/** /**
* @Cache() * @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}, orphanRemoval=true) * @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}, orphanRemoval=true)
* *
* @var \Doctrine\Common\Collections\Collection * @var \Doctrine\Common\Collections\Collection

View File

@ -2,10 +2,10 @@
namespace Doctrine\Tests\ORM\Functional; namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\Models\Cache\ComplexAction; use Doctrine\Tests\Models\Cache\ComplexAction;
use Doctrine\Tests\Models\Cache\Country; use Doctrine\Tests\Models\Cache\Country;
use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\State;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Tests\Models\Cache\Token; use Doctrine\Tests\Models\Cache\Token;
use Doctrine\Tests\Models\Cache\Action; use Doctrine\Tests\Models\Cache\Action;
@ -98,6 +98,40 @@ class SecondLevelCacheManyToOneTest extends SecondLevelCacheAbstractTest
$this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName()); $this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName());
} }
public function testInverseSidePutShouldEvictCollection()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->_em->clear();
$this->cache->evictEntityRegion(State::CLASSNAME);
$this->cache->evictEntityRegion(Country::CLASSNAME);
//evict collection on add
$c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId());
$prev = $c3->getCities();
$count = $prev->count();
$city = new City("Buenos Aires", $c3);
$c3->addCity($city);
$this->_em->persist($city);
$this->_em->persist($c3);
$this->_em->flush();
$this->_em->clear();
$state = $this->_em->find(State::CLASSNAME, $c3->getId());
$queryCount = $this->getCurrentQueryCount();
// Association was cleared from EM
$this->assertNotEquals($prev, $state->getCities());
// New association has one more item (cache was evicted)
$this->assertEquals($count + 1, $state->getCities()->count());
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
}
public function testShouldNotReloadWhenAssociationIsMissing() public function testShouldNotReloadWhenAssociationIsMissing()
{ {
$this->loadFixturesCountries(); $this->loadFixturesCountries();

View File

@ -14,18 +14,18 @@ use Doctrine\Tests\Models\Cache\Traveler;
*/ */
class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
{ {
public function testShouldNotPutCollectionInverseSideOnPersist() public function testShouldPutCollectionInverseSideOnPersist()
{ {
$this->loadFixturesCountries(); $this->loadFixturesCountries();
$this->loadFixturesStates(); $this->loadFixturesStates();
$this->loadFixturesCities(); $this->loadFixturesCities();
$this->_em->clear(); $this->_em->clear();
$this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId()));
$this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId()));
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId()));
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId()));
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId()));
} }
public function testPutAndLoadOneToManyRelation() public function testPutAndLoadOneToManyRelation()
@ -187,6 +187,7 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->loadFixturesCountries(); $this->loadFixturesCountries();
$this->loadFixturesStates(); $this->loadFixturesStates();
$this->loadFixturesCities(); $this->loadFixturesCities();
$this->_em->clear(); $this->_em->clear();
$this->secondLevelCacheLogger->clearStats(); $this->secondLevelCacheLogger->clearStats();
@ -247,8 +248,8 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->_em->remove($city0); $this->_em->remove($city0);
$this->_em->persist($state); $this->_em->persist($state);
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();
$this->secondLevelCacheLogger->clearStats(); $this->secondLevelCacheLogger->clearStats();
$queryCount = $this->getCurrentQueryCount(); $queryCount = $this->getCurrentQueryCount();
@ -261,19 +262,19 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->assertInstanceOf(City::CLASSNAME, $city1); $this->assertInstanceOf(City::CLASSNAME, $city1);
$this->assertEquals($entity->getCities()->get(1)->getName(), $city1->getName()); $this->assertEquals($entity->getCities()->get(1)->getName(), $city1->getName());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME)));
$this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertEquals($queryCount, $this->getCurrentQueryCount());
$state->getCities()->remove(0); $state->getCities()->remove(0);
$this->_em->remove($city1); $this->_em->remove($city1);
$this->_em->persist($state); $this->_em->persist($state);
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();
$this->secondLevelCacheLogger->clearStats(); $this->secondLevelCacheLogger->clearStats();
$queryCount = $this->getCurrentQueryCount(); $queryCount = $this->getCurrentQueryCount();
@ -281,9 +282,9 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->assertCount(0, $state->getCities()); $this->assertCount(0, $state->getCities());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME)));
$this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
} }
public function testOneToManyWithEmptyRelation() public function testOneToManyWithEmptyRelation()
@ -346,11 +347,12 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
public function testCacheInitializeCollectionWithNewObjects() public function testCacheInitializeCollectionWithNewObjects()
{ {
$this->_em->clear(); $this->_em->clear();
$this->evictRegions(); $this->evictRegions();
$traveler = new Traveler("Doctrine Bot"); $traveler = new Traveler("Doctrine Bot");
for ($i=0; $i<3; ++$i) { for ($i = 0; $i < 3; ++$i) {
$traveler->getTravels()->add(new Travel($traveler)); $traveler->getTravels()->add(new Travel($traveler));
} }
@ -373,7 +375,7 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->assertFalse($entity->getTravels()->isInitialized()); $this->assertFalse($entity->getTravels()->isInitialized());
$this->assertCount(4, $entity->getTravels()); $this->assertCount(4, $entity->getTravels());
$this->assertTrue($entity->getTravels()->isInitialized()); $this->assertTrue($entity->getTravels()->isInitialized());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertEquals($queryCount, $this->getCurrentQueryCount());
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();