diff --git a/CHANGELOG.md b/CHANGELOG.md index 803989c3..215f7350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ - PHP 8.4 forward compatibility: Added missing type hint to `SleekDbal::__get()` magic method - Fixed deprecated `E_STRICT` constant usage in test bootstrap - Fixed cURL error message assertions for cross-version compatibility +- Fixed SleekDB paginator query-state regression where `count()` could clear criteria before paginated data fetch on the same model instance (#514) ### Added - `AppContext` class representing the runtime identity of a single application execution diff --git a/src/Paginator/Adapters/ModelPaginator.php b/src/Paginator/Adapters/ModelPaginator.php index b4bac85c..7071b35e 100644 --- a/src/Paginator/Adapters/ModelPaginator.php +++ b/src/Paginator/Adapters/ModelPaginator.php @@ -19,6 +19,7 @@ use Quantum\Paginator\Contracts\PaginatorInterface; use Quantum\Paginator\Traits\PaginatorTrait; use Quantum\App\Exceptions\BaseException; +use Quantum\Model\Exceptions\ModelException; use Quantum\Di\Exceptions\DiException; use Quantum\Model\ModelCollection; use Quantum\Model\DbModel; @@ -38,7 +39,7 @@ class ModelPaginator implements PaginatorInterface private DbModel $model; /** - * @throws DiException|ReflectionException + * @throws DiException|ReflectionException|ModelException|BaseException */ public function __construct(DbModel $model, int $perPage, int $page = 1) { @@ -46,7 +47,7 @@ public function __construct(DbModel $model, int $perPage, int $page = 1) $this->model = $model; $this->modelClass = $model->getModelName(); - $this->total = $model->count(); + $this->total = $this->getCountFromClonedModel($model); } /** @@ -99,4 +100,17 @@ public function lastItem(): ?Model return $data->last(); } + + /** + * Counts results on an isolated model/ORM clone so total calculation cannot mutate + * the live model query state later used by paginator data(). + * @throws ModelException|BaseException + */ + private function getCountFromClonedModel(DbModel $model): int + { + $countModel = clone $model; + $countModel->setOrmInstance(clone $model->getOrmInstance()); + + return $countModel->count(); + } } diff --git a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php index bcf3f83f..e1213390 100644 --- a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php +++ b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php @@ -3,6 +3,7 @@ namespace Quantum\Tests\Unit\Database\Adapters\Sleekdb\Statements; use Quantum\Tests\Unit\Database\Adapters\Sleekdb\SleekDbalTestCase; +use Quantum\Tests\_root\shared\Models\TestEventModel; use Quantum\Database\Adapters\Sleekdb\SleekDbal; class ResultSleekTest extends SleekDbalTestCase @@ -85,4 +86,69 @@ public function testSleekAsArray(): void $this->assertIsArray($user->asArray()); } + + public function testSleekCountResetsCriteriaState(): void + { + $eventsModel = new SleekDbal('events'); + + $eventsModel->criteria('country', '=', 'Ireland'); + + $this->assertEquals(3, $eventsModel->count()); + + $events = $eventsModel + ->orderBy('title', 'asc') + ->get(); + + $this->assertCount(7, $events); + $this->assertEquals('Art', $events[0]->prop('title')); + } + + public function testSleekPaginateRetainsCriteriaAfterCount(): void + { + $eventModel = model(TestEventModel::class); + + $page = $eventModel + ->criteria('country', '=', 'Ireland') + ->orderBy('title', 'asc') + ->paginate(2, 1) + ->data(); + + $this->assertCount(2, $page); + $this->assertEquals('Design', $page->first()->title); + $this->assertEquals('Film', $page->last()->title); + } + + public function testSleekGroupedCriteriasPersistAcrossPaginateCountAndData(): void + { + $baseline = model(TestEventModel::class) + ->criterias( + [ + ['title', '=', 'Dance'], + ['title', '=', 'Art'], + ], + ['country', '=', 'Ireland'] + ) + ->orderBy('title', 'asc') + ->get(); + + $paginator = model(TestEventModel::class) + ->criterias( + [ + ['title', '=', 'Dance'], + ['title', '=', 'Art'], + ], + ['country', '=', 'Ireland'] + ) + ->orderBy('title', 'asc') + ->paginate(10, 1); + + $page = $paginator->data(); + + $baselineTitles = array_map(fn ($event) => $event->title, iterator_to_array($baseline)); + $pageTitles = array_map(fn ($event) => $event->title, iterator_to_array($page)); + + $this->assertSame(count($baseline), $paginator->total()); + $this->assertCount(count($baseline), $page); + $this->assertSame($baselineTitles, $pageTitles); + } }