Skip to content

Commit 2bed7d9

Browse files
Merge pull request #77 from aarondfrancis/fix-74
Fix #74: Add $total parameter to fastPaginate()
2 parents 2435af1 + 87aff5b commit 2bed7d9

File tree

5 files changed

+97
-7
lines changed

5 files changed

+97
-7
lines changed

.claude/commands/issue.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Fix GitHub issue #$ARGUMENTS
2+
3+
Follow these steps:
4+
5+
1. **Fetch the issue**: Use `gh issue view $ARGUMENTS` to read the issue details from the GitHub repository. Understand what the bug or feature request is about.
6+
7+
2. **Create a branch**: Create and checkout a new branch named `fix-$ARGUMENTS` from main:
8+
```
9+
git checkout -b fix-$ARGUMENTS
10+
```
11+
12+
3. **Write a failing test**: Based on the issue description, write a test that reproduces the problem. The test should fail initially, demonstrating the bug exists.
13+
14+
4. **Run the test**: Execute the failing test to confirm it fails as expected. If the test requires a database and fails due to connection issues, that's acceptable - focus on the test logic being correct.
15+
16+
5. **Diagnose the issue**: Analyze the codebase to understand why the bug occurs. Look at the relevant source files and trace through the logic.
17+
18+
6. **Implement the fix**: Make the necessary code changes to fix the issue. Keep changes minimal and focused.
19+
20+
7. **Verify the fix**: Run the test again to confirm it now passes (or would pass with a database connection).
21+
22+
8. **Commit and push**: Stage all changes, commit with a descriptive message referencing the issue number, and push the branch:
23+
```
24+
git add -A
25+
git commit -m "Fix #$ARGUMENTS: <brief description>"
26+
git push -u origin fix-$ARGUMENTS
27+
```
28+
29+
9. **Report**: Summarize what was done, what the fix was, and provide the branch name for creating a PR.

.claude/settings.local.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(composer update:*)",
5+
"Bash(./vendor/bin/phpunit:*)",
6+
"Bash(git add:*)",
7+
"Bash(git commit:*)",
8+
"Bash(gh issue view:*)",
9+
"Bash(gh api:*)",
10+
"Bash(git checkout:*)",
11+
"Bash(php -l:*)",
12+
"Bash(git push:*)",
13+
"Bash(git remote set-url:*)",
14+
"Bash(gh run list:*)",
15+
"Bash(gh run view:*)",
16+
"Bash(gh run watch:*)"
17+
],
18+
"deny": [],
19+
"ask": []
20+
}
21+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Changelog
22
## Unreleased
33
- Add default `ORDER BY` primary key when no order is specified to ensure deterministic pagination results ([#73](https://github.com/aarondfrancis/fast-paginate/issues/73))
4+
- Add `$total` parameter to `fastPaginate()` to allow skipping the COUNT query ([#74](https://github.com/aarondfrancis/fast-paginate/issues/74))

src/FastPaginate.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@
1111

1212
class FastPaginate
1313
{
14+
/**
15+
* Laravel 11+ added a $total parameter to paginate(). We need to
16+
* conditionally pass it to avoid errors on Laravel 10.
17+
*
18+
* @internal
19+
*/
20+
public static function callPaginator($builder, string $method, $perPage, $columns, $pageName, $page, $total)
21+
{
22+
if (version_compare(app()->version(), '11.0.0', '>=')) {
23+
return $builder->{$method}($perPage, $columns, $pageName, $page, $total);
24+
}
25+
26+
return $builder->{$method}($perPage, $columns, $pageName, $page);
27+
}
28+
1429
public function fastPaginate()
1530
{
1631
return $this->paginate('paginate', function (array $items, $paginator) {
@@ -38,7 +53,7 @@ public function simpleFastPaginate()
3853

3954
protected function paginate(string $paginationMethod, Closure $paginatorOutput)
4055
{
41-
return function ($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) use (
56+
return function ($perPage = null, $columns = ['*'], $pageName = 'page', $page = null, $total = null) use (
4257
$paginationMethod,
4358
$paginatorOutput
4459
) {
@@ -48,7 +63,7 @@ protected function paginate(string $paginationMethod, Closure $paginatorOutput)
4863
// we are counting on each row of the inner query to return a primary key
4964
// that we can use. When grouping, that's not always the case.
5065
if (filled($base->havings) || filled($base->groups) || filled($base->unions)) {
51-
return $this->{$paginationMethod}($perPage, $columns, $pageName, $page);
66+
return FastPaginate::callPaginator($this, $paginationMethod, $perPage, $columns, $pageName, $page, $total);
5267
}
5368

5469
$model = $this->newModelInstance();
@@ -60,13 +75,13 @@ protected function paginate(string $paginationMethod, Closure $paginatorOutput)
6075
// fast pagination, we'll just return the normal paginator in that case.
6176
// https://github.com/aarondfrancis/fast-paginate/issues/39
6277
if ($perPage === -1) {
63-
return $this->{$paginationMethod}($perPage, $columns, $pageName, $page);
78+
return FastPaginate::callPaginator($this, $paginationMethod, $perPage, $columns, $pageName, $page, $total);
6479
}
6580

6681
try {
6782
$innerSelectColumns = FastPaginate::getInnerSelectColumns($this);
6883
} catch (QueryIncompatibleWithFastPagination $e) {
69-
return $this->{$paginationMethod}($perPage, $columns, $pageName, $page);
84+
return FastPaginate::callPaginator($this, $paginationMethod, $perPage, $columns, $pageName, $page, $total);
7085
}
7186

7287
// If no order is specified, we need to add a default order by
@@ -79,15 +94,16 @@ protected function paginate(string $paginationMethod, Closure $paginatorOutput)
7994

8095
// This is the copy of the query that becomes
8196
// the inner query that selects keys only.
82-
$paginator = $this->clone()
97+
$innerQuery = $this->clone()
8398
// Only select the primary key, we'll get the full
8499
// records in a second query below.
85100
->select($innerSelectColumns)
86101
// We don't need eager loads for this cloned query, they'll
87102
// remain on the query that actually gets the records.
88103
// (withoutEagerLoads not available on Laravel 8.)
89-
->setEagerLoads([])
90-
->{$paginationMethod}($perPage, ['*'], $pageName, $page);
104+
->setEagerLoads([]);
105+
106+
$paginator = FastPaginate::callPaginator($innerQuery, $paginationMethod, $perPage, ['*'], $pageName, $page, $total);
91107

92108
// Get the key values from the records on the current page without mutating them.
93109
$ids = $paginator->getCollection()->map->getRawOriginal($key)->toArray();

tests/Integration/BuilderTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ public function page_2()
8787
$this->assertEquals(self::TOTAL_USERS, $results->total());
8888
}
8989

90+
#[Test]
91+
public function total_can_be_passed_to_skip_count_query()
92+
{
93+
$queries = $this->withQueriesLogged(function () use (&$results) {
94+
// Pass a custom total to skip the COUNT(*) query
95+
$results = User::query()->fastPaginate(5, ['*'], 'page', 1, 100);
96+
});
97+
98+
/** @var \Illuminate\Pagination\LengthAwarePaginator $results */
99+
$this->assertEquals(5, $results->count());
100+
101+
// The $total parameter was added in Laravel 11. On Laravel 10,
102+
// we can't pass it through, so the COUNT query still runs.
103+
if (version_compare(app()->version(), '11.0.0', '>=')) {
104+
// Should only be 2 queries (inner select + outer select), no COUNT query
105+
$this->assertCount(2, $queries);
106+
107+
// The total should be the custom value we passed
108+
$this->assertEquals(100, $results->total());
109+
$this->assertTrue($results->hasMorePages());
110+
}
111+
}
112+
90113
#[Test]
91114
public function pk_attribute_mutations_are_skipped()
92115
{

0 commit comments

Comments
 (0)