From c998797c55e20603f70fe465e864cced7122bc50 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 4 Dec 2010 19:44:10 +0100 Subject: [PATCH] DDC-546 - Add Extra Lazy Collection prototype. --- .../ORM/Mapping/ClassMetadataInfo.php | 6 + lib/Doctrine/ORM/PersistentCollection.php | 25 +- .../AbstractCollectionPersister.php | 25 ++ .../ORM/Persisters/BasicEntityPersister.php | 45 ++- .../Persisters/JoinedSubclassPersister.php | 17 +- .../ORM/Persisters/ManyToManyPersister.php | 49 +++ .../ORM/Persisters/OneToManyPersister.php | 37 +++ lib/Doctrine/ORM/UnitOfWork.php | 8 +- .../Tests/ORM/Functional/AllTests.php | 1 + .../ORM/Functional/EntityRepositoryTest.php | 2 - .../Functional/ExtraLazyCollectionTest.php | 284 ++++++++++++++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 10 + 12 files changed, 483 insertions(+), 26 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 9612bf2ac..42ebfe96b 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -121,6 +121,12 @@ class ClassMetadataInfo * association is fetched. */ const FETCH_EAGER = 3; + /** + * Specifies that an association is to be fetched lazy (on first access) and that + * commands such as Collection#count, Collection#slice are issued directly against + * the database if the collection is not yet initialized. + */ + const FETCH_EXTRALAZY = 4; /** * Identifies a one-to-one association. */ diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index bf7c6da1e..41d360f02 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -59,16 +59,16 @@ final class PersistentCollection implements Collection * The association mapping the collection belongs to. * This is currently either a OneToManyMapping or a ManyToManyMapping. * - * @var Doctrine\ORM\Mapping\AssociationMapping + * @var array */ - private $association; + protected $association; /** * The EntityManager that manages the persistence of the collection. * * @var Doctrine\ORM\EntityManager */ - private $em; + protected $em; /** * The name of the field on the target entities that points to the owner @@ -96,7 +96,7 @@ final class PersistentCollection implements Collection * * @var boolean */ - private $initialized = true; + protected $initialized = true; /** * The wrapped Collection instance. @@ -475,6 +475,17 @@ final class PersistentCollection implements Collection */ public function count() { + if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRALAZY) { + // use a dynamic public property here. That may be slower, but its not using so much + // memory as having a count variable in each collection. + if (!isset($this->doctrineCollectionCount)) { + $this->doctrineCollectionCount = $this->em->getUnitOfWork() + ->getCollectionPersister($this->association) + ->count($this) + count ($this->coll->toArray()); + } + return $this->doctrineCollectionCount; + } + $this->initialize(); return $this->coll->count(); } @@ -675,6 +686,12 @@ final class PersistentCollection implements Collection */ public function slice($offset, $length = null) { + if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRALAZY) { + return $this->em->getUnitOfWork() + ->getCollectionPersister($this->association) + ->slice($this, $offset, $length); + } + $this->initialize(); return $this->coll->slice($offset, $length); } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 489bb82fc..1847c7136 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -125,6 +125,31 @@ abstract class AbstractCollectionPersister } } + public function count(PersistentCollection $coll) + { + throw new \BadMethodCallException("Counting the size of this persistent collection is not supported by this CollectionPersister."); + } + + public function slice(PersistentCollection $coll, $offset, $length = null) + { + throw new \BadMethodCallException("Slicing elements is not supported by this CollectionPersister."); + } + + public function contains(PersistentCollection $coll, $key) + { + throw new \BadMethodCallException("Checking for existance of an element is not supported by this CollectionPersister."); + } + + public function containsKey(PersistentCollection $coll, $element) + { + throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister."); + } + + public function get(PersistentCollection $coll, $index) + { + throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister."); + } + /** * Gets the SQL statement used for deleting a row from the collection. * diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 4069b657d..5f1a29859 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -723,9 +723,12 @@ class BasicEntityPersister * @param ManyToManyMapping $assoc The association mapping of the association being loaded. * @param object $sourceEntity The entity that owns the collection. * @param PersistentCollection $coll The collection to fill. + * @param int|null $offset + * @param int|null $limit + * @return array */ - public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) - { + public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll = null, $offset = null, $limit = null) + { $criteria = array(); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $joinTableConditions = array(); @@ -772,10 +775,17 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); - while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { - $coll->hydrateAdd($this->_createEntity($result)); + if ($coll) { + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $coll->hydrateAdd($this->_createEntity($result)); + } + } else { + $entities = array(); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $entities[] = $this->_createEntity($result); + } + return $entities; } - $stmt->closeCursor(); } /** @@ -854,7 +864,7 @@ class BasicEntityPersister * @return string * @todo Refactor: _getSelectSQL(...) */ - protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null) { $joinSql = $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ? $this->_getSelectManyToManyJoinSQL($assoc) : ''; @@ -872,12 +882,12 @@ class BasicEntityPersister $lockSql = ' ' . $this->_platform->getWriteLockSql(); } - return 'SELECT ' . $this->_getSelectColumnListSQL() + return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL() . $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name), $lockMode) . $joinSql . ($conditionSql ? ' WHERE ' . $conditionSql : '') - . $orderBySql + . $orderBySql, $limit, $offset) . $lockSql; } @@ -1181,8 +1191,10 @@ class BasicEntityPersister * @param OneToManyMapping $assoc * @param array $criteria The criteria by which to select the entities. * @param PersistentCollection The collection to load/fill. + * @param int|null $offset + * @param int|null $limit */ - public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) + public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll = null, $offset = null, $limit = null) { $criteria = array(); $owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']]; @@ -1204,10 +1216,17 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); - while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { - $coll->hydrateAdd($this->_createEntity($result)); + if ($coll) { + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $coll->hydrateAdd($this->_createEntity($result)); + } + } else { + $entities = array(); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $entities[] = $this->_createEntity($result); + } + return $entities; } - $stmt->closeCursor(); } /** @@ -1252,4 +1271,6 @@ class BasicEntityPersister { }*/ + + #public function countCollection } diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index d75656b06..9ae242a45 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -20,7 +20,8 @@ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\ORMException, - Doctrine\ORM\Mapping\ClassMetadata; + Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\DBAL\LockMode; /** * The joined subclass persister maps a single entity instance to several tables in the @@ -239,7 +240,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * {@inheritdoc} */ - protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null) { $idColumns = $this->_class->getIdentifierColumnNames(); $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); @@ -348,10 +349,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $this->_selectColumnListSql = $columnList; } - return 'SELECT ' . $this->_selectColumnListSql + $lockSql = ''; + if ($lockMode == LockMode::PESSIMISTIC_READ) { + $lockSql = ' ' . $this->_platform->getReadLockSql(); + } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { + $lockSql = ' ' . $this->_platform->getWriteLockSql(); + } + + return $this->_platform->modifyLimitQuery('SELECT ' . $this->_selectColumnListSql . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias . $joinSql - . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql; + . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset) + . $lockSql; } /** diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 5f24188ca..a5c861e30 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -181,4 +181,53 @@ class ManyToManyPersister extends AbstractCollectionPersister return $params; } + + /** + * {@inheritdoc} + */ + public function count(PersistentCollection $coll) + { + $params = array(); + $mapping = $coll->getMapping(); + $class = $this->_em->getClassMetadata($mapping['sourceEntity']); + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); + + if ($mapping['isOwningSide']) { + $joinTable = $mapping['joinTable']; + $joinColumns = $mapping['relationToSourceKeyColumns']; + } else { + $mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']]; + $joinTable = $mapping['joinTable']; + $joinColumns = $mapping['relationToTargetKeyColumns']; + } + + $whereClause = ''; + foreach ($mapping['joinTableColumns'] as $joinTableColumn) { + if (isset($joinColumns[$joinTableColumn])) { + if ($whereClause !== '') { + $whereClause .= ' AND '; + } + $whereClause .= "$joinTableColumn = ?"; + + $params[] = $id[$class->fieldNames[$joinColumns[$joinTableColumn]]]; + } + } + $sql = 'SELECT count(*) FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause; + + return $this->_conn->fetchColumn($sql, $params); + } + + /** + * @param PersistentCollection $coll + * @param int $offset + * @param int $length + * @return array + */ + public function slice(PersistentCollection $coll, $offset, $length = null) + { + $mapping = $coll->getMapping(); + return $this->_em->getUnitOfWork() + ->getEntityPersister($mapping['targetEntity']) + ->loadManyToManyCollection($mapping, $coll->getOwner(), null, $offset, $length); + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index f0d3aeafd..1656f98e7 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -116,4 +116,41 @@ class OneToManyPersister extends AbstractCollectionPersister */ protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element) {} + + /** + * {@inheritdoc} + */ + public function count(PersistentCollection $coll) + { + $mapping = $coll->getMapping(); + $class = $this->_em->getClassMetadata($mapping['targetEntity']); + $params = array(); + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); + + $where = ''; + foreach ($class->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) { + if ($where != '') { + $where .= ' AND '; + } + $where .= $joinColumn['name'] . " = ?"; + $params[] = $id[$class->fieldNames[$joinColumn['referencedColumnName']]]; + } + + $sql = "SELECT count(*) FROM " . $class->getQuotedTableName($this->_conn->getDatabasePlatform()) . " WHERE " . $where; + return $this->_conn->fetchColumn($sql, $params); + } + + /** + * @param PersistentCollection $coll + * @param int $offset + * @param int $length + * @return \Doctrine\Common\Collections\ArrayCollection + */ + public function slice(PersistentCollection $coll, $offset, $length = null) + { + $mapping = $coll->getMapping(); + return $this->_em->getUnitOfWork() + ->getEntityPersister($mapping['targetEntity']) + ->loadOneToManyCollection($mapping, $coll->getOwner(), null, $offset, $length); + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ead7e20b9..f35c20eff 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1958,11 +1958,11 @@ class UnitOfWork implements PropertyChangedListener $reflField = $class->reflFields[$field]; $reflField->setValue($entity, $pColl); - if ($assoc['fetch'] == ClassMetadata::FETCH_LAZY) { - $pColl->setInitialized(false); - } else { + if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) { $this->loadCollection($pColl); $pColl->takeSnapshot(); + } else { + $pColl->setInitialized(false); } $this->originalEntityData[$oid][$field] = $pColl; } @@ -2123,7 +2123,7 @@ class UnitOfWork implements PropertyChangedListener * Gets the EntityPersister for an Entity. * * @param string $entityName The name of the Entity. - * @return Doctrine\ORM\Persister\AbstractEntityPersister + * @return Doctrine\ORM\Persisters\AbstractEntityPersister */ public function getEntityPersister($entityName) { diff --git a/tests/Doctrine/Tests/ORM/Functional/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/AllTests.php index 9d25377c4..0759bcf7f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Functional/AllTests.php @@ -55,6 +55,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Functional\IdentityMapTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\DatabaseDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\PostgreSQLIdentityStrategyTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ExtraLazyCollectionTest'); $suite->addTest(Locking\AllTests::suite()); $suite->addTest(Ticket\AllTests::suite()); diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index d6c29e016..ebe2c19f2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -9,8 +9,6 @@ use Doctrine\Tests\Models\CMS\CmsAddress; require_once __DIR__ . '/../../TestInit.php'; /** - * Description of DetachedEntityTest - * * @author robo */ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php new file mode 100644 index 000000000..8be44bbea --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -0,0 +1,284 @@ +useModelSet('cms'); + parent::setUp(); + + $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_EXTRALAZY; + $class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_EXTRALAZY; + + $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'); + $class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRALAZY; + + $this->loadFixture(); + } + + public function tearDown() + { + parent::tearDown(); + + $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_LAZY; + $class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_LAZY; + + $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'); + $class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_LAZY; + } + + /** + * @group DDC-546 + */ + public function testCountNotInitializesCollection() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $queryCount = $this->getCurrentQueryCount(); + + $this->assertFalse($user->groups->isInitialized()); + $this->assertEquals(3, count($user->groups)); + $this->assertFalse($user->groups->isInitialized()); + + foreach ($user->groups AS $group) { } + + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount(), "Expecting two queries to be fired for count, then iteration."); + } + + /** + * @group DDC-546 + */ + public function testCountWhenNewEntitysPresent() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + + $newGroup = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $newGroup->name = "Test4"; + + $user->addGroup($newGroup); + $this->_em->persist($newGroup); + + $this->assertFalse($user->groups->isInitialized()); + $this->assertEquals(4, count($user->groups)); + $this->assertFalse($user->groups->isInitialized()); + } + + /** + * @group DDC-546 + */ + public function testCountWhenInitialized() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $queryCount = $this->getCurrentQueryCount(); + + foreach ($user->groups AS $group) { } + + $this->assertTrue($user->groups->isInitialized()); + $this->assertEquals(3, count($user->groups)); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Should only execute one query to initialize colleciton, no extra query for count() more."); + } + + /** + * @group DDC-546 + */ + public function testCountInverseCollection() + { + $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); + $this->assertFalse($group->users->isInitialized(), "Pre-Condition"); + + $this->assertEquals(4, count($group->users)); + $this->assertFalse($group->users->isInitialized(), "Extra Lazy collection should not be initialized by counting the collection."); + } + + /** + * @group DDC-546 + */ + public function testCountOneToMany() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertFalse($user->groups->isInitialized(), "Pre-Condition"); + + $this->assertEquals(2, count($user->articles)); + } + + /** + * @group DDC-546 + */ + public function testFullSlice() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); + + $someGroups = $user->groups->slice(null); + $this->assertEquals(3, count($someGroups)); + } + + /** + * @group DDC-546 + */ + public function testSlice() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); + + $queryCount = $this->getCurrentQueryCount(); + + $someGroups = $user->groups->slice(0, 2); + + $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsGroup', $someGroups); + $this->assertEquals(2, count($someGroups)); + $this->assertFalse($user->groups->isInitialized(), "Slice should not initialize the collection if it wasn't before!"); + + $otherGroup = $user->groups->slice(2, 1); + + $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsGroup', $otherGroup); + $this->assertEquals(1, count($otherGroup)); + $this->assertFalse($user->groups->isInitialized()); + + foreach ($user->groups AS $group) { } + + $this->assertTrue($user->groups->isInitialized()); + $this->assertEquals(3, count($user->groups)); + + $this->assertEquals($queryCount + 3, $this->getCurrentQueryCount()); + } + + /** + * @group DDC-546 + */ + public function testSliceInitializedCollection() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $queryCount = $this->getCurrentQueryCount(); + + foreach ($user->groups AS $group) { } + + $someGroups = $user->groups->slice(0, 2); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->assertEquals(2, count($someGroups)); + $this->assertTrue($user->groups->contains($someGroups[0])); + $this->assertTrue($user->groups->contains($someGroups[1])); + } + + /** + * @group DDC-546 + */ + public function testSliceInverseCollection() + { + $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); + $this->assertFalse($group->users->isInitialized(), "Pre-Condition"); + $queryCount = $this->getCurrentQueryCount(); + + $someUsers = $group->users->slice(0, 2); + $otherUsers = $group->users->slice(2, 2); + + $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsUser', $someUsers); + $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsUser', $otherUsers); + $this->assertEquals(2, count($someUsers)); + $this->assertEquals(2, count($otherUsers)); + + // +2 queries executed by slice, +4 are executed by EAGER fetching of User Address. + $this->assertEquals($queryCount + 2 + 4, $this->getCurrentQueryCount()); + } + + /** + * @group DDC-546 + */ + public function testSliceOneToMany() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized."); + + $queryCount = $this->getCurrentQueryCount(); + + $someArticle = $user->articles->slice(0, 1); + $otherArticle = $user->articles->slice(1, 1); + + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + } + + private function loadFixture() + { + $user1 = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user1->username = "beberlei"; + $user1->name = "Benjamin"; + $user1->status = "active"; + + $user2 = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user2->username = "jwage"; + $user2->name = "Jonathan"; + $user2->status = "active"; + + $user3 = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user3->username = "romanb"; + $user3->name = "Roman"; + $user3->status = "active"; + + $user4 = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user4->username = "gblanco"; + $user4->name = "Guilherme"; + $user4->status = "active"; + + $this->_em->persist($user1); + $this->_em->persist($user2); + $this->_em->persist($user3); + $this->_em->persist($user4); + + $group1 = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $group1->name = "Test1"; + + $group2 = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $group2->name = "Test2"; + + $group3 = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $group3->name = "Test3"; + + $user1->addGroup($group1); + $user1->addGroup($group2); + $user1->addGroup($group3); + + $user2->addGroup($group1); + $user3->addGroup($group1); + $user4->addGroup($group1); + + $this->_em->persist($group1); + $this->_em->persist($group2); + $this->_em->persist($group3); + + $article1 = new \Doctrine\Tests\Models\CMS\CmsArticle(); + $article1->topic = "Test"; + $article1->text = "Test"; + $article1->setAuthor($user1); + + $article2 = new \Doctrine\Tests\Models\CMS\CmsArticle(); + $article2->topic = "Test"; + $article2->text = "Test"; + $article2->setAuthor($user1); + + $this->_em->persist($article1); + $this->_em->persist($article2); + + $this->_em->flush(); + $this->_em->clear(); + + $this->userId = $user1->getId(); + $this->groupId = $group1->id; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index abf04efab..d9dd9bc77 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -314,4 +314,14 @@ abstract class OrmFunctionalTestCase extends OrmTestCase } throw $e; } + + /** + * Using the SQL Logger Stack this method retrieves the current query count executed in this test. + * + * @return int + */ + protected function getCurrentQueryCount() + { + return count($this->_sqlLoggerStack->queries); + } }