Lazy Chained Matching
If you call matching()
on a PersistentCollection
, it will immediately query
the database and return the result, even if you won't use the result. If you
call matching()
again on the result, it will be processed from the in-memory
ArrayCollection
, not from the database.
With EXTRA_LAZY
fetch mode, PersistentCollection
will return a
LazyCriteriaCollection
. But subsequent matching()
call on the result
will still be processed immediately.
We can decorate the collection so that chained-matching()
will be done lazily.
The database query will only be done when the caller asks for the result.
Calling matching()
will only merge the supplied criteria to the existing
criteria.
With the standard behavior, the following code will do the processing three
times (twice with EXTRA_LAZY
). With our decorator, it will only be done once
when foreach
is called.
/** @var Collection<array-key,mixed> $collection */
/** @var Criteria $criteria1 */
/** @var Criteria $criteria2 */
/** @var Criteria $criteria3 */
$result = $collection
->matching($criteria1)
->matching($criteria2)
->matching($criteria3);
foreach ($result as $item) {
// ...
}
The Decorator Class
This package already comes with LazyMatchingCollection
that you can use for
this purpose.
Usage Example in Entities
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Rekalogika\Collections\Decorator\LazyMatching\LazyMatchingCollection;
#[ORM\Entity()]
class BookShelf
{
#[ORM\OneToMany(targetEntity: Book::class, fetch: 'EXTRA_LAZY', indexBy: 'id')]
private Collection $books;
public function __construct()
{
$this->books = new ArrayCollection();
}
/**
* @return Collection<array-key,Book>&Selectable<array-key,Book>
*/
public function getBooks(): Collection&Selectable
{
return new LazyMatchingCollection($this->books);
}
public function getScienceBooks(): Collection
{
return $this->getBooks()->matching(
Criteria::create()
->where(Criteria::expr()->eq('genre', 'science'))
);
}
public function getOldScienceBooks(): Collection
{
return $this->getScienceBook()->matching(
Criteria::create()
->where(Criteria::expr()->lt('publishedAt', new \DateTime('-10 years')))
);
}
}
The Caller Side
Then the caller will be able to do something like this:
$bookShelf = $entityManager->find(BookShelf::class, 1);
$oldScienceBook = $bookShelf->getOldScienceBook();
foreach ($oldScienceBook as $book) {
echo $book->getTitle();
}
With this example, there will be only two database queries, one due to
$entityManager->find()
, and one due to the foreach
call.