From 40c633d41242945072fe8d734e69880b5e2e9c76 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 2 Nov 2023 13:46:15 -0400 Subject: [PATCH] feat: make `PersistentCollection::first()` "extra" lazy --- lib/Doctrine/ORM/PersistentCollection.php | 14 +++++ .../Functional/ExtraLazyCollectionTest.php | 59 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index c39252d7a27..f81d25bf9f0 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -590,6 +590,20 @@ public function __sleep(): array return ['collection', 'initialized']; } + /** + * {@inheritDoc} + */ + public function first() + { + if (! $this->initialized && ! $this->isDirty && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) { + $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); + + return array_values($persister->slice($this, 0, 1))[0] ?? false; + } + + return parent::first(); + } + /** * Extracts a slice of $length elements starting at position $offset from the Collection. * diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index e422d2cde78..2baddb79d2c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -191,6 +191,65 @@ public function testCountOneToManyJoinedInheritance(): void self::assertCount(2, $otherClass->childClasses); } + /** + * @group non-cacheable + */ + public function testFirstWhenInitialized(): void + { + $user = $this->_em->find(CmsUser::class, $this->userId); + $this->getQueryLog()->reset()->enable(); + $user->groups->toArray(); + + self::assertTrue($user->groups->isInitialized()); + self::assertInstanceOf(CmsGroup::class, $user->groups->first()); + $this->assertQueryCount(1, 'Should only execute one query to initialize collection, no extra query for first().'); + } + + public function testFirstOnEmptyCollectionWhenInitialized(): void + { + foreach ($this->_em->getRepository(CmsGroup::class)->findAll() as $group) { + $this->_em->remove($group); + } + + $this->_em->flush(); + + $user = $this->_em->find(CmsUser::class, $this->userId); + $this->getQueryLog()->reset()->enable(); + $user->groups->toArray(); + + self::assertTrue($user->groups->isInitialized()); + self::assertFalse($user->groups->first()); + $this->assertQueryCount(1, 'Should only execute one query to initialize collection, no extra query for first().'); + } + + public function testFirstWhenNotInitialized(): void + { + $user = $this->_em->find(CmsUser::class, $this->userId); + $this->getQueryLog()->reset()->enable(); + + self::assertFalse($user->groups->isInitialized()); + self::assertInstanceOf(CmsGroup::class, $user->groups->first()); + self::assertFalse($user->groups->isInitialized()); + $this->assertQueryCount(1, 'Should only execute one query for first().'); + } + + public function testFirstOnEmptyCollectionWhenNotInitialized(): void + { + foreach ($this->_em->getRepository(CmsGroup::class)->findAll() as $group) { + $this->_em->remove($group); + } + + $this->_em->flush(); + + $user = $this->_em->find(CmsUser::class, $this->userId); + $this->getQueryLog()->reset()->enable(); + + self::assertFalse($user->groups->isInitialized()); + self::assertFalse($user->groups->first()); + self::assertFalse($user->groups->isInitialized()); + $this->assertQueryCount(1, 'Should only execute one query for first().'); + } + /** @group DDC-546 */ public function testFullSlice(): void {