Compare commits

..

33 Commits

Author SHA1 Message Date
Marco Pivetta
e6c434196c Merge pull request #6178 from doctrine/fix/#6174-#5570-merging-new-entities-should-also-trigger-prepersist-lifecycle-callbacks-2.5
Backport #6177 - fix #6174 #5570: merging new entities should also trigger prepersist lifecycle callbacks with the merged data
2016-12-18 16:42:34 +01:00
Marco Pivetta
d52dbe62ac #6174 #5570 switching ::class to string constants for PHP 5.4 compat (still supported in ORM 2.5.x) 2016-12-18 16:24:42 +01:00
Marco Pivetta
b0ede40f47 #6174 #5570 removed modifications applied to the CompanyContractListener, since UnitOfWorkTest now completely encapsulates the scenarios being covered 2016-12-18 16:13:11 +01:00
Marco Pivetta
3645a9c44d #6174 #5570 removed unused imports 2016-12-18 16:13:04 +01:00
Marco Pivetta
39ce6f96a0 #6174 #5570 renamed entity for better fitting the use-cases it's in 2016-12-18 16:12:57 +01:00
Marco Pivetta
e43f5304ef #6174 #5570 removed unused test class 2016-12-18 16:12:46 +01:00
Marco Pivetta
67724eb7ae #6174 #5570 adding group annotations to newly introduced test 2016-12-18 16:12:36 +01:00
Marco Pivetta
8d4bc0638d #6174 #5570 prePersist listeners should never be called when entities are merged, but are already in the UoW 2016-12-18 16:12:29 +01:00
Marco Pivetta
81186105b6 #6174 #5570 started moving tests around prePersist event subscriber triggering on UnitOfWork into the UnitOfWorkTest 2016-12-18 16:12:03 +01:00
Marco Pivetta
beef8acdf5 #6174 #5570 CS fixes around the EntityListenersOnMergeTest 2016-12-18 16:10:17 +01:00
Marco Pivetta
26fc8d60e6 #6174 #5570 adding group annotation to newly introduced tests 2016-12-18 16:10:08 +01:00
Marco Pivetta
12e8ab216a #6174 #5570 CS - spacing/variable naming 2016-12-18 16:09:59 +01:00
Marco Pivetta
dac1a16964 #6174 #5570 removed unused/dead code 2016-12-18 16:09:52 +01:00
Marco Pivetta
d9821d3fda #6174 #5570 CS - spacing 2016-12-18 16:09:45 +01:00
Marco Pivetta
576a4d7e31 #6174 #5570 CS - spacing 2016-12-18 16:09:38 +01:00
Marco Pivetta
eaee924180 #6174 #5570 flattened nested conditionals 2016-12-18 16:09:32 +01:00
Marco Pivetta
cf941ce54f #6174 #5570 documenting thrown exception types 2016-12-18 16:09:25 +01:00
Marco Pivetta
cfb7461f51 #6174 #5570 CS - alignment 2016-12-18 16:09:18 +01:00
bilouwan
569c08ce55 Rename test 2016-12-18 16:09:11 +01:00
bilouwan
295523cdca Cherry pick unit test from PR #5570 (Fix PrePersist EventListener when using merge instead of persist) 2016-12-18 16:09:00 +01:00
bilouwan
25efabdb74 doMerge will mergeEntityStateIntoManagedCopy BEFORE persistNew to let lifecyle events changes be persisted 2016-12-18 16:08:15 +01:00
bilouwan
1d96178097 Create failing test to reveal the issue 2016-12-18 16:08:05 +01:00
Marco Pivetta
20cb50451d 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
2016-12-12 08:34:27 +01:00
Guilherme Blanco
0ff512ba8f Fixed support for inverse side second level cache 2016-12-05 14:36:33 -03:00
Marco Pivetta
5e9014fd99 Merge pull request #6156 from Slamdunk/patch-2
Allow doctrine/common 2.7
2016-12-04 06:53:07 +01:00
Filippo Tessarotto
a90cd9dfe8 Allow doctrine/common 2.7 2016-12-02 09:00:50 +01:00
Marco Pivetta
4d699789a2 Merge branch 'fix/#6110-collection-clear-should-also-clear-keys-2.5' into 2.5
Close #6110
2016-11-26 06:06:11 +01:00
Marco Pivetta
1486c8f8e2 split test into multiple sub-scenarios involving PersistentCollection key checking #6110 2016-11-26 06:05:31 +01:00
Steevan BARBOYON
3dadfa49d5 Clear $this->collection even when empty, to reset indexes 2016-11-26 06:04:35 +01:00
Marco Pivetta
9b36947a48 Merge branch 'fix/#6028-l2c-inheritance-query-cache-use-parent-entity-name-2.5' into 2.5
Backport #6028 to 2.5.x
2016-11-23 18:06:28 +01:00
Marco Pivetta
2122297fdb #6028 removed specific ::class usage, since 2.5.x still supports PHP 5.4.x 2016-11-23 18:06:14 +01:00
Marco Pivetta
af99cba28c #6028 removed specific ::class usage, since 2.5.x still supports PHP 5.4.x 2016-11-23 18:02:15 +01:00
Luís Cobucci
9bcee455ca Make child entity share the timestamp region with parent class 2016-11-23 17:58:05 +01:00
18 changed files with 409 additions and 72 deletions

View File

@@ -18,7 +18,7 @@
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.7-dev",
"doctrine/common": ">=2.5-dev,<2.8-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5|~3.0"
},

View File

@@ -1038,7 +1038,7 @@ abstract class AbstractQuery
$metadata = $this->_em->getClassMetadata($entityName);
return new Cache\TimestampCacheKey($metadata->getTableName());
return new Cache\TimestampCacheKey($metadata->rootEntityName);
}
/**

View File

@@ -32,6 +32,7 @@ use Doctrine\Common\Util\ClassUtils;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
abstract class AbstractCollectionPersister implements CachedCollectionPersister
@@ -164,10 +165,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
public function storeCollectionCache(CollectionCacheKey $key, $elements)
{
/* @var $targetPersister CachedEntityPersister */
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
$targetRegion = $targetPersister->getCacheRegion();
$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) {
if ($targetRegion->contains($entityKey)) {

View File

@@ -131,7 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
}
/**

View File

@@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
* Specific non-strict read/write cached entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
@@ -78,13 +79,16 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
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->queuedCache['delete'][] = $key;
return $deleted;
}
/**

View File

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

View File

@@ -536,6 +536,8 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
public function clear()
{
if ($this->initialized && $this->isEmpty()) {
$this->collection->clear();
return;
}

View File

@@ -90,10 +90,6 @@ abstract class AbstractCollectionPersister implements CollectionPersister
// 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
if ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity)) {
return false;
}
return true;
return ! ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity));
}
}

View File

@@ -733,7 +733,6 @@ class UnitOfWork implements PropertyChangedListener
// Look for changes in associations of the entity
foreach ($class->associationMappings as $field => $assoc) {
if (($val = $class->reflFields[$field]->getValue($entity)) === null) {
continue;
}
@@ -799,7 +798,7 @@ class UnitOfWork implements PropertyChangedListener
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
$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);
}
}
@@ -826,10 +825,7 @@ class UnitOfWork implements PropertyChangedListener
if ($value instanceof PersistentCollection && $value->isDirty()) {
$coid = spl_object_hash($value);
if ($assoc['isOwningSide']) {
$this->collectionUpdates[$coid] = $value;
}
$this->collectionUpdates[$coid] = $value;
$this->visitedCollections[$coid] = $value;
}
@@ -1803,7 +1799,7 @@ class UnitOfWork implements PropertyChangedListener
* @throws OptimisticLockException If the entity uses optimistic locking through a version
* attribute and the version check against the managed copy fails.
* @throws ORMInvalidArgumentException If the entity instance is NEW.
* @throws EntityNotFoundException
* @throws EntityNotFoundException if an assigned identifier is used in the entity, but none is provided
*/
private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
{
@@ -1835,6 +1831,7 @@ class UnitOfWork implements PropertyChangedListener
if ( ! $id) {
$managedCopy = $this->newInstance($class);
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
$this->persistNew($class, $managedCopy);
} else {
$flatId = ($class->containsForeignIdentifier)
@@ -1866,31 +1863,16 @@ class UnitOfWork implements PropertyChangedListener
$managedCopy = $this->newInstance($class);
$class->setIdentifierValues($managedCopy, $id);
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
$this->persistNew($class, $managedCopy);
}
}
if ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity)) {
$reflField = $class->reflFields[$class->versionField];
$managedCopyVersion = $reflField->getValue($managedCopy);
$entityVersion = $reflField->getValue($entity);
// Throw exception if versions don't match.
if ($managedCopyVersion != $entityVersion) {
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
} else {
$this->ensureVersionMatch($class, $entity, $managedCopy);
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
}
}
$visited[$oid] = $managedCopy; // mark visited
if ($this->isLoaded($entity)) {
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized()) {
$managedCopy->__load();
}
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
}
if ($class->isChangeTrackingDeferredExplicit()) {
$this->scheduleForDirtyCheck($entity);
}
@@ -1908,6 +1890,33 @@ class UnitOfWork implements PropertyChangedListener
return $managedCopy;
}
/**
* @param ClassMetadata $class
* @param object $entity
* @param object $managedCopy
*
* @return void
*
* @throws OptimisticLockException
*/
private function ensureVersionMatch(ClassMetadata $class, $entity, $managedCopy)
{
if (! ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity))) {
return;
}
$reflField = $class->reflFields[$class->versionField];
$managedCopyVersion = $reflField->getValue($managedCopy);
$entityVersion = $reflField->getValue($entity);
// Throw exception if versions don't match.
if ($managedCopyVersion == $entityVersion) {
return;
}
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
}
/**
* Tests if an entity is loaded - must either be a loaded proxy or not a proxy
*
@@ -3360,6 +3369,14 @@ class UnitOfWork implements PropertyChangedListener
*/
private function mergeEntityStateIntoManagedCopy($entity, $managedCopy)
{
if (! $this->isLoaded($entity)) {
return;
}
if (! $this->isLoaded($managedCopy)) {
$managedCopy->__load();
}
$class = $this->em->getClassMetadata(get_class($entity));
foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) {

View File

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

View File

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

View File

@@ -6,17 +6,17 @@ class CompanyContractListener
{
public $postPersistCalls;
public $prePersistCalls;
public $postUpdateCalls;
public $preUpdateCalls;
public $postRemoveCalls;
public $preRemoveCalls;
public $preFlushCalls;
public $postLoadCalls;
/**
* @PostPersist
*/
@@ -80,5 +80,4 @@ class CompanyContractListener
{
$this->postLoadCalls[] = func_get_args();
}
}

View File

@@ -3,8 +3,8 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\Attraction;
use Doctrine\Tests\Models\Cache\AttractionInfo;
use Doctrine\Tests\Models\Cache\AttractionContactInfo;
use Doctrine\Tests\Models\Cache\AttractionInfo;
use Doctrine\Tests\Models\Cache\AttractionLocationInfo;
/**
@@ -188,4 +188,47 @@ class SecondLevelCacheJoinTableInheritanceTest extends SecondLevelCacheAbstractT
$this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity->getInfos()->get(0));
$this->assertEquals($this->attractionsInfo[0]->getFone(), $entity->getInfos()->get(0)->getFone());
}
}
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesAttractions();
$this->loadFixturesAttractionsInfo();
$this->evictRegions();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$dql = 'SELECT attractionInfo FROM Doctrine\Tests\Models\Cache\AttractionInfo attractionInfo';
$result1 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractionsInfo), $result1);
$this->assertEquals($queryCount + 5, $this->getCurrentQueryCount());
$contact = new AttractionContactInfo(
'1234-1234',
$this->_em->find(Attraction::CLASSNAME, $this->attractions[5]->getId())
);
$this->_em->persist($contact);
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$result2 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractionsInfo) + 1, $result2);
$this->assertEquals($queryCount + 6, $this->getCurrentQueryCount());
foreach ($result2 as $entity) {
$this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity);
}
}
}

View File

@@ -2,10 +2,10 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\Models\Cache\ComplexAction;
use Doctrine\Tests\Models\Cache\Country;
use Doctrine\Tests\Models\Cache\State;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Tests\Models\Cache\Token;
use Doctrine\Tests\Models\Cache\Action;
@@ -98,6 +98,40 @@ class SecondLevelCacheManyToOneTest extends SecondLevelCacheAbstractTest
$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()
{
$this->loadFixturesCountries();

View File

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

View File

@@ -210,4 +210,45 @@ class SecondLevelCacheSingleTableInheritanceTest extends SecondLevelCacheAbstrac
$this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName());
$this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName());
}
}
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesAttractions();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$dql = 'SELECT attraction FROM Doctrine\Tests\Models\Cache\Attraction attraction';
$result1 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractions), $result1);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$contact = new Beach(
'Botafogo',
$this->_em->find(City::CLASSNAME, $this->cities[1]->getId())
);
$this->_em->persist($contact);
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$result2 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractions) + 1, $result2);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
foreach ($result2 as $entity) {
$this->assertInstanceOf(Attraction::CLASSNAME, $entity);
}
}
}

View File

@@ -3,7 +3,6 @@
namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Tests\Mocks\ConnectionMock;
use Doctrine\Tests\Mocks\DriverMock;
@@ -83,4 +82,44 @@ class PersistentCollectionTest extends OrmTestCase
$this->collection->next();
$this->assertTrue($this->collection->isInitialized());
}
/**
* @group 6110
*/
public function testRemovingElementsAlsoRemovesKeys()
{
$this->setUpPersistentCollection();
$this->collection->add('dummy');
$this->assertEquals([0], array_keys($this->collection->toArray()));
$this->collection->removeElement('dummy');
$this->assertEquals([], array_keys($this->collection->toArray()));
}
/**
* @group 6110
*/
public function testClearWillAlsoClearKeys()
{
$this->setUpPersistentCollection();
$this->collection->add('dummy');
$this->collection->clear();
$this->assertEquals([], array_keys($this->collection->toArray()));
}
/**
* @group 6110
*/
public function testClearWillAlsoResetKeyPositions()
{
$this->setUpPersistentCollection();
$this->collection->add('dummy');
$this->collection->removeElement('dummy');
$this->collection->clear();
$this->collection->add('dummy');
$this->assertEquals([0], array_keys($this->collection->toArray()));
}
}

View File

@@ -3,8 +3,11 @@
namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\EventManager;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\Common\PropertyChangedListener;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\ConnectionMock;
@@ -47,18 +50,22 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
*/
private $_emMock;
protected function setUp() {
/**
* @var EventManager|\PHPUnit_Framework_MockObject_MockObject
*/
private $eventManager;
protected function setUp()
{
parent::setUp();
$this->_connectionMock = new ConnectionMock(array(), new DriverMock());
$this->_emMock = EntityManagerMock::create($this->_connectionMock);
$this->_connectionMock = new ConnectionMock([], new DriverMock());
$this->eventManager = $this->getMockBuilder('Doctrine\Common\EventManager')->getMock();
$this->_emMock = EntityManagerMock::create($this->_connectionMock, null, $this->eventManager);
// SUT
$this->_unitOfWork = new UnitOfWorkMock($this->_emMock);
$this->_emMock->setUnitOfWork($this->_unitOfWork);
}
protected function tearDown() {
}
public function testRegisterRemovedOnNewEntityIsIgnored()
{
$user = new ForumUser();
@@ -392,6 +399,88 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored');
}
/**
* @group DDC-1955
* @group 5570
* @group 6174
*/
public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData()
{
$entity = new EntityWithRandomlyGeneratedField();
$generatedFieldValue = $entity->generatedField;
$this
->eventManager
->expects(self::any())
->method('hasListeners')
->willReturnCallback(function ($eventName) {
return $eventName === Events::prePersist;
});
$this
->eventManager
->expects(self::once())
->method('dispatchEvent')
->with(
self::anything(),
self::callback(function (LifecycleEventArgs $args) use ($entity, $generatedFieldValue) {
/* @var $object EntityWithRandomlyGeneratedField */
$object = $args->getObject();
self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object);
self::assertNotSame($entity, $object);
self::assertSame($generatedFieldValue, $object->generatedField);
return true;
})
);
/* @var $object EntityWithRandomlyGeneratedField */
$object = $this->_unitOfWork->merge($entity);
self::assertNotSame($object, $entity);
self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object);
self::assertSame($object->generatedField, $entity->generatedField);
}
/**
* @group DDC-1955
* @group 5570
* @group 6174
*/
public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners()
{
$persistedEntity = new EntityWithRandomlyGeneratedField();
$mergedEntity = new EntityWithRandomlyGeneratedField();
$mergedEntity->id = $persistedEntity->id;
$mergedEntity->generatedField = mt_rand(
$persistedEntity->generatedField + 1,
$persistedEntity->generatedField + 1000
);
$this
->eventManager
->expects(self::any())
->method('hasListeners')
->willReturnCallback(function ($eventName) {
return $eventName === Events::prePersist;
});
$this->eventManager->expects(self::never())->method('dispatchEvent');
$this->_unitOfWork->registerManaged(
$persistedEntity,
['id' => $persistedEntity->id],
['generatedField' => $persistedEntity->generatedField]
);
/* @var $merged EntityWithRandomlyGeneratedField */
$merged = $this->_unitOfWork->merge($mergedEntity);
self::assertSame($merged, $persistedEntity);
self::assertSame($persistedEntity->generatedField, $mergedEntity->generatedField);
}
}
/**
@@ -498,3 +587,61 @@ class VersionedAssignedIdentifierEntity
*/
public $version;
}
/** @Entity */
class EntityWithStringIdentifier
{
/**
* @Id @Column(type="string")
*
* @var string|null
*/
public $id;
}
/** @Entity */
class EntityWithBooleanIdentifier
{
/**
* @Id @Column(type="boolean")
*
* @var bool|null
*/
public $id;
}
/** @Entity */
class EntityWithCompositeStringIdentifier
{
/**
* @Id @Column(type="string")
*
* @var string|null
*/
public $id1;
/**
* @Id @Column(type="string")
*
* @var string|null
*/
public $id2;
}
/** @Entity */
class EntityWithRandomlyGeneratedField
{
/** @Id @Column(type="string") */
public $id;
/**
* @Column(type="integer")
*/
public $generatedField;
public function __construct()
{
$this->id = uniqid('id', true);
$this->generatedField = mt_rand(0, 100000);
}
}