diff --git a/docs/en/tutorials/extra-lazy-associations.rst b/docs/en/tutorials/extra-lazy-associations.rst index 4c31357eec..1dae001a36 100644 --- a/docs/en/tutorials/extra-lazy-associations.rst +++ b/docs/en/tutorials/extra-lazy-associations.rst @@ -17,6 +17,7 @@ can be called without triggering a full load of the collection: - ``Collection#contains($entity)`` - ``Collection#containsKey($key)`` - ``Collection#count()`` +- ``Collection#first()`` - ``Collection#get($key)`` - ``Collection#slice($offset, $length = null)`` diff --git a/src/PersistentCollection.php b/src/PersistentCollection.php index d54d3d1b99..ef5f28c3cb 100644 --- a/src/PersistentCollection.php +++ b/src/PersistentCollection.php @@ -504,6 +504,20 @@ public function __wakeup(): void $this->em = null; } + /** + * {@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/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Tests/ORM/Functional/ExtraLazyCollectionTest.php index a092f6555c..569ea13500 100644 --- a/tests/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -179,6 +179,63 @@ 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 {