diff --git a/docs/driver.rst b/docs/driver.rst index 1c17eca..9f95085 100644 --- a/docs/driver.rst +++ b/docs/driver.rst @@ -5,8 +5,9 @@ The Driver class The Driver class is the gateway to much of the functionality of this library. It has many options and top-level functions, detailed here. -Creating a ``Driver`` with all config options ---------------------------------------------- + +Creating a Driver with all config options +========================================= .. code-block:: php diff --git a/docs/events.rst b/docs/events.rst index 36633ce..df2c08f 100644 --- a/docs/events.rst +++ b/docs/events.rst @@ -65,12 +65,19 @@ user, create a listener. } ); -The ``QueryBuilder`` event has one function in addition to getters for +Functions of the ``QueryBuilder`` event in addition to getters for all resolve parameters: * ``getQueryBuilder`` - Will return a query builder with the user specified filters already applied. - +* ``getOffset`` - Will return the offset for the query. The QueryBuilder passed + to the event is not modified with the offset and limit yet. So if you have a + large dataset and need to fetch it within the event, you may use this method + to get the offset. +* ``getLimit`` - Will return the limit for the query. The QueryBuilder passed + to the event is not modified with the offset and limit yet. So if you have a + large dataset and need to fetch it within the event, you may use this method + to get the limit. Criteria Event ============== @@ -148,6 +155,22 @@ all resolve parameters: if you need to fetch the collection to apply additional criteria. * ``setCollection`` - Will set the collection object. This is useful if you need to filter the collection directly. +* ``getOffset`` - Will return the projected offset for the collection. The collection passed + to the event is not modified with the offset and limit yet. So if you have a + large dataset and need to fetch it within the event, you may use this method + to get the expected offset. +* ``getLimit`` - Will return the projectd limit for the collection. The collection passed + to the event is not modified with the offset and limit yet. So if you have a + large dataset and need to fetch it within the event, you may use this method + to get the expected limit. + +.. note:: + + The offset and limit is calculated before this event is fired and calculated + again after the event. This is because the collection may be fetched and + filtered before the limit is applied. The offset and limit are recalculated + after the event is fired to ensure the correct data is returned. + Modify an Entity Definition =========================== diff --git a/src/Event/Criteria.php b/src/Event/Criteria.php index 354df70..08dfde4 100644 --- a/src/Event/Criteria.php +++ b/src/Event/Criteria.php @@ -25,6 +25,8 @@ public function __construct( protected readonly string $eventName, protected readonly DoctrineCriteria $criteria, protected Collection $collection, + protected readonly int $offset, + protected readonly int $limit, protected readonly mixed $objectValue, protected readonly array $args, protected readonly mixed $context, @@ -54,6 +56,16 @@ public function setCollection(Collection $collection): void $this->collection = $collection; } + public function getOffset(): int + { + return $this->offset; + } + + public function getLimit(): int + { + return $this->limit; + } + public function getObjectValue(): mixed { return $this->objectValue; diff --git a/src/Event/QueryBuilder.php b/src/Event/QueryBuilder.php index dc95866..dd11961 100644 --- a/src/Event/QueryBuilder.php +++ b/src/Event/QueryBuilder.php @@ -16,8 +16,10 @@ class QueryBuilder implements { /** @param mixed[] $args */ public function __construct( - protected readonly DoctrineQueryBuilder $queryBuilder, protected readonly string $eventName, + protected readonly DoctrineQueryBuilder $queryBuilder, + protected readonly int $offset, + protected readonly int $limit, protected readonly mixed $objectValue, protected readonly array $args, protected readonly mixed $context, @@ -35,6 +37,16 @@ public function getQueryBuilder(): DoctrineQueryBuilder return $this->queryBuilder; } + public function getOffset(): int + { + return $this->offset; + } + + public function getLimit(): int + { + return $this->limit; + } + public function getObjectValue(): mixed { return $this->objectValue; diff --git a/src/Resolve/ResolveCollectionFactory.php b/src/Resolve/ResolveCollectionFactory.php index 1ec96bc..58f6db6 100644 --- a/src/Resolve/ResolveCollectionFactory.php +++ b/src/Resolve/ResolveCollectionFactory.php @@ -149,6 +149,10 @@ protected function buildPagination( $paginationFields[$field] = (int) base64_decode($value, true); } + // Calculate offset and limit + $itemCount = count($collection->matching($criteria)); + $offsetAndLimit = $this->calculateOffsetAndLimit($resolve[3]->fieldName, $entityClassName, $targetClassName, $paginationFields, $itemCount); + /** * Fire the event dispatcher using the passed event name. */ @@ -157,6 +161,8 @@ protected function buildPagination( $criteriaEventName, $criteria, $collection, + $offsetAndLimit['offset'], + $offsetAndLimit['limit'], ...$resolve, ); @@ -164,10 +170,11 @@ protected function buildPagination( $collection = $event->getCollection(); } - $itemCount = count($collection->matching($criteria)); + // Recalculate offset and limit after Criteria event + $itemCount = count($collection->matching($criteria)); + $offsetAndLimit = $this->calculateOffsetAndLimit($resolve[3]->fieldName, $entityClassName, $targetClassName, $paginationFields, $itemCount); // Add offset and limit after Criteria event - $offsetAndLimit = $this->calculateOffsetAndLimit($resolve[3]->fieldName, $entityClassName, $targetClassName, $paginationFields, $itemCount); if ($offsetAndLimit['offset']) { $criteria->setFirstResult($offsetAndLimit['offset']); } diff --git a/src/Resolve/ResolveEntityFactory.php b/src/Resolve/ResolveEntityFactory.php index 2892940..8ffe175 100644 --- a/src/Resolve/ResolveEntityFactory.php +++ b/src/Resolve/ResolveEntityFactory.php @@ -90,14 +90,6 @@ public function buildPagination( $offsetAndLimit = $this->calculateOffsetAndLimit($entity, $paginationFields); - if ($offsetAndLimit['offset']) { - $queryBuilder->setFirstResult($offsetAndLimit['offset']); - } - - if ($offsetAndLimit['limit']) { - $queryBuilder->setMaxResults($offsetAndLimit['limit']); - } - /** * Fire the event dispatcher using the passed event name. * Include all resolve variables. @@ -105,13 +97,23 @@ public function buildPagination( if ($eventName) { $this->eventDispatcher->dispatch( new QueryBuilderEvent( - $queryBuilder, $eventName, + $queryBuilder, + (int) $offsetAndLimit['offset'], + (int) $offsetAndLimit['limit'], ...$resolve, ), ); } + if ($offsetAndLimit['offset']) { + $queryBuilder->setFirstResult($offsetAndLimit['offset']); + } + + if ($offsetAndLimit['limit']) { + $queryBuilder->setMaxResults($offsetAndLimit['limit']); + } + $edgesAndCursors = $this->buildEdgesAndCursors($queryBuilder, $offsetAndLimit, $paginationFields); return [ diff --git a/test/Feature/Event/CriteriaTest.php b/test/Feature/Event/CriteriaTest.php index be3f331..8413664 100644 --- a/test/Feature/Event/CriteriaTest.php +++ b/test/Feature/Event/CriteriaTest.php @@ -94,7 +94,10 @@ function (CriteriaEvent $event): void { public function testEventFilterCollection(): void { - $driver = new Driver($this->getEntityManager(), new Config(['group' => 'CriteriaEvent'])); + $driver = new Driver($this->getEntityManager(), new Config([ + 'group' => 'CriteriaEvent', + 'limit' => 25, + ])); $driver->get(EventDispatcher::class)->subscribeTo( Artist::class . '.performances.criteria', @@ -109,6 +112,8 @@ static function ($performance) { }, )); + $this->assertEquals(0, $event->getOffset()); + $this->assertEquals(25, $event->getLimit()); $this->assertInstanceOf(Collection::class, $event->getCollection()); $this->assertInstanceOf(Artist::class, $event->getObjectValue()); $this->assertEquals('contextTest', $event->getContext()); diff --git a/test/Feature/Event/FilterQueryBuilderTest.php b/test/Feature/Event/FilterQueryBuilderTest.php index ecc610c..37e6372 100644 --- a/test/Feature/Event/FilterQueryBuilderTest.php +++ b/test/Feature/Event/FilterQueryBuilderTest.php @@ -4,6 +4,7 @@ namespace ApiSkeletonsTest\Doctrine\ORM\GraphQL\Feature\Event; +use ApiSkeletons\Doctrine\ORM\GraphQL\Config; use ApiSkeletons\Doctrine\ORM\GraphQL\Driver; use ApiSkeletons\Doctrine\ORM\GraphQL\Event\QueryBuilder as QueryBuilderEvent; use ApiSkeletonsTest\Doctrine\ORM\GraphQL\AbstractTest; @@ -18,11 +19,13 @@ class FilterQueryBuilderTest extends AbstractTest { public function testEvent(): void { - $driver = new Driver($this->getEntityManager()); + $driver = new Driver($this->getEntityManager(), new Config(['limit' => 10])); $driver->get(EventDispatcher::class)->subscribeTo( 'artist.querybuilder', function (QueryBuilderEvent $event): void { $this->assertInstanceOf(QueryBuilder::class, $event->getQueryBuilder()); + $this->assertEquals(0, $event->getOffset()); + $this->assertEquals(10, $event->getLimit()); }, );