From aa4b2e59ce10ab345cd658ef2d24172c908bf42f Mon Sep 17 00:00:00 2001 From: Bill Schaller Date: Fri, 3 Apr 2015 13:22:06 -0400 Subject: [PATCH 001/144] fix rare query test failures due to nondeterminism without order by clause --- .../ORM/Functional/QueryDqlFunctionTest.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php index 6478fc40458..b0a4905ac33 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php @@ -61,7 +61,7 @@ public function testAggregateCount() public function testFunctionAbs() { - $result = $this->_em->createQuery('SELECT m, ABS(m.salary * -1) AS abs FROM Doctrine\Tests\Models\Company\CompanyManager m') + $result = $this->_em->createQuery('SELECT m, ABS(m.salary * -1) AS abs FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getResult(); $this->assertEquals(4, count($result)); @@ -73,7 +73,7 @@ public function testFunctionAbs() public function testFunctionConcat() { - $arg = $this->_em->createQuery('SELECT m, CONCAT(m.name, m.department) AS namedep FROM Doctrine\Tests\Models\Company\CompanyManager m') + $arg = $this->_em->createQuery('SELECT m, CONCAT(m.name, m.department) AS namedep FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getArrayResult(); $this->assertEquals(4, count($arg)); @@ -85,7 +85,7 @@ public function testFunctionConcat() public function testFunctionLength() { - $result = $this->_em->createQuery('SELECT m, LENGTH(CONCAT(m.name, m.department)) AS namedeplength FROM Doctrine\Tests\Models\Company\CompanyManager m') + $result = $this->_em->createQuery('SELECT m, LENGTH(CONCAT(m.name, m.department)) AS namedeplength FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getArrayResult(); $this->assertEquals(4, count($result)); @@ -98,7 +98,7 @@ public function testFunctionLength() public function testFunctionLocate() { $dql = "SELECT m, LOCATE('e', LOWER(m.name)) AS loc, LOCATE('e', LOWER(m.name), 7) AS loc2 ". - "FROM Doctrine\Tests\Models\Company\CompanyManager m"; + "FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC"; $result = $this->_em->createQuery($dql) ->getArrayResult(); @@ -116,7 +116,7 @@ public function testFunctionLocate() public function testFunctionLower() { - $result = $this->_em->createQuery("SELECT m, LOWER(m.name) AS lowername FROM Doctrine\Tests\Models\Company\CompanyManager m") + $result = $this->_em->createQuery("SELECT m, LOWER(m.name) AS lowername FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC") ->getArrayResult(); $this->assertEquals(4, count($result)); @@ -128,7 +128,7 @@ public function testFunctionLower() public function testFunctionMod() { - $result = $this->_em->createQuery("SELECT m, MOD(m.salary, 3500) AS amod FROM Doctrine\Tests\Models\Company\CompanyManager m") + $result = $this->_em->createQuery("SELECT m, MOD(m.salary, 3500) AS amod FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC") ->getArrayResult(); $this->assertEquals(4, count($result)); @@ -140,7 +140,7 @@ public function testFunctionMod() public function testFunctionSqrt() { - $result = $this->_em->createQuery("SELECT m, SQRT(m.salary) AS sqrtsalary FROM Doctrine\Tests\Models\Company\CompanyManager m") + $result = $this->_em->createQuery("SELECT m, SQRT(m.salary) AS sqrtsalary FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC") ->getArrayResult(); $this->assertEquals(4, count($result)); @@ -152,7 +152,7 @@ public function testFunctionSqrt() public function testFunctionUpper() { - $result = $this->_em->createQuery("SELECT m, UPPER(m.name) AS uppername FROM Doctrine\Tests\Models\Company\CompanyManager m") + $result = $this->_em->createQuery("SELECT m, UPPER(m.name) AS uppername FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC") ->getArrayResult(); $this->assertEquals(4, count($result)); @@ -186,7 +186,7 @@ public function testFunctionTrim() { $dql = "SELECT m, TRIM(TRAILING '.' FROM m.name) AS str1, ". " TRIM(LEADING '.' FROM m.name) AS str2, TRIM(CONCAT(' ', CONCAT(m.name, ' '))) AS str3 ". - "FROM Doctrine\Tests\Models\Company\CompanyManager m"; + "FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC"; $result = $this->_em->createQuery($dql)->getArrayResult(); @@ -207,7 +207,7 @@ public function testFunctionTrim() public function testOperatorAdd() { - $result = $this->_em->createQuery('SELECT m, m.salary+2500 AS add FROM Doctrine\Tests\Models\Company\CompanyManager m') + $result = $this->_em->createQuery('SELECT m, m.salary+2500 AS add FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getResult(); $this->assertEquals(4, count($result)); @@ -219,7 +219,7 @@ public function testOperatorAdd() public function testOperatorSub() { - $result = $this->_em->createQuery('SELECT m, m.salary-2500 AS sub FROM Doctrine\Tests\Models\Company\CompanyManager m') + $result = $this->_em->createQuery('SELECT m, m.salary-2500 AS sub FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getResult(); $this->assertEquals(4, count($result)); @@ -231,7 +231,7 @@ public function testOperatorSub() public function testOperatorMultiply() { - $result = $this->_em->createQuery('SELECT m, m.salary*2 AS op FROM Doctrine\Tests\Models\Company\CompanyManager m') + $result = $this->_em->createQuery('SELECT m, m.salary*2 AS op FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getResult(); $this->assertEquals(4, count($result)); @@ -246,7 +246,7 @@ public function testOperatorMultiply() */ public function testOperatorDiv() { - $result = $this->_em->createQuery('SELECT m, (m.salary/0.5) AS op FROM Doctrine\Tests\Models\Company\CompanyManager m') + $result = $this->_em->createQuery('SELECT m, (m.salary/0.5) AS op FROM Doctrine\Tests\Models\Company\CompanyManager m ORDER BY m.salary ASC') ->getResult(); $this->assertEquals(4, count($result)); From a1602bd91f6cb2bce7f328b89a326cfec2e92d96 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 2 Apr 2015 23:43:16 +0100 Subject: [PATCH 002/144] Hydration of fetch-joined results fails when an entity has a default value of `array` for the collection property --- .../EntityWithArrayDefaultArrayValueM2M.php | 15 +++++++++++ .../Tests/Models/Hydration/SimpleEntity.php | 12 +++++++++ .../ORM/Hydration/ObjectHydratorTest.php | 27 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/Doctrine/Tests/Models/Hydration/EntityWithArrayDefaultArrayValueM2M.php create mode 100644 tests/Doctrine/Tests/Models/Hydration/SimpleEntity.php diff --git a/tests/Doctrine/Tests/Models/Hydration/EntityWithArrayDefaultArrayValueM2M.php b/tests/Doctrine/Tests/Models/Hydration/EntityWithArrayDefaultArrayValueM2M.php new file mode 100644 index 00000000000..8ba57db685a --- /dev/null +++ b/tests/Doctrine/Tests/Models/Hydration/EntityWithArrayDefaultArrayValueM2M.php @@ -0,0 +1,15 @@ +_em); $hydrator->hydrateAll($stmt, $rsm); } + + public function testFetchJoinCollectionValuedAssociationWithDefaultArrayValue() + { + $rsm = new ResultSetMapping; + + $rsm->addEntityResult(EntityWithArrayDefaultArrayValueM2M::CLASSNAME, 'e1', null); + $rsm->addJoinedEntityResult(SimpleEntity::CLASSNAME, 'e2', 'e1', 'collection'); + $rsm->addFieldResult('e1', 'a1__id', 'id'); + $rsm->addFieldResult('e2', 'e2__id', 'id'); + + $result = (new ObjectHydrator($this->_em)) + ->hydrateAll( + new HydratorMockStatement([[ + 'a1__id' => '1', + 'e2__id' => '1', + ]]), + $rsm + ); + + $this->assertCount(1, $result); + $this->assertInstanceOf(EntityWithArrayDefaultArrayValueM2M::CLASSNAME, $result[0]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0]->collection); + $this->assertCount(1, $result[0]->collection); + $this->assertInstanceOf(SimpleEntity::CLASSNAME, $result[0]->collection[0]); + } } From cb3179865bfc8604f86f20be257566cb1c52e8e5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 2 Apr 2015 23:43:41 +0100 Subject: [PATCH 003/144] Minor docblock correction (discovered during testing) --- lib/Doctrine/ORM/Query/ResultSetMapping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 07b89669513..bdd5de75eff 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -347,7 +347,7 @@ public function addFieldResult($alias, $columnName, $fieldName, $declaringClass * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. - * @param object $relation The association field that connects the parent entity result + * @param string $relation The association field that connects the parent entity result * with the joined entity result. * * @return ResultSetMapping This ResultSetMapping instance. From c2f6b09ee0bd4dd970b319050d95ab54bfd3c1f3 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 2 Apr 2015 23:44:12 +0100 Subject: [PATCH 004/144] `PersistentCollection` should still accept `null` and `array` as constructor argument, as it did before --- .../Tests/ORM/PersistentCollectionTest.php | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php index 9b9a067fc7a..6c139ba3094 100644 --- a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php @@ -3,8 +3,10 @@ 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; use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Models\ECommerce\ECommerceCart; use Doctrine\Tests\OrmTestCase; @@ -21,15 +23,16 @@ class PersistentCollectionTest extends OrmTestCase */ protected $collection; - private $_connectionMock; + /** + * @var \Doctrine\ORM\EntityManagerInterface + */ private $_emMock; protected function setUp() { parent::setUp(); - // SUT - $this->_connectionMock = new ConnectionMock(array(), new \Doctrine\Tests\Mocks\DriverMock()); - $this->_emMock = EntityManagerMock::create($this->_connectionMock); + + $this->_emMock = EntityManagerMock::create(new ConnectionMock([], new DriverMock())); } /** @@ -80,4 +83,39 @@ public function testNextInitializesCollection() $this->collection->next(); $this->assertTrue($this->collection->isInitialized()); } + + public function testAcceptsArrayAsConstructorArgument() + { + $metadata = $this->_emMock->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCart'); + + $collection = new PersistentCollection($this->_emMock, $metadata, []); + + $this->assertEmpty($collection); + $this->tryGenericCollectionOperations($collection); + } + + public function testAcceptsNullAsConstructorArgument() + { + $metadata = $this->_emMock->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCart'); + + $collection = new PersistentCollection($this->_emMock, $metadata, null); + + $this->assertEmpty($collection); + $this->tryGenericCollectionOperations($collection); + } + + private function tryGenericCollectionOperations(Collection $collection) + { + $count = count($collection); + $object = new \stdClass(); + + $collection->add($object); + + $this->assertTrue($collection->contains($object)); + $this->assertCount($count + 1, $collection); + + $collection->removeElement($object); + + $this->assertCount($count, $collection); + } } From 4792b4f9741db351cdfcabcd5aa3e5ad17531e15 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 2 Apr 2015 23:45:12 +0100 Subject: [PATCH 005/144] FQCN reference (class was not imported correctly) --- tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 3dc5025ae1e..eaa0b75b702 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -1968,7 +1968,7 @@ public function testFetchJoinCollectionValuedAssociationWithDefaultArrayValue() $rsm->addFieldResult('e1', 'a1__id', 'id'); $rsm->addFieldResult('e2', 'e2__id', 'id'); - $result = (new ObjectHydrator($this->_em)) + $result = (new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em)) ->hydrateAll( new HydratorMockStatement([[ 'a1__id' => '1', From e8c9cb2f2302681094b268fff439dfe30287c533 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 2 Apr 2015 23:45:46 +0100 Subject: [PATCH 006/144] Reverting BC break: `PersistentConnection#__construct()` now accepts `null|array|Collection` again --- lib/Doctrine/ORM/PersistentCollection.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 98e3b91d8bf..7564133b93d 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -101,16 +101,18 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec /** * Creates a new persistent collection. * - * @param EntityManagerInterface $em The EntityManager the collection will be associated with. - * @param ClassMetadata $class The class descriptor of the entity type of this collection. - * @param Collection $coll The collection elements. + * @param EntityManagerInterface $em The EntityManager the collection will be associated with. + * @param ClassMetadata $class The class descriptor of the entity type of this collection. + * @param Collection|array|null $collection The collection elements. */ - public function __construct(EntityManagerInterface $em, $class, $coll) + public function __construct(EntityManagerInterface $em, $class, $collection) { - $this->collection = $coll; $this->em = $em; $this->typeClass = $class; $this->initialized = true; + $this->collection = $collection instanceof Collection + ? $collection + : new ArrayCollection((array) $collection); } /** From 6e563a313e2d9ff79b3c97f33358d3ed196dd61d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 3 Apr 2015 15:26:38 +0100 Subject: [PATCH 007/144] a `PersistentCollection` should only allow another collection as a wrapped collection --- lib/Doctrine/ORM/PersistentCollection.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 7564133b93d..83eab8cf1ab 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -103,16 +103,14 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec * * @param EntityManagerInterface $em The EntityManager the collection will be associated with. * @param ClassMetadata $class The class descriptor of the entity type of this collection. - * @param Collection|array|null $collection The collection elements. + * @param Collection $collection The collection elements. */ - public function __construct(EntityManagerInterface $em, $class, $collection) + public function __construct(EntityManagerInterface $em, $class, Collection $collection) { + $this->collection = $collection; $this->em = $em; $this->typeClass = $class; $this->initialized = true; - $this->collection = $collection instanceof Collection - ? $collection - : new ArrayCollection((array) $collection); } /** From 45804296161dbd55792be5f75e835e1965ad985a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 3 Apr 2015 15:27:13 +0100 Subject: [PATCH 008/144] Removing irrelevant tests (as per discussion with @guilhermeblanco and @stof --- .../Tests/ORM/PersistentCollectionTest.php | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php index 6c139ba3094..e77f898d139 100644 --- a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php @@ -83,39 +83,4 @@ public function testNextInitializesCollection() $this->collection->next(); $this->assertTrue($this->collection->isInitialized()); } - - public function testAcceptsArrayAsConstructorArgument() - { - $metadata = $this->_emMock->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCart'); - - $collection = new PersistentCollection($this->_emMock, $metadata, []); - - $this->assertEmpty($collection); - $this->tryGenericCollectionOperations($collection); - } - - public function testAcceptsNullAsConstructorArgument() - { - $metadata = $this->_emMock->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCart'); - - $collection = new PersistentCollection($this->_emMock, $metadata, null); - - $this->assertEmpty($collection); - $this->tryGenericCollectionOperations($collection); - } - - private function tryGenericCollectionOperations(Collection $collection) - { - $count = count($collection); - $object = new \stdClass(); - - $collection->add($object); - - $this->assertTrue($collection->contains($object)); - $this->assertCount($count + 1, $collection); - - $collection->removeElement($object); - - $this->assertCount($count, $collection); - } } From 58a6013d15fa6ef8e547fb2feaebdd7b90e8caf9 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 3 Apr 2015 15:28:13 +0100 Subject: [PATCH 009/144] Correcting static introspection issue in cache specific tests (`null` was being passed to a `PersistentCollection`) --- .../Cache/Persister/Entity/AbstractEntityPersisterTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/Entity/AbstractEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/Entity/AbstractEntityPersisterTest.php index f682723c156..bf94b1e4682 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/Entity/AbstractEntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/Entity/AbstractEntityPersisterTest.php @@ -11,6 +11,7 @@ use Doctrine\Tests\Models\Cache\Country; use Doctrine\Common\Collections\Criteria; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\ORM\PersistentCollection; @@ -390,7 +391,7 @@ public function testInvokeLoadManyToManyCollection() { $mapping = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country'); $assoc = array('type' => 1); - $coll = new PersistentCollection($this->em, $mapping, null); + $coll = new PersistentCollection($this->em, $mapping, new ArrayCollection()); $persister = $this->createPersisterDefault(); $entity = new Country("Foo"); @@ -406,7 +407,7 @@ public function testInvokeLoadOneToManyCollection() { $mapping = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country'); $assoc = array('type' => 1); - $coll = new PersistentCollection($this->em, $mapping, null); + $coll = new PersistentCollection($this->em, $mapping, new ArrayCollection()); $persister = $this->createPersisterDefault(); $entity = new Country("Foo"); From 39592ba59c31ae9377bc95a558a0292df6169050 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 3 Apr 2015 15:28:53 +0100 Subject: [PATCH 010/144] Correcting `ObjectHydrator` logic: if an `array` is a default value for a collection-valued property, it should be cast to a `Collection` --- lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index c1bcc2591b7..3cedaada0c3 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -189,8 +189,8 @@ private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAl $relation = $class->associationMappings[$fieldName]; $value = $class->reflFields[$fieldName]->getValue($entity); - if ($value === null) { - $value = new ArrayCollection; + if ($value === null || is_array($value)) { + $value = new ArrayCollection((array) $value); } if ( ! $value instanceof PersistentCollection) { From 786791b8fef55db2c54ef8ece07e8425dcdaf99b Mon Sep 17 00:00:00 2001 From: Matteo Beccati Date: Sun, 5 Apr 2015 21:50:25 +0200 Subject: [PATCH 011/144] Fix DDC767Test failing on php7 + pg94 The failure happens when running the full suite or even just: phpunit tests/Doctrine/Tests/ORM/Functional/Ticket --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC767Test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC767Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC767Test.php index eb3b052b003..1072fc00c97 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC767Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC767Test.php @@ -49,7 +49,7 @@ public function testCollectionChangesInsideTransaction() $this->assertNotNull($pUser, "User not retrieved from database."); - $groups = array(2, 3); + $groups = array($group2->id, $group3->id); try { $this->_em->beginTransaction(); From 3e6c6af8456a1b2a1ecd5c868be6f792bc857828 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 14 Apr 2015 11:37:54 -0400 Subject: [PATCH 012/144] Merge pull request #1382 from holtkamp/patch-second-level-cache-association-hydration Patch second level cache association hydration --- lib/Doctrine/ORM/Cache/MultiGetRegion.php | 4 +- .../Cache/Region/DefaultMultiGetRegion.php | 4 +- .../ORM/Cache/Region/DefaultRegion.php | 43 ++++++++++--------- .../Tests/ORM/Cache/DefaultRegionTest.php | 25 +++++++++++ .../Tests/ORM/Cache/MultiGetRegionTest.php | 7 ++- 5 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/MultiGetRegion.php b/lib/Doctrine/ORM/Cache/MultiGetRegion.php index 6de9c888d62..be32b08f0fe 100644 --- a/lib/Doctrine/ORM/Cache/MultiGetRegion.php +++ b/lib/Doctrine/ORM/Cache/MultiGetRegion.php @@ -23,7 +23,7 @@ /** * Defines a region that supports multi-get reading. * - * With one method call we can get multipe items. + * With one method call we can get multiple items. * * @since 2.5 * @author Asmir Mustafic @@ -31,7 +31,7 @@ interface MultiGetRegion { /** - * Get all items from the cache indentifed by $keys. + * Get all items from the cache identified by $keys. * It returns NULL if some elements can not be found. * * @param CollectionCacheEntry $collection The collection of the items to be retrieved. diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php index 39bf9c9e866..7ecf7331116 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php @@ -57,8 +57,9 @@ public function __construct($name, MultiGetCache $cache, $lifetime = 0) public function getMultiple(CollectionCacheEntry $collection) { $keysToRetrieve = array(); + foreach ($collection->identifiers as $index => $key) { - $keysToRetrieve[$index] = $this->name . '_' . $key->hash; + $keysToRetrieve[$index] = $this->getCacheEntryKey($key); } $items = $this->cache->fetchMultiple($keysToRetrieve); @@ -70,6 +71,7 @@ public function getMultiple(CollectionCacheEntry $collection) foreach ($keysToRetrieve as $index => $key) { $returnableItems[$index] = $items[$key]; } + return $returnableItems; } } diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index f4525ee1131..3f214d0b0e9 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -36,6 +36,8 @@ */ class DefaultRegion implements Region { + const REGION_KEY_SEPARATOR = '_'; + /** * @var CacheAdapter */ @@ -84,7 +86,7 @@ public function getCache() */ public function contains(CacheKey $key) { - return $this->cache->contains($this->name . '_' . $key->hash); + return $this->cache->contains($this->getCacheEntryKey($key)); } /** @@ -92,7 +94,7 @@ public function contains(CacheKey $key) */ public function get(CacheKey $key) { - return $this->cache->fetch($this->name . '_' . $key->hash) ?: null; + return $this->cache->fetch($this->getCacheEntryKey($key)) ?: null; } /** @@ -100,30 +102,29 @@ public function get(CacheKey $key) */ public function getMultiple(CollectionCacheEntry $collection) { - $keysToRetrieve = array(); + $result = array(); - foreach ($collection->identifiers as $index => $key) { - $keysToRetrieve[$index] = $this->name . '_' . $key->hash; - } + foreach ($collection->identifiers as $key) { + $entryKey = $this->getCacheEntryKey($key); + $entryValue = $this->cache->fetch($entryKey); - $items = array_filter( - array_map([$this->cache, 'fetch'], $keysToRetrieve), - function ($retrieved) { - return false !== $retrieved; + if ($entryValue === false) { + return null; } - ); - if (count($items) !== count($keysToRetrieve)) { - return null; + $result[] = $entryValue; } - $returnableItems = array(); - - foreach ($keysToRetrieve as $index => $key) { - $returnableItems[$index] = $items[$key]; - } + return $result; + } - return $returnableItems; + /** + * @param CacheKey $key + * @return string + */ + protected function getCacheEntryKey(CacheKey $key) + { + return $this->name . self::REGION_KEY_SEPARATOR . $key->hash; } /** @@ -131,7 +132,7 @@ function ($retrieved) { */ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) { - return $this->cache->save($this->name . '_' . $key->hash, $entry, $this->lifetime); + return $this->cache->save($this->getCacheEntryKey($key), $entry, $this->lifetime); } /** @@ -139,7 +140,7 @@ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) */ public function evict(CacheKey $key) { - return $this->cache->delete($this->name . '_' . $key->hash); + return $this->cache->delete($this->getCacheEntryKey($key)); } /** diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index 94cc99e35b8..914bcf1917e 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -3,10 +3,12 @@ namespace Doctrine\Tests\ORM\Cache; use Doctrine\Common\Cache\ArrayCache; +use Doctrine\ORM\Cache\CollectionCacheEntry; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Tests\Mocks\CacheEntryMock; use Doctrine\Tests\Mocks\CacheKeyMock; + /** * @group DDC-2183 */ @@ -72,4 +74,27 @@ public function testEvictAllWithGenericCacheThrowsUnsupportedException() $region->evictAll(); } + + public function testGetMulti() + { + $key1 = new CacheKeyMock('key.1'); + $value1 = new CacheEntryMock(array('id' => 1, 'name' => 'bar')); + + $key2 = new CacheKeyMock('key.2'); + $value2 = new CacheEntryMock(array('id' => 2, 'name' => 'bar')); + + $this->assertFalse($this->region->contains($key1)); + $this->assertFalse($this->region->contains($key2)); + + $this->region->put($key1, $value1); + $this->region->put($key2, $value2); + + $this->assertTrue($this->region->contains($key1)); + $this->assertTrue($this->region->contains($key2)); + + $actual = $this->region->getMultiple(new CollectionCacheEntry(array($key1, $key2))); + + $this->assertEquals($value1, $actual[0]); + $this->assertEquals($value2, $actual[1]); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/MultiGetRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/MultiGetRegionTest.php index 4c3258a1292..091ec672a8f 100644 --- a/tests/Doctrine/Tests/ORM/Cache/MultiGetRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/MultiGetRegionTest.php @@ -22,10 +22,10 @@ protected function createRegion() public function testGetMulti() { $key1 = new CacheKeyMock('key.1'); - $value1 = new CacheEntryMock(array('id'=>1, 'name' => 'bar')); + $value1 = new CacheEntryMock(array('id' => 1, 'name' => 'bar')); $key2 = new CacheKeyMock('key.2'); - $value2 = new CacheEntryMock(array('id'=>2, 'name' => 'bar')); + $value2 = new CacheEntryMock(array('id' => 2, 'name' => 'bar')); $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); @@ -33,6 +33,9 @@ public function testGetMulti() $this->region->put($key1, $value1); $this->region->put($key2, $value2); + $this->assertTrue($this->region->contains($key1)); + $this->assertTrue($this->region->contains($key2)); + $actual = $this->region->getMultiple(new CollectionCacheEntry(array($key1, $key2))); $this->assertEquals($value1, $actual[0]); From 0e208f7538b77be71f73fbc9986639ad4bf3d47e Mon Sep 17 00:00:00 2001 From: Restless-ET Date: Fri, 5 Jun 2015 17:08:50 +0100 Subject: [PATCH 013/144] [2.5][Bug] Fix ConvertDoctrine1Schema->getMetadata This bug was introduced at #1205 while resolving #1200. --- lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php b/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php index 8003000ce9b..52f3c92b972 100644 --- a/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php +++ b/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php @@ -80,7 +80,7 @@ public function getMetadata() $schema = array_merge($schema, (array) Yaml::parse(file_get_contents($file))); } } else { - $schema = array_merge($schema, (array) Yaml::parse(file_get_contents($file))); + $schema = array_merge($schema, (array) Yaml::parse(file_get_contents($path))); } } From 10ed690d9925ef30e8fa531ca0fa97208357b6c0 Mon Sep 17 00:00:00 2001 From: Bill Schaller Date: Thu, 18 Jun 2015 10:41:56 -0400 Subject: [PATCH 014/144] Backport Merge pull request #1430 from michael-lavaveshkul/master "INSTANCE OF" example doesn't match description. --- docs/en/reference/inheritance-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/reference/inheritance-mapping.rst b/docs/en/reference/inheritance-mapping.rst index 663c8901ed3..9a0d3c208d3 100644 --- a/docs/en/reference/inheritance-mapping.rst +++ b/docs/en/reference/inheritance-mapping.rst @@ -601,5 +601,5 @@ Querying for the staffs without getting any technicians can be achieved by this .. code-block:: php createQuery("SELECT staff FROM MyProject\Model\Staff staff WHERE staff INSTANCE OF MyProject\Model\Staff"); + $query = $em->createQuery("SELECT staff FROM MyProject\Model\Staff staff WHERE staff NOT INSTANCE OF MyProject\Model\Technician"); $staffs = $query->getResult(); From 768c291cd10a6e4e36a702b3429e288a3a807038 Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Fri, 10 Apr 2015 11:15:30 -0700 Subject: [PATCH 015/144] Stumbled across a bug where signatures didn't match, but also the current persister-type didn't support getCacheRegion(). Unsure of exact mechanism, but clearly the constructor doesn't take the second argument anyway, may be old code. --- lib/Doctrine/ORM/Cache/DefaultCacheFactory.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index 69d2fd1c16b..82843d35933 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -180,13 +180,7 @@ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) */ public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping) { - /* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */ - $targetPersister = $em->getUnitOfWork()->getEntityPersister($mapping['targetEntity']); - - return new DefaultCollectionHydrator( - $em, - $targetPersister->getCacheRegion() - ); + return new DefaultCollectionHydrator($em); } /** From d29cc3660fbbbeffe567755bfea1f416b83ec6f5 Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Fri, 10 Apr 2015 16:02:38 -0700 Subject: [PATCH 016/144] Change the test listener than layers on second-level-caching so that it is more conservative, only turning on caching-associations when it knows the target entity is cache-able. --- .../EventListener/CacheMetadataListener.php | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php index f9e2616c6c0..daacef07e86 100644 --- a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php +++ b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php @@ -3,33 +3,74 @@ namespace Doctrine\Tests\EventListener; use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; class CacheMetadataListener { + + /** + * Tracks which entities we have already forced caching enabled on. This is + * important to avoid some potential infinite-recursion issues. + * @var array + */ + protected $enabledItems = array(); + /** * @param \Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs $event */ public function loadClassMetadata(LoadClassMetadataEventArgs $event) { $metadata = $event->getClassMetadata(); - $cache = array( - 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE - ); + $em = $event->getObjectManager(); /** @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ if (strstr($metadata->name, 'Doctrine\Tests\Models\Cache')) { return; } + if( ! $em instanceof EntityManager){ + return; + } + + $this->enableCaching($metadata, $em); + } + + /** + * @param ClassMetadata $metadata + * @param EntityManager $em + */ + protected function enableCaching(ClassMetadata $metadata, EntityManager $em){ + + if(array_key_exists($metadata->getName(), $this->enabledItems)){ + return; // Already handled in the past + } + + $cache = array( + 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE + ); + if ($metadata->isVersioned) { return; } $metadata->enableCache($cache); + $this->enabledItems[$metadata->getName()] = $metadata; + + /* + * Only enable association-caching when the target has already been + * given caching settings + */ foreach ($metadata->associationMappings as $mapping) { - $metadata->enableAssociationCache($mapping['fieldName'], $cache); + + $targetMeta = $em->getClassMetadata($mapping['targetEntity']); + $this->enableCaching($targetMeta, $em); + + if(array_key_exists($targetMeta->getName(), $this->enabledItems)){ + $metadata->enableAssociationCache($mapping['fieldName'], $cache); + } } } } From 08be905fc38ced42cb7b3de20ec33dd256b346ef Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Tue, 5 May 2015 13:52:08 -0700 Subject: [PATCH 017/144] Refactor LoadClassMetadataEventArgs to ensure it contains an EntityManager --- .../ORM/Event/LoadClassMetadataEventArgs.php | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php index 5bebf7541c3..1a43e089053 100644 --- a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php +++ b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php @@ -20,6 +20,8 @@ namespace Doctrine\ORM\Event; use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManager; /** * Class that holds event arguments for a loadMetadata event. @@ -29,6 +31,22 @@ */ class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs { + /** + * @param ClassMetadata $classMetadata + * @param EntityManager $entityManager + */ + function __construct(ClassMetadata $classMetadata, EntityManager $entityManager) + { + /* + We use our own constructor here to enforce type-hinting requirements, + since both inputs are specialized subclasses compared to what the super- + class is willing to accept. + + In particular, we want to have EntityManager rather than ObjectManager. + */ + parent::__construct($classMetadata, $entityManager); + } + /** * Retrieve associated EntityManager. * @@ -36,6 +54,18 @@ class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs */ public function getEntityManager() { - return $this->getObjectManager(); + $em = $this->getObjectManager(); + assert($em instanceof EntityManager); + return $em; + } + + /** + * Retrieves the associated ClassMetadata. + * + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + public function getClassMetadata() + { + return parent::getClassMetadata(); } } From c507b52f20d0418f3957bdc2bc808f1875c5ca0b Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Tue, 5 May 2015 13:56:22 -0700 Subject: [PATCH 018/144] Remove now-superfluous EntityManager check --- tests/Doctrine/Tests/EventListener/CacheMetadataListener.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php index daacef07e86..dfb870d4b58 100644 --- a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php +++ b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php @@ -30,10 +30,6 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event) return; } - if( ! $em instanceof EntityManager){ - return; - } - $this->enableCaching($metadata, $em); } From d5adda954d4a7514c9b088d71b20ce4cfc30a8d1 Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Tue, 5 May 2015 13:57:52 -0700 Subject: [PATCH 019/144] Whitespace formatting tweaks --- .../Tests/EventListener/CacheMetadataListener.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php index dfb870d4b58..fd0a922f865 100644 --- a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php +++ b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php @@ -37,13 +37,13 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event) * @param ClassMetadata $metadata * @param EntityManager $em */ - protected function enableCaching(ClassMetadata $metadata, EntityManager $em){ + protected function enableCaching(ClassMetadata $metadata, EntityManager $em) { - if(array_key_exists($metadata->getName(), $this->enabledItems)){ + if (array_key_exists($metadata->getName(), $this->enabledItems)) { return; // Already handled in the past } - $cache = array( + $cache = array( 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE ); @@ -64,7 +64,7 @@ protected function enableCaching(ClassMetadata $metadata, EntityManager $em){ $targetMeta = $em->getClassMetadata($mapping['targetEntity']); $this->enableCaching($targetMeta, $em); - if(array_key_exists($targetMeta->getName(), $this->enabledItems)){ + if (array_key_exists($targetMeta->getName(), $this->enabledItems)) { $metadata->enableAssociationCache($mapping['fieldName'], $cache); } } From 97e90ddefcd144899dea2baa2771c79895ebff45 Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Tue, 5 May 2015 15:28:23 -0700 Subject: [PATCH 020/144] Clarify state-changes, replace array_key_exists() with isset() for speed --- .../EventListener/CacheMetadataListener.php | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php index fd0a922f865..82a4f0db6cc 100644 --- a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php +++ b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php @@ -13,6 +13,9 @@ class CacheMetadataListener /** * Tracks which entities we have already forced caching enabled on. This is * important to avoid some potential infinite-recursion issues. + * + * Key is the name of the entity, payload is unimportant. + * * @var array */ protected $enabledItems = array(); @@ -33,13 +36,28 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event) $this->enableCaching($metadata, $em); } + /** + * @param ClassMetadata $metadata + * @return bool + */ + private function isVisited(ClassMetaData $metadata) { + return isset($this->enabledItems[$metadata->getName()]); + } + + /** + * @param ClassMetadata $metadata + */ + private function recordVisit(ClassMetaData $metadata) { + $this->enabledItems[$metadata->getName()] = true; + } + /** * @param ClassMetadata $metadata * @param EntityManager $em */ protected function enableCaching(ClassMetadata $metadata, EntityManager $em) { - if (array_key_exists($metadata->getName(), $this->enabledItems)) { + if ($this->isVisited($metadata)) { return; // Already handled in the past } @@ -53,7 +71,7 @@ protected function enableCaching(ClassMetadata $metadata, EntityManager $em) { $metadata->enableCache($cache); - $this->enabledItems[$metadata->getName()] = $metadata; + $this->recordVisit($metadata); /* * Only enable association-caching when the target has already been @@ -64,7 +82,7 @@ protected function enableCaching(ClassMetadata $metadata, EntityManager $em) { $targetMeta = $em->getClassMetadata($mapping['targetEntity']); $this->enableCaching($targetMeta, $em); - if (array_key_exists($targetMeta->getName(), $this->enabledItems)) { + if ($this->isVisited($targetMeta)) { $metadata->enableAssociationCache($mapping['fieldName'], $cache); } } From fff56c7f3f19b2d25e3654b37ea121c8ecea8582 Mon Sep 17 00:00:00 2001 From: Darien Hager Date: Wed, 8 Jul 2015 15:52:09 -0700 Subject: [PATCH 021/144] Remove runtime assertion --- lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php index 1a43e089053..c9d4c1fe60e 100644 --- a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php +++ b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php @@ -54,9 +54,11 @@ class is willing to accept. */ public function getEntityManager() { - $em = $this->getObjectManager(); - assert($em instanceof EntityManager); - return $em; + /* + We can safely assume our ObjectManager is also an EventManager due to + our restrictions in the constructor. + */ + return $this->getObjectManager(); } /** From ed1c4de2b62ca7eeabf7e5fb36f86ef6d17a8b38 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 15 Jul 2015 20:35:21 +0100 Subject: [PATCH 022/144] DDC-3683 - #1380 - reverting BC break, annotating correct types, cs fixes --- .../ORM/Event/LoadClassMetadataEventArgs.php | 37 +++---------------- .../EventListener/CacheMetadataListener.php | 19 +++++----- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php index c9d4c1fe60e..95e75616e12 100644 --- a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php +++ b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php @@ -20,33 +20,20 @@ namespace Doctrine\ORM\Event; use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\EntityManager; /** * Class that holds event arguments for a loadMetadata event. * * @author Jonathan H. Wage * @since 2.0 + * + * Note: method annotations are used instead of method overrides (due to BC policy) + * + * @method __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata, \Doctrine\ORM\EntityManager $objectManager) + * @method \Doctrine\ORM\EntityManager getClassMetadata() */ class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs { - /** - * @param ClassMetadata $classMetadata - * @param EntityManager $entityManager - */ - function __construct(ClassMetadata $classMetadata, EntityManager $entityManager) - { - /* - We use our own constructor here to enforce type-hinting requirements, - since both inputs are specialized subclasses compared to what the super- - class is willing to accept. - - In particular, we want to have EntityManager rather than ObjectManager. - */ - parent::__construct($classMetadata, $entityManager); - } - /** * Retrieve associated EntityManager. * @@ -54,20 +41,6 @@ class is willing to accept. */ public function getEntityManager() { - /* - We can safely assume our ObjectManager is also an EventManager due to - our restrictions in the constructor. - */ return $this->getObjectManager(); } - - /** - * Retrieves the associated ClassMetadata. - * - * @return \Doctrine\ORM\Mapping\ClassMetadata - */ - public function getClassMetadata() - { - return parent::getClassMetadata(); - } } diff --git a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php index 82a4f0db6cc..7a7caf18edc 100644 --- a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php +++ b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php @@ -3,7 +3,6 @@ namespace Doctrine\Tests\EventListener; use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs; -use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; @@ -38,16 +37,19 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event) /** * @param ClassMetadata $metadata + * * @return bool */ - private function isVisited(ClassMetaData $metadata) { + private function isVisited(ClassMetaData $metadata) + { return isset($this->enabledItems[$metadata->getName()]); } /** * @param ClassMetadata $metadata */ - private function recordVisit(ClassMetaData $metadata) { + private function recordVisit(ClassMetaData $metadata) + { $this->enabledItems[$metadata->getName()] = true; } @@ -55,8 +57,8 @@ private function recordVisit(ClassMetaData $metadata) { * @param ClassMetadata $metadata * @param EntityManager $em */ - protected function enableCaching(ClassMetadata $metadata, EntityManager $em) { - + protected function enableCaching(ClassMetadata $metadata, EntityManager $em) + { if ($this->isVisited($metadata)) { return; // Already handled in the past } @@ -73,12 +75,9 @@ protected function enableCaching(ClassMetadata $metadata, EntityManager $em) { $this->recordVisit($metadata); - /* - * Only enable association-caching when the target has already been - * given caching settings - */ + // only enable association-caching when the target has already been + // given caching settings foreach ($metadata->associationMappings as $mapping) { - $targetMeta = $em->getClassMetadata($mapping['targetEntity']); $this->enableCaching($targetMeta, $em); From 9097014c3d68f50632075f7f1d62400bf8f0f817 Mon Sep 17 00:00:00 2001 From: Nico Vogelaar Date: Sat, 11 Apr 2015 19:43:34 +0200 Subject: [PATCH 023/144] Fixes ClassMetadata wakeupReflection with embeddable and StaticReflectionService --- .../ORM/Mapping/ClassMetadataInfo.php | 2 +- .../Tests/ORM/Mapping/ClassMetadataTest.php | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index dd47c97885f..79dcdb2cb27 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -945,7 +945,7 @@ public function wakeupReflection($reflService) } foreach ($this->fieldMappings as $field => $mapping) { - if (isset($mapping['declaredField'])) { + if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) { $this->reflFields[$field] = new ReflectionEmbeddedProperty( $parentReflFields[$mapping['declaredField']], $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']), diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index 0ffb41c181b..1af2334dc71 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -3,6 +3,7 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\Common\Persistence\Mapping\RuntimeReflectionService; +use Doctrine\Common\Persistence\Mapping\StaticReflectionService; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\DefaultNamingStrategy; @@ -1125,6 +1126,30 @@ public function testCanInstantiateInternalPhpClassSubclassFromUnserializedMetada $this->assertInstanceOf(__NAMESPACE__ . '\\MyArrayObjectEntity', $classMetadata->newInstance()); } + + public function testWakeupReflectionWithEmbeddableAndStaticReflectionService() + { + $classMetadata = new ClassMetadata('Doctrine\Tests\ORM\Mapping\TestEntity1'); + + $classMetadata->mapEmbedded(array( + 'fieldName' => 'test', + 'class' => 'Doctrine\Tests\ORM\Mapping\TestEntity1', + 'columnPrefix' => false, + )); + + $field = array( + 'fieldName' => 'test.embeddedProperty', + 'type' => 'string', + 'originalClass' => 'Doctrine\Tests\ORM\Mapping\TestEntity1', + 'declaredField' => 'test', + 'originalField' => 'embeddedProperty' + ); + + $classMetadata->mapField($field); + $classMetadata->wakeupReflection(new StaticReflectionService()); + + $this->assertEquals(array('test' => null, 'test.embeddedProperty' => null), $classMetadata->getReflectionProperties()); + } } /** From c68edec0c243c9459e80b959748f68753b2b9da5 Mon Sep 17 00:00:00 2001 From: Lenard Palko Date: Fri, 17 Apr 2015 14:50:31 +0300 Subject: [PATCH 024/144] Fix skipping properties if they are listed after a not loaded relation. --- lib/Doctrine/ORM/UnitOfWork.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index fe12e55df8c..194e45e30ae 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -3378,7 +3378,7 @@ private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) } else { if ($other instanceof Proxy && !$other->__isInitialized()) { // do not merge fields marked lazy that have not been fetched. - return; + continue; } if ( ! $assoc2['isCascadeMerge']) { @@ -3406,7 +3406,7 @@ private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) { // do not merge fields marked lazy that have not been fetched. // keep the lazy persistent collection of the managed copy. - return; + continue; } $managedCol = $prop->getValue($managedCopy); From 69ef75ff2dfcf60117a6872af83da395602950bc Mon Sep 17 00:00:00 2001 From: Lenard Palko Date: Sun, 19 Apr 2015 16:43:28 +0300 Subject: [PATCH 025/144] Added test cases for both one-to-one and one-to-many cases. --- .../Tests/Models/DDC3699/DDC3699Child.php | 91 ++++++++++++++++++ .../Tests/Models/DDC3699/DDC3699Parent.php | 26 +++++ .../Models/DDC3699/DDC3699RelationMany.php | 44 +++++++++ .../Models/DDC3699/DDC3699RelationOne.php | 43 +++++++++ .../ORM/Functional/Ticket/DDC3699Test.php | 96 +++++++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php create mode 100644 tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php create mode 100644 tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php create mode 100644 tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php new file mode 100644 index 00000000000..d96649f3746 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php @@ -0,0 +1,91 @@ +id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getChildField() + { + return $this->childField; + } + + public function setChildField($childField) + { + $this->childField = $childField; + } + + public function getOneRelation() + { + return $this->oneRelation; + } + + public function setOneRelation($oneRelation) + { + $this->oneRelation = $oneRelation; + } + + public function hasRelation($relation) + { + return $this->relations && $this->relations->contains($relation); + } + + public function addRelation($relation) + { + if (!$this->hasRelation($relation)) { + $this->relations[] = $relation; + } + + return $this; + } + + public function removeRelation($relation) + { + $this->relations->removeElement($relation); + + return $this; + } + + public function getRelations() + { + return $this->relations; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php new file mode 100644 index 00000000000..aff175c025a --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php @@ -0,0 +1,26 @@ +parentField; + } + + public function setParentField($parentField) + { + $this->parentField = $parentField; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php new file mode 100644 index 00000000000..374ad92bb24 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php @@ -0,0 +1,44 @@ +id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getChild() + { + return $this->child; + } + + public function setChild($child) + { + $this->child = $child; + } +} diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php new file mode 100644 index 00000000000..f28a43abfe0 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php @@ -0,0 +1,43 @@ +id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getChild() + { + return $this->child; + } + + public function setChild($child) + { + $this->child = $child; + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php new file mode 100644 index 00000000000..b782980eed2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php @@ -0,0 +1,96 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(DDC3699Parent::CLASSNAME), + $this->_em->getClassMetadata(DDC3699RelationOne::CLASSNAME), + $this->_em->getClassMetadata(DDC3699RelationMany::CLASSNAME), + $this->_em->getClassMetadata(DDC3699Child::CLASSNAME), + )); + } catch (\Exception $e) { + // should throw error on second because schema is already created + } + } + + private function createChild($id, $relationClass, $relationMethod) + { + // element in DB + $child = new DDC3699Child(); + $child->setId($id); + $child->setChildField('childValue'); + $child->setParentField('parentValue'); + + $relation = new $relationClass(); + $relation->setId($id); + $relation->setChild($child); + $child->$relationMethod($relation); + + $this->_em->persist($relation); + $this->_em->persist($child); + $this->_em->flush(); + + // detach + $this->_em->detach($relation); + $this->_em->detach($child); + } + + /** + * @group DDC-3699 + */ + public function testMergeParentEntityFieldsOne() + { + $id = 1; + $this->createChild($id, DDC3699RelationOne::CLASSNAME, 'setOneRelation'); + + $unmanagedChild = $this->_em->find(DDC3699Child::CLASSNAME, $id); + $this->_em->detach($unmanagedChild); + + // make it managed again + $this->_em->find(DDC3699Child::CLASSNAME, $id); + + $unmanagedChild->setChildField('modifiedChildValue'); + $unmanagedChild->setParentField('modifiedParentValue'); + + $mergedChild = $this->_em->merge($unmanagedChild); + + $this->assertEquals($mergedChild->getChildField(), 'modifiedChildValue'); + $this->assertEquals($mergedChild->getParentField(), 'modifiedParentValue'); + } + + /** + * @group DDC-3699 + */ + public function testMergeParentEntityFieldsMany() + { + $id = 2; + $this->createChild($id, DDC3699RelationMany::CLASSNAME, 'addRelation'); + + $unmanagedChild = $this->_em->find(DDC3699Child::CLASSNAME, $id); + $this->_em->detach($unmanagedChild); + + // make it managed again + $this->_em->find(DDC3699Child::CLASSNAME, $id); + + $unmanagedChild->setChildField('modifiedChildValue'); + $unmanagedChild->setParentField('modifiedParentValue'); + + $mergedChild = $this->_em->merge($unmanagedChild); + + $this->assertEquals($mergedChild->getChildField(), 'modifiedChildValue'); + $this->assertEquals($mergedChild->getParentField(), 'modifiedParentValue'); + } +} \ No newline at end of file From 86abbb0e786c63a5b9ed997c983f963aa26362be Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 15 Jul 2015 21:46:23 +0100 Subject: [PATCH 026/144] DDC-3699 - #1387 - simpifying tests, clarifying on test method names --- .../Tests/Models/DDC3699/DDC3699Child.php | 88 ++----------------- .../Tests/Models/DDC3699/DDC3699Parent.php | 20 +---- .../Models/DDC3699/DDC3699RelationMany.php | 34 +------ .../Models/DDC3699/DDC3699RelationOne.php | 33 +------ .../ORM/Functional/Ticket/DDC3699Test.php | 84 +++++++++++------- 5 files changed, 70 insertions(+), 189 deletions(-) diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php index d96649f3746..320f73138eb 100644 --- a/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Child.php @@ -2,90 +2,20 @@ namespace Doctrine\Tests\Models\DDC3699; -use Doctrine\Common\Collections\ArrayCollection; - -/** - * @Entity - * @Table(name="ddc3699_child") - */ +/** @Entity @Table(name="ddc3699_child") */ class DDC3699Child extends DDC3699Parent { const CLASSNAME = __CLASS__; - /** - * @Id - * @Column(type="integer") - */ - protected $id; - - /** - * @Column(type="string") - */ - protected $childField; - - /** - * @OneToOne(targetEntity="DDC3699RelationOne", inversedBy="child") - */ - protected $oneRelation; - - /** - * @OneToMany(targetEntity="DDC3699RelationMany", mappedBy="child") - */ - protected $relations; - - public function getId() - { - return $this->id; - } - - public function setId($id) - { - $this->id = $id; - } - - public function getChildField() - { - return $this->childField; - } - - public function setChildField($childField) - { - $this->childField = $childField; - } - - public function getOneRelation() - { - return $this->oneRelation; - } - - public function setOneRelation($oneRelation) - { - $this->oneRelation = $oneRelation; - } - - public function hasRelation($relation) - { - return $this->relations && $this->relations->contains($relation); - } - - public function addRelation($relation) - { - if (!$this->hasRelation($relation)) { - $this->relations[] = $relation; - } - - return $this; - } + /** @Id @Column(type="integer") */ + public $id; - public function removeRelation($relation) - { - $this->relations->removeElement($relation); + /** @Column(type="string") */ + public $childField; - return $this; - } + /** @OneToOne(targetEntity="DDC3699RelationOne", inversedBy="child") */ + public $oneRelation; - public function getRelations() - { - return $this->relations; - } + /** @OneToMany(targetEntity="DDC3699RelationMany", mappedBy="child") */ + public $relations; } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php index aff175c025a..09cfedaefdf 100644 --- a/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699Parent.php @@ -2,25 +2,11 @@ namespace Doctrine\Tests\Models\DDC3699; -/** - * @MappedSuperclass - */ +/** @MappedSuperclass */ abstract class DDC3699Parent { const CLASSNAME = __CLASS__; - /** - * @Column(type="string") - */ - protected $parentField; - - public function getParentField() - { - return $this->parentField; - } - - public function setParentField($parentField) - { - $this->parentField = $parentField; - } + /** @Column(type="string") */ + public $parentField; } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php index 374ad92bb24..3e0c06a67b2 100644 --- a/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationMany.php @@ -10,35 +10,9 @@ class DDC3699RelationMany { const CLASSNAME = __CLASS__; - /** - * @Id - * @Column(type="integer") - */ - protected $id; + /** @Id @Column(type="integer") */ + public $id; - /** - * @ManyToOne(targetEntity="DDC3699Child", inversedBy="relations") - * @JoinColumn(name="child", referencedColumnName="id") - */ - protected $child; - - public function getId() - { - return $this->id; - } - - public function setId($id) - { - $this->id = $id; - } - - public function getChild() - { - return $this->child; - } - - public function setChild($child) - { - $this->child = $child; - } + /** @ManyToOne(targetEntity="DDC3699Child", inversedBy="relations") */ + public $child; } diff --git a/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php index f28a43abfe0..c63558d351c 100644 --- a/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php +++ b/tests/Doctrine/Tests/Models/DDC3699/DDC3699RelationOne.php @@ -10,34 +10,9 @@ class DDC3699RelationOne { const CLASSNAME = __CLASS__; - /** - * @Id - * @Column(type="integer") - */ - protected $id; + /** @Id @Column(type="integer") */ + public $id; - /** - * @OneToOne(targetEntity="DDC3699Child", mappedBy="oneRelation") - */ - protected $child; - - public function getId() - { - return $this->id; - } - - public function setId($id) - { - $this->id = $id; - } - - public function getChild() - { - return $this->child; - } - - public function setChild($child) - { - $this->child = $child; - } + /** @OneToOne(targetEntity="DDC3699Child", mappedBy="oneRelation") */ + public $child; } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php index b782980eed2..c3366ec810a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php @@ -26,71 +26,87 @@ protected function setUp() } } - private function createChild($id, $relationClass, $relationMethod) + /** + * @group DDC-3699 + */ + public function testMergingParentClassFieldsDoesNotStopMergingScalarFieldsForToOneUninitializedAssociations() { - // element in DB + $id = 1; + $child = new DDC3699Child(); - $child->setId($id); - $child->setChildField('childValue'); - $child->setParentField('parentValue'); - $relation = new $relationClass(); - $relation->setId($id); - $relation->setChild($child); - $child->$relationMethod($relation); + $child->id = $id; + $child->childField = 'childValue'; + $child->parentField = 'parentValue'; + + $relation = new DDC3699RelationOne(); + + $relation->id = $id; + $relation->child = $child ; + $child->oneRelation = $relation; $this->_em->persist($relation); $this->_em->persist($child); $this->_em->flush(); + $this->_em->clear(); - // detach - $this->_em->detach($relation); - $this->_em->detach($child); - } - - /** - * @group DDC-3699 - */ - public function testMergeParentEntityFieldsOne() - { - $id = 1; - $this->createChild($id, DDC3699RelationOne::CLASSNAME, 'setOneRelation'); + // fixtures loaded + /* @var $unManagedChild DDC3699Child */ + $unManagedChild = $this->_em->find(DDC3699Child::CLASSNAME, $id); - $unmanagedChild = $this->_em->find(DDC3699Child::CLASSNAME, $id); - $this->_em->detach($unmanagedChild); + $this->_em->detach($unManagedChild); // make it managed again $this->_em->find(DDC3699Child::CLASSNAME, $id); - $unmanagedChild->setChildField('modifiedChildValue'); - $unmanagedChild->setParentField('modifiedParentValue'); + $unManagedChild->childField = 'modifiedChildValue'; + $unManagedChild->parentField = 'modifiedParentValue'; - $mergedChild = $this->_em->merge($unmanagedChild); + /* @var $mergedChild DDC3699Child */ + $mergedChild = $this->_em->merge($unManagedChild); - $this->assertEquals($mergedChild->getChildField(), 'modifiedChildValue'); - $this->assertEquals($mergedChild->getParentField(), 'modifiedParentValue'); + $this->assertSame($mergedChild->childField, 'modifiedChildValue'); + $this->assertSame($mergedChild->parentField, 'modifiedParentValue'); } /** * @group DDC-3699 */ - public function testMergeParentEntityFieldsMany() + public function testMergingParentClassFieldsDoesNotStopMergingScalarFieldsForToManyUninitializedAssociations() { $id = 2; - $this->createChild($id, DDC3699RelationMany::CLASSNAME, 'addRelation'); + $child = new DDC3699Child(); + + $child->id = $id; + $child->childField = 'childValue'; + $child->parentField = 'parentValue'; + + $relation = new DDC3699RelationMany(); + + $relation->id = $id; + $relation->child = $child ; + $child->relations[] = $relation; + + $this->_em->persist($relation); + $this->_em->persist($child); + $this->_em->flush(); + $this->_em->clear(); + + /* @var $unmanagedChild DDC3699Child */ $unmanagedChild = $this->_em->find(DDC3699Child::CLASSNAME, $id); $this->_em->detach($unmanagedChild); // make it managed again $this->_em->find(DDC3699Child::CLASSNAME, $id); - $unmanagedChild->setChildField('modifiedChildValue'); - $unmanagedChild->setParentField('modifiedParentValue'); + $unmanagedChild->childField = 'modifiedChildValue'; + $unmanagedChild->parentField = 'modifiedParentValue'; + /* @var $mergedChild DDC3699Child */ $mergedChild = $this->_em->merge($unmanagedChild); - $this->assertEquals($mergedChild->getChildField(), 'modifiedChildValue'); - $this->assertEquals($mergedChild->getParentField(), 'modifiedParentValue'); + $this->assertSame($mergedChild->childField, 'modifiedChildValue'); + $this->assertSame($mergedChild->parentField, 'modifiedParentValue'); } } \ No newline at end of file From 173729e5608c065f347c2b078c17d8dfee34cf7d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 15 Jul 2015 21:47:37 +0100 Subject: [PATCH 027/144] DDC-3699 - #1387 - catching specific exceptions --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php index c3366ec810a..58f67dd3c78 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php @@ -1,5 +1,6 @@ _em->getClassMetadata(DDC3699RelationMany::CLASSNAME), $this->_em->getClassMetadata(DDC3699Child::CLASSNAME), )); - } catch (\Exception $e) { + } catch (SchemaException $e) { // should throw error on second because schema is already created } } From 6bc405455ec6c397139189a5f0be918cc2f48399 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 15 Jul 2015 21:51:04 +0100 Subject: [PATCH 028/144] DDC-3699 - #1387 - leveraging the `OrmFunctionalTestCase` API --- .../Tests/ORM/Functional/Ticket/DDC3699Test.php | 13 ++----------- tests/Doctrine/Tests/OrmFunctionalTestCase.php | 6 ++++++ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php index 58f67dd3c78..a2eff6b5720 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3699Test.php @@ -13,18 +13,9 @@ class DDC3597Test extends \Doctrine\Tests\OrmFunctionalTestCase { protected function setUp() { - parent::setUp(); + $this->useModelSet('ddc3699'); - try { - $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(DDC3699Parent::CLASSNAME), - $this->_em->getClassMetadata(DDC3699RelationOne::CLASSNAME), - $this->_em->getClassMetadata(DDC3699RelationMany::CLASSNAME), - $this->_em->getClassMetadata(DDC3699Child::CLASSNAME), - )); - } catch (SchemaException $e) { - // should throw error on second because schema is already created - } + parent::setUp(); } /** diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index f48d635f204..855fac61751 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -139,6 +139,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\DDC117\DDC117Editor', 'Doctrine\Tests\Models\DDC117\DDC117Link', ), + 'ddc3699' => array( + 'Doctrine\Tests\Models\DDC3699\DDC3699Parent', + 'Doctrine\Tests\Models\DDC3699\DDC3699RelationOne', + 'Doctrine\Tests\Models\DDC3699\DDC3699RelationMany', + 'Doctrine\Tests\Models\DDC3699\DDC3699Child', + ), 'stockexchange' => array( 'Doctrine\Tests\Models\StockExchange\Bond', 'Doctrine\Tests\Models\StockExchange\Stock', From 89eed31e79eea56ee99d7bb028fa8aa5fa33edcd Mon Sep 17 00:00:00 2001 From: Bill Schaller Date: Tue, 4 Aug 2015 14:17:50 -0400 Subject: [PATCH 029/144] Merge pull request #1463 from ehimen/paginate-order-by-subselect Fixed issue when paginator orders by a subselect expression Conflicts: tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php --- lib/Doctrine/ORM/Query/SqlWalker.php | 4 + .../Tests/ORM/Functional/PaginationTest.php | 22 ++++++ .../LimitSubqueryOutputWalkerTest.php | 75 +++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 3c4fc8ed1a9..3dea8de4b2e 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1093,6 +1093,10 @@ public function walkOrderByItem($orderByItem) $this->orderedColumnsMap[$sql] = $type; + if ($expr instanceof AST\Subselect) { + return '(' . $sql . ') ' . $type; + } + return $sql . ' ' . $type; } diff --git a/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php b/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php index efd8a7c2c6d..eea2e8375cd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php @@ -669,6 +669,28 @@ public function testCountQueryStripsParametersInSelect() $this->assertCount(9, $paginator); } + /** + * @dataProvider useOutputWalkersAndFetchJoinCollection + */ + public function testPaginationWithSubSelectOrderByExpression($useOutputWalker, $fetchJoinCollection) + { + $query = $this->_em->createQuery( + "SELECT u, + ( + SELECT MAX(a.version) + FROM Doctrine\\Tests\\Models\\CMS\\CmsArticle a + WHERE a.user = u + ) AS HIDDEN max_version + FROM Doctrine\\Tests\\Models\\CMS\\CmsUser u + ORDER BY max_version DESC" + ); + + $paginator = new Paginator($query, $fetchJoinCollection); + $paginator->setUseOutputWalkers($useOutputWalker); + + $this->assertCount(9, $paginator->getIterator()); + } + public function populate() { $groups = array(); diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php index 205be7d1f8c..40fe1575af6 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php @@ -350,5 +350,80 @@ public function testLimitSubqueryWithOrderByAndSubSelectInWhereClausePgSql() $query->getSQL() ); } + + /** + * Tests order by on a subselect expression (mysql). + */ + public function testLimitSubqueryOrderBySubSelectOrderByExpression() + { + $this->entityManager->getConnection()->setDatabasePlatform(new MysqlPlatform()); + + $query = $this->entityManager->createQuery( + 'SELECT a, + ( + SELECT MIN(bp.title) + FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost bp + WHERE bp.author = a + ) AS HIDDEN first_blog_post + FROM Doctrine\Tests\ORM\Tools\Pagination\Author a + ORDER BY first_blog_post DESC' + ); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker'); + + $this->assertEquals( + 'SELECT DISTINCT id_0 FROM (SELECT a0_.id AS id_0, a0_.name AS name_1, (SELECT MIN(m1_.title) AS dctrn__1 FROM MyBlogPost m1_ WHERE m1_.author_id = a0_.id) AS sclr_2 FROM Author a0_) dctrn_result ORDER BY sclr_2 DESC', + $query->getSQL() + ); + } + + /** + * Tests order by on a subselect expression invoking RowNumberOverFunction (postgres). + */ + public function testLimitSubqueryOrderBySubSelectOrderByExpressionPg() + { + $this->entityManager->getConnection()->setDatabasePlatform(new PostgreSqlPlatform()); + + $query = $this->entityManager->createQuery( + 'SELECT a, + ( + SELECT MIN(bp.title) + FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost bp + WHERE bp.author = a + ) AS HIDDEN first_blog_post + FROM Doctrine\Tests\ORM\Tools\Pagination\Author a + ORDER BY first_blog_post DESC' + ); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker'); + + $this->assertEquals( + 'SELECT DISTINCT id_0, MIN(sclr_3) AS dctrn_minrownum FROM (SELECT a0_.id AS id_0, a0_.name AS name_1, (SELECT MIN(m1_.title) AS dctrn__1 FROM MyBlogPost m1_ WHERE m1_.author_id = a0_.id) AS sclr_2, ROW_NUMBER() OVER(ORDER BY (SELECT MIN(m1_.title) AS dctrn__2 FROM MyBlogPost m1_ WHERE m1_.author_id = a0_.id) DESC) AS sclr_3 FROM Author a0_) dctrn_result GROUP BY id_0 ORDER BY dctrn_minrownum ASC', + $query->getSQL() + ); + } + + /** + * Tests order by on a subselect expression invoking RowNumberOverFunction (oracle). + */ + public function testLimitSubqueryOrderBySubSelectOrderByExpressionOracle() + { + $this->entityManager->getConnection()->setDatabasePlatform(new OraclePlatform()); + + $query = $this->entityManager->createQuery( + 'SELECT a, + ( + SELECT MIN(bp.title) + FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost bp + WHERE bp.author = a + ) AS HIDDEN first_blog_post + FROM Doctrine\Tests\ORM\Tools\Pagination\Author a + ORDER BY first_blog_post DESC' + ); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker'); + + $this->assertEquals( + 'SELECT DISTINCT ID_0, MIN(SCLR_3) AS dctrn_minrownum FROM (SELECT a0_.id AS ID_0, a0_.name AS NAME_1, (SELECT MIN(m1_.title) AS dctrn__1 FROM MyBlogPost m1_ WHERE m1_.author_id = a0_.id) AS SCLR_2, ROW_NUMBER() OVER(ORDER BY (SELECT MIN(m1_.title) AS dctrn__2 FROM MyBlogPost m1_ WHERE m1_.author_id = a0_.id) DESC) AS SCLR_3 FROM Author a0_) dctrn_result GROUP BY ID_0 ORDER BY dctrn_minrownum ASC', + $query->getSQL() + ); + } } From 6366d190d70ab8831d465c93e91953d30d957a9b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Aug 2015 13:57:29 +0200 Subject: [PATCH 030/144] [DCOM-293] Fix security misconfiguration vulnerability allowing local remote arbitrary code execution. --- lib/Doctrine/ORM/Cache/Region/FileLockRegion.php | 3 ++- .../ORM/Tools/Console/Command/ConvertMappingCommand.php | 2 +- .../ORM/Tools/Console/Command/GenerateProxiesCommand.php | 2 +- lib/Doctrine/ORM/Tools/EntityGenerator.php | 3 ++- lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php | 3 ++- lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php | 5 +++-- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php b/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php index 69167bc901a..d8d4b26948d 100644 --- a/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php @@ -61,7 +61,7 @@ class FileLockRegion implements ConcurrentRegion */ public function __construct(Region $region, $directory, $lockLifetime) { - if ( ! is_dir($directory) && ! @mkdir($directory, 0777, true)) { + if ( ! is_dir($directory) && ! @mkdir($directory, 0775, true)) { throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory)); } @@ -242,6 +242,7 @@ public function lock(CacheKey $key) if ( ! @file_put_contents($filename, $lock->value, LOCK_EX)) { return null; } + chmod($filename, 0664); return $lock; } diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php index 1f97a5074c7..b229f4a6c08 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php @@ -137,7 +137,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // Process destination directory if ( ! is_dir($destPath = $input->getArgument('dest-path'))) { - mkdir($destPath, 0777, true); + mkdir($destPath, 0775, true); } $destPath = realpath($destPath); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php index 52211879411..21edb9dab83 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php @@ -79,7 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if ( ! is_dir($destPath)) { - mkdir($destPath, 0777, true); + mkdir($destPath, 0775, true); } $destPath = realpath($destPath); diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index ec83c4e4949..9027d9aa5b7 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -364,7 +364,7 @@ public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) $dir = dirname($path); if ( ! is_dir($dir)) { - mkdir($dir, 0777, true); + mkdir($dir, 0775, true); } $this->isNew = !file_exists($path) || (file_exists($path) && $this->regenerateEntityIfExists); @@ -389,6 +389,7 @@ public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) } elseif ( ! $this->isNew && $this->updateEntityIfExists) { file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path)); } + chmod($path, 0664); } /** diff --git a/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php b/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php index f94292afc01..f431588fb09 100644 --- a/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php @@ -147,11 +147,12 @@ public function writeEntityRepositoryClass($fullClassName, $outputDirectory) $dir = dirname($path); if ( ! is_dir($dir)) { - mkdir($dir, 0777, true); + mkdir($dir, 0775, true); } if ( ! file_exists($path)) { file_put_contents($path, $code); + chmod($path, 0664); } } diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php index 3e96af821b9..b2ed435bc42 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php @@ -130,7 +130,7 @@ public function setOutputDir($dir) public function export() { if ( ! is_dir($this->_outputDir)) { - mkdir($this->_outputDir, 0777, true); + mkdir($this->_outputDir, 0775, true); } foreach ($this->_metadata as $metadata) { @@ -139,12 +139,13 @@ public function export() $path = $this->_generateOutputPath($metadata); $dir = dirname($path); if ( ! is_dir($dir)) { - mkdir($dir, 0777, true); + mkdir($dir, 0775, true); } if (file_exists($path) && !$this->_overwriteExistingFiles) { throw ExportException::attemptOverwriteExistingFile($path); } file_put_contents($path, $output); + chmod($path, 0664); } } } From b6e5464b98332605dd3260cc3dae411c37b32db8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Aug 2015 14:59:36 +0200 Subject: [PATCH 031/144] Fix version --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index fee5b82c0a1..20e37303ba6 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.6.0-DEV'; + const VERSION = '2.5.1-DEV'; /** * Compares a Doctrine version with the current one. From e6a83bedbe67579cb0bfb688e982e617943a2945 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Aug 2015 14:59:39 +0200 Subject: [PATCH 032/144] Release 2.5.1 --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 20e37303ba6..2fff86432b6 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.1-DEV'; + const VERSION = '2.5.1'; /** * Compares a Doctrine version with the current one. From 8070b50150dcbc5da7bebbce4dd35bdec8ce4f08 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Aug 2015 14:59:39 +0200 Subject: [PATCH 033/144] Bump version to 2.5.2 --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 2fff86432b6..5085392d839 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.1'; + const VERSION = '2.5.2-DEV'; /** * Compares a Doctrine version with the current one. From eaf8b1c7cac23095c020f07b18ff0432a0492549 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 19 Sep 2015 01:15:39 +0200 Subject: [PATCH 034/144] Fix tests related to caches, as per doctrine/cache 1.5.0 changes Backports #1510 Fixes DDC-3908 https://github.com/doctrine/cache/commit/dd47003641aa5425820c0ec8a6f4a85e7412ffcd removes the 'DoctrineNamespaceCacheKey[]' entry from the cache. Thus, all tests counting cache entries were off by one. --- tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php | 6 +++--- tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php | 2 +- tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index 443c8c89a0d..cf35d8617b8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -45,12 +45,12 @@ public function testQueryCache_DependsOnHints() $query->setQueryCacheDriver($cache); $query->getResult(); - $this->assertEquals(2, $this->getCacheSize($cache)); + $this->assertEquals(1, $this->getCacheSize($cache)); $query->setHint('foo', 'bar'); $query->getResult(); - $this->assertEquals(3, $this->getCacheSize($cache)); + $this->assertEquals(2, $this->getCacheSize($cache)); return $query; } @@ -112,7 +112,7 @@ public function testQueryCache_NoHitSaveParserResult() $users = $query->getResult(); $data = $this->cacheDataReflection->getValue($cache); - $this->assertEquals(2, count($data)); + $this->assertEquals(1, count($data)); $this->assertInstanceOf('Doctrine\ORM\Query\ParserResult', array_pop($data)); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php index 08ca99dd910..810271acfbc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php @@ -141,7 +141,7 @@ public function testNativeQueryResultCaching() $this->assertEquals(0, $this->getCacheSize($cache)); $query->getResult(); - $this->assertEquals(2, $this->getCacheSize($cache)); + $this->assertEquals(1, $this->getCacheSize($cache)); return $query; } diff --git a/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php b/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php index eddbc4c4212..dd76a93a8cb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php @@ -343,18 +343,18 @@ public function testQueryCache_DependsOnFilters() $query->setQueryCacheDriver($cache); $query->getResult(); - $this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache))); + $this->assertEquals(1, sizeof($cacheDataReflection->getValue($cache))); $conf = $this->_em->getConfiguration(); $conf->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter"); $this->_em->getFilters()->enable("locale"); $query->getResult(); - $this->assertEquals(3, sizeof($cacheDataReflection->getValue($cache))); + $this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache))); // Another time doesn't add another cache entry $query->getResult(); - $this->assertEquals(3, sizeof($cacheDataReflection->getValue($cache))); + $this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache))); } public function testQueryGeneration_DependsOnFilters() From a3ece3b419fa7bfe50ccf2a48d83545aa007fbe8 Mon Sep 17 00:00:00 2001 From: neoglez Date: Mon, 21 Sep 2015 08:52:46 +0200 Subject: [PATCH 035/144] UnitTest backport of "Failing test case for broken paginator case" UnitTest backport of "Failing test case for broken paginator case" ( https://github.com/doctrine/doctrine2/commit/192da148428e62cea53fa2b918daf14f85cd7286 ). The branch 2.x is very important because it's related to ZF2 doctrine module (see https://github.com/doctrine/DoctrineORMModule/blob/master/composer.json ) and specially this issue affects the use case of extending the ZF2 user entity defined in ZfcUser ( https://github.com/ZF-Commons/ZfcUser ). This test is meant to show the need of the backport of https://github.com/doctrine/doctrine2/commit/e501137d1afff2c08963828b61b0b8b6668edd83 --- .../LimitSubqueryOutputWalkerTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php index 40fe1575af6..95b9793f632 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php @@ -350,7 +350,26 @@ public function testLimitSubqueryWithOrderByAndSubSelectInWhereClausePgSql() $query->getSQL() ); } + + /** + * This tests ordering by property that has the 'declared' field. + */ + public function testLimitSubqueryOrderByFieldFromMappedSuperclass() + { + $this->entityManager->getConnection()->setDatabasePlatform(new MySqlPlatform()); + + // now use the third one in query + $query = $this->entityManager->createQuery( + 'SELECT b FROM Doctrine\Tests\ORM\Tools\Pagination\Banner b ORDER BY b.id DESC' + ); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker'); + $this->assertEquals( + 'SELECT DISTINCT id_0 FROM (SELECT b0_.id AS id_0, b0_.name AS name_1 FROM Banner b0_) dctrn_result ORDER BY id_0 DESC', + $query->getSQL() + ); + } + /** * Tests order by on a subselect expression (mysql). */ From ed637e51b9657114e34dc54a999d2278be6ef572 Mon Sep 17 00:00:00 2001 From: neoglez Date: Mon, 21 Sep 2015 08:58:30 +0200 Subject: [PATCH 036/144] Backport of "fix aliasing of property in OrderBy from MappedSuperclass" Backport of "LimitSubqueryOutputWalker: fix aliasing of property in OrderBy from MappedSuperclass" ( https://github.com/doctrine/doctrine2/commit/e501137d1afff2c08963828b61b0b8b6668edd83 ) See my comment on https://github.com/neoglez/doctrine2/commit/a3ece3b419fa7bfe50ccf2a48d83545aa007fbe8 --- .../ORM/Tools/Pagination/LimitSubqueryOutputWalker.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php b/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php index 22d0628ba07..b1d7bd9eb8d 100644 --- a/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php +++ b/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php @@ -438,7 +438,10 @@ private function rebuildOrderByClauseForOuterScope(OrderByClause $orderByClause) // Field was declared in a parent class, so we need to get the proper SQL table alias // for the joined parent table. $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']); - $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias); + if (!$otherClassMetadata->isMappedSuperclass) { + $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias); + + } } // Compose search/replace patterns From ef73249bc7a3cc1ebbb03621225b83237d33a2ea Mon Sep 17 00:00:00 2001 From: neoglez Date: Mon, 21 Sep 2015 09:32:54 +0200 Subject: [PATCH 037/144] Entity to test a mapped superclass Backport of https://github.com/doctrine/doctrine2/pull/1377/files to branch 2.5 --- .../Tools/Pagination/PaginationTestCase.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php index f1553c13c7b..f1e56ff0ca7 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php @@ -168,4 +168,22 @@ class Avatar public $image_width; /** @Column(type="string", length=255) */ public $image_alt_desc; -} \ No newline at end of file +} + +/** @MappedSuperclass */ +abstract class Identified +{ + /** @Id @Column(type="integer") @GeneratedValue */ + private $id; + public function getId() + { + return $this->id; + } +} + +/** @Entity */ +class Banner extends Identified +{ + /** @Column(type="string") */ + public $name; +} From 16802d26142d443228b110ef4d817bd352f35432 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 22 Feb 2015 20:13:44 +0100 Subject: [PATCH 038/144] Allow symfony 3.0 components Tests should tell if any deprecated interfaces of Symfony are used. If not, then the bundle is defacto compatible with 3.0 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1e0ceb08e19..dd7544aeb80 100644 --- a/composer.json +++ b/composer.json @@ -20,10 +20,10 @@ "doctrine/instantiator": "~1.0.1", "doctrine/common": ">=2.5-dev,<2.6-dev", "doctrine/cache": "~1.4", - "symfony/console": "~2.5" + "symfony/console": "~2.5|~3.0" }, "require-dev": { - "symfony/yaml": "~2.1", + "symfony/yaml": "~2.3|~3.0", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master" }, From ebbc443ec37d5f6fdb54f9a352aec50d32ee69cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Fri, 11 Sep 2015 17:17:28 +0200 Subject: [PATCH 039/144] Fixed wrong property name --- lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 413ac60bb2d..6fc5b061156 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -438,8 +438,8 @@ public function generateSelectClause($tableAliases = array()) $sql .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName']; } else if (isset($this->metaMappings[$columnName])) { $sql .= $this->metaMappings[$columnName]; - } else if (isset($this->discriminatorColumn[$columnName])) { - $sql .= $this->discriminatorColumn[$columnName]; + } else if (isset($this->discriminatorColumns[$columnName])) { + $sql .= $this->discriminatorColumns[$columnName]; } $sql .= " AS " . $columnName; From f2f53ba9dce26bc9fbaf8a5386674fecfb605d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Mon, 14 Sep 2015 18:34:55 +0200 Subject: [PATCH 040/144] Fixed wrong variable used as array key --- lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 6fc5b061156..804a208efbe 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -438,8 +438,8 @@ public function generateSelectClause($tableAliases = array()) $sql .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName']; } else if (isset($this->metaMappings[$columnName])) { $sql .= $this->metaMappings[$columnName]; - } else if (isset($this->discriminatorColumns[$columnName])) { - $sql .= $this->discriminatorColumns[$columnName]; + } else if (isset($this->discriminatorColumns[$dqlAlias])) { + $sql .= $this->discriminatorColumns[$dqlAlias]; } $sql .= " AS " . $columnName; From 1eb9c8a7f6ab27e39da50ae7b1b14872f04577a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Mon, 14 Sep 2015 18:35:49 +0200 Subject: [PATCH 041/144] Added test --- .../Tests/Models/DDC3899/DDC3899Contract.php | 25 +++++++++++++++++++ .../Models/DDC3899/DDC3899FixContract.php | 12 +++++++++ .../Models/DDC3899/DDC3899FlexContract.php | 15 +++++++++++ .../Tests/Models/DDC3899/DDC3899User.php | 16 ++++++++++++ .../Tests/ORM/Functional/NativeQueryTest.php | 16 ++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 tests/Doctrine/Tests/Models/DDC3899/DDC3899Contract.php create mode 100644 tests/Doctrine/Tests/Models/DDC3899/DDC3899FixContract.php create mode 100644 tests/Doctrine/Tests/Models/DDC3899/DDC3899FlexContract.php create mode 100644 tests/Doctrine/Tests/Models/DDC3899/DDC3899User.php diff --git a/tests/Doctrine/Tests/Models/DDC3899/DDC3899Contract.php b/tests/Doctrine/Tests/Models/DDC3899/DDC3899Contract.php new file mode 100644 index 00000000000..ddccd8c9a48 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3899/DDC3899Contract.php @@ -0,0 +1,25 @@ +assertSQLEquals('u.id AS id0, u.status AS status1, u.username AS username2, u.name AS name3, u.email_id AS email_id4', (string)$rsm); } + + /** + * @group DDC-3899 + */ + public function testGenerateSelectClauseWithDiscriminatorColumn() + { + $rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT); + $rsm->addEntityResult('Doctrine\Tests\Models\DDC3899\DDC3899User', 'u'); + $rsm->addJoinedEntityResult('Doctrine\Tests\Models\DDC3899\DDC3899FixContract', 'c', 'u', 'contracts'); + $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); + $rsm->setDiscriminatorColumn('c', $this->platform->getSQLResultCasing('discr')); + + $selectClause = $rsm->generateSelectClause(array('u' => 'u1', 'c' => 'c1')); + + $this->assertSQLEquals('u1.id as id, c1.discr as discr', $selectClause); + } } From 567220ef7193bf4360d3176102a9e5d5b5d891d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bundyra?= Date: Wed, 8 Apr 2015 10:49:21 +0100 Subject: [PATCH 042/144] prevent duplicate unique index --- lib/Doctrine/ORM/Tools/SchemaTool.php | 10 +++++ .../Tests/ORM/Tools/SchemaToolTest.php | 39 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index fb1ebfcc4e4..b8e62739bd0 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -21,6 +21,7 @@ use Doctrine\ORM\ORMException; use Doctrine\DBAL\Schema\Comparator; +use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; @@ -274,6 +275,15 @@ public function getSchemaFromMetadata(array $classes) if (isset($class->table['uniqueConstraints'])) { foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) { + $uniqIndex = new Index($indexName, $indexData['columns'], true, false, [], isset($indexData['options']) ? $indexData['options'] : []); + + foreach ($table->getIndexes() as $tableIndexName => $tableIndex) { + if ($tableIndex->isFullfilledBy($uniqIndex)) { + $table->dropIndex($tableIndexName); + break; + } + } + $table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, isset($indexData['options']) ? $indexData['options'] : array()); } } diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php index abae9977f71..2bd9916ceef 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php @@ -116,6 +116,28 @@ public function testNullDefaultNotAddedToCustomSchemaOptions() $this->assertSame(array(), $customSchemaOptions); } + + /** + * @group DDC-3671 + */ + public function testSchemaHasProperIndexesFromUniqueConstraintAnnotation() + { + $em = $this->_getTestEntityManager(); + $schemaTool = new SchemaTool($em); + + $classes = [ + $em->getClassMetadata(__NAMESPACE__ . '\\UniqueConstraintAnnotationModel'), + ]; + + $schema = $schemaTool->getSchemaFromMetadata($classes); + + $this->assertTrue($schema->hasTable('unique_constraint_annotation_table')); + $table = $schema->getTable('unique_constraint_annotation_table'); + + $this->assertEquals(2, count($table->getIndexes())); + $this->assertTrue($table->hasIndex('primary')); + $this->assertTrue($table->hasIndex('uniq_hash')); + } } /** @@ -148,3 +170,20 @@ public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs) $this->schemaCalled = true; } } + +/** + * @Entity + * @Table(name="unique_constraint_annotation_table", uniqueConstraints={ + * @UniqueConstraint(name="uniq_hash", columns={"hash"}) + * }) + */ +class UniqueConstraintAnnotationModel +{ + /** @Id @Column */ + private $id; + + /** + * @Column(name="hash", type="string", length=8, nullable=false, unique=true) + */ + private $hash; +} From 17ae8d1b2d579225453531dfc1dd60ba36e3b98e Mon Sep 17 00:00:00 2001 From: Pantel Date: Sat, 31 Oct 2015 16:02:14 +0100 Subject: [PATCH 043/144] [DDC-3711] Correct Error on manyToMany with composite primary key --- lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 95d148318fc..eab510b1e7b 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -514,9 +514,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } - - $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } if (isset($joinTableElement['inverseJoinColumns'])) { @@ -524,9 +523,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } - - $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $mapping['joinTable'] = $joinTable; From 581e1638a2cc535e58b9a7c7562375493bfce3e2 Mon Sep 17 00:00:00 2001 From: Pantel Date: Sat, 31 Oct 2015 17:15:06 +0100 Subject: [PATCH 044/144] [DDC-3711] add Tests that check if the association key are composite --- .../Doctrine.Tests.Models.DDC3711.DDC3711EntityB.dcm.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityB.dcm.yml diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityB.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityB.dcm.yml new file mode 100644 index 00000000000..24ec96932c6 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityB.dcm.yml @@ -0,0 +1,8 @@ +Doctrine\Tests\Models\DDC3711\DDC3711EntityB: + type: entity + table: ddc3711.entityB + id: + id1: + type: int + id2: + type: int From d606efd4ebb5cebab7968c22db47b030a506471f Mon Sep 17 00:00:00 2001 From: Pantel Date: Sat, 31 Oct 2015 17:19:39 +0100 Subject: [PATCH 045/144] [DDC-3711] add Test that check if the association key are composite --- .../Tests/Models/DDC3711/DDC3711EntityA.php | 78 +++++++++++++++++++ .../Tests/Models/DDC3711/DDC3711EntityB.php | 75 ++++++++++++++++++ .../ORM/Functional/Ticket/DDC3711Test.php | 30 +++++++ ...ests.Models.DDC3711.DDC3711EntityA.dcm.yml | 23 ++++++ 4 files changed, 206 insertions(+) create mode 100644 tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityA.php create mode 100644 tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityB.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityA.dcm.yml diff --git a/tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityA.php b/tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityA.php new file mode 100644 index 00000000000..3146969ed57 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityA.php @@ -0,0 +1,78 @@ + + */ +namespace Doctrine\Tests\Models\DDC3711; + +use Doctrine\Common\Collections\ArrayCollection; + +class DDC3711EntityA +{ + /** + * @var int + */ + private $id1; + + /** + * @var int + */ + private $id2; + + /** + * @var ArrayCollection + */ + private $entityB; + + /** + * @return mixed + */ + public function getId1() + { + return $this->id1; + } + + /** + * @param mixed $id1 + */ + public function setId1($id1) + { + $this->id1 = $id1; + } + + /** + * @return mixed + */ + public function getId2() + { + return $this->id2; + } + + /** + * @param mixed $id2 + */ + public function setId2($id2) + { + $this->id2 = $id2; + } + + /** + * @return ArrayCollection + */ + public function getEntityB() + { + return $this->entityB; + } + + /** + * @param ArrayCollection $entityB + * + * @return DDC3711EntityA + */ + public function addEntityB($entityB) + { + $this->entityB[] = $entityB; + + return $this; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityB.php b/tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityB.php new file mode 100644 index 00000000000..ff589e0923a --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityB.php @@ -0,0 +1,75 @@ + + */ +namespace Doctrine\Tests\Models\DDC3711; + +use Doctrine\Common\Collections\ArrayCollection; + +class DDC3711EntityB +{ + /** + * @var int + */ + private $id1; + + /** + * @var int + */ + private $id2; + + /** + * @var ArrayCollection + */ + private $entityA; + + /** + * @return int + */ + public function getId1() + { + return $this->id1; + } + + /** + * @param int $id1 + */ + public function setId1($id1) + { + $this->id1 = $id1; + } + + /** + * @return int + */ + public function getId2() + { + return $this->id2; + } + + /** + * @param int $id2 + */ + public function setId2($id2) + { + $this->id2 = $id2; + } + + /** + * @return ArrayCollection + */ + public function getEntityA() + { + return $this->entityA; + } + + /** + * @param ArrayCollection $entityA + */ + public function addEntityA($entityA) + { + $this->entityA[] = $entityA; + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php new file mode 100644 index 00000000000..52c96154f22 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php @@ -0,0 +1,30 @@ + + */ + +namespace Doctrine\Tests\ORM\Functional\Ticket; + + +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\Tests\ORM\Mapping\YamlMappingDriverTest; + +class DDC3711Test extends YamlMappingDriverTest +{ + public function testCompositeKeyForJoinTableInManyToManyCreation() + { + $yamlDriver = $this->_loadDriver(); + + $em = $this->_getTestEntityManager(); + $em->getConfiguration()->setMetadataDriverImpl($yamlDriver); + $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); + $factory->setEntityManager($em); + + $entityA = new ClassMetadata('Doctrine\Tests\Models\DDC3711\DDC3711EntityA'); + $entityA = $factory->getMetadataFor('Doctrine\Tests\Models\DDC3711\DDC3711EntityA'); + + $this->assertEquals(array('link_a_id1' => "id1", 'link_a_id2' => "id2"), $entityA->associationMappings['entityB']['relationToSourceKeyColumns']); + $this->assertEquals(array('link_b_id1' => "id1", 'link_b_id2' => "id2"), $entityA->associationMappings['entityB']['relationToTargetKeyColumns']); + + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityA.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityA.dcm.yml new file mode 100644 index 00000000000..c8a87331dcb --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC3711.DDC3711EntityA.dcm.yml @@ -0,0 +1,23 @@ +Doctrine\Tests\Models\DDC3711\DDC3711EntityA: + type: entity + table: ddc3711.entityA + id: + id1: + type: int + id2: + type: int + manyToMany: + entityB: + targetEntity: Doctrine\Tests\Models\DDC3711\DDC3711EntityB + joinTable: + name: link + joinColumns: + link_a_id1: + referencedColumnName: id1 + link_a_id2: + referencedColumnName: id2 + inverseJoinColumns: + link_b_id1: + referencedColumnName: id1 + link_b_id2: + referencedColumnName: id2 \ No newline at end of file From b9af1c8fa55efb2b669ebe7bee3ebee5b5ca9ff8 Mon Sep 17 00:00:00 2001 From: Klein Thomas Date: Tue, 6 Oct 2015 12:04:48 +0200 Subject: [PATCH 046/144] Update Upgrade.md after minor bc break in 2.5.1 The introduction of the second parameter in EntityRepository#createQueryBuilder generates a runtime notice if you have a sub-class of EntityRepository that has a second parameter in the createQueryBuilder method --- UPGRADE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UPGRADE.md b/UPGRADE.md index 052d879fecf..d83b301d8d4 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,9 @@ +# Upgrade to 2.5.1 + +## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature + +Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder() + # Upgrade to 2.5 ## Minor BC BREAK: discriminator map must now include all non-transient classes From bc82e94afc09be0f1906bf810d1e7d19d8575306 Mon Sep 17 00:00:00 2001 From: Klein Thomas Date: Fri, 9 Oct 2015 09:05:52 +0200 Subject: [PATCH 047/144] Move to 2.5 section --- UPGRADE.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index d83b301d8d4..ed9a538b3ec 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,9 +1,3 @@ -# Upgrade to 2.5.1 - -## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature - -Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder() - # Upgrade to 2.5 ## Minor BC BREAK: discriminator map must now include all non-transient classes @@ -144,6 +138,10 @@ From now on, the resultset will look like this: ... ) +## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature + +Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder() + # Upgrade to 2.4 ## BC BREAK: Compatibility Bugfix in PersistentCollection#matching() From 464b5fdbfbbeb4a65465ac173c4c5d90960f41ff Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 23 Nov 2015 13:44:25 +0100 Subject: [PATCH 048/144] Release 2.5.2 --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 5085392d839..95faf5787de 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.2-DEV'; + const VERSION = '2.5.2'; /** * Compares a Doctrine version with the current one. From 2983081a602905917aa10ca26331661682b0b25e Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 23 Nov 2015 13:44:56 +0100 Subject: [PATCH 049/144] Bumping current dev version to 2.5.3-DEV --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 95faf5787de..c00ec6fe2e1 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.2'; + const VERSION = '2.5.3-DEV'; /** * Compares a Doctrine version with the current one. From 752d4f9eac0c5a991f3114852d2414c123128a5b Mon Sep 17 00:00:00 2001 From: oprokidnev Date: Fri, 27 Nov 2015 16:00:56 +0500 Subject: [PATCH 050/144] Target entity resolver for DQL Since we have target entity resolver in doctrine this class check is not enought. To gain interface resolution it is better to add interface check in addition to class_check here. --- lib/Doctrine/ORM/Query/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 442dbac80b9..695e7ed1089 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -965,7 +965,7 @@ public function AbstractSchemaName() $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } - $exists = class_exists($schemaName, true); + $exists = class_exists($schemaName, true) || interface_exists($schemaName, true); if ( ! $exists) { $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token); From 6e3ce26429b4309f311a0ea7d77997639c26c0a5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Dec 2015 19:15:48 +0100 Subject: [PATCH 051/144] Correcting minor test case incompatibility with XDebug 2.4.x In PHP 5.x + XDebug < 2.4, the output would be "string:..." In PHP 7.x + XDebug >= 2.4, the output would be "the/file/name.php:11:string:..." This is an improvement in XDebug that is quite annoying for our purposes, but is actually welcome to most users anyway. This commit simply fixes that incompatibility --- .../Tests/ORM/Tools/Console/Command/RunDqlCommandTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/Console/Command/RunDqlCommandTest.php b/tests/Doctrine/Tests/ORM/Tools/Console/Command/RunDqlCommandTest.php index 00aeea3a848..8a78e497035 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Console/Command/RunDqlCommandTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Console/Command/RunDqlCommandTest.php @@ -85,6 +85,6 @@ public function testWillShowQuery() )) ); - $this->assertStringMatchesFormat('string%sSELECT %a', $this->tester->getDisplay()); + $this->assertStringMatchesFormat('%Astring%sSELECT %a', $this->tester->getDisplay()); } } From 216c4662333780fb7f09b71795ed75478286d0fa Mon Sep 17 00:00:00 2001 From: bilouwan Date: Fri, 27 Nov 2015 17:28:45 +0100 Subject: [PATCH 052/144] Unit test & fix for merge versionned entity --- lib/Doctrine/ORM/UnitOfWork.php | 14 +++- .../Models/VersionedOneToMany/Article.php | 47 +++++++++++++ .../Models/VersionedOneToMany/Category.php | 49 ++++++++++++++ .../MergeVersionedOneToManyTest.php | 67 +++++++++++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php create mode 100644 tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 194e45e30ae..1818b86cdff 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1870,7 +1870,7 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass } } - if ($class->isVersioned) { + if ($class->isVersioned && !($this->isNotInitializedProxy($managedCopy) || $this->isNotInitializedProxy($entity))) { $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); $entityVersion = $reflField->getValue($entity); @@ -1908,6 +1908,18 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass return $managedCopy; } + /** + * Tests if an entity is a non initialized proxy class + * + * @param $entity + * + * @return bool + */ + private function isNotInitializedProxy($entity) + { + return $entity instanceof Proxy && !$entity->__isInitialized(); + } + /** * Sets/adds associated managed copies into the previous entity's association field * diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php b/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php new file mode 100644 index 00000000000..c284fc15fcf --- /dev/null +++ b/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php @@ -0,0 +1,47 @@ +tags = new ArrayCollection(); + } +} diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php b/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php new file mode 100644 index 00000000000..5ace0460383 --- /dev/null +++ b/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php @@ -0,0 +1,49 @@ +articles = new ArrayCollection(); + } + + +} diff --git a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php new file mode 100644 index 00000000000..c5f022c52b4 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php @@ -0,0 +1,67 @@ +_schemaTool->createSchema( + [ + $this->_em->getClassMetadata(Category::class), + $this->_em->getClassMetadata(Article::class), + ] + ); + } catch (ORMException $e) { + } + } + + /** + * This test case tests that a versionable entity, that has a oneToOne relationship as it's id can be created + * without this bug fix (DDC-3318), you could not do this + */ + public function testSetVersionOnCreate() + { + $category = new Category(); + $category->name = 'Category'; + + $article = new Article(); + $article->name = 'Article'; + $article->category = $category; + + $this->_em->persist($article); + $this->_em->flush(); + $this->_em->clear(); + + $mergeSucceed = false; + try { + $articleMerged = $this->_em->merge($article); + $mergeSucceed = true; + } catch (OptimisticLockException $e) { + } + $this->assertTrue($mergeSucceed); + + $articleMerged->name = 'Article Merged'; + + $flushSucceed = false; + try { + $this->_em->flush(); + $flushSucceed = true; + } catch (OptimisticLockException $e) { + } + $this->assertTrue($flushSucceed); + } +} From 7071984559f1afd3e74f53c510aeff5d89572961 Mon Sep 17 00:00:00 2001 From: bilouwan Date: Mon, 30 Nov 2015 10:35:42 +0100 Subject: [PATCH 053/144] Fix compatibility with php5.4 --- .../MergeVersionedOneToManyTest.php | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php index c5f022c52b4..178e16ab08f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php @@ -6,7 +6,6 @@ use Doctrine\ORM\ORMException; use Doctrine\Tests\Models\VersionedOneToMany\Article; use Doctrine\Tests\Models\VersionedOneToMany\Category; -use Doctrine\Tests\Models\VersionedOneToMany\Tag; /** * @@ -21,8 +20,8 @@ protected function setUp() try { $this->_schemaTool->createSchema( [ - $this->_em->getClassMetadata(Category::class), - $this->_em->getClassMetadata(Article::class), + $this->_em->getClassMetadata('Doctrine\Tests\Models\VersionedOneToMany\Category'), + $this->_em->getClassMetadata('Doctrine\Tests\Models\VersionedOneToMany\Article'), ] ); } catch (ORMException $e) { @@ -46,22 +45,11 @@ public function testSetVersionOnCreate() $this->_em->flush(); $this->_em->clear(); - $mergeSucceed = false; - try { - $articleMerged = $this->_em->merge($article); - $mergeSucceed = true; - } catch (OptimisticLockException $e) { - } - $this->assertTrue($mergeSucceed); + $articleMerged = $this->_em->merge($article); $articleMerged->name = 'Article Merged'; - $flushSucceed = false; - try { - $this->_em->flush(); - $flushSucceed = true; - } catch (OptimisticLockException $e) { - } - $this->assertTrue($flushSucceed); + $this->_em->flush(); + $this->assertEquals(2, $articleMerged->version); } } From e173c930ec03bbbf270eba4dd085ad2351a114f0 Mon Sep 17 00:00:00 2001 From: bilouwan Date: Wed, 2 Dec 2015 14:09:14 +0100 Subject: [PATCH 054/144] Fix superflous whitespaces & empty lines --- tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php | 2 -- tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php | 4 ---- .../Tests/ORM/Functional/MergeVersionedOneToManyTest.php | 4 ++-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php b/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php index c284fc15fcf..bd0cfdf4803 100644 --- a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php +++ b/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php @@ -5,7 +5,6 @@ use Doctrine\Common\Collections\ArrayCollection; /** - * * @Entity * @Table(name="article") */ @@ -38,7 +37,6 @@ class Article /** * Category constructor. - * */ public function __construct() { diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php b/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php index 5ace0460383..d4dbdefbba5 100644 --- a/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php +++ b/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php @@ -5,7 +5,6 @@ use Doctrine\Common\Collections\ArrayCollection; /** - * * @Entity * @Table(name="category") */ @@ -38,12 +37,9 @@ class Category /** * Category constructor. - * */ public function __construct() { $this->articles = new ArrayCollection(); } - - } diff --git a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php index 178e16ab08f..b51a24013c1 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php @@ -29,8 +29,8 @@ protected function setUp() } /** - * This test case tests that a versionable entity, that has a oneToOne relationship as it's id can be created - * without this bug fix (DDC-3318), you could not do this + * This test case asserts that a detached and unmodified entity could be merge without firing + * OptimisticLockException. */ public function testSetVersionOnCreate() { From 4148220f9ca6523ae6df858bb7560fa47ecb655e Mon Sep 17 00:00:00 2001 From: bilouwan Date: Fri, 4 Dec 2015 14:49:01 +0100 Subject: [PATCH 055/144] Refactor testing Proxy not initilized --- lib/Doctrine/ORM/UnitOfWork.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1818b86cdff..fb35b158926 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1870,7 +1870,7 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass } } - if ($class->isVersioned && !($this->isNotInitializedProxy($managedCopy) || $this->isNotInitializedProxy($entity))) { + if ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity)) { $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); $entityVersion = $reflField->getValue($entity); @@ -1883,7 +1883,7 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass $visited[$oid] = $managedCopy; // mark visited - if (!($entity instanceof Proxy && ! $entity->__isInitialized())) { + if ($this->isLoaded($entity)) { if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized()) { $managedCopy->__load(); } @@ -1909,15 +1909,15 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass } /** - * Tests if an entity is a non initialized proxy class + * Tests if an entity is loaded (Not a proxy or a non initialized proxy) * * @param $entity * * @return bool */ - private function isNotInitializedProxy($entity) + private function isLoaded($entity) { - return $entity instanceof Proxy && !$entity->__isInitialized(); + return !($entity instanceof Proxy) || $entity->__isInitialized(); } /** From d5c82094dfa0b3e57902db6fc9f6730597781973 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Dec 2015 19:59:08 +0100 Subject: [PATCH 056/144] #1573 removing unused API --- .../Tests/Models/VersionedOneToMany/Article.php | 12 +----------- .../Tests/Models/VersionedOneToMany/Category.php | 15 --------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php b/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php index bd0cfdf4803..9194801622c 100644 --- a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php +++ b/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php @@ -2,8 +2,6 @@ namespace Doctrine\Tests\Models\VersionedOneToMany; -use Doctrine\Common\Collections\ArrayCollection; - /** * @Entity * @Table(name="article") @@ -23,7 +21,7 @@ class Article public $name; /** - * @ManyToOne(targetEntity="Category", inversedBy="category", cascade={"merge", "persist"}) + * @ManyToOne(targetEntity="Category", cascade={"merge", "persist"}) */ public $category; @@ -34,12 +32,4 @@ class Article * @Version */ public $version; - - /** - * Category constructor. - */ - public function __construct() - { - $this->tags = new ArrayCollection(); - } } diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php b/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php index d4dbdefbba5..f5d2936516b 100644 --- a/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php +++ b/tests/Doctrine/Tests/Models/VersionedOneToMany/Category.php @@ -2,8 +2,6 @@ namespace Doctrine\Tests\Models\VersionedOneToMany; -use Doctrine\Common\Collections\ArrayCollection; - /** * @Entity * @Table(name="category") @@ -17,11 +15,6 @@ class Category */ public $id; - /** - * @OneToMany(targetEntity="Article", mappedBy="category", cascade={"merge", "persist"}) - */ - public $articles; - /** * @Column(name="name") */ @@ -34,12 +27,4 @@ class Category * @Version */ public $version; - - /** - * Category constructor. - */ - public function __construct() - { - $this->articles = new ArrayCollection(); - } } From 596e8957635a81e208f9482a6b9b718b75e4649b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Dec 2015 20:00:08 +0100 Subject: [PATCH 057/144] #1573 - correcting docblock arguments/description --- lib/Doctrine/ORM/UnitOfWork.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index fb35b158926..d27748a99a7 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1909,9 +1909,9 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass } /** - * Tests if an entity is loaded (Not a proxy or a non initialized proxy) + * Tests if an entity is loaded - must either be a loaded proxy or not a proxy * - * @param $entity + * @param object $entity * * @return bool */ From 42691c21b46a055086c5a24056bcf04d9cd634a9 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Dec 2015 20:00:59 +0100 Subject: [PATCH 058/144] Removing empty newline --- .../Tests/ORM/Functional/MergeVersionedOneToManyTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php index b51a24013c1..f1c3145da43 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MergeVersionedOneToManyTest.php @@ -8,7 +8,6 @@ use Doctrine\Tests\Models\VersionedOneToMany\Category; /** - * * @group MergeVersionedOneToMany */ class MergeVersionedOneToManyTest extends \Doctrine\Tests\OrmFunctionalTestCase From 66770c5bfe4456ad9df456ae2927035c40ace0d7 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Dec 2015 20:14:53 +0100 Subject: [PATCH 059/144] #1573 - correcting test asset namespace, removing unused properties and bi-directional association --- .../Article.php | 6 +++-- .../Category.php | 11 ++++----- ...st.php => MergeVersionedManyToOneTest.php} | 23 ++++++------------- .../Doctrine/Tests/OrmFunctionalTestCase.php | 9 ++++++++ 4 files changed, 24 insertions(+), 25 deletions(-) rename tests/Doctrine/Tests/Models/{VersionedOneToMany => VersionedManyToOne}/Article.php (77%) rename tests/Doctrine/Tests/Models/{VersionedOneToMany => VersionedManyToOne}/Category.php (68%) rename tests/Doctrine/Tests/ORM/Functional/{MergeVersionedOneToManyTest.php => MergeVersionedManyToOneTest.php} (56%) diff --git a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php b/tests/Doctrine/Tests/Models/VersionedManyToOne/Article.php similarity index 77% rename from tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php rename to tests/Doctrine/Tests/Models/VersionedManyToOne/Article.php index 9194801622c..0e5b1683fb4 100644 --- a/tests/Doctrine/Tests/Models/VersionedOneToMany/Article.php +++ b/tests/Doctrine/Tests/Models/VersionedManyToOne/Article.php @@ -1,13 +1,15 @@ useModelSet('versioned_many_to_one'); - try { - $this->_schemaTool->createSchema( - [ - $this->_em->getClassMetadata('Doctrine\Tests\Models\VersionedOneToMany\Category'), - $this->_em->getClassMetadata('Doctrine\Tests\Models\VersionedOneToMany\Article'), - ] - ); - } catch (ORMException $e) { - } + parent::setUp(); } /** @@ -34,10 +26,9 @@ protected function setUp() public function testSetVersionOnCreate() { $category = new Category(); - $category->name = 'Category'; + $article = new Article(); - $article = new Article(); - $article->name = 'Article'; + $article->name = 'Article'; $article->category = $category; $this->_em->persist($article); diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 855fac61751..b36a8348735 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -280,6 +280,10 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\Pagination\User', 'Doctrine\Tests\Models\Pagination\User1', ), + 'versioned_many_to_one' => array( + 'Doctrine\Tests\Models\VersionedManyToOne\Category', + 'Doctrine\Tests\Models\VersionedManyToOne\Article', + ), ); /** @@ -535,6 +539,11 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM pagination_user'); } + if (isset($this->_usedModelSets['versioned_many_to_one'])) { + $conn->executeUpdate('DELETE FROM versioned_many_to_one_article'); + $conn->executeUpdate('DELETE FROM versioned_many_to_one_category'); + } + $this->_em->clear(); } From aa61328e9024f327dc65768bd9eaddff550dd1ca Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Dec 2015 21:30:19 +0100 Subject: [PATCH 060/144] #1572 - test coverage - interfaces should also resolve to target entities when in DQL --- .../Tools/ResolveTargetEntityListenerTest.php | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/ResolveTargetEntityListenerTest.php b/tests/Doctrine/Tests/ORM/Tools/ResolveTargetEntityListenerTest.php index fdee6e8c2f1..2934693f8c1 100644 --- a/tests/Doctrine/Tests/ORM/Tools/ResolveTargetEntityListenerTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/ResolveTargetEntityListenerTest.php @@ -9,19 +9,19 @@ class ResolveTargetEntityListenerTest extends \Doctrine\Tests\OrmTestCase { /** - * @var EntityManager + * @var \Doctrine\ORM\EntityManager */ - private $em = null; + private $em; /** * @var ResolveTargetEntityListener */ - private $listener = null; + private $listener; /** * @var ClassMetadataFactory */ - private $factory = null; + private $factory; public function setUp() { @@ -106,6 +106,32 @@ public function testAssertTableColumnsAreNotAddedInManyToMany() $this->assertSame('Doctrine\Tests\ORM\Tools\TargetEntity', $meta['targetEntity']); $this->assertEquals(array('resolvetargetentity_id', 'targetinterface_id'), $meta['joinTableColumns']); } + + /** + * @group 1572 + * @group functional + * + * @coversNothing + */ + public function testDoesResolveTargetEntitiesInDQLAlsoWithInterfaces() + { + $evm = $this->em->getEventManager(); + $this->listener->addResolveTargetEntity( + 'Doctrine\Tests\ORM\Tools\ResolveTargetInterface', + 'Doctrine\Tests\ORM\Tools\ResolveTargetEntity', + array() + ); + + $evm->addEventSubscriber($this->listener); + + $this->assertStringMatchesFormat( + 'SELECT%AFROM ResolveTargetEntity%A', + $this + ->em + ->createQuery('SELECT f FROM Doctrine\Tests\ORM\Tools\ResolveTargetInterface f') + ->getSQL() + ); + } } interface ResolveTargetInterface From 6d43195669421c96dc9d40dee2f5fa2738c41874 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 14:44:30 +0100 Subject: [PATCH 061/144] #4884 - allow installation of doctrine/common 2.6.x, which allows generating type-hints on proxies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dd7544aeb80..3f4671281f6 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "php": ">=5.4", "ext-pdo": "*", "doctrine/collections": "~1.2", - "doctrine/dbal": ">=2.5-dev,<2.6-dev", + "doctrine/dbal": ">=2.5-dev,<2.7-dev", "doctrine/instantiator": "~1.0.1", "doctrine/common": ">=2.5-dev,<2.6-dev", "doctrine/cache": "~1.4", From 0086d17afe35b7fae57a31922444cb839f36eb35 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 15:10:21 +0100 Subject: [PATCH 062/144] Common 2.6 compatibility Internal structure of the ArrayCache has changed, therefore we should fix the tests depending on it instead --- .../Doctrine/Tests/ORM/Functional/QueryCacheTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index cf35d8617b8..039bb165532 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -105,16 +105,16 @@ public function testQueryCache_NoHitSaveParserResult() $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $cache = new \Doctrine\Common\Cache\ArrayCache(); + $cache = $this->getMock('Doctrine\Common\Cache\Cache'); $query->setQueryCacheDriver($cache); - $users = $query->getResult(); - - $data = $this->cacheDataReflection->getValue($cache); - $this->assertEquals(1, count($data)); + $cache + ->expects(self::once()) + ->method('save') + ->with(self::isType('string'), self::isInstanceOf('Doctrine\ORM\Query\ParserResult')); - $this->assertInstanceOf('Doctrine\ORM\Query\ParserResult', array_pop($data)); + $query->getResult(); } public function testQueryCache_HitDoesNotSaveParserResult() From 27a5284899fd291b5e81d0a0a856da08e758d5b5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 15:20:35 +0100 Subject: [PATCH 063/144] doctrine/common 2.6.0 compat Less strict assertion - no need to check the exact file name --- tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php index 405260185b4..99e436ae866 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php @@ -49,7 +49,7 @@ public function testFindMappingFileNamespacedFoundFileNotFound() { $this->setExpectedException( 'Doctrine\Common\Persistence\Mapping\MappingException', - "No mapping file found named '".$this->dir."/Foo".$this->getFileExtension()."' for class 'MyNamespace\MySubnamespace\Entity\Foo'." + "No mapping file found named" ); $driver = $this->getDriver(array( From 5a6ae4686f1dfb52fb580cb7d87717d7114640a3 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 15:58:40 +0100 Subject: [PATCH 064/144] Allowing doctrine/common 2.6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3f4671281f6..eec4351d5d8 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "doctrine/collections": "~1.2", "doctrine/dbal": ">=2.5-dev,<2.7-dev", "doctrine/instantiator": "~1.0.1", - "doctrine/common": ">=2.5-dev,<2.6-dev", + "doctrine/common": ">=2.5-dev,<2.7-dev", "doctrine/cache": "~1.4", "symfony/console": "~2.5|~3.0" }, From d2e51eacff73f565880b8714edd5f9d4a735e37a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 16:49:48 +0100 Subject: [PATCH 065/144] Reverting incorrect DBAL 2.6 bump --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eec4351d5d8..d16ccf0255a 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "php": ">=5.4", "ext-pdo": "*", "doctrine/collections": "~1.2", - "doctrine/dbal": ">=2.5-dev,<2.7-dev", + "doctrine/dbal": ">=2.5-dev,<2.6-dev", "doctrine/instantiator": "~1.0.1", "doctrine/common": ">=2.5-dev,<2.7-dev", "doctrine/cache": "~1.4", From d9fc5388f1aa1751a0e148e76b4569bd207338e9 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 16:50:05 +0100 Subject: [PATCH 066/144] 2.5.3 release --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index c00ec6fe2e1..8589933345f 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.3-DEV'; + const VERSION = '2.5.3'; /** * Compares a Doctrine version with the current one. From 1c6524db5566cf9e4c5d2c85693890a1517b1e3c Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 25 Dec 2015 16:50:31 +0100 Subject: [PATCH 067/144] Bumping to development version 2.5.4-DEV --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 8589933345f..cdfd6203384 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.3'; + const VERSION = '2.5.4-DEV'; /** * Compares a Doctrine version with the current one. From 5092da074a818546149e1718a935fd42f8ddd198 Mon Sep 17 00:00:00 2001 From: Guido Contreras Woda Date: Tue, 24 Nov 2015 10:06:05 -0300 Subject: [PATCH 068/144] Test that reflects the issue described in http://www.doctrine-project.org/jira/browse/DDC-3967 --- .../ORM/Functional/Ticket/DDC3967Test.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3967Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3967Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3967Test.php new file mode 100644 index 00000000000..d5c3dd554ff --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3967Test.php @@ -0,0 +1,35 @@ +loadFixturesCountries(); + $this->_em->getCache()->evictEntityRegion(Country::CLASSNAME); + $this->_em->clear(); + } + + public function testIdentifierCachedWithProperType() + { + $country = array_pop($this->countries); + $id = $country->getId(); + + // First time, loaded from database + $this->_em->find(Country::CLASSNAME, "$id"); + $this->_em->clear(); + + // Second time, loaded from cache + /** @var Country $country */ + $country = $this->_em->find(Country::CLASSNAME, "$id"); + + // Identifier type should be integer + $this->assertSame($country->getId(), $id); + } +} From db6cb8dedc0d7ccbb519799f675f932457c0ed5c Mon Sep 17 00:00:00 2001 From: Jan Langer Date: Sat, 14 Nov 2015 10:36:19 +0100 Subject: [PATCH 069/144] Second level cache stores identifier with correct type even if findById is called with wrong identifier type --- .../ORM/Cache/DefaultEntityHydrator.php | 2 +- .../ORM/Cache/DefaultEntityHydratorTest.php | 36 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php index 200af9c0df7..70fd1f39a68 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php @@ -73,7 +73,7 @@ public function __construct(EntityManagerInterface $em) public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity) { $data = $this->uow->getOriginalEntityData($entity); - $data = array_merge($data, $key->identifier); // why update has no identifier values ? + $data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ? foreach ($metadata->associationMappings as $name => $assoc) { diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php index d81cade87b7..73878ee46d9 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php @@ -119,7 +119,7 @@ public function testBuildCacheEntryAssociation() $this->assertArrayHasKey('name', $cache->data); $this->assertArrayHasKey('country', $cache->data); $this->assertEquals(array( - 'id' => 11, + 'id' => 12, 'name' => 'Bar', 'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)), ), $cache->data); @@ -147,9 +147,39 @@ public function testBuildCacheEntryNonInitializedAssocProxy() $this->assertArrayHasKey('name', $cache->data); $this->assertArrayHasKey('country', $cache->data); $this->assertEquals(array( - 'id' => 11, + 'id' => 12, 'name' => 'Bar', 'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)), ), $cache->data); } -} \ No newline at end of file + + public function testCacheEntryWithWrongIdentifierType() + { + $proxy = $this->em->getReference(Country::CLASSNAME, 11); + $entity = new State('Bat', $proxy); + $uow = $this->em->getUnitOfWork(); + $entityData = array('id'=> 12, 'name'=>'Bar', 'country' => $proxy); + $metadata = $this->em->getClassMetadata(State::CLASSNAME); + $key = new EntityCacheKey($metadata->name, array('id'=>'12')); + + $entity->setId(12); + + $uow->registerManaged($entity, array('id'=>12), $entityData); + + $cache = $this->structure->buildCacheEntry($metadata, $key, $entity); + + $this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache); + + $this->assertArrayHasKey('id', $cache->data); + $this->assertArrayHasKey('name', $cache->data); + $this->assertArrayHasKey('country', $cache->data); + $this->assertSame($entity->getId(), $cache->data['id']); + $this->assertEquals(array( + 'id' => 12, + 'name' => 'Bar', + 'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)), + ), $cache->data); + } + +} From 14499f5021d7d2de51aeca5db7c38ae0b05d1b6c Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 5 Jan 2016 22:25:57 +0100 Subject: [PATCH 070/144] Removing coveralls installation/reporting from 2.5: not required --- .travis.yml | 3 --- composer.json | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index fb6637849ac..c6bcf3072a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,6 @@ script: - ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS - ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional -after_script: - - php vendor/bin/coveralls -v - matrix: exclude: - php: hhvm diff --git a/composer.json b/composer.json index d16ccf0255a..ce740f88a1e 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,7 @@ }, "require-dev": { "symfony/yaml": "~2.3|~3.0", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master" + "phpunit/phpunit": "~4.0" }, "suggest": { "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" From bc4ddbfb0114cb33438cc811c9a740d8aa304aab Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 5 Jan 2016 22:34:58 +0100 Subject: [PATCH 071/144] Release 2.5.4 --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index cdfd6203384..284f39bb845 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.4-DEV'; + const VERSION = '2.5.4'; /** * Compares a Doctrine version with the current one. From d05aa6a5e015fd412ef85a8721c51da1a917ec78 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 5 Jan 2016 22:36:06 +0100 Subject: [PATCH 072/144] Bumping to development version 2.5.5-DEV --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 284f39bb845..44de432175d 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.5.4'; + const VERSION = '2.5.5-DEV'; /** * Compares a Doctrine version with the current one. From 6279c80e0511108a104fc80b27963fdf627bd249 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 8 Jan 2016 17:56:41 +0100 Subject: [PATCH 073/144] Regression test: HAVING clause does not translate variable name when used with * and / math operators --- .../ORM/Query/SelectSqlGenerationTest.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 39c20715283..a3a34f8224f 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -2242,6 +2242,27 @@ public function testHavingSupportResultVariableInAggregateFunction() 'SELECT COUNT(c0_.name) AS sclr_0 FROM cms_users c0_ HAVING sclr_0 IS NULL' ); } + + /** + * GitHub issue #4764: https://github.com/doctrine/doctrine2/issues/4764 + * @group DDC-3907 + * @dataProvider mathematicOperatorsProvider + */ + public function testHavingRegressionUsingVariableWithMathOperatorsExpression($operator) + { + $this->assertSqlGeneration( + 'SELECT COUNT(u.name) AS countName FROM Doctrine\Tests\Models\CMS\CmsUser u HAVING 1 ' . $operator . ' countName > 0', + 'SELECT COUNT(c0_.name) AS sclr_0 FROM cms_users c0_ HAVING 1 ' . $operator . ' sclr_0 > 0' + ); + } + + /** + * @return array + */ + public function mathematicOperatorsProvider() + { + return [['+'], ['-'], ['*'], ['/']]; + } } class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode From c4209b46549d928449330a1ff143c3ecfa7437b6 Mon Sep 17 00:00:00 2001 From: Bill Schaller Date: Fri, 8 Jan 2016 12:53:05 -0500 Subject: [PATCH 074/144] Fix issue were identifier operands in /,* arithmetic terms were not checked to see if they're query components --- lib/Doctrine/ORM/Query/SqlWalker.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 3dea8de4b2e..9c4ac2a88af 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -2264,7 +2264,9 @@ public function walkArithmeticTerm($term) public function walkArithmeticFactor($factor) { if (is_string($factor)) { - return $factor; + return (isset($this->queryComponents[$factor])) + ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value']) + : $factor; } // Phase 2 AST optimization: Skip processing of ArithmeticFactor From cd746beae2a30b4413aadca9005b9815273231f2 Mon Sep 17 00:00:00 2001 From: Rico Humme Date: Fri, 3 Jun 2016 13:48:05 +0200 Subject: [PATCH 075/144] Clear entityInsertions for specific entityName --- lib/Doctrine/ORM/UnitOfWork.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d27748a99a7..3f0b6f7a092 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2418,6 +2418,13 @@ public function clear($entityName = null) $this->doDetach($entity, $visited, false); } } + + foreach ($this->entityInsertions as $hash => $entity) { + if (get_class($entity) != $entityName) { + continue; + } + unset($this->entityInsertions[$hash]); + } } if ($this->evm->hasListeners(Events::onClear)) { From 996c5048abdf3138515912c4860e708eb00d5e40 Mon Sep 17 00:00:00 2001 From: Rico Humme Date: Fri, 3 Jun 2016 16:13:52 +0200 Subject: [PATCH 076/144] Test Case for Clear entityInsertions for specific entityName --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index d855c7817b0..b24598c8176 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -321,6 +321,27 @@ public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGar $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity)); } + public function testPersistedEntityAndClearManager() + { + $entity1 = new ForumUser(); + $entity1->id = 123; + + $entity2 = new ForumAvatar(); + $entity2->id = 456; + + $this->_unitOfWork->persist($entity1); + $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1)); + + $this->_unitOfWork->persist($entity2); + $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity2)); + + $this->_unitOfWork->clear(ForumAvatar::class); + $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1)); + $this->assertFalse($this->_unitOfWork->isInIdentityMap($entity2)); + $this->assertTrue($this->_unitOfWork->isScheduledForInsert($entity1)); + $this->assertFalse($this->_unitOfWork->isScheduledForInsert($entity2)); + } + /** * Data Provider * From 110d771883bfdb215142b191243f5bf45cfe35cf Mon Sep 17 00:00:00 2001 From: Rico Humme Date: Fri, 3 Jun 2016 16:27:27 +0200 Subject: [PATCH 077/144] Split of functionality in separate functions --- lib/Doctrine/ORM/UnitOfWork.php | 50 +++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 3f0b6f7a092..1b7425d7450 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2407,24 +2407,8 @@ public function clear($entityName = null) $this->commitOrderCalculator->clear(); } } else { - $visited = array(); - - foreach ($this->identityMap as $className => $entities) { - if ($className !== $entityName) { - continue; - } - - foreach ($entities as $entity) { - $this->doDetach($entity, $visited, false); - } - } - - foreach ($this->entityInsertions as $hash => $entity) { - if (get_class($entity) != $entityName) { - continue; - } - unset($this->entityInsertions[$hash]); - } + $this->clearIdentityMap($entityName); + $this->clearIdentityInsertions($entityName); } if ($this->evm->hasListeners(Events::onClear)) { @@ -3478,4 +3462,34 @@ public function hydrationComplete() { $this->hydrationCompleteHandler->hydrationComplete(); } + + /** + * @param $entityName + */ + private function clearIdentityMap($entityName) + { + $visited = array(); + + foreach ($this->identityMap as $className => $entities) { + if ($className !== $entityName) { + continue; + } + + foreach ($entities as $entity) { + $this->doDetach($entity, $visited, false); + } + } + } + + /** + * @param $entityName + */ + private function clearIdentityInsertions($entityName) + { + foreach ($this->entityInsertions as $hash => $entity) { + if (get_class($entity) === $entityName) { + unset($this->entityInsertions[$hash]); + } + } + } } From 4a38c96ec57e3c860c2e0c85cd98f09e685f398c Mon Sep 17 00:00:00 2001 From: Rico Humme Date: Fri, 3 Jun 2016 16:31:35 +0200 Subject: [PATCH 078/144] Correct naming convention of function. Was confusing otherwise --- lib/Doctrine/ORM/UnitOfWork.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1b7425d7450..1547ab72a01 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2408,7 +2408,7 @@ public function clear($entityName = null) } } else { $this->clearIdentityMap($entityName); - $this->clearIdentityInsertions($entityName); + $this->clearEntityInsertions($entityName); } if ($this->evm->hasListeners(Events::onClear)) { @@ -3484,7 +3484,7 @@ private function clearIdentityMap($entityName) /** * @param $entityName */ - private function clearIdentityInsertions($entityName) + private function clearEntityInsertions($entityName) { foreach ($this->entityInsertions as $hash => $entity) { if (get_class($entity) === $entityName) { From 7fbcbfa271612fbacdd6a2e23b9648270c20cdd0 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 5 Jun 2016 23:54:16 +0200 Subject: [PATCH 079/144] #5849 #5850 adding group annotations to the newly introduced test case --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index b24598c8176..1e9f0f33c80 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -321,6 +321,10 @@ public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGar $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity)); } + /** + * @group 5849 + * @group 5850 + */ public function testPersistedEntityAndClearManager() { $entity1 = new ForumUser(); From 7378035f68696b28e95f1d98b82d2b28f8e064c5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 6 Jun 2016 00:08:26 +0200 Subject: [PATCH 080/144] #5849 #5850 correcting test scenario: identity map could not be built with auto-generated identities+persist --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 1e9f0f33c80..2e0233ba2d9 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -14,6 +14,9 @@ use Doctrine\Tests\Mocks\UnitOfWorkMock; use Doctrine\Tests\Models\Forum\ForumAvatar; use Doctrine\Tests\Models\Forum\ForumUser; +use Doctrine\Tests\Models\GeoNames\City; +use Doctrine\Tests\Models\GeoNames\Country; +use Doctrine\Tests\OrmTestCase; use stdClass; /** @@ -327,11 +330,8 @@ public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGar */ public function testPersistedEntityAndClearManager() { - $entity1 = new ForumUser(); - $entity1->id = 123; - - $entity2 = new ForumAvatar(); - $entity2->id = 456; + $entity1 = new City(123, 'London'); + $entity2 = new Country(456, 'United Kingdom'); $this->_unitOfWork->persist($entity1); $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1)); @@ -339,7 +339,7 @@ public function testPersistedEntityAndClearManager() $this->_unitOfWork->persist($entity2); $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity2)); - $this->_unitOfWork->clear(ForumAvatar::class); + $this->_unitOfWork->clear(Country::class); $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1)); $this->assertFalse($this->_unitOfWork->isInIdentityMap($entity2)); $this->assertTrue($this->_unitOfWork->isScheduledForInsert($entity1)); From ec4dd4ab441bbff8b2cbfca65ae1d826fedd62af Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 6 Jun 2016 00:10:18 +0200 Subject: [PATCH 081/144] #5849 #5850 renamed `clearIdentityMap` to `clearIdentityMapForEntityName`, for clarity --- lib/Doctrine/ORM/UnitOfWork.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1547ab72a01..ee499a1a8dd 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2407,7 +2407,7 @@ public function clear($entityName = null) $this->commitOrderCalculator->clear(); } } else { - $this->clearIdentityMap($entityName); + $this->clearIdentityMapForEntityName($entityName); $this->clearEntityInsertions($entityName); } @@ -3464,9 +3464,9 @@ public function hydrationComplete() } /** - * @param $entityName + * @param string $entityName */ - private function clearIdentityMap($entityName) + private function clearIdentityMapForEntityName($entityName) { $visited = array(); From 800215040a31f0f03e73d09cfcbdd9e81f8182a5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 6 Jun 2016 00:11:19 +0200 Subject: [PATCH 082/144] #5849 #5850 refactored `clearIdentityMapForEntityName` to remove useless looping --- lib/Doctrine/ORM/UnitOfWork.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ee499a1a8dd..ab91b8bab21 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -3468,16 +3468,14 @@ public function hydrationComplete() */ private function clearIdentityMapForEntityName($entityName) { - $visited = array(); + if (! isset($this->identityMap[$entityName])) { + return; + } - foreach ($this->identityMap as $className => $entities) { - if ($className !== $entityName) { - continue; - } + $visited = []; - foreach ($entities as $entity) { - $this->doDetach($entity, $visited, false); - } + foreach ($this->identityMap[$entityName] as $entity) { + $this->doDetach($entity, $visited, false); } } From fecadf059c21eecac732d5acb031c2fbcf28b496 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 6 Jun 2016 00:13:39 +0200 Subject: [PATCH 083/144] #5849 #5850 renamed `clearEntityInsertions` to `clearEntityInsertionsForEntityName`, for clarity --- lib/Doctrine/ORM/UnitOfWork.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ab91b8bab21..c2f037e2d5f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2408,7 +2408,7 @@ public function clear($entityName = null) } } else { $this->clearIdentityMapForEntityName($entityName); - $this->clearEntityInsertions($entityName); + $this->clearEntityInsertionsForEntityName($entityName); } if ($this->evm->hasListeners(Events::onClear)) { @@ -3480,9 +3480,9 @@ private function clearIdentityMapForEntityName($entityName) } /** - * @param $entityName + * @param string $entityName */ - private function clearEntityInsertions($entityName) + private function clearEntityInsertionsForEntityName($entityName) { foreach ($this->entityInsertions as $hash => $entity) { if (get_class($entity) === $entityName) { From 224ac9725ea1629f4fed485f3996fc844015edcf Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 6 Jun 2016 01:34:20 +0200 Subject: [PATCH 084/144] Removed annotation reader constructor argument (incorrect argument used) --- tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php index 927755f999d..e95dc7bffc1 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php @@ -16,7 +16,7 @@ public function testLoadMetadataForNonEntityThrowsException() { $cm = new ClassMetadata('stdClass'); $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); - $reader = new \Doctrine\Common\Annotations\AnnotationReader(new \Doctrine\Common\Cache\ArrayCache()); + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); $annotationDriver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader); $this->setExpectedException('Doctrine\ORM\Mapping\MappingException'); From 9a393ccba72b6b45e1911e12cb352807eddb4e12 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 6 Jun 2016 01:48:32 +0200 Subject: [PATCH 085/144] Removed reliance on `::class` meta-constant (only available in PHP 5.5+) --- tests/Doctrine/Tests/Models/GeoNames/Country.php | 2 ++ tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/Models/GeoNames/Country.php b/tests/Doctrine/Tests/Models/GeoNames/Country.php index ebde305f805..8af430a93ae 100644 --- a/tests/Doctrine/Tests/Models/GeoNames/Country.php +++ b/tests/Doctrine/Tests/Models/GeoNames/Country.php @@ -9,6 +9,8 @@ */ class Country { + const CLASSNAME = __CLASS__; + /** * @Id * @Column(type="string", length=2) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 2e0233ba2d9..9623cfbf484 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -339,7 +339,7 @@ public function testPersistedEntityAndClearManager() $this->_unitOfWork->persist($entity2); $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity2)); - $this->_unitOfWork->clear(Country::class); + $this->_unitOfWork->clear(Country::CLASSNAME); $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1)); $this->assertFalse($this->_unitOfWork->isInIdentityMap($entity2)); $this->assertTrue($this->_unitOfWork->isScheduledForInsert($entity1)); From a5eb0f2e820b1c2836a18868b76799ae65a729cc Mon Sep 17 00:00:00 2001 From: Thomas Ploch Date: Tue, 7 Jun 2016 17:33:29 +0200 Subject: [PATCH 086/144] Exporters should only inspect `joinColumns` for owning side in bi-directional OneToOne rebased commits: - Added test case for bi-directional OneToOne in YamlExporter - Only inspect joinColumns for owning side in bi-directional OneToOne in YamlExporter - Adding bi-directional test case without joinColumn to XmlExporter test - Same testcase also applied to PhpExporter - Fixing bi-directional issue in PhpExporter when inspecting joinColumns index - Implemented @Ocramius suggestions --- lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php | 2 +- .../ORM/Tools/Export/Driver/YamlExporter.php | 2 +- .../php/Doctrine.Tests.ORM.Tools.Export.User.php | 12 ++++++++++++ .../xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml | 6 ++++++ .../Doctrine.Tests.ORM.Tools.Export.User.dcm.yml | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php index 29eb37afc81..5d88901a56a 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php @@ -117,7 +117,7 @@ public function exportClassMetadata(ClassMetadataInfo $metadata) $oneToOneMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], - 'joinColumns' => $associationMapping['joinColumns'], + 'joinColumns' => $associationMapping['isOwningSide'] ? $associationMapping['joinColumns'] : [], 'orphanRemoval' => $associationMapping['orphanRemoval'], ); diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php index 177cebfabbf..7677ad9501e 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php @@ -163,7 +163,7 @@ public function exportClassMetadata(ClassMetadataInfo $metadata) } if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - $joinColumns = $associationMapping['joinColumns']; + $joinColumns = $associationMapping['isOwningSide'] ? $associationMapping['joinColumns'] : []; $newJoinColumns = array(); foreach ($joinColumns as $joinColumn) { diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php b/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php index 061934b9b7c..78ceb4e7eeb 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php @@ -57,6 +57,18 @@ 'orphanRemoval' => true, 'fetch' => ClassMetadataInfo::FETCH_EAGER, )); +$metadata->mapOneToOne(array( + 'fieldName' => 'cart', + 'targetEntity' => 'Doctrine\\Tests\\ORM\\Tools\\Export\\Cart', + 'mappedBy' => 'user', + 'cascade' => + array( + 0 => 'persist', + ), + 'inversedBy' => NULL, + 'orphanRemoval' => false, + 'fetch' => ClassMetadataInfo::FETCH_EAGER, +)); $metadata->mapOneToMany(array( 'fieldName' => 'phonenumbers', 'targetEntity' => 'Doctrine\\Tests\\ORM\\Tools\\Export\\Phonenumber', diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml b/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml index 8e30948e894..a84fc553aed 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml @@ -30,6 +30,12 @@ + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml b/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml index 1a5b7760957..02ecab2e07b 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml @@ -30,6 +30,10 @@ Doctrine\Tests\ORM\Tools\Export\User: inversedBy: user orphanRemoval: true fetch: EAGER + cart: + targetEntity: Doctrine\Tests\ORM\Tools\Export\Cart + mappedBy: user + cascade: [ remove ] manyToOne: mainGroup: targetEntity: Doctrine\Tests\ORM\Tools\Export\Group From 62431ae4771c3714247685e724c9a77016465430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Thu, 9 Jun 2016 19:46:37 +0000 Subject: [PATCH 087/144] Allow the usage of embedded objects on parent classes. The `ClassMetadataInfo` was always using the "current class" to fetch the reflection of a property even when a field is declared on the parent class (which causes `ReflectionProperty` to throw an exception). --- .../ORM/Mapping/ClassMetadataInfo.php | 9 ++- .../Tests/Models/DDC3303/DDC3303Address.php | 60 ++++++++++++++++++ .../Tests/Models/DDC3303/DDC3303Employee.php | 39 ++++++++++++ .../Tests/Models/DDC3303/DDC3303Person.php | 61 +++++++++++++++++++ .../ORM/Functional/Ticket/DDC3303Test.php | 32 ++++++++++ 5 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php create mode 100644 tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php create mode 100644 tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 79dcdb2cb27..5670069a854 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -940,8 +940,13 @@ public function wakeupReflection($reflService) continue; } - $parentReflFields[$property] = $reflService->getAccessibleProperty($this->name, $property); - $this->reflFields[$property] = $reflService->getAccessibleProperty($this->name, $property); + $fieldRefl = $reflService->getAccessibleProperty( + isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->name, + $property + ); + + $parentReflFields[$property] = $fieldRefl; + $this->reflFields[$property] = $fieldRefl; } foreach ($this->fieldMappings as $field => $mapping) { diff --git a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php new file mode 100644 index 00000000000..c3b634abf6c --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php @@ -0,0 +1,60 @@ +street = $street; + $this->number = $number; + $this->city = $city; + } + + /** + * @return string + */ + public function getStreet() + { + return $this->street; + } + + /** + * @return mixed + */ + public function getNumber() + { + return $this->number; + } + + /** + * @return string + */ + public function getCity() + { + return $this->city; + } +} diff --git a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php new file mode 100644 index 00000000000..334dc20529b --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php @@ -0,0 +1,39 @@ +company = $company; + } + + /** + * @return string + */ + public function getCompany() + { + return $this->company; + } + + /** + * @param string $company + */ + public function setCompany($company) + { + $this->company = $company; + } +} diff --git a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php new file mode 100644 index 00000000000..da42d1c5446 --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php @@ -0,0 +1,61 @@ +name = $name; + $this->address = $address; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return DDC3303Address + */ + public function getAddress() + { + return $this->address; + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php new file mode 100644 index 00000000000..e6415d3937a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php @@ -0,0 +1,32 @@ +_schemaTool->createSchema(array($this->_em->getClassMetadata(DDC3303Employee::class))); + } + + public function testEmbeddedObjectsAreAlsoInherited() + { + $employee = new DDC3303Employee( + 'John Doe', + new DDC3303Address('Somewhere', 123, 'Over the rainbow'), + 'Doctrine Inc' + ); + + $this->_em->persist($employee); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertEquals($employee, $this->_em->find(DDC3303Employee::class, 1)); + } +} From f181cf6c6b1aef0daaae37e2d13aadc924cd47c2 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 19 Jun 2016 12:42:49 +0200 Subject: [PATCH 088/144] #5867 simplifying test case by inlining all required models into the test case --- .../Tests/Models/DDC3303/DDC3303Address.php | 60 ---------------- .../Tests/Models/DDC3303/DDC3303Employee.php | 39 ----------- .../Tests/Models/DDC3303/DDC3303Person.php | 61 ----------------- .../ORM/Functional/Ticket/DDC3303Test.php | 68 ++++++++++++++++--- 4 files changed, 60 insertions(+), 168 deletions(-) delete mode 100644 tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php delete mode 100644 tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php delete mode 100644 tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php diff --git a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php deleted file mode 100644 index c3b634abf6c..00000000000 --- a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Address.php +++ /dev/null @@ -1,60 +0,0 @@ -street = $street; - $this->number = $number; - $this->city = $city; - } - - /** - * @return string - */ - public function getStreet() - { - return $this->street; - } - - /** - * @return mixed - */ - public function getNumber() - { - return $this->number; - } - - /** - * @return string - */ - public function getCity() - { - return $this->city; - } -} diff --git a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php deleted file mode 100644 index 334dc20529b..00000000000 --- a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Employee.php +++ /dev/null @@ -1,39 +0,0 @@ -company = $company; - } - - /** - * @return string - */ - public function getCompany() - { - return $this->company; - } - - /** - * @param string $company - */ - public function setCompany($company) - { - $this->company = $company; - } -} diff --git a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php b/tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php deleted file mode 100644 index da42d1c5446..00000000000 --- a/tests/Doctrine/Tests/Models/DDC3303/DDC3303Person.php +++ /dev/null @@ -1,61 +0,0 @@ -name = $name; - $this->address = $address; - } - - /** - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @return DDC3303Address - */ - public function getAddress() - { - return $this->address; - } -} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php index e6415d3937a..b122d9df662 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php @@ -1,18 +1,15 @@ _schemaTool->createSchema(array($this->_em->getClassMetadata(DDC3303Employee::class))); + parent::setUp(); + + $this->_schemaTool->createSchema([$this->_em->getClassMetadata(DDC3303Employee::class)]); } public function testEmbeddedObjectsAreAlsoInherited() @@ -27,6 +24,61 @@ public function testEmbeddedObjectsAreAlsoInherited() $this->_em->flush(); $this->_em->clear(); - $this->assertEquals($employee, $this->_em->find(DDC3303Employee::class, 1)); + self::assertEquals($employee, $this->_em->find(DDC3303Employee::class, 'John Doe')); + } +} + +/** @MappedSuperclass */ +abstract class DDC3303Person +{ + /** @Id @GeneratedValue(strategy="NONE") @Column(type="string") @var string */ + private $name; + + /** @Embedded(class="DDC3303Address") @var DDC3303Address */ + private $address; + + public function __construct($name, DDC3303Address $address) + { + $this->name = $name; + $this->address = $address; + } +} + +/** + * @Embeddable + */ +class DDC3303Address +{ + /** @Column(type="string") @var string */ + private $street; + + /** @Column(type="integer") @var int */ + private $number; + + /** @Column(type="string") @var string */ + private $city; + + public function __construct($street, $number, $city) + { + $this->street = $street; + $this->number = $number; + $this->city = $city; + } +} + +/** + * @Entity + * @Table(name="ddc3303_employee") + */ +class DDC3303Employee extends DDC3303Person +{ + /** @Column(type="string") @var string */ + private $company; + + public function __construct($name, DDC3303Address $address, $company) + { + parent::__construct($name, $address); + + $this->company = $company; } } From 2af84c6025f8308e9e91cc356ab2409e5fa78974 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 19 Jun 2016 12:44:19 +0200 Subject: [PATCH 089/144] #5867 `@group` annotations, describing scenario --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php index b122d9df662..b40f99ff9d6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php @@ -12,6 +12,13 @@ protected function setUp() $this->_schemaTool->createSchema([$this->_em->getClassMetadata(DDC3303Employee::class)]); } + /** + * @group 4097 + * @group 4277 + * @group 5867 + * + * When using an embedded field in an inheritance, private properties should also be inherited. + */ public function testEmbeddedObjectsAreAlsoInherited() { $employee = new DDC3303Employee( From b183818fa8bed47a6cb190e9386625395c568be1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 19 Jun 2016 12:48:15 +0200 Subject: [PATCH 090/144] #5867 s/::class/::CLASSNAME for PHP 5.4 compat --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php index b40f99ff9d6..df96bf901cc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php @@ -9,7 +9,7 @@ protected function setUp() { parent::setUp(); - $this->_schemaTool->createSchema([$this->_em->getClassMetadata(DDC3303Employee::class)]); + $this->_schemaTool->createSchema([$this->_em->getClassMetadata(DDC3303Employee::CLASSNAME)]); } /** @@ -31,7 +31,7 @@ public function testEmbeddedObjectsAreAlsoInherited() $this->_em->flush(); $this->_em->clear(); - self::assertEquals($employee, $this->_em->find(DDC3303Employee::class, 'John Doe')); + self::assertEquals($employee, $this->_em->find(DDC3303Employee::CLASSNAME, 'John Doe')); } } @@ -79,6 +79,8 @@ public function __construct($street, $number, $city) */ class DDC3303Employee extends DDC3303Person { + const CLASSNAME = __CLASS__; + /** @Column(type="string") @var string */ private $company; From 592122fbcb0734cef41103de50e2266e6dc58daf Mon Sep 17 00:00:00 2001 From: John Keller Date: Tue, 12 Apr 2016 13:43:45 -0700 Subject: [PATCH 091/144] add functional test and bug fix for issue #5762 --- .../ORM/Internal/Hydration/ObjectHydrator.php | 3 + .../ORM/Functional/Ticket/GH5762Test.php | 228 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 3cedaada0c3..d7a429f0038 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -332,6 +332,9 @@ protected function hydrateRowData(array $row, array &$result) // Split the row data into chunks of class data. $rowData = $this->gatherRowData($row, $id, $nonemptyComponents); + // reset result pointers for each data row + $this->resultPointers = array(); + // Hydrate the data chunks foreach ($rowData['data'] as $dqlAlias => $data) { $entityName = $this->_rsm->aliasMap[$dqlAlias]; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php new file mode 100644 index 00000000000..809417c0470 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php @@ -0,0 +1,228 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\GH5762Driver'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\GH5762DriverRide'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\GH5762Car'), + )); + } + + public function testIssue() + { + $result = $this->fetchData(); + + $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Driver', $result); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result->getDriverRides()); + $this->assertInstanceOf(__NAMESPACE__ . '\GH5762DriverRide', $result->getDriverRides()->get(0)); + $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Car', $result->getDriverRides()->get(0)->getCar()); + + $cars = array(); + foreach ($result->getDriverRides() as $ride) { + $cars[] = $ride->getCar()->getBrand(); + } + $this->assertEquals(count($cars), count(array_unique($cars))); + + $this->assertContains('BMW', $cars); + $this->assertContains('Crysler', $cars); + $this->assertContains('Dodge', $cars); + $this->assertContains('Mercedes', $cars); + $this->assertContains('Volvo', $cars); + } + + private function fetchData() + { + $this->createData(); + + $qb = $this->_em->createQueryBuilder(); + $qb->select('d, dr, c') + ->from(__NAMESPACE__ . '\GH5762Driver', 'd') + ->leftJoin('d.driverRides', 'dr') + ->leftJoin('dr.car', 'c') + ->where('d.id = 1'); + + return $qb->getQuery()->getSingleResult(); + } + + private function createData() + { + $car1 = new GH5762Car('BMW', '7 Series'); + $car2 = new GH5762Car('Crysler', '300'); + $car3 = new GH5762Car('Dodge', 'Dart'); + $car4 = new GH5762Car('Mercedes', 'C-Class'); + $car5 = new GH5762Car('Volvo', 'XC90'); + + $driver = new GH5762Driver(1, 'John Doe'); + + $ride1 = new GH5762DriverRide($driver, $car1); + $ride2 = new GH5762DriverRide($driver, $car2); + $ride3 = new GH5762DriverRide($driver, $car3); + $ride4 = new GH5762DriverRide($driver, $car4); + $ride5 = new GH5762DriverRide($driver, $car5); + + $this->_em->persist($car1); + $this->_em->persist($car2); + $this->_em->persist($car3); + $this->_em->persist($car4); + $this->_em->persist($car5); + + $this->_em->persist($driver); + + $this->_em->persist($ride1); + $this->_em->persist($ride2); + $this->_em->persist($ride3); + $this->_em->persist($ride4); + $this->_em->persist($ride5); + + $this->_em->flush(); + $this->_em->clear(); + } +} + +/** + * @Entity + * @Table(name="driver") + */ +class GH5762Driver +{ + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="NONE") + */ + private $id; + + /** + * @Column(type="string", length=255); + */ + private $name; + + /** + * @OneToMany(targetEntity="GH5762DriverRide", mappedBy="driver") + */ + private $driverRides; + + function __construct($id, $name) + { + $this->driverRides = new ArrayCollection(); + $this->id = $id; + $this->name = $name; + } + + function getId() + { + return $this->id; + } + + function getName() + { + return $this->name; + } + + function getDriverRides() + { + return $this->driverRides; + } +} + +/** + * @Entity + * @Table(name="driver_ride") + */ +class GH5762DriverRide +{ + + /** + * @Id + * @ManyToOne(targetEntity="GH5762Driver", inversedBy="driverRides") + * @JoinColumn(name="driver_id", referencedColumnName="id") + */ + private $driver; + + /** + * @Id + * @ManyToOne(targetEntity="GH5762Car", inversedBy="carRides") + * @JoinColumn(name="car", referencedColumnName="brand") + */ + private $car; + + function __construct(GH5762Driver $driver, GH5762Car $car) + { + $this->driver = $driver; + $this->car = $car; + + $this->driver->getDriverRides()->add($this); + $this->car->getCarRides()->add($this); + } + + function getDriver() + { + return $this->driver; + } + + function getCar() + { + return $this->car; + } +} + +/** + * @Entity + * @Table(name="car") + */ +class GH5762Car +{ + + /** + * @Id + * @Column(type="string", length=25) + * @GeneratedValue(strategy="NONE") + */ + private $brand; + + /** + * @Column(type="string", length=255); + */ + private $model; + + /** + * @OneToMany(targetEntity="GH5762DriverRide", mappedBy="car") + */ + private $carRides; + + function __construct($brand, $model) + { + $this->carRides = new ArrayCollection(); + $this->brand = $brand; + $this->model = $model; + } + + function getBrand() + { + return $this->brand; + } + + function getModel() + { + return $this->model; + } + + function getCarRides() + { + return $this->carRides; + } +} From 5eedccc22ab6c46f5563e4dbb6e4b9c1b2da9ea9 Mon Sep 17 00:00:00 2001 From: Alexander Kurilo Date: Mon, 29 Aug 2016 23:17:24 +0300 Subject: [PATCH 092/144] Remove irrelevant accessors (#5762) --- .../ORM/Functional/Ticket/GH5762Test.php | 84 +++++-------------- 1 file changed, 21 insertions(+), 63 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php index 809417c0470..ff3dac7e6e2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php @@ -15,9 +15,9 @@ protected function setup() parent::setup(); $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\GH5762Driver'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\GH5762DriverRide'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\GH5762Car'), + $this->_em->getClassMetadata(GH5762Driver::class), + $this->_em->getClassMetadata(GH5762DriverRide::class), + $this->_em->getClassMetadata(GH5762Car::class), )); } @@ -26,13 +26,13 @@ public function testIssue() $result = $this->fetchData(); $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Driver', $result); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result->getDriverRides()); - $this->assertInstanceOf(__NAMESPACE__ . '\GH5762DriverRide', $result->getDriverRides()->get(0)); - $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Car', $result->getDriverRides()->get(0)->getCar()); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result->driverRides); + $this->assertInstanceOf(__NAMESPACE__ . '\GH5762DriverRide', $result->driverRides->get(0)); + $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Car', $result->driverRides->get(0)->car); $cars = array(); - foreach ($result->getDriverRides() as $ride) { - $cars[] = $ride->getCar()->getBrand(); + foreach ($result->driverRides as $ride) { + $cars[] = $ride->car->brand; } $this->assertEquals(count($cars), count(array_unique($cars))); @@ -93,50 +93,34 @@ private function createData() } /** - * @Entity + * @Entity * @Table(name="driver") */ class GH5762Driver { - /** * @Id * @Column(type="integer") * @GeneratedValue(strategy="NONE") */ - private $id; + public $id; /** * @Column(type="string", length=255); */ - private $name; + public $name; /** * @OneToMany(targetEntity="GH5762DriverRide", mappedBy="driver") */ - private $driverRides; + public $driverRides; - function __construct($id, $name) + public function __construct($id, $name) { $this->driverRides = new ArrayCollection(); $this->id = $id; $this->name = $name; } - - function getId() - { - return $this->id; - } - - function getName() - { - return $this->name; - } - - function getDriverRides() - { - return $this->driverRides; - } } /** @@ -145,38 +129,27 @@ function getDriverRides() */ class GH5762DriverRide { - /** * @Id * @ManyToOne(targetEntity="GH5762Driver", inversedBy="driverRides") * @JoinColumn(name="driver_id", referencedColumnName="id") */ - private $driver; + public $driver; /** * @Id * @ManyToOne(targetEntity="GH5762Car", inversedBy="carRides") * @JoinColumn(name="car", referencedColumnName="brand") */ - private $car; + public $car; function __construct(GH5762Driver $driver, GH5762Car $car) { $this->driver = $driver; $this->car = $car; - $this->driver->getDriverRides()->add($this); - $this->car->getCarRides()->add($this); - } - - function getDriver() - { - return $this->driver; - } - - function getCar() - { - return $this->car; + $this->driver->driverRides->add($this); + $this->car->carRides->add($this); } } @@ -192,37 +165,22 @@ class GH5762Car * @Column(type="string", length=25) * @GeneratedValue(strategy="NONE") */ - private $brand; + public $brand; /** * @Column(type="string", length=255); */ - private $model; + public $model; /** * @OneToMany(targetEntity="GH5762DriverRide", mappedBy="car") */ - private $carRides; + public $carRides; - function __construct($brand, $model) + public function __construct($brand, $model) { $this->carRides = new ArrayCollection(); $this->brand = $brand; $this->model = $model; } - - function getBrand() - { - return $this->brand; - } - - function getModel() - { - return $this->model; - } - - function getCarRides() - { - return $this->carRides; - } } From 6ab27993fc08810689bc11ac50ac25b853943951 Mon Sep 17 00:00:00 2001 From: Alexander Kurilo Date: Mon, 29 Aug 2016 22:29:41 +0300 Subject: [PATCH 093/144] Use ::class const instead of FQCN string (#5762) --- .../Tests/ORM/Functional/Ticket/GH5762Test.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php index ff3dac7e6e2..a602921de99 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php @@ -3,11 +3,13 @@ namespace Doctrine\Tests\ORM\Functional\Ticket; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\PersistentCollection; +use Doctrine\Tests\OrmFunctionalTestCase; /** * @group GH-5762 */ -class GH5762Test extends \Doctrine\Tests\OrmFunctionalTestCase +class GH5762Test extends OrmFunctionalTestCase { protected function setup() @@ -25,10 +27,10 @@ public function testIssue() { $result = $this->fetchData(); - $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Driver', $result); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result->driverRides); - $this->assertInstanceOf(__NAMESPACE__ . '\GH5762DriverRide', $result->driverRides->get(0)); - $this->assertInstanceOf(__NAMESPACE__ . '\GH5762Car', $result->driverRides->get(0)->car); + $this->assertInstanceOf(GH5762Driver::class, $result); + $this->assertInstanceOf(PersistentCollection::class, $result->driverRides); + $this->assertInstanceOf(GH5762DriverRide::class, $result->driverRides->get(0)); + $this->assertInstanceOf(GH5762Car::class, $result->driverRides->get(0)->car); $cars = array(); foreach ($result->driverRides as $ride) { @@ -49,7 +51,7 @@ private function fetchData() $qb = $this->_em->createQueryBuilder(); $qb->select('d, dr, c') - ->from(__NAMESPACE__ . '\GH5762Driver', 'd') + ->from(GH5762Driver::class, 'd') ->leftJoin('d.driverRides', 'dr') ->leftJoin('dr.car', 'c') ->where('d.id = 1'); From c4a2a348f4e1ca72da76cea187c939d6a8fbd2f7 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 7 Sep 2016 23:17:40 +0200 Subject: [PATCH 094/144] #5975 short array syntax --- lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index d7a429f0038..f9039e7abee 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -333,7 +333,7 @@ protected function hydrateRowData(array $row, array &$result) $rowData = $this->gatherRowData($row, $id, $nonemptyComponents); // reset result pointers for each data row - $this->resultPointers = array(); + $this->resultPointers = []; // Hydrate the data chunks foreach ($rowData['data'] as $dqlAlias => $data) { From aa6dc9695d8d81c14561606b8634907605ba3c0d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 7 Sep 2016 23:18:39 +0200 Subject: [PATCH 095/144] #5975 minor test cleanups --- .../ORM/Functional/Ticket/GH5762Test.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php index a602921de99..b42dd653496 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php @@ -11,10 +11,9 @@ */ class GH5762Test extends OrmFunctionalTestCase { - - protected function setup() + protected function setUp() { - parent::setup(); + parent::setUp(); $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata(GH5762Driver::class), @@ -27,22 +26,23 @@ public function testIssue() { $result = $this->fetchData(); - $this->assertInstanceOf(GH5762Driver::class, $result); - $this->assertInstanceOf(PersistentCollection::class, $result->driverRides); - $this->assertInstanceOf(GH5762DriverRide::class, $result->driverRides->get(0)); - $this->assertInstanceOf(GH5762Car::class, $result->driverRides->get(0)->car); + self::assertInstanceOf(GH5762Driver::class, $result); + self::assertInstanceOf(PersistentCollection::class, $result->driverRides); + self::assertInstanceOf(GH5762DriverRide::class, $result->driverRides->get(0)); + self::assertInstanceOf(GH5762Car::class, $result->driverRides->get(0)->car); $cars = array(); foreach ($result->driverRides as $ride) { $cars[] = $ride->car->brand; } - $this->assertEquals(count($cars), count(array_unique($cars))); - $this->assertContains('BMW', $cars); - $this->assertContains('Crysler', $cars); - $this->assertContains('Dodge', $cars); - $this->assertContains('Mercedes', $cars); - $this->assertContains('Volvo', $cars); + self::assertEquals(count($cars), count(array_unique($cars))); + + self::assertContains('BMW', $cars); + self::assertContains('Crysler', $cars); + self::assertContains('Dodge', $cars); + self::assertContains('Mercedes', $cars); + self::assertContains('Volvo', $cars); } private function fetchData() From dce0aeaa15582970773849e0e739004890a9a199 Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Fri, 2 Sep 2016 10:31:26 +0300 Subject: [PATCH 096/144] Create a failing test for issue #5989 Field with type=simple_array in a joined inheritance gets overridden by empty array in the hydrator --- .../Models/Issue5989/Issue5989Employee.php | 27 ++++++++++ .../Models/Issue5989/Issue5989Manager.php | 27 ++++++++++ .../Models/Issue5989/Issue5989Person.php | 29 +++++++++++ .../ORM/Functional/Ticket/Issue5989Test.php | 51 +++++++++++++++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 11 ++++ 5 files changed, 145 insertions(+) create mode 100644 tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php create mode 100644 tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php create mode 100644 tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php new file mode 100644 index 00000000000..e3eaac7d8be --- /dev/null +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php @@ -0,0 +1,27 @@ +tags; + } + + public function setTags(array $tags) + { + $this->tags = $tags; + } +} diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php new file mode 100644 index 00000000000..32c05398487 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php @@ -0,0 +1,27 @@ +tags; + } + + public function setTags(array $tags) + { + $this->tags = $tags; + } +} diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php new file mode 100644 index 00000000000..c9e00c4c188 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php @@ -0,0 +1,29 @@ +id; + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php new file mode 100644 index 00000000000..29a5a3fa2b0 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php @@ -0,0 +1,51 @@ +useModelSet('issue5989'); + parent::setUp(); + } + + public function testSimpleArrayTypeHydratedCorrectlyInJoinedInheritance() + { + $manager = new Issue5989Manager(); + + $managerTags = array('tag1', 'tag2'); + $manager->setTags($managerTags); + $this->_em->persist($manager); + + $employee = new Issue5989Employee(); + + $employeeTags = array('tag2', 'tag3'); + $employee->setTags($employeeTags); + $this->_em->persist($employee); + + $this->_em->flush(); + + $managerId = $manager->getId(); + $employeeId = $employee->getId(); + + // clear entity manager so that $repository->find actually fetches them and uses the hydrator + // instead of just returning the existing managed entities + $this->_em->clear(); + + $repository = $this->_em->getRepository(Issue5989Person::class); + + $manager = $repository->find($managerId); + $employee = $repository->find($employeeId); + + static::assertEquals($managerTags, $manager->getTags()); + static::assertEquals($employeeTags, $employee->getTags()); + } +} diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index b36a8348735..611f357e856 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -284,6 +284,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\VersionedManyToOne\Category', 'Doctrine\Tests\Models\VersionedManyToOne\Article', ), + 'issue5989' => array( + 'Doctrine\Tests\Models\Issue5989\Issue5989Person', + 'Doctrine\Tests\Models\Issue5989\Issue5989Employee', + 'Doctrine\Tests\Models\Issue5989\Issue5989Manager', + ), ); /** @@ -544,6 +549,12 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM versioned_many_to_one_category'); } + if (isset($this->_usedModelSets['issue5989'])) { + $conn->executeUpdate('DELETE FROM issue5989_persons'); + $conn->executeUpdate('DELETE FROM issue5989_employees'); + $conn->executeUpdate('DELETE FROM issue5989_managers'); + } + $this->_em->clear(); } From 7352b97b1457a9460911c1d333692078d47f9a5a Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Sat, 3 Sep 2016 22:25:11 +0300 Subject: [PATCH 097/144] Fix hydration in a joined inheritance with simple array or json array SimpleArrayType and JsonArrayType convert NULL value to an empty array, which fails the null check that is used to prevent overwrite Fixes issue #5989 --- lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 1c21369e346..5eb8cc117fe 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -122,6 +122,9 @@ protected function hydrateRowData(array $sqlResult, array &$result) continue; } + // Check if value is null before conversion (because some types convert null to something else) + $valueIsNull = $value === null; + // Convert field to a valid PHP value if (isset($cacheKeyInfo['type'])) { $type = $cacheKeyInfo['type']; @@ -131,7 +134,7 @@ protected function hydrateRowData(array $sqlResult, array &$result) $fieldName = $cacheKeyInfo['fieldName']; // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) - if ( ! isset($data[$fieldName]) || $value !== null) { + if ( ! isset($data[$fieldName]) || ! $valueIsNull) { $data[$fieldName] = $value; } } From c47c23a101a6acebf63156c3d70917c49fd8905a Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Sun, 4 Sep 2016 18:37:03 +0300 Subject: [PATCH 098/144] Use yoda condition in the null check --- lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 5eb8cc117fe..5fb77d67e67 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -123,7 +123,7 @@ protected function hydrateRowData(array $sqlResult, array &$result) } // Check if value is null before conversion (because some types convert null to something else) - $valueIsNull = $value === null; + $valueIsNull = null === $value; // Convert field to a valid PHP value if (isset($cacheKeyInfo['type'])) { From 33e23cdddb6366d3daa2d941db58d16f72818224 Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Thu, 8 Sep 2016 11:48:34 +0300 Subject: [PATCH 099/144] PR fixes (public properties & correct letter case in annotations) --- .../Tests/Models/Issue5989/Issue5989Employee.php | 14 ++------------ .../Tests/Models/Issue5989/Issue5989Manager.php | 14 ++------------ .../Tests/Models/Issue5989/Issue5989Person.php | 7 +------ .../ORM/Functional/Ticket/Issue5989Test.php | 16 ++++++++-------- 4 files changed, 13 insertions(+), 38 deletions(-) diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php index e3eaac7d8be..9f5e248d8c6 100644 --- a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php @@ -9,19 +9,9 @@ class Issue5989Employee extends Issue5989Person { /** - * @column(type="simple_array", nullable=true) + * @Column(type="simple_array", nullable=true) * * @var array */ - private $tags; - - public function getTags() - { - return $this->tags; - } - - public function setTags(array $tags) - { - $this->tags = $tags; - } + public $tags; } diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php index 32c05398487..e00067d9c57 100644 --- a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php @@ -9,19 +9,9 @@ class Issue5989Manager extends Issue5989Person { /** - * @column(type="simple_array", nullable=true) + * @Column(type="simple_array", nullable=true) * * @var array */ - private $tags; - - public function getTags() - { - return $this->tags; - } - - public function setTags(array $tags) - { - $this->tags = $tags; - } + public $tags; } diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php index c9e00c4c188..d31e65def25 100644 --- a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php @@ -20,10 +20,5 @@ class Issue5989Person * @Column(type="integer") * @GeneratedValue */ - private $id; - - public function getId() - { - return $this->id; - } + public $id; } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php index 29a5a3fa2b0..cf601b99aed 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php @@ -21,20 +21,20 @@ public function testSimpleArrayTypeHydratedCorrectlyInJoinedInheritance() { $manager = new Issue5989Manager(); - $managerTags = array('tag1', 'tag2'); - $manager->setTags($managerTags); + $managerTags = ['tag1', 'tag2']; + $manager->tags = $managerTags; $this->_em->persist($manager); $employee = new Issue5989Employee(); - $employeeTags = array('tag2', 'tag3'); - $employee->setTags($employeeTags); + $employeeTags =['tag2', 'tag3']; + $employee->tags = $employeeTags; $this->_em->persist($employee); $this->_em->flush(); - $managerId = $manager->getId(); - $employeeId = $employee->getId(); + $managerId = $manager->id; + $employeeId = $employee->id; // clear entity manager so that $repository->find actually fetches them and uses the hydrator // instead of just returning the existing managed entities @@ -45,7 +45,7 @@ public function testSimpleArrayTypeHydratedCorrectlyInJoinedInheritance() $manager = $repository->find($managerId); $employee = $repository->find($employeeId); - static::assertEquals($managerTags, $manager->getTags()); - static::assertEquals($employeeTags, $employee->getTags()); + static::assertEquals($managerTags, $manager->tags); + static::assertEquals($employeeTags, $employee->tags); } } From da41161d73e5fca87b3659219eda56026d95a515 Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Thu, 8 Sep 2016 13:50:28 +0300 Subject: [PATCH 100/144] Add unit test for SimpleObjectHydrator --- .../Hydration/SimpleObjectHydratorTest.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Hydration/SimpleObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/SimpleObjectHydratorTest.php index 459ce9ba1ec..9878728f2c8 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/SimpleObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/SimpleObjectHydratorTest.php @@ -86,4 +86,34 @@ public function testInvalidDiscriminatorValueException() $hydrator = new \Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator($this->_em); $hydrator->hydrateAll($stmt, $rsm); } + + /** + * @group issue-5989 + */ + public function testNullValueShouldNotOverwriteFieldWithSameNameInJoinedInheritance() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\Issue5989\Issue5989Person', 'p'); + $rsm->addFieldResult('p', 'p__id', 'id'); + $rsm->addFieldResult('p', 'm__tags', 'tags', 'Doctrine\Tests\Models\Issue5989\Issue5989Manager'); + $rsm->addFieldResult('p', 'e__tags', 'tags', 'Doctrine\Tests\Models\Issue5989\Issue5989Employee'); + $rsm->addMetaResult('p', 'discr', 'discr', false, 'string'); + $resultSet = array( + array( + 'p__id' => '1', + 'm__tags' => 'tag1,tag2', + 'e__tags' => null, + 'discr' => 'manager' + ), + ); + + $expectedEntity = new \Doctrine\Tests\Models\Issue5989\Issue5989Manager(); + $expectedEntity->id = 1; + $expectedEntity->tags = ['tag1', 'tag2']; + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + $this->assertEquals($result[0], $expectedEntity); + } } From 4d16e30a16d4cfc1ee45ac6f7279bdcd2b64d3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Fri, 2 Sep 2016 09:53:41 +0000 Subject: [PATCH 101/144] Use microtime to have more precision on cache time --- UPGRADE.md | 6 ++++++ lib/Doctrine/ORM/Cache/QueryCacheEntry.php | 6 +++--- lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php | 2 +- tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index ed9a538b3ec..2195f5b820d 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,11 @@ # Upgrade to 2.5 +## Minor BC BREAK: query cache key time is now a float + +As of 2.5, the `QueryCacheEntry#time` property will contain a float value +instead of an integer in order to have more precision and also to be consistent +with the `TimestampCacheEntry#time`. + ## Minor BC BREAK: discriminator map must now include all non-transient classes It is now required that you declare the root of an inheritance in the diff --git a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php index 1ba61bcf090..a8a26f6d2d8 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php @@ -38,18 +38,18 @@ class QueryCacheEntry implements CacheEntry /** * READ-ONLY: Public only for performance reasons, it should be considered immutable. * - * @var integer Time creation of this cache entry + * @var float Time creation of this cache entry */ public $time; /** * @param array $result - * @param integer $time + * @param float $time */ public function __construct($result, $time = null) { $this->result = $result; - $this->time = $time ?: time(); + $this->time = $time ?: microtime(true); } /** diff --git a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php index 4e504e4f8da..71ac3e828c6 100644 --- a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php @@ -35,6 +35,6 @@ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry) return true; } - return ($entry->time + $key->lifetime) > time(); + return ($entry->time + $key->lifetime) > microtime(true); } } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php index 3af1a3bc4be..ad1da8d1b50 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -386,7 +386,7 @@ public function testGetShouldIgnoreOldQueryCacheEntryResult() array('id'=>2, 'name' => 'Bar') ); - $entry->time = time() - 100; + $entry->time = microtime(true) - 100; $this->region->addReturn('get', $entry); $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[0])); From bf18aac62dbdb50e3b8d5c67a16282377feac712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Wed, 7 Sep 2016 22:19:45 +0000 Subject: [PATCH 102/144] Add timestamp key to QueryCacheKey --- lib/Doctrine/ORM/AbstractQuery.php | 36 +++++++++++++++---- .../Entity/AbstractEntityPersister.php | 36 +++++++++---------- lib/Doctrine/ORM/Cache/QueryCacheKey.php | 29 ++++++++++----- 3 files changed, 68 insertions(+), 33 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index e1667add231..216fca542ec 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -28,7 +28,7 @@ use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\ORM\Cache; -use Doctrine\ORM\Query\QueryException; +use Doctrine\ORM\Query\ResultSetMapping; /** * Base contract for ORM queries. Base class for Query and NativeQuery. @@ -993,32 +993,54 @@ private function executeIgnoreQueryCache($parameters = null, $hydrationMode = nu private function executeUsingQueryCache($parameters = null, $hydrationMode = null) { $rsm = $this->getResultSetMapping(); - $querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL); $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); - $result = $queryCache->get($querykey, $rsm, $this->_hints); + $queryKey = new QueryCacheKey( + $this->getHash(), + $this->lifetime, + $this->cacheMode ?: Cache::MODE_NORMAL, + $this->getTimestampKey() + ); + + $result = $queryCache->get($queryKey, $rsm, $this->_hints); if ($result !== null) { if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey); + $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey); } return $result; } $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode); - $cached = $queryCache->put($querykey, $rsm, $result, $this->_hints); + $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints); if ($this->cacheLogger) { - $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey); + $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey); if ($cached) { - $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey); + $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey); } } return $result; } + /** + * @return \Doctrine\ORM\Cache\TimestampCacheKey|null + */ + private function getTimestampKey() + { + $entityName = reset($this->_resultSetMapping->aliasMap); + + if (empty($entityName)) { + return null; + } + + $metadata = $this->_em->getClassMetadata($entityName); + + return new Cache\TimestampCacheKey($metadata->getTableName()); + } + /** * Get the result cache id to use to store the result set cache entry. * Will return the configured id if it exists otherwise a hash will be diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php index eaead7cf750..ba850ed9d16 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php @@ -381,13 +381,13 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint $query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy); $hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null); $rsm = $this->getResultSetMapping(); - $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); - $result = $queryCache->get($querykey, $rsm); + $result = $queryCache->get($queryKey, $rsm); if ($result !== null) { if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($this->regionName, $querykey); + $this->cacheLogger->queryCacheHit($this->regionName, $queryKey); } return $result[0]; @@ -397,15 +397,15 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint return null; } - $cached = $queryCache->put($querykey, $rsm, array($result)); + $cached = $queryCache->put($queryKey, $rsm, array($result)); if ($this->cacheLogger) { if ($result) { - $this->cacheLogger->queryCacheMiss($this->regionName, $querykey); + $this->cacheLogger->queryCacheMiss($this->regionName, $queryKey); } if ($cached) { - $this->cacheLogger->queryCachePut($this->regionName, $querykey); + $this->cacheLogger->queryCachePut($this->regionName, $queryKey); } } @@ -421,28 +421,28 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); $hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null); $rsm = $this->getResultSetMapping(); - $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); - $result = $queryCache->get($querykey, $rsm); + $result = $queryCache->get($queryKey, $rsm); if ($result !== null) { if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($this->regionName, $querykey); + $this->cacheLogger->queryCacheHit($this->regionName, $queryKey); } return $result; } $result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset); - $cached = $queryCache->put($querykey, $rsm, $result); + $cached = $queryCache->put($queryKey, $rsm, $result); if ($this->cacheLogger) { if ($result) { - $this->cacheLogger->queryCacheMiss($this->regionName, $querykey); + $this->cacheLogger->queryCacheMiss($this->regionName, $queryKey); } if ($cached) { - $this->cacheLogger->queryCachePut($this->regionName, $querykey); + $this->cacheLogger->queryCachePut($this->regionName, $queryKey); } } @@ -520,28 +520,28 @@ public function loadCriteria(Criteria $criteria) $timestamp = $this->timestampRegion->get($this->timestampKey); $hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null); $rsm = $this->getResultSetMapping(); - $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); + $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); - $cacheResult = $queryCache->get($querykey, $rsm); + $cacheResult = $queryCache->get($queryKey, $rsm); if ($cacheResult !== null) { if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($this->regionName, $querykey); + $this->cacheLogger->queryCacheHit($this->regionName, $queryKey); } return $cacheResult; } $result = $this->persister->loadCriteria($criteria); - $cached = $queryCache->put($querykey, $rsm, $result); + $cached = $queryCache->put($queryKey, $rsm, $result); if ($this->cacheLogger) { if ($result) { - $this->cacheLogger->queryCacheMiss($this->regionName, $querykey); + $this->cacheLogger->queryCacheMiss($this->regionName, $queryKey); } if ($cached) { - $this->cacheLogger->queryCachePut($this->regionName, $querykey); + $this->cacheLogger->queryCachePut($this->regionName, $queryKey); } } diff --git a/lib/Doctrine/ORM/Cache/QueryCacheKey.php b/lib/Doctrine/ORM/Cache/QueryCacheKey.php index 9a7d2b7bcc8..0e072a36fc0 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheKey.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheKey.php @@ -45,14 +45,27 @@ class QueryCacheKey extends CacheKey public $cacheMode; /** - * @param string $hash Result cache id - * @param integer $lifetime Query lifetime - * @param integer $cacheMode Query cache mode + * READ-ONLY: Public only for performance reasons, it should be considered immutable. + * + * @var TimestampCacheKey|null + */ + public $timestampKey; + + /** + * @param string $hash Result cache id + * @param integer $lifetime Query lifetime + * @param int $cacheMode Query cache mode + * @param TimestampCacheKey|null $timestampKey */ - public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL) - { - $this->hash = $hash; - $this->lifetime = $lifetime; - $this->cacheMode = $cacheMode; + public function __construct( + $hash, + $lifetime = 0, + $cacheMode = Cache::MODE_NORMAL, + TimestampCacheKey $timestampKey = null + ) { + $this->hash = $hash; + $this->lifetime = $lifetime; + $this->cacheMode = $cacheMode; + $this->timestampKey = $timestampKey; } } From 88567ea395db6c73895e2ad9148a77c3a032bf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Fri, 2 Sep 2016 09:54:14 +0000 Subject: [PATCH 103/144] Evict query cache when entities are updated --- lib/Doctrine/ORM/Cache/CacheConfiguration.php | 4 ++- .../Cache/TimestampQueryCacheValidator.php | 34 ++++++++++++++++++ .../Tests/ORM/Cache/CacheConfigTest.php | 9 +++-- .../SecondLevelCacheQueryCacheTest.php | 35 ++++++++++++++++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/CacheConfiguration.php b/lib/Doctrine/ORM/Cache/CacheConfiguration.php index 4891cac4909..eb3ec428df3 100644 --- a/lib/Doctrine/ORM/Cache/CacheConfiguration.php +++ b/lib/Doctrine/ORM/Cache/CacheConfiguration.php @@ -110,7 +110,9 @@ public function setRegionsConfiguration(RegionsConfiguration $regionsConfig) public function getQueryValidator() { if ($this->queryValidator === null) { - $this->queryValidator = new TimestampQueryCacheValidator(); + $this->queryValidator = new TimestampQueryCacheValidator( + $this->cacheFactory->getTimestampRegion() + ); } return $this->queryValidator; diff --git a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php index 71ac3e828c6..c6404d3b25d 100644 --- a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php @@ -26,15 +26,49 @@ */ class TimestampQueryCacheValidator implements QueryCacheValidator { + /** + * @var TimestampRegion + */ + private $timestampRegion; + + /** + * @param TimestampRegion $timestampRegion + */ + public function __construct(TimestampRegion $timestampRegion) + { + $this->timestampRegion = $timestampRegion; + } + /** * {@inheritdoc} */ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry) { + if ($this->regionUpdated($key, $entry)) { + return false; + } + if ($key->lifetime == 0) { return true; } return ($entry->time + $key->lifetime) > microtime(true); } + + /** + * @param QueryCacheKey $key + * @param QueryCacheEntry $entry + * + * @return bool + */ + private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry) + { + if ($key->timestampKey === null) { + return false; + } + + $timestamp = $this->timestampRegion->get($key->timestampKey); + + return $timestamp && $timestamp->time > $entry->time; + } } diff --git a/tests/Doctrine/Tests/ORM/Cache/CacheConfigTest.php b/tests/Doctrine/Tests/ORM/Cache/CacheConfigTest.php index 412d3020719..d3d97bb0477 100644 --- a/tests/Doctrine/Tests/ORM/Cache/CacheConfigTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/CacheConfigTest.php @@ -2,8 +2,8 @@ namespace Doctrine\Tests\ORM\Cache; -use Doctrine\Tests\DoctrineTestCase; use Doctrine\ORM\Cache\CacheConfiguration; +use Doctrine\Tests\DoctrineTestCase; /** * @group DDC-2183 @@ -64,6 +64,11 @@ public function testSetGetCacheFactory() public function testSetGetQueryValidator() { + $factory = $this->getMock('Doctrine\ORM\Cache\CacheFactory'); + $factory->method('getTimestampRegion')->willReturn($this->getMock('Doctrine\ORM\Cache\TimestampRegion')); + + $this->config->setCacheFactory($factory); + $validator = $this->getMock('Doctrine\ORM\Cache\QueryCacheValidator'); $this->assertInstanceOf('Doctrine\ORM\Cache\TimestampQueryCacheValidator', $this->config->getQueryValidator()); @@ -72,4 +77,4 @@ public function testSetGetQueryValidator() $this->assertEquals($validator, $this->config->getQueryValidator()); } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 995c1d07f41..18618336a22 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -1035,4 +1035,37 @@ public function testNonCacheableQueryUpdateStatementException() ->setCacheable(true) ->getResult(); } -} \ No newline at end of file + + public function testQueryCacheShouldBeEvictedOnTimestampUpdate() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT country FROM Doctrine\Tests\Models\Cache\Country country'; + + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(2, $result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->_em->persist(new Country('France')); + $this->_em->flush(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(3, $result2); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + foreach ($result2 as $entity) { + $this->assertInstanceOf(Country::CLASSNAME, $entity); + } + } +} From 9e9864c684dcd78fca3e0936424c4bcf1679393e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Wed, 7 Sep 2016 22:32:59 +0000 Subject: [PATCH 104/144] The timestamp verification is now done by the validator So it's useless to keep it here too. --- .../Persister/Entity/AbstractEntityPersister.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php index ba850ed9d16..2fd8abe6f6f 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php @@ -296,17 +296,16 @@ private function storeJoinedAssociations($entity) * @param array $orderBy * @param integer $limit * @param integer $offset - * @param integer $timestamp * * @return string */ - protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null) + protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null) { list($params) = ($criteria instanceof Criteria) ? $this->persister->expandCriteriaParameters($criteria) : $this->persister->expandParameters($criteria); - return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp); + return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset); } /** @@ -377,9 +376,8 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint } //handle only EntityRepository#findOneBy - $timestamp = $this->timestampRegion->get($this->timestampKey); $query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy); - $hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null); + $hash = $this->getHash($query, $criteria, null, null, null); $rsm = $this->getResultSetMapping(); $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); @@ -417,9 +415,8 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint */ public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) { - $timestamp = $this->timestampRegion->get($this->timestampKey); $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); - $hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null); + $hash = $this->getHash($query, $criteria, null, null, null); $rsm = $this->getResultSetMapping(); $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); @@ -517,8 +514,7 @@ public function count($criteria = array()) public function loadCriteria(Criteria $criteria) { $query = $this->persister->getSelectSQL($criteria); - $timestamp = $this->timestampRegion->get($this->timestampKey); - $hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null); + $hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset); $rsm = $this->getResultSetMapping(); $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); From 5e702ad5249184bca6ad0c78960296fe005660aa Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 8 Sep 2016 13:51:21 +0200 Subject: [PATCH 105/144] #6001 documenting minor BC break in `QueryCacheEntry#time` type - specific version used --- UPGRADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 2195f5b820d..b71e10adb65 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -2,7 +2,7 @@ ## Minor BC BREAK: query cache key time is now a float -As of 2.5, the `QueryCacheEntry#time` property will contain a float value +As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value instead of an integer in order to have more precision and also to be consistent with the `TimestampCacheEntry#time`. From 3e57c46afd52d55ceaaefc2b8bf0c1791d89f7ce Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 8 Sep 2016 14:01:56 +0200 Subject: [PATCH 106/144] #6001 adding `orderBy`, `limit` and `offset` variables (defined in `master`, not in `2.5`) --- .../ORM/Cache/Persister/Entity/AbstractEntityPersister.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php index 2fd8abe6f6f..a2071f42b7e 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php @@ -513,6 +513,9 @@ public function count($criteria = array()) */ public function loadCriteria(Criteria $criteria) { + $orderBy = $criteria->getOrderings(); + $limit = $criteria->getMaxResults(); + $offset = $criteria->getFirstResult(); $query = $this->persister->getSelectSQL($criteria); $hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset); $rsm = $this->getResultSetMapping(); From 8e95672f49715fc3c5cf2b28f67bb77d7a20c4a0 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 9 Sep 2016 22:51:42 +0200 Subject: [PATCH 107/144] Removing `::class` usage (PHP 5.5 is not yet required on ORM 2.5) --- .../ORM/Functional/Ticket/GH5762Test.php | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php index b42dd653496..1b45d4274c6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php @@ -16,9 +16,9 @@ protected function setUp() parent::setUp(); $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(GH5762Driver::class), - $this->_em->getClassMetadata(GH5762DriverRide::class), - $this->_em->getClassMetadata(GH5762Car::class), + $this->_em->getClassMetadata(GH5762Driver::CLASSNAME), + $this->_em->getClassMetadata(GH5762DriverRide::CLASSNAME), + $this->_em->getClassMetadata(GH5762Car::CLASSNAME), )); } @@ -26,10 +26,10 @@ public function testIssue() { $result = $this->fetchData(); - self::assertInstanceOf(GH5762Driver::class, $result); - self::assertInstanceOf(PersistentCollection::class, $result->driverRides); - self::assertInstanceOf(GH5762DriverRide::class, $result->driverRides->get(0)); - self::assertInstanceOf(GH5762Car::class, $result->driverRides->get(0)->car); + self::assertInstanceOf(GH5762Driver::CLASSNAME, $result); + self::assertInstanceOf(PersistentCollection::CLASSNAME, $result->driverRides); + self::assertInstanceOf(GH5762DriverRide::CLASSNAME, $result->driverRides->get(0)); + self::assertInstanceOf(GH5762Car::CLASSNAME, $result->driverRides->get(0)->car); $cars = array(); foreach ($result->driverRides as $ride) { @@ -51,7 +51,7 @@ private function fetchData() $qb = $this->_em->createQueryBuilder(); $qb->select('d, dr, c') - ->from(GH5762Driver::class, 'd') + ->from(GH5762Driver::CLASSNAME, 'd') ->leftJoin('d.driverRides', 'dr') ->leftJoin('dr.car', 'c') ->where('d.id = 1'); @@ -100,6 +100,8 @@ private function createData() */ class GH5762Driver { + const CLASSNAME = __CLASS__; + /** * @Id * @Column(type="integer") @@ -131,6 +133,8 @@ public function __construct($id, $name) */ class GH5762DriverRide { + const CLASSNAME = __CLASS__; + /** * @Id * @ManyToOne(targetEntity="GH5762Driver", inversedBy="driverRides") @@ -161,6 +165,7 @@ function __construct(GH5762Driver $driver, GH5762Car $car) */ class GH5762Car { + const CLASSNAME = __CLASS__; /** * @Id From 47ce079b64a3419be5bf7629627ae6cf83d9d815 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 9 Sep 2016 22:52:54 +0200 Subject: [PATCH 108/144] Removing `::class` usage (PHP 5.5 is not yet required on ORM 2.5) --- tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php | 2 ++ tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php index d31e65def25..a200722ffc0 100644 --- a/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php +++ b/tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php @@ -15,6 +15,8 @@ */ class Issue5989Person { + const CLASSNAME = __CLASS__; + /** * @Id * @Column(type="integer") diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php index cf601b99aed..7a94bbe33ac 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php @@ -40,7 +40,7 @@ public function testSimpleArrayTypeHydratedCorrectlyInJoinedInheritance() // instead of just returning the existing managed entities $this->_em->clear(); - $repository = $this->_em->getRepository(Issue5989Person::class); + $repository = $this->_em->getRepository(Issue5989Person::CLASSNAME); $manager = $repository->find($managerId); $employee = $repository->find($employeeId); From 5365a418e9c3259bab05a60bc617a838fb1043e5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 9 Sep 2016 23:25:54 +0200 Subject: [PATCH 109/144] Removed non-existing `CLASSNAME` reference --- tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php index 1b45d4274c6..938472921c5 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php @@ -27,7 +27,7 @@ public function testIssue() $result = $this->fetchData(); self::assertInstanceOf(GH5762Driver::CLASSNAME, $result); - self::assertInstanceOf(PersistentCollection::CLASSNAME, $result->driverRides); + self::assertInstanceOf('Doctrine\ORM\PersistentCollection', $result->driverRides); self::assertInstanceOf(GH5762DriverRide::CLASSNAME, $result->driverRides->get(0)); self::assertInstanceOf(GH5762Car::CLASSNAME, $result->driverRides->get(0)->car); From 95dcf51ad50e32354f88773e92fc9c34bd79f9b3 Mon Sep 17 00:00:00 2001 From: Mathieu De Zutter Date: Tue, 1 Mar 2016 10:58:37 +0100 Subject: [PATCH 110/144] Avoid conflicts due to spl_object_hash(). When merging an entity with a to-many association, it will store the original entity data using the object hash of the to-be-merged entity instead of the managed entity. Since this to-be-merged entity is not managed by Doctrine, it can disappear from the memory. A new object can reuse the same memory location and thus have the same object hash. When one tries to persist this object as new, Doctrine will refuse it because it thinks that the entity is managed+dirty. This patch is a very naive fix: it just disables storing the original entity data in case of to-many associations. It may not be the ideal or even a good solution at all, but it solves the problem of object hash reuse. The test case relies on the immediate reusing of memory locations by PHP. The variable $user has twice the same object hash, though referring a different object. Tested on PHP 5.6.17 Without the fix, the test fails on the last line with: A managed+dirty entity Doctrine\Tests\Models\CMS\CmsUser@[...] can not be scheduled for insertion. --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- .../Tests/ORM/Functional/OidReuseTest.php | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index c2f037e2d5f..bed79caa316 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -3423,7 +3423,7 @@ private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); - $this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol; +// $this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol; } if ($assoc2['isCascadeMerge']) { diff --git a/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php b/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php new file mode 100644 index 00000000000..40aaf423830 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php @@ -0,0 +1,34 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testOidReuse() + { + $user = new CmsUser(); + $this->_em->merge($user); + + $user = null; + + $user = new CmsUser(); + $this->_em->persist($user); + } + +} \ No newline at end of file From b0e4e3eda4b7742c286a5f8a82bc566dcd54750b Mon Sep 17 00:00:00 2001 From: Mathieu De Zutter Date: Tue, 1 Mar 2016 19:36:57 +0100 Subject: [PATCH 111/144] Remove old code in comments. --- lib/Doctrine/ORM/UnitOfWork.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index bed79caa316..184b291da80 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -3422,8 +3422,6 @@ private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) ); $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); - -// $this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol; } if ($assoc2['isCascadeMerge']) { From a3d93afc4fb4b8c984f79a7b4a88d689c9b41305 Mon Sep 17 00:00:00 2001 From: Mathieu De Zutter Date: Tue, 1 Mar 2016 19:51:38 +0100 Subject: [PATCH 112/144] Additional assertion to check that unreferenced objects are not in UOW. --- tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php b/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php index 40aaf423830..742b183fe33 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php @@ -22,11 +22,19 @@ protected function setUp() public function testOidReuse() { + $uow = $this->_em->getUnitOfWork(); + $reflexion = new \ReflectionClass(get_class($uow)); + $originalEntityDataProperty = $reflexion->getProperty('originalEntityData'); + $originalEntityDataProperty->setAccessible(true); + $user = new CmsUser(); + $oid = spl_object_hash($user); $this->_em->merge($user); $user = null; + $this->assertArrayNotHasKey($oid, $originalEntityDataProperty->getValue($uow)); + $user = new CmsUser(); $this->_em->persist($user); } From e73428a051a95edb131b27fc3642493ea53ada16 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 10 Sep 2016 20:15:33 +0200 Subject: [PATCH 113/144] #5689 moved `OidReuseTest` contents into the `UnitOfWork` tests --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 9623cfbf484..20ae887a2f1 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -12,6 +12,8 @@ use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Mocks\EntityPersisterMock; use Doctrine\Tests\Mocks\UnitOfWorkMock; +use Doctrine\Tests\Models\CMS\CmsPhonenumber; +use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\Forum\ForumAvatar; use Doctrine\Tests\Models\Forum\ForumUser; use Doctrine\Tests\Models\GeoNames\City; @@ -362,6 +364,38 @@ public function invalidAssociationValuesDataProvider() [new ArrayCollection()], ]; } + + /** + * @group 5689 + * @group 1465 + */ + public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMap() + { + $reflectionOriginalEntityData = new \ReflectionProperty('Doctrine\ORM\UnitOfWork', 'originalEntityData'); + + $reflectionOriginalEntityData->setAccessible(true); + + $user = new CmsUser(); + $user->name = 'ocramius'; + $mergedUser = $this->_unitOfWork->merge($user); + + self::assertSame([], $this->_unitOfWork->getOriginalEntityData($user), 'No original data was stored'); + self::assertSame([], $this->_unitOfWork->getOriginalEntityData($mergedUser), 'No original data was stored'); + + + $user = null; + $mergedUser = null; + + // force garbage collection of $user (frees the used object hashes, which may be recycled) + gc_collect_cycles(); + + $newUser = new CmsUser(); + $newUser->name = 'ocramius'; + + $this->_unitOfWork->persist($newUser); + + self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored'); + } } /** From 147f8fffff7ef4de6035509b24e375ee6bc768a3 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 10 Sep 2016 20:15:59 +0200 Subject: [PATCH 114/144] #5689 removed `OidReuseTest`, which was moved to `UnitOfWork` tests --- .../Tests/ORM/Functional/OidReuseTest.php | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php diff --git a/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php b/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php deleted file mode 100644 index 742b183fe33..00000000000 --- a/tests/Doctrine/Tests/ORM/Functional/OidReuseTest.php +++ /dev/null @@ -1,42 +0,0 @@ -useModelSet('cms'); - parent::setUp(); - } - - public function testOidReuse() - { - $uow = $this->_em->getUnitOfWork(); - $reflexion = new \ReflectionClass(get_class($uow)); - $originalEntityDataProperty = $reflexion->getProperty('originalEntityData'); - $originalEntityDataProperty->setAccessible(true); - - $user = new CmsUser(); - $oid = spl_object_hash($user); - $this->_em->merge($user); - - $user = null; - - $this->assertArrayNotHasKey($oid, $originalEntityDataProperty->getValue($uow)); - - $user = new CmsUser(); - $this->_em->persist($user); - } - -} \ No newline at end of file From c9161fcd6f10d15bae24073d712623c03b7eb5db Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 10 Sep 2016 20:19:15 +0200 Subject: [PATCH 115/144] #5689 removed unused reflection access --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 20ae887a2f1..306fceaba89 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -371,10 +371,6 @@ public function invalidAssociationValuesDataProvider() */ public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMap() { - $reflectionOriginalEntityData = new \ReflectionProperty('Doctrine\ORM\UnitOfWork', 'originalEntityData'); - - $reflectionOriginalEntityData->setAccessible(true); - $user = new CmsUser(); $user->name = 'ocramius'; $mergedUser = $this->_unitOfWork->merge($user); From d7026c46eca1fab193e670b26b9877e2d8f95eba Mon Sep 17 00:00:00 2001 From: Ed Hartwell Goose Date: Mon, 11 Apr 2016 17:01:55 +0100 Subject: [PATCH 116/144] Fixes #5755, uses '->getReflectionProperties()' instead of '->getReflectionClass()->getProperties()' to ensure all fields are copied, and adds test to confirm behaviour --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- .../Tests/ORM/Proxy/ProxyFactoryTest.php | 55 ++++++++++++++++--- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index c6f1cbd3d6a..7a1a2c67186 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -228,7 +228,7 @@ private function createCloner(ClassMetadata $classMetadata, EntityPersister $ent ); } - foreach ($class->getReflectionClass()->getProperties() as $property) { + foreach ($class->getReflectionProperties() as $property) { if ( ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) { continue; } diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php index a8a2adaba0d..fd4fa664867 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php @@ -2,15 +2,17 @@ namespace Doctrine\Tests\ORM\Proxy; +use Doctrine\Common\Persistence\Mapping\RuntimeReflectionService; +use Doctrine\Common\Proxy\AbstractProxyFactory; use Doctrine\ORM\EntityNotFoundException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Proxy\ProxyFactory; -use Doctrine\Common\Proxy\ProxyGenerator; use Doctrine\Tests\Mocks\ConnectionMock; +use Doctrine\Tests\Mocks\DriverMock; use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Mocks\UnitOfWorkMock; -use Doctrine\Tests\Mocks\DriverMock; -use Doctrine\Common\Proxy\AbstractProxyFactory; +use Doctrine\Tests\OrmTestCase; +use Doctrine\Tests\Models\Company\CompanyEmployee; /** * Test the proxy generator. Its work is generating on-the-fly subclasses of a given model, which implement the Proxy pattern. @@ -62,9 +64,9 @@ public function testReferenceProxyDelegatesLoadingToThePersister() $persister ->expects($this->atLeastOnce()) - ->method('load') - ->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass)) - ->will($this->returnValue(new \stdClass())); + ->method('load') + ->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass)) + ->will($this->returnValue(new \stdClass())); $proxy->getDescription(); } @@ -75,7 +77,7 @@ public function testReferenceProxyDelegatesLoadingToThePersister() public function testSkipAbstractClassesOnGeneration() { $cm = new ClassMetadata(__NAMESPACE__ . '\\AbstractClass'); - $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->initializeReflection(new RuntimeReflectionService); $this->assertNotNull($cm->reflClass); $num = $this->proxyFactory->generateProxyClasses(array($cm)); @@ -136,6 +138,45 @@ public function testFailedProxyCloningDoesNotMarkTheProxyAsInitialized() $this->assertInstanceOf('Closure', $proxy->__getInitializer(), 'The initializer wasn\'t removed'); $this->assertInstanceOf('Closure', $proxy->__getCloner(), 'The cloner wasn\'t removed'); } + + public function testProxyClonesParentFields() + { + $companyEmployee = new CompanyEmployee(); + $companyEmployee->setSalary(1000); // A property on the CompanyEmployee + $companyEmployee->setName("Bob"); // A property on the parent class, CompanyPerson + + // Set the id of the CompanyEmployee (which is in the parent CompanyPerson) + $class = new \ReflectionClass('Doctrine\Tests\Models\Company\CompanyPerson'); + + $property = $class->getProperty('id'); + $property->setAccessible(true); + + $property->setValue($companyEmployee, 42); + + $classMetaData = $this->emMock->getClassMetadata('Doctrine\Tests\Models\Company\CompanyEmployee'); + + $persister = $this->getMock('Doctrine\ORM\Persisters\Entity\BasicEntityPersister', array('load', 'getClassMetadata'), array(), '', false); + $this->uowMock->setEntityPersister('Doctrine\Tests\Models\Company\CompanyEmployee', $persister); + + /* @var $proxy \Doctrine\Common\Proxy\Proxy */ + $proxy = $this->proxyFactory->getProxy('Doctrine\Tests\Models\Company\CompanyEmployee', array('id' => 42)); + + $persister + ->expects($this->atLeastOnce()) + ->method('load') + ->will($this->returnValue($companyEmployee)); + + $persister + ->expects($this->atLeastOnce()) + ->method('getClassMetadata') + ->will($this->returnValue($classMetaData)); + + $cloned = clone $proxy; + + $this->assertEquals(42, $cloned->getId(), "Expected the Id to be cloned"); + $this->assertEquals(1000, $cloned->getSalary(), "Expect properties on the CompanyEmployee class to be cloned"); + $this->assertEquals("Bob", $cloned->getName(), "Expect properties on the CompanyPerson class to be cloned"); + } } abstract class AbstractClass From 9bcee455cad4a13cb8b8011529d50dff850532c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Tue, 13 Sep 2016 12:56:20 +0000 Subject: [PATCH 117/144] Make child entity share the timestamp region with parent class --- lib/Doctrine/ORM/AbstractQuery.php | 2 +- .../Entity/AbstractEntityPersister.php | 2 +- ...condLevelCacheJoinTableInheritanceTest.php | 47 ++++++++++++++++++- ...ndLevelCacheSingleTableInheritanceTest.php | 43 ++++++++++++++++- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 216fca542ec..7ac726f466a 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -1038,7 +1038,7 @@ private function getTimestampKey() $metadata = $this->_em->getClassMetadata($entityName); - return new Cache\TimestampCacheKey($metadata->getTableName()); + return new Cache\TimestampCacheKey($metadata->rootEntityName); } /** diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php index a2071f42b7e..0a5aa1c656b 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php @@ -131,7 +131,7 @@ public function __construct(EntityPersister $persister, Region $region, EntityMa $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); } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php index e81b08b96f9..9864172fab4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php @@ -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 @@ public function testOneToManyRelationJoinTable() $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity->getInfos()->get(0)); $this->assertEquals($this->attractionsInfo[0]->getFone(), $entity->getInfos()->get(0)->getFone()); } -} \ No newline at end of file + + 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::class, $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); + } + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index 3f8487d9946..0ee1df176fd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -210,4 +210,45 @@ public function testOneToManyRelationSingleTable() $this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName()); $this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName()); } -} \ No newline at end of file + + 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::class, $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); + } + } +} From af99cba28cd292ecca8b95ccf15250804f6545c5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 23 Nov 2016 18:02:15 +0100 Subject: [PATCH 118/144] #6028 removed specific `::class` usage, since 2.5.x still supports PHP 5.4.x --- .../ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php index 9864172fab4..5e70effee09 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php @@ -211,7 +211,7 @@ public function testQueryCacheShouldBeEvictedOnTimestampUpdate() $contact = new AttractionContactInfo( '1234-1234', - $this->_em->find(Attraction::class, $this->attractions[5]->getId()) + $this->_em->find(Attraction::CLASSNAME, $this->attractions[5]->getId()) ); $this->_em->persist($contact); From 2122297fdbeb5cca6d5da9059712924a80b7e8b3 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 23 Nov 2016 18:06:14 +0100 Subject: [PATCH 119/144] #6028 removed specific `::class` usage, since 2.5.x still supports PHP 5.4.x --- .../Functional/SecondLevelCacheSingleTableInheritanceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index 0ee1df176fd..de505992891 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -231,7 +231,7 @@ public function testQueryCacheShouldBeEvictedOnTimestampUpdate() $contact = new Beach( 'Botafogo', - $this->_em->find(City::class, $this->cities[1]->getId()) + $this->_em->find(City::CLASSNAME, $this->cities[1]->getId()) ); $this->_em->persist($contact); From 3dadfa49d5fb4e2e3866eaa535d63a6ac44d8e4e Mon Sep 17 00:00:00 2001 From: Steevan BARBOYON Date: Mon, 31 Oct 2016 12:03:19 +0100 Subject: [PATCH 120/144] Clear $this->collection even when empty, to reset indexes --- lib/Doctrine/ORM/PersistentCollection.php | 2 ++ .../Tests/ORM/PersistentCollectionTest.php | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 83eab8cf1ab..c2e11dfbe30 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -536,6 +536,8 @@ public function isEmpty() public function clear() { if ($this->initialized && $this->isEmpty()) { + $this->collection->clear(); + return; } diff --git a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php index e77f898d139..757483a97a4 100644 --- a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php @@ -83,4 +83,29 @@ public function testNextInitializesCollection() $this->collection->next(); $this->assertTrue($this->collection->isInitialized()); } + + /** + * Test that PersistentCollection::clear() clear elements, and reset keys + */ + public function testClear() + { + $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())); + + $this->collection->add('dummy'); + $this->collection->clear(); + $this->assertEquals([], array_keys($this->collection->toArray())); + + // test fix clear doesn't reset collection keys when collection is empty + $this->collection->add('dummy'); + $this->collection->removeElement('dummy'); + $this->collection->clear(); + $this->collection->add('dummy'); + $this->assertEquals([0], array_keys($this->collection->toArray())); + } } From 1486c8f8e22c774605ab9c142da579556f0598e5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 26 Nov 2016 06:02:16 +0100 Subject: [PATCH 121/144] split test into multiple sub-scenarios involving `PersistentCollection` key checking #6110 --- .../Tests/ORM/PersistentCollectionTest.php | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php index 757483a97a4..190fe2ef1cf 100644 --- a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php @@ -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; @@ -85,9 +84,9 @@ public function testNextInitializesCollection() } /** - * Test that PersistentCollection::clear() clear elements, and reset keys + * @group 6110 */ - public function testClear() + public function testRemovingElementsAlsoRemovesKeys() { $this->setUpPersistentCollection(); @@ -96,12 +95,27 @@ public function testClear() $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(); - // test fix clear doesn't reset collection keys when collection is empty $this->collection->add('dummy'); $this->collection->removeElement('dummy'); $this->collection->clear(); From a90cd9dfe89de3f23d53ffc76133872c12893967 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Fri, 2 Dec 2016 09:00:50 +0100 Subject: [PATCH 122/144] Allow doctrine/common 2.7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ce740f88a1e..759ae5c0400 100644 --- a/composer.json +++ b/composer.json @@ -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" }, From 0ff512ba8fbc23ffd6501f257dbfe4efe72d3991 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 11 Nov 2015 04:12:38 +0000 Subject: [PATCH 123/144] Fixed support for inverse side second level cache --- .../AbstractCollectionPersister.php | 11 +++++- ...onStrictReadWriteCachedEntityPersister.php | 8 +++-- .../Entity/ReadWriteCachedEntityPersister.php | 12 ++++--- .../AbstractCollectionPersister.php | 6 +--- lib/Doctrine/ORM/UnitOfWork.php | 8 ++--- tests/Doctrine/Tests/Models/Cache/State.php | 2 +- .../Doctrine/Tests/Models/Cache/Traveler.php | 2 +- .../SecondLevelCacheManyToOneTest.php | 36 ++++++++++++++++++- .../SecondLevelCacheOneToManyTest.php | 28 ++++++++------- 9 files changed, 79 insertions(+), 34 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/Persister/Collection/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/Collection/AbstractCollectionPersister.php index 3172fe9994b..abaef17a680 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Collection/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Collection/AbstractCollectionPersister.php @@ -32,6 +32,7 @@ /** * @author Fabio B. Silva + * @author Guilherme Blanco * @since 2.5 */ abstract class AbstractCollectionPersister implements CachedCollectionPersister @@ -164,10 +165,18 @@ public function loadCollectionCache(PersistentCollection $collection, Collection 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)) { diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php index d50d8c5009b..b4f63e560ed 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php @@ -27,6 +27,7 @@ * Specific non-strict read/write cached entity persister * * @author Fabio B. Silva + * @author Guilherme Blanco * @since 2.5 */ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister @@ -78,13 +79,16 @@ public function afterTransactionRolledBack() */ 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; } /** diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php index 8413082deac..07cac510d4c 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php @@ -30,6 +30,7 @@ * Specific read-write entity persister * * @author Fabio B. Silva + * @author Guilherme Blanco * @since 2.5 */ class ReadWriteCachedEntityPersister extends AbstractEntityPersister @@ -100,21 +101,24 @@ public function afterTransactionRolledBack() */ 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; } /** diff --git a/lib/Doctrine/ORM/Persisters/Collection/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/Collection/AbstractCollectionPersister.php index 094895671c5..2e85b67f3b2 100644 --- a/lib/Doctrine/ORM/Persisters/Collection/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/Collection/AbstractCollectionPersister.php @@ -90,10 +90,6 @@ protected function isValidEntityState($entity) // 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)); } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 184b291da80..ed7e52e69d6 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -733,7 +733,6 @@ public function computeChangeSet(ClassMetadata $class, $entity) // 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 @@ public function computeChangeSets() // 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 @@ private function computeAssociationChanges($assoc, $value) 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; } diff --git a/tests/Doctrine/Tests/Models/Cache/State.php b/tests/Doctrine/Tests/Models/Cache/State.php index 64812869dd6..0d37e4d8faa 100644 --- a/tests/Doctrine/Tests/Models/Cache/State.php +++ b/tests/Doctrine/Tests/Models/Cache/State.php @@ -33,7 +33,7 @@ class State protected $country; /** - * @Cache + * @Cache("NONSTRICT_READ_WRITE") * @OneToMany(targetEntity="City", mappedBy="state") */ protected $cities; diff --git a/tests/Doctrine/Tests/Models/Cache/Traveler.php b/tests/Doctrine/Tests/Models/Cache/Traveler.php index 32d7cf9361a..3f720b46595 100644 --- a/tests/Doctrine/Tests/Models/Cache/Traveler.php +++ b/tests/Doctrine/Tests/Models/Cache/Traveler.php @@ -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 diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php index 8c0225e7f0d..00fa409736f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php @@ -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 @@ public function testPutAndLoadManyToOneRelation() $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(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 3e4833dc826..829cc4056b6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -14,18 +14,18 @@ */ 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 @@ public function testOneToManyRemove() $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); + $this->_em->clear(); $this->secondLevelCacheLogger->clearStats(); @@ -247,8 +248,8 @@ public function testOneToManyRemove() $this->_em->remove($city0); $this->_em->persist($state); $this->_em->flush(); - $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); $queryCount = $this->getCurrentQueryCount(); @@ -261,19 +262,19 @@ public function testOneToManyRemove() $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 @@ public function testOneToManyRemove() $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 @@ public function testOneToManyCount() 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 @@ public function testCacheInitializeCollectionWithNewObjects() $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(); From 1d961780975ae245eb86e7e095d31f2522b8780c Mon Sep 17 00:00:00 2001 From: bilouwan Date: Thu, 15 Dec 2016 12:19:56 +0100 Subject: [PATCH 124/144] Create failing test to reveal the issue --- .../Functional/EntityListenersOnMergeTest.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php new file mode 100644 index 00000000000..a3575a821e8 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php @@ -0,0 +1,50 @@ +_schemaTool->createSchema( + [ + $this->_em->getClassMetadata(DDC3597Root::class), + $this->_em->getClassMetadata(DDC3597Media::class), + $this->_em->getClassMetadata(DDC3597Image::class), + ] + ); + } + + protected function tearDown() + { + parent::tearDown(); + $this->_schemaTool->dropSchema( + [ + $this->_em->getClassMetadata(DDC3597Root::class), + $this->_em->getClassMetadata(DDC3597Media::class), + $this->_em->getClassMetadata(DDC3597Image::class), + ] + ); + } + + public function testMergeNewEntityLifecyleEventsModificationsShouldBeKept() + { + $imageEntity = new DDC3597Image('foobar'); + $imageEntity->setFormat('JPG'); + $imageEntity->setSize(123); + $imageEntity->getDimension()->setWidth(300); + $imageEntity->getDimension()->setHeight(500); + + $imageEntity = $this->_em->merge($imageEntity); + + $this->assertNotNull($imageEntity->getCreatedAt()); + $this->assertNotNull($imageEntity->getUpdatedAt()); + } +} \ No newline at end of file From 25efabdb7495585a772aaf3b38f34c047ec8c578 Mon Sep 17 00:00:00 2001 From: bilouwan Date: Thu, 15 Dec 2016 12:49:11 +0100 Subject: [PATCH 125/144] doMerge will mergeEntityStateIntoManagedCopy BEFORE persistNew to let lifecyle events changes be persisted --- lib/Doctrine/ORM/UnitOfWork.php | 46 +++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ed7e52e69d6..ac1251425a1 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1831,6 +1831,7 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass if ( ! $id) { $managedCopy = $this->newInstance($class); + $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); $this->persistNew($class, $managedCopy); } else { $flatId = ($class->containsForeignIdentifier) @@ -1862,30 +1863,16 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass $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); - } + // $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); @@ -1904,6 +1891,19 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass return $managedCopy; } + private function ensureVersionMatch(ClassMetadata $class, $entity, $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); + } + } + } + /** * Tests if an entity is loaded - must either be a loaded proxy or not a proxy * @@ -3356,6 +3356,14 @@ private function isIdentifierEquals($entity1, $entity2) */ 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) { From 295523cdca6269c60c494271d6bb301b28819d4b Mon Sep 17 00:00:00 2001 From: bilouwan Date: Thu, 15 Dec 2016 13:03:53 +0100 Subject: [PATCH 126/144] Cherry pick unit test from PR #5570 (Fix PrePersist EventListener when using merge instead of persist) --- .../Company/CompanyContractListener.php | 29 ++++++++++-- .../Functional/EntityListenersOnMergeTest.php | 46 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php b/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php index 61c1d4719f2..c5862887835 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php @@ -4,19 +4,23 @@ class CompanyContractListener { + const PRE_PERSIST = 0; + public $postPersistCalls; public $prePersistCalls; - + public $postUpdateCalls; public $preUpdateCalls; - + public $postRemoveCalls; public $preRemoveCalls; public $preFlushCalls; - + public $postLoadCalls; - + + public $snapshots = []; + /** * @PostPersist */ @@ -30,6 +34,7 @@ public function postPersistHandler(CompanyContract $contract) */ public function prePersistHandler(CompanyContract $contract) { + $this->snapshots[self::PRE_PERSIST][] = $this->takeSnapshot($contract); $this->prePersistCalls[] = func_get_args(); } @@ -81,4 +86,20 @@ public function postLoadHandler(CompanyContract $contract) $this->postLoadCalls[] = func_get_args(); } + public function takeSnapshot(CompanyContract $contract) + { + $snapshot = []; + $reflexion = new \ReflectionClass($contract); + foreach ($reflexion->getProperties() as $property) { + $property->setAccessible(true); + $value = $property->getValue($contract); + if (is_object($value) || is_array($value)) { + continue; + } + $snapshot[$property->getName()] = $property->getValue($contract); + } + + return $snapshot; + } + } diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php index a3575a821e8..5354c0a30aa 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php @@ -2,17 +2,28 @@ namespace Doctrine\Tests\ORM\Functional; +use Doctrine\Tests\Models\Company\CompanyContractListener; +use Doctrine\Tests\Models\Company\CompanyFixContract; use Doctrine\Tests\Models\DDC3597\DDC3597Image; use Doctrine\Tests\Models\DDC3597\DDC3597Media; use Doctrine\Tests\Models\DDC3597\DDC3597Root; /** + * @group DDC-1955 */ class EntityListenersOnMergeTest extends \Doctrine\Tests\OrmFunctionalTestCase { + + /** + * @var \Doctrine\Tests\Models\Company\CompanyContractListener + */ + private $listener; + protected function setUp() { + $this->useModelSet('company'); parent::setUp(); + $this->_schemaTool->createSchema( [ $this->_em->getClassMetadata(DDC3597Root::class), @@ -20,6 +31,10 @@ protected function setUp() $this->_em->getClassMetadata(DDC3597Image::class), ] ); + + $this->listener = $this->_em->getConfiguration() + ->getEntityListenerResolver() + ->resolve('Doctrine\Tests\Models\Company\CompanyContractListener'); } protected function tearDown() @@ -47,4 +62,35 @@ public function testMergeNewEntityLifecyleEventsModificationsShouldBeKept() $this->assertNotNull($imageEntity->getCreatedAt()); $this->assertNotNull($imageEntity->getUpdatedAt()); } + + public function testPrePersistListeners() + { + $fix = new CompanyFixContract(); + $fix->setFixPrice(2000); + + $this->listener->prePersistCalls = []; + + $fix = $this->_em->merge($fix); + $this->_em->flush(); + + $this->assertCount(1, $this->listener->prePersistCalls); + + $this->assertSame($fix, $this->listener->prePersistCalls[0][0]); + + $this->assertInstanceOf( + 'Doctrine\Tests\Models\Company\CompanyFixContract', + $this->listener->prePersistCalls[0][0] + ); + + $this->assertInstanceOf( + 'Doctrine\ORM\Event\LifecycleEventArgs', + $this->listener->prePersistCalls[0][1] + ); + + $this->assertArrayHasKey('fixPrice', $this->listener->snapshots[CompanyContractListener::PRE_PERSIST][0]); + $this->assertEquals( + $fix->getFixPrice(), + $this->listener->snapshots[CompanyContractListener::PRE_PERSIST][0]['fixPrice'] + ); + } } \ No newline at end of file From 569c08ce558c518b418b66930cc3975dfe9c5f3f Mon Sep 17 00:00:00 2001 From: bilouwan Date: Thu, 15 Dec 2016 15:12:29 +0100 Subject: [PATCH 127/144] Rename test --- .../Tests/ORM/Functional/EntityListenersOnMergeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php index 5354c0a30aa..6123fe99770 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php @@ -63,7 +63,7 @@ public function testMergeNewEntityLifecyleEventsModificationsShouldBeKept() $this->assertNotNull($imageEntity->getUpdatedAt()); } - public function testPrePersistListeners() + public function testPrePersistListenersShouldBeFiredWithCorrectEntityData() { $fix = new CompanyFixContract(); $fix->setFixPrice(2000); From cfb7461f512e67ce1c14f98e897016def3a5cc7d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:27:35 +0100 Subject: [PATCH 128/144] #6174 #5570 CS - alignment --- lib/Doctrine/ORM/UnitOfWork.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ac1251425a1..90e30f839ed 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1891,7 +1891,8 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass return $managedCopy; } - private function ensureVersionMatch(ClassMetadata $class, $entity, $managedCopy) { + private function ensureVersionMatch(ClassMetadata $class, $entity, $managedCopy) + { if ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity)) { $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); From cf941ce54fbe6342b7a3d2d86b33a1d211a20815 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:32:31 +0100 Subject: [PATCH 129/144] #6174 #5570 documenting thrown exception types --- lib/Doctrine/ORM/UnitOfWork.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 90e30f839ed..9e213199a34 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1799,7 +1799,7 @@ public function merge($entity) * @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) { @@ -1891,6 +1891,15 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass 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)) { From eaee924180f4ed869f3cb07fb7c55bafe584fd8b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:36:39 +0100 Subject: [PATCH 130/144] #6174 #5570 flattened nested conditionals --- lib/Doctrine/ORM/UnitOfWork.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 9e213199a34..3867b1b149e 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1902,16 +1902,20 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass */ private function ensureVersionMatch(ClassMetadata $class, $entity, $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); - } + 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); } /** From 576a4d7e311702e91e6ae229258b62adf3edb3f3 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:38:10 +0100 Subject: [PATCH 131/144] #6174 #5570 CS - spacing --- lib/Doctrine/ORM/UnitOfWork.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 3867b1b149e..2bf9deb9e86 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -3370,11 +3370,11 @@ private function isIdentifierEquals($entity1, $entity2) */ private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) { - if (!$this->isLoaded($entity)) { + if (! $this->isLoaded($entity)) { return; } - if (!$this->isLoaded($managedCopy)) { + if (! $this->isLoaded($managedCopy)) { $managedCopy->__load(); } From d9821d3fda098f034979af48f459cadfc6d83c63 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:39:46 +0100 Subject: [PATCH 132/144] #6174 #5570 CS - spacing --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 2bf9deb9e86..6570f1a419c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1865,7 +1865,7 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); $this->persistNew($class, $managedCopy); - }else{ + } else { $this->ensureVersionMatch($class, $entity, $managedCopy); $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); } From dac1a169645a6b7ff155a5a82a8c36e40c357eb1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:45:08 +0100 Subject: [PATCH 133/144] #6174 #5570 removed unused/dead code --- lib/Doctrine/ORM/UnitOfWork.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 6570f1a419c..fcbd3ab942d 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1872,7 +1872,6 @@ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $ass } $visited[$oid] = $managedCopy; // mark visited - // $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); From 12e8ab216abada6e2f2f863281b37ce484ec7237 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:47:16 +0100 Subject: [PATCH 134/144] #6174 #5570 CS - spacing/variable naming --- .../Tests/Models/Company/CompanyContractListener.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php b/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php index c5862887835..ad4133d5064 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php @@ -89,17 +89,19 @@ public function postLoadHandler(CompanyContract $contract) public function takeSnapshot(CompanyContract $contract) { $snapshot = []; - $reflexion = new \ReflectionClass($contract); - foreach ($reflexion->getProperties() as $property) { + + foreach ((new \ReflectionClass($contract))->getProperties() as $property) { $property->setAccessible(true); + $value = $property->getValue($contract); + if (is_object($value) || is_array($value)) { continue; } + $snapshot[$property->getName()] = $property->getValue($contract); } return $snapshot; } - } From 26fc8d60e6cc5af02f4771df2a4db907087170f1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:47:55 +0100 Subject: [PATCH 135/144] #6174 #5570 adding group annotation to newly introduced tests --- .../Tests/ORM/Functional/EntityListenersOnMergeTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php index 6123fe99770..e6f3d5fe843 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php @@ -10,6 +10,8 @@ /** * @group DDC-1955 + * @group 5570 + * @group 6174 */ class EntityListenersOnMergeTest extends \Doctrine\Tests\OrmFunctionalTestCase { From beef8acdf5dadf40d71a7306bc94c9c9536644de Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 14:53:54 +0100 Subject: [PATCH 136/144] #6174 #5570 CS fixes around the `EntityListenersOnMergeTest` --- .../Functional/EntityListenersOnMergeTest.php | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php index e6f3d5fe843..6c38d932683 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php @@ -2,22 +2,23 @@ namespace Doctrine\Tests\ORM\Functional; +use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\Tests\Models\Company\CompanyContractListener; use Doctrine\Tests\Models\Company\CompanyFixContract; use Doctrine\Tests\Models\DDC3597\DDC3597Image; use Doctrine\Tests\Models\DDC3597\DDC3597Media; use Doctrine\Tests\Models\DDC3597\DDC3597Root; +use Doctrine\Tests\OrmFunctionalTestCase; /** * @group DDC-1955 * @group 5570 * @group 6174 */ -class EntityListenersOnMergeTest extends \Doctrine\Tests\OrmFunctionalTestCase +class EntityListenersOnMergeTest extends OrmFunctionalTestCase { - /** - * @var \Doctrine\Tests\Models\Company\CompanyContractListener + * @var CompanyContractListener */ private $listener; @@ -26,34 +27,32 @@ protected function setUp() $this->useModelSet('company'); parent::setUp(); - $this->_schemaTool->createSchema( - [ - $this->_em->getClassMetadata(DDC3597Root::class), - $this->_em->getClassMetadata(DDC3597Media::class), - $this->_em->getClassMetadata(DDC3597Image::class), - ] - ); + $this->_schemaTool->createSchema([ + $this->_em->getClassMetadata(DDC3597Root::class), + $this->_em->getClassMetadata(DDC3597Media::class), + $this->_em->getClassMetadata(DDC3597Image::class), + ]); $this->listener = $this->_em->getConfiguration() ->getEntityListenerResolver() - ->resolve('Doctrine\Tests\Models\Company\CompanyContractListener'); + ->resolve(CompanyContractListener::class); } protected function tearDown() { parent::tearDown(); - $this->_schemaTool->dropSchema( - [ - $this->_em->getClassMetadata(DDC3597Root::class), - $this->_em->getClassMetadata(DDC3597Media::class), - $this->_em->getClassMetadata(DDC3597Image::class), - ] - ); + + $this->_schemaTool->dropSchema([ + $this->_em->getClassMetadata(DDC3597Root::class), + $this->_em->getClassMetadata(DDC3597Media::class), + $this->_em->getClassMetadata(DDC3597Image::class), + ]); } public function testMergeNewEntityLifecyleEventsModificationsShouldBeKept() { $imageEntity = new DDC3597Image('foobar'); + $imageEntity->setFormat('JPG'); $imageEntity->setSize(123); $imageEntity->getDimension()->setWidth(300); @@ -68,6 +67,7 @@ public function testMergeNewEntityLifecyleEventsModificationsShouldBeKept() public function testPrePersistListenersShouldBeFiredWithCorrectEntityData() { $fix = new CompanyFixContract(); + $fix->setFixPrice(2000); $this->listener->prePersistCalls = []; @@ -79,15 +79,8 @@ public function testPrePersistListenersShouldBeFiredWithCorrectEntityData() $this->assertSame($fix, $this->listener->prePersistCalls[0][0]); - $this->assertInstanceOf( - 'Doctrine\Tests\Models\Company\CompanyFixContract', - $this->listener->prePersistCalls[0][0] - ); - - $this->assertInstanceOf( - 'Doctrine\ORM\Event\LifecycleEventArgs', - $this->listener->prePersistCalls[0][1] - ); + $this->assertInstanceOf(CompanyFixContract::class, $this->listener->prePersistCalls[0][0]); + $this->assertInstanceOf(LifecycleEventArgs::class, $this->listener->prePersistCalls[0][1]); $this->assertArrayHasKey('fixPrice', $this->listener->snapshots[CompanyContractListener::PRE_PERSIST][0]); $this->assertEquals( @@ -95,4 +88,4 @@ public function testPrePersistListenersShouldBeFiredWithCorrectEntityData() $this->listener->snapshots[CompanyContractListener::PRE_PERSIST][0]['fixPrice'] ); } -} \ No newline at end of file +} From 81186105b687636d1a5fed34c2e3817757ec1485 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:37:49 +0100 Subject: [PATCH 137/144] #6174 #5570 started moving tests around `prePersist` event subscriber triggering on `UnitOfWork` into the `UnitOfWorkTest` --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 119 +++++++++++++++++++- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 306fceaba89..693da447a8f 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -3,8 +3,12 @@ namespace Doctrine\Tests\ORM; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\EventManager; +use Doctrine\Common\EventSubscriber; 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 +51,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(EventManager::class)->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 +400,45 @@ public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMa self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored'); } + + public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData() + { + $entity = new EntityWithListenerPopulatedField(); + + $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 EntityWithListenerPopulatedField */ + $object = $args->getObject(); + + self::assertInstanceOf(EntityWithListenerPopulatedField::class, $object); + self::assertNotSame($entity, $object); + self::assertSame($generatedFieldValue, $object->generatedField); + + return true; + }) + ); + + /* @var $object EntityWithListenerPopulatedField */ + $object = $this->_unitOfWork->merge($entity); + + self::assertNotSame($object, $entity); + self::assertInstanceOf(EntityWithListenerPopulatedField::class, $object); + self::assertSame($object->generatedField, $entity->generatedField); + } } /** @@ -498,3 +545,63 @@ 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 EntityWithListenerPopulatedField +{ + const MAX_GENERATED_FIELD_VALUE = 10000; + + /** @Id @Column(type="string") */ + public $id; + + /** + * @Column(type="integer") + */ + public $generatedField; + + public function __construct() + { + $this->id = uniqid('id', true); + $this->generatedField = mt_rand(0, self::MAX_GENERATED_FIELD_VALUE); + } +} From 8d4bc0638dee807cddf851970877535f0beb76e5 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:43:29 +0100 Subject: [PATCH 138/144] #6174 #5570 `prePersist` listeners should never be called when entities are merged, but are already in the UoW --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 693da447a8f..23c04374de3 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -439,6 +439,39 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners self::assertInstanceOf(EntityWithListenerPopulatedField::class, $object); self::assertSame($object->generatedField, $entity->generatedField); } + + public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners() + { + $persistedEntity = new EntityWithListenerPopulatedField(); + $mergedEntity = new EntityWithListenerPopulatedField(); + + $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 EntityWithListenerPopulatedField */ + $merged = $this->_unitOfWork->merge($mergedEntity); + + self::assertSame($merged, $persistedEntity); + self::assertSame($persistedEntity->generatedField, $mergedEntity->generatedField); + } } /** From 67724eb7ae1dd73a7b5f9f37dfcf135fcefe992f Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:44:48 +0100 Subject: [PATCH 139/144] #6174 #5570 adding group annotations to newly introduced test --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 23c04374de3..0aa3e86bbcb 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -401,6 +401,11 @@ public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMa self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored'); } + /** + * @group DDC-1955 + * @group 5570 + * @group 6174 + */ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData() { $entity = new EntityWithListenerPopulatedField(); @@ -440,6 +445,11 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners self::assertSame($object->generatedField, $entity->generatedField); } + /** + * @group DDC-1955 + * @group 5570 + * @group 6174 + */ public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners() { $persistedEntity = new EntityWithListenerPopulatedField(); From e43f5304eff48301b6b4fcd0448da8f5efd42c6e Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:45:03 +0100 Subject: [PATCH 140/144] #6174 #5570 removed unused test class --- .../Functional/EntityListenersOnMergeTest.php | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php deleted file mode 100644 index 6c38d932683..00000000000 --- a/tests/Doctrine/Tests/ORM/Functional/EntityListenersOnMergeTest.php +++ /dev/null @@ -1,91 +0,0 @@ -useModelSet('company'); - parent::setUp(); - - $this->_schemaTool->createSchema([ - $this->_em->getClassMetadata(DDC3597Root::class), - $this->_em->getClassMetadata(DDC3597Media::class), - $this->_em->getClassMetadata(DDC3597Image::class), - ]); - - $this->listener = $this->_em->getConfiguration() - ->getEntityListenerResolver() - ->resolve(CompanyContractListener::class); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->_schemaTool->dropSchema([ - $this->_em->getClassMetadata(DDC3597Root::class), - $this->_em->getClassMetadata(DDC3597Media::class), - $this->_em->getClassMetadata(DDC3597Image::class), - ]); - } - - public function testMergeNewEntityLifecyleEventsModificationsShouldBeKept() - { - $imageEntity = new DDC3597Image('foobar'); - - $imageEntity->setFormat('JPG'); - $imageEntity->setSize(123); - $imageEntity->getDimension()->setWidth(300); - $imageEntity->getDimension()->setHeight(500); - - $imageEntity = $this->_em->merge($imageEntity); - - $this->assertNotNull($imageEntity->getCreatedAt()); - $this->assertNotNull($imageEntity->getUpdatedAt()); - } - - public function testPrePersistListenersShouldBeFiredWithCorrectEntityData() - { - $fix = new CompanyFixContract(); - - $fix->setFixPrice(2000); - - $this->listener->prePersistCalls = []; - - $fix = $this->_em->merge($fix); - $this->_em->flush(); - - $this->assertCount(1, $this->listener->prePersistCalls); - - $this->assertSame($fix, $this->listener->prePersistCalls[0][0]); - - $this->assertInstanceOf(CompanyFixContract::class, $this->listener->prePersistCalls[0][0]); - $this->assertInstanceOf(LifecycleEventArgs::class, $this->listener->prePersistCalls[0][1]); - - $this->assertArrayHasKey('fixPrice', $this->listener->snapshots[CompanyContractListener::PRE_PERSIST][0]); - $this->assertEquals( - $fix->getFixPrice(), - $this->listener->snapshots[CompanyContractListener::PRE_PERSIST][0]['fixPrice'] - ); - } -} From 39ce6f96a08d9ae23cf0a9fe88ddaec9f7f24829 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:46:34 +0100 Subject: [PATCH 141/144] #6174 #5570 renamed entity for better fitting the use-cases it's in --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 0aa3e86bbcb..fca72fc7164 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -408,7 +408,7 @@ public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMa */ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData() { - $entity = new EntityWithListenerPopulatedField(); + $entity = new EntityWithRandomlyGeneratedField(); $generatedFieldValue = $entity->generatedField; @@ -426,10 +426,10 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners ->with( self::anything(), self::callback(function (LifecycleEventArgs $args) use ($entity, $generatedFieldValue) { - /* @var $object EntityWithListenerPopulatedField */ + /* @var $object EntityWithRandomlyGeneratedField */ $object = $args->getObject(); - self::assertInstanceOf(EntityWithListenerPopulatedField::class, $object); + self::assertInstanceOf(EntityWithRandomlyGeneratedField::class, $object); self::assertNotSame($entity, $object); self::assertSame($generatedFieldValue, $object->generatedField); @@ -437,11 +437,11 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners }) ); - /* @var $object EntityWithListenerPopulatedField */ + /* @var $object EntityWithRandomlyGeneratedField */ $object = $this->_unitOfWork->merge($entity); self::assertNotSame($object, $entity); - self::assertInstanceOf(EntityWithListenerPopulatedField::class, $object); + self::assertInstanceOf(EntityWithRandomlyGeneratedField::class, $object); self::assertSame($object->generatedField, $entity->generatedField); } @@ -452,8 +452,8 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners */ public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners() { - $persistedEntity = new EntityWithListenerPopulatedField(); - $mergedEntity = new EntityWithListenerPopulatedField(); + $persistedEntity = new EntityWithRandomlyGeneratedField(); + $mergedEntity = new EntityWithRandomlyGeneratedField(); $mergedEntity->id = $persistedEntity->id; $mergedEntity->generatedField = mt_rand( @@ -476,7 +476,7 @@ public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistL ['generatedField' => $persistedEntity->generatedField] ); - /* @var $merged EntityWithListenerPopulatedField */ + /* @var $merged EntityWithRandomlyGeneratedField */ $merged = $this->_unitOfWork->merge($mergedEntity); self::assertSame($merged, $persistedEntity); @@ -630,10 +630,8 @@ class EntityWithCompositeStringIdentifier } /** @Entity */ -class EntityWithListenerPopulatedField +class EntityWithRandomlyGeneratedField { - const MAX_GENERATED_FIELD_VALUE = 10000; - /** @Id @Column(type="string") */ public $id; @@ -645,6 +643,6 @@ class EntityWithListenerPopulatedField public function __construct() { $this->id = uniqid('id', true); - $this->generatedField = mt_rand(0, self::MAX_GENERATED_FIELD_VALUE); + $this->generatedField = mt_rand(0, 100000); } } From 3645a9c44d9206424b80586adc8c0feeff3a6f5b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:46:49 +0100 Subject: [PATCH 142/144] #6174 #5570 removed unused imports --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index fca72fc7164..b847d82bdef 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -4,7 +4,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\EventManager; -use Doctrine\Common\EventSubscriber; use Doctrine\Common\NotifyPropertyChanged; use Doctrine\Common\Persistence\Event\LifecycleEventArgs; use Doctrine\Common\PropertyChangedListener; From b0ede40f47b8616649e1824c4e89ccd7affe33e9 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 15:48:10 +0100 Subject: [PATCH 143/144] #6174 #5570 removed modifications applied to the `CompanyContractListener`, since `UnitOfWorkTest` now completely encapsulates the scenarios being covered --- .../Company/CompanyContractListener.php | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php b/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php index ad4133d5064..23714f32983 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyContractListener.php @@ -4,8 +4,6 @@ class CompanyContractListener { - const PRE_PERSIST = 0; - public $postPersistCalls; public $prePersistCalls; @@ -19,8 +17,6 @@ class CompanyContractListener public $postLoadCalls; - public $snapshots = []; - /** * @PostPersist */ @@ -34,7 +30,6 @@ public function postPersistHandler(CompanyContract $contract) */ public function prePersistHandler(CompanyContract $contract) { - $this->snapshots[self::PRE_PERSIST][] = $this->takeSnapshot($contract); $this->prePersistCalls[] = func_get_args(); } @@ -85,23 +80,4 @@ public function postLoadHandler(CompanyContract $contract) { $this->postLoadCalls[] = func_get_args(); } - - public function takeSnapshot(CompanyContract $contract) - { - $snapshot = []; - - foreach ((new \ReflectionClass($contract))->getProperties() as $property) { - $property->setAccessible(true); - - $value = $property->getValue($contract); - - if (is_object($value) || is_array($value)) { - continue; - } - - $snapshot[$property->getName()] = $property->getValue($contract); - } - - return $snapshot; - } } From d52dbe62ace7453d06909953d5980f7a54ba15ad Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 18 Dec 2016 16:24:42 +0100 Subject: [PATCH 144/144] #6174 #5570 switching `::class` to string constants for PHP 5.4 compat (still supported in ORM 2.5.x) --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index b847d82bdef..f49fe489a6c 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -59,7 +59,7 @@ protected function setUp() { parent::setUp(); $this->_connectionMock = new ConnectionMock([], new DriverMock()); - $this->eventManager = $this->getMockBuilder(EventManager::class)->getMock(); + $this->eventManager = $this->getMockBuilder('Doctrine\Common\EventManager')->getMock(); $this->_emMock = EntityManagerMock::create($this->_connectionMock, null, $this->eventManager); // SUT $this->_unitOfWork = new UnitOfWorkMock($this->_emMock); @@ -428,7 +428,7 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners /* @var $object EntityWithRandomlyGeneratedField */ $object = $args->getObject(); - self::assertInstanceOf(EntityWithRandomlyGeneratedField::class, $object); + self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object); self::assertNotSame($entity, $object); self::assertSame($generatedFieldValue, $object->generatedField); @@ -440,7 +440,7 @@ public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListeners $object = $this->_unitOfWork->merge($entity); self::assertNotSame($object, $entity); - self::assertInstanceOf(EntityWithRandomlyGeneratedField::class, $object); + self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object); self::assertSame($object->generatedField, $entity->generatedField); }