From a083c75e3aa02a3b5dec248ffa6a188e06b203e7 Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Sun, 30 Jun 2024 00:59:39 +0700 Subject: [PATCH] feat: add `createCriteriaPageable` DX method (#79) --- CHANGELOG.md | 1 + .../src/CriteriaPageable.php | 161 ++++++++++++++++++ .../src/MinimalRecollectionDecorator.php | 24 +++ .../src/RecollectionDecorator.php | 24 +++ .../src/Trait/RepositoryDxTrait.php | 22 +-- 5 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 packages/collections-domain/src/CriteriaPageable.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 07fa323..65e48fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * refactor: refresh count * fix: fix various problems with criteria recollections * feat: add several DX methods +* feat: add `createCriteriaPageable` DX method ## 0.4.0 diff --git a/packages/collections-domain/src/CriteriaPageable.php b/packages/collections-domain/src/CriteriaPageable.php new file mode 100644 index 0000000..adc5397 --- /dev/null +++ b/packages/collections-domain/src/CriteriaPageable.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Domain\Collections; + +use Doctrine\Common\Collections\Criteria; +use Doctrine\Common\Collections\Order; +use Doctrine\Common\Collections\ReadableCollection; +use Doctrine\Common\Collections\Selectable; +use Rekalogika\Contracts\Collections\Exception\UnexpectedValueException; +use Rekalogika\Contracts\Rekapager\PageableInterface; +use Rekalogika\Domain\Collections\Common\Count\CountStrategy; +use Rekalogika\Domain\Collections\Common\Count\RestrictedCountStrategy; +use Rekalogika\Domain\Collections\Common\Trait\PageableTrait; +use Rekalogika\Domain\Collections\Trait\RecollectionPageableTrait; + +/** + * @template TKey of array-key + * @template T + * @implements PageableInterface + */ +class CriteriaPageable implements PageableInterface +{ + /** @use RecollectionPageableTrait */ + use RecollectionPageableTrait; + + /** @use PageableTrait */ + use PageableTrait; + + /** + * @var null|\WeakMap>> + */ + private static ?\WeakMap $instances = null; + + /** + * @var Selectable + */ + private readonly Selectable $collection; + + private readonly Criteria $criteria; + private readonly CountStrategy $count; + + /** + * @param ReadableCollection|Selectable $collection + * @param int<1,max> $itemsPerPage + */ + final private function __construct( + ReadableCollection|Selectable $collection, + ?Criteria $criteria = null, + private readonly ?string $indexBy = null, + private readonly int $itemsPerPage = 50, + ?CountStrategy $count = null, + ) { + // save collection + + if (!$collection instanceof Selectable) { + throw new UnexpectedValueException('The wrapped collection must implement the Selectable interface.'); + } + + $this->collection = $collection; + + // save criteria + + $criteria = clone ($criteria ?? Criteria::create()); + + if (\count($criteria->orderings()) === 0) { + $criteria->orderBy(['id' => Order::Descending]); + } + + $this->criteria = $criteria; + + // save count strategy + + $this->count = $count ?? new RestrictedCountStrategy(); + } + + /** + * @template STKey of array-key + * @template ST + * @param ReadableCollection|Selectable $collection + * @param int<1,max> $itemsPerPage + * @return static + */ + final public static function create( + ReadableCollection|Selectable $collection, + ?Criteria $criteria = null, + ?string $instanceId = null, + ?string $indexBy = null, + int $itemsPerPage = 50, + ?CountStrategy $count = null, + ): PageableInterface { + if (self::$instances === null) { + /** @var \WeakMap>> */ + $weakmap = new \WeakMap(); + // @phpstan-ignore-next-line + self::$instances = $weakmap; + } + + $cacheKey = hash('xxh128', serialize([ + $instanceId ?? $criteria, + $indexBy, + $itemsPerPage, + ])); + + if (isset(self::$instances[$collection][$cacheKey])) { + /** @var static */ + return self::$instances[$collection][$cacheKey]; + } + + /** @psalm-suppress UnsafeGenericInstantiation */ + $newInstance = new static( + collection: $collection, + criteria: $criteria, + indexBy: $indexBy, + itemsPerPage: $itemsPerPage, + count: $count, + ); + + if (!isset(self::$instances[$collection])) { + // @phpstan-ignore-next-line + self::$instances[$collection] = []; + } + + /** + * @psalm-suppress InvalidArgument + * @phpstan-ignore-next-line + */ + self::$instances[$collection][$cacheKey] = $newInstance; + + /** @var static */ + return $newInstance; + } + + /** + * @param int<1,max> $itemsPerPage + */ + public function withItemsPerPage(int $itemsPerPage): static + { + return self::create( + collection: $this->collection, + criteria: $this->criteria, + itemsPerPage: $itemsPerPage, + count: $this->count, + ); + } + + private function getUnderlyingCountable(): \Countable + { + return $this->collection->matching($this->criteria); + } +} diff --git a/packages/collections-domain/src/MinimalRecollectionDecorator.php b/packages/collections-domain/src/MinimalRecollectionDecorator.php index 59a329d..b58ebc2 100644 --- a/packages/collections-domain/src/MinimalRecollectionDecorator.php +++ b/packages/collections-domain/src/MinimalRecollectionDecorator.php @@ -19,6 +19,7 @@ use Doctrine\Common\Collections\Selectable; use Rekalogika\Contracts\Collections\Exception\UnexpectedValueException; use Rekalogika\Contracts\Collections\MinimalRecollection; +use Rekalogika\Contracts\Rekapager\PageableInterface; use Rekalogika\Domain\Collections\Common\Configuration; use Rekalogika\Domain\Collections\Common\Count\CountStrategy; use Rekalogika\Domain\Collections\Common\Count\RestrictedCountStrategy; @@ -215,4 +216,27 @@ final protected function createCriteriaCollection( count: $count, ); } + + /** + * @return PageableInterface + */ + final protected function createCriteriaPageable( + Criteria $criteria, + ?string $instanceId = null, + ?CountStrategy $count = null, + ): PageableInterface { + // if $criteria has no orderings, add the current ordering + if (\count($criteria->orderings()) === 0) { + $criteria = $criteria->orderBy($this->orderBy); + } + + /** @var PageableInterface */ + return CriteriaPageable::create( + collection: $this->collection, + criteria: $criteria, + instanceId: $instanceId, + itemsPerPage: $this->itemsPerPage, + count: $count, + ); + } } diff --git a/packages/collections-domain/src/RecollectionDecorator.php b/packages/collections-domain/src/RecollectionDecorator.php index 3d074da..48120e0 100644 --- a/packages/collections-domain/src/RecollectionDecorator.php +++ b/packages/collections-domain/src/RecollectionDecorator.php @@ -19,6 +19,7 @@ use Doctrine\Common\Collections\Selectable; use Rekalogika\Contracts\Collections\Exception\UnexpectedValueException; use Rekalogika\Contracts\Collections\Recollection; +use Rekalogika\Contracts\Rekapager\PageableInterface; use Rekalogika\Domain\Collections\Common\Configuration; use Rekalogika\Domain\Collections\Common\Count\CountStrategy; use Rekalogika\Domain\Collections\Common\Count\RestrictedCountStrategy; @@ -259,4 +260,27 @@ final protected function createCriteriaCollection( hardLimit: $this->hardLimit, ); } + + /** + * @return PageableInterface + */ + final protected function createCriteriaPageable( + Criteria $criteria, + ?string $instanceId = null, + ?CountStrategy $count = null, + ): PageableInterface { + // if $criteria has no orderings, add the current ordering + if (\count($criteria->orderings()) === 0) { + $criteria = $criteria->orderBy($this->orderBy); + } + + /** @var PageableInterface */ + return CriteriaPageable::create( + collection: $this->collection, + criteria: $criteria, + instanceId: $instanceId, + itemsPerPage: $this->itemsPerPage, + count: $count, + ); + } } diff --git a/packages/collections-orm/src/Trait/RepositoryDxTrait.php b/packages/collections-orm/src/Trait/RepositoryDxTrait.php index 55cfc70..036f32e 100644 --- a/packages/collections-orm/src/Trait/RepositoryDxTrait.php +++ b/packages/collections-orm/src/Trait/RepositoryDxTrait.php @@ -20,10 +20,8 @@ use Doctrine\Persistence\ObjectRepository; use Rekalogika\Contracts\Rekapager\PageableInterface; use Rekalogika\Domain\Collections\Common\Count\CountStrategy; -use Rekalogika\Domain\Collections\Common\Exception\GettingCountUnsupportedException; +use Rekalogika\Domain\Collections\CriteriaPageable; use Rekalogika\Domain\Collections\CriteriaRecollection; -use Rekalogika\Rekapager\Doctrine\Collections\SelectableAdapter; -use Rekalogika\Rekapager\Keyset\KeysetPageable; /** * @template TKey of array-key @@ -93,26 +91,12 @@ final protected function createCriteriaPageable( ?string $indexBy = null, ?CountStrategy $count = null, ): PageableInterface { - $adapter = new SelectableAdapter( + return CriteriaPageable::create( collection: $this->getDoctrineRepository(), criteria: $criteria, - indexBy: $indexBy ?? $this->indexBy - ); - - $count = function (): int|bool { - try { - return $this->count->getCount($this->getUnderlyingCountable()); - } catch (GettingCountUnsupportedException) { - return false; - } - }; - - $pageable = new KeysetPageable( - adapter: $adapter, + indexBy: $indexBy ?? $this->indexBy, itemsPerPage: $this->itemsPerPage, count: $count, ); - - return $pageable; } }