Skip to content

Commit 9eaf5a3

Browse files
aarondfrancisclaude
andcommitted
Update to PHP 8.2+, modernize CI and test setup
- Require PHP 8.2+ (drop 8.1) - Add PHP 8.4 to test matrix - Update dev dependencies (PHPUnit 10.5+/11, Mockery 1.6+, Scout 10.8+/11) - Upgrade MySQL to 8.4 in CI - Update PHPUnit config to v11 schema format - Replace @test annotations with #[Test] attributes - Clean up FUNDING.yml - Add CLAUDE.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f705e74 commit 9eaf5a3

File tree

10 files changed

+110
-62
lines changed

10 files changed

+110
-62
lines changed

.github/FUNDING.yml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,2 @@
1-
# These are supported funding model platforms
2-
31
github: [aarondfrancis]
4-
patreon: # Replace with a single Patreon username
5-
open_collective: # Replace with a single Open Collective username
6-
ko_fi: # Replace with a single Ko-fi username
7-
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8-
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9-
liberapay: # Replace with a single Liberapay username
10-
issuehunt: # Replace with a single IssueHunt username
11-
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12-
polar: # Replace with a single Polar username
13-
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14-
thanks_dev: # Replace with a single thanks.dev username
152
custom: ['https://aaronfrancis.com/backstage']

.github/workflows/style.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Setup PHP
1919
uses: shivammathur/setup-php@v2
2020
with:
21-
php-version: "8.3"
21+
php-version: "8.4"
2222
extensions: json, dom, curl, libxml, mbstring
2323
coverage: none
2424

.github/workflows/tests.yml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,15 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
php: [8.1, 8.2, 8.3]
20+
php: ['8.2', '8.3', '8.4']
2121
laravel: ['10.*', '11.*', '12.*']
22-
dependency-version: [prefer-lowest, prefer-stable]
23-
24-
exclude:
25-
- laravel: 11.*
26-
php: 8.1
27-
- laravel: 12.*
28-
php: 8.1
22+
dependency-version: [prefer-stable]
2923

3024
name: P${{ matrix.php }} / L${{ matrix.laravel }} / ${{ matrix.dependency-version }}
3125

3226
services:
3327
mysql:
34-
image: mysql:8.0
28+
image: mysql:8.4
3529
env:
3630
MYSQL_DATABASE: fast_paginate
3731
MYSQL_HOST: 127.0.0.1

CLAUDE.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Fast Paginate is a Laravel package that provides an optimized `limit`/`offset` pagination method using a "deferred join" technique. Instead of fetching full rows during pagination, it first retrieves only primary keys in an optimized subquery, then fetches full records for those specific IDs. This significantly improves performance on large datasets.
8+
9+
## Commands
10+
11+
**Run all tests:**
12+
```bash
13+
./vendor/bin/phpunit
14+
```
15+
16+
**Run a specific test file:**
17+
```bash
18+
./vendor/bin/phpunit tests/Integration/BuilderTest.php
19+
```
20+
21+
**Run a specific test method:**
22+
```bash
23+
./vendor/bin/phpunit --filter basic_test
24+
```
25+
26+
**Note:** Tests require a MySQL database (8.4+). Configure via environment variables or `phpunit.xml`:
27+
- DB_DATABASE=fast_paginate
28+
- DB_USERNAME=test
29+
- DB_PASSWORD=root
30+
31+
## Architecture
32+
33+
The package registers macros on Laravel's Eloquent Builder and Relation classes via `FastPaginateProvider`:
34+
35+
- **`FastPaginate`** (`src/FastPaginate.php`): Core pagination logic. Implements `fastPaginate()` and `simpleFastPaginate()` methods that:
36+
1. Clone the query and select only primary keys
37+
2. Run pagination on the key-only query
38+
3. Use the retrieved IDs in a `WHERE IN` clause on the original query
39+
4. Return full records with proper pagination metadata
40+
41+
- **`BuilderMixin`** (`src/BuilderMixin.php`): Adds `fastPaginate()` and `simpleFastPaginate()` methods to Eloquent Builder
42+
43+
- **`RelationMixin`** (`src/RelationMixin.php`): Adds the same methods to Eloquent Relations, with special handling for `BelongsToMany` and `HasManyThrough`
44+
45+
- **`ScoutMixin`** (`src/ScoutMixin.php`): Provides `fastPaginate()` on Laravel Scout builders (defers to standard pagination as Scout already optimizes)
46+
47+
### Fallback Behavior
48+
49+
The package automatically falls back to standard pagination when:
50+
- Query contains `havings`, `groups`, or `unions`
51+
- `perPage` is set to `-1` (get all records)
52+
- Query contains incompatible expressions (detected via `QueryIncompatibleWithFastPagination`)
53+
54+
### Key Implementation Detail
55+
56+
Order-by columns that reference computed/aliased columns are preserved in the inner query to maintain correct ordering during the key-selection phase.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ sure to test on your data!
3434
3535
## Installation
3636

37-
This package supports Laravel 10, 11, and 12.
37+
This package supports Laravel 10, 11, and 12, and requires PHP 8.2 or higher.
3838

3939
To install, require the package via composer:
4040

composer.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
}
1111
],
1212
"require": {
13-
"php": "^8.1",
13+
"php": "^8.2",
1414
"illuminate/database": "^10.0|^11.0|^12.0"
1515
},
1616
"require-dev": {
1717
"orchestra/testbench": "^8.0|^9.0|^10.0",
18-
"mockery/mockery": "^1.3.3",
19-
"phpunit/phpunit": ">=8.5.23|^9|^10",
20-
"laravel/scout": "^9.4|^10.8"
18+
"mockery/mockery": "^1.6",
19+
"phpunit/phpunit": "^10.5|^11.0",
20+
"laravel/scout": "^10.8|^11.0"
2121
},
2222
"autoload": {
2323
"psr-4": {

phpunit.xml.dist

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
3-
<coverage>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
bootstrap="vendor/autoload.php"
4+
backupGlobals="false"
5+
colors="true"
6+
processIsolation="false"
7+
stopOnFailure="false"
8+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
9+
cacheDirectory=".phpunit.cache"
10+
backupStaticProperties="false">
11+
<source>
412
<include>
513
<directory suffix=".php">src/</directory>
614
</include>
7-
</coverage>
15+
</source>
816
<testsuites>
917
<testsuite name="unit">
1018
<directory>tests/Unit</directory>

tests/Integration/BuilderTest.php

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
use Illuminate\Database\QueryException;
1717
use Illuminate\Pagination\LengthAwarePaginator;
1818
use Illuminate\Pagination\Paginator;
19+
use PHPUnit\Framework\Attributes\Test;
1920

2021
class BuilderTest extends Base
2122
{
2223
private const TOTAL_USERS = 29;
2324

2425
private const TOTAL_POSTS_FIRST_USER = 1;
2526

26-
/** @test */
27+
#[Test]
2728
public function basic_test()
2829
{
2930
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -46,7 +47,7 @@ public function basic_test()
4647
$this->assertEquals(self::TOTAL_USERS, $results->total());
4748
}
4849

49-
/** @test */
50+
#[Test]
5051
public function different_page_size()
5152
{
5253
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -66,7 +67,7 @@ public function different_page_size()
6667
$this->assertEquals(self::TOTAL_USERS, $results->total());
6768
}
6869

69-
/** @test */
70+
#[Test]
7071
public function page_2()
7172
{
7273
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -86,7 +87,7 @@ public function page_2()
8687
$this->assertEquals(self::TOTAL_USERS, $results->total());
8788
}
8889

89-
/** @test */
90+
#[Test]
9091
public function pk_attribute_mutations_are_skipped()
9192
{
9293
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -102,7 +103,7 @@ public function pk_attribute_mutations_are_skipped()
102103
);
103104
}
104105

105-
/** @test */
106+
#[Test]
106107
public function custom_page_is_preserved()
107108
{
108109
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -122,7 +123,7 @@ public function custom_page_is_preserved()
122123
$this->assertEquals(self::TOTAL_USERS, $results->total());
123124
}
124125

125-
/** @test */
126+
#[Test]
126127
public function not_exists_page_is_preserved()
127128
{
128129
$exists = User::query()->fastPaginate();
@@ -140,7 +141,7 @@ public function not_exists_page_is_preserved()
140141
$this->assertFalse($doesnt->hasMorePages());
141142
}
142143

143-
/** @test */
144+
#[Test]
144145
public function custom_table_is_preserved()
145146
{
146147
$this->expectException(QueryException::class);
@@ -149,7 +150,7 @@ public function custom_table_is_preserved()
149150
UserCustomTable::query()->fastPaginate();
150151
}
151152

152-
/** @test */
153+
#[Test]
153154
public function order_is_propagated()
154155
{
155156
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -162,7 +163,7 @@ public function order_is_propagated()
162163
);
163164
}
164165

165-
/** @test */
166+
#[Test]
166167
public function order_by_raw_is_propagated()
167168
{
168169
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -175,7 +176,7 @@ public function order_by_raw_is_propagated()
175176
);
176177
}
177178

178-
/** @test */
179+
#[Test]
179180
public function eager_loads_are_cleared_on_inner_query()
180181
{
181182
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -192,7 +193,7 @@ public function eager_loads_are_cleared_on_inner_query()
192193
);
193194
}
194195

195-
/** @test */
196+
#[Test]
196197
public function eager_loads_are_loaded_on_outer_query()
197198
{
198199
$this->withQueriesLogged(function () use (&$results) {
@@ -203,7 +204,7 @@ public function eager_loads_are_loaded_on_outer_query()
203204
$this->assertEquals(1, $results->first()->posts->count());
204205
}
205206

206-
/** @test */
207+
#[Test]
207208
public function selects_are_overwritten()
208209
{
209210
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -223,7 +224,7 @@ public function selects_are_overwritten()
223224
);
224225
}
225226

226-
/** @test */
227+
#[Test]
227228
public function unquoted_selects_are_preserved_if_used_in_order_by()
228229
{
229230
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -236,7 +237,7 @@ public function unquoted_selects_are_preserved_if_used_in_order_by()
236237
);
237238
}
238239

239-
/** @test */
240+
#[Test]
240241
public function using_expressions_for_order_work()
241242
{
242243
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -251,7 +252,7 @@ public function using_expressions_for_order_work()
251252
);
252253
}
253254

254-
/** @test */
255+
#[Test]
255256
public function havings_defer()
256257
{
257258
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -268,7 +269,7 @@ public function havings_defer()
268269
);
269270
}
270271

271-
/** @test */
272+
#[Test]
272273
public function standard_with_count_works()
273274
{
274275
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -287,7 +288,7 @@ public function standard_with_count_works()
287288
$this->assertEquals(self::TOTAL_USERS, $results->total());
288289
}
289290

290-
/** @test */
291+
#[Test]
291292
public function aliased_with_count()
292293
{
293294
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -301,7 +302,7 @@ public function aliased_with_count()
301302
);
302303
}
303304

304-
/** @test */
305+
#[Test]
305306
public function unordered_with_count_is_ignored()
306307
{
307308
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -315,7 +316,7 @@ public function unordered_with_count_is_ignored()
315316
);
316317
}
317318

318-
/** @test */
319+
#[Test]
319320
public function uuids_are_bound_correctly()
320321
{
321322
$this->seedStringNotifications();
@@ -334,7 +335,7 @@ public function uuids_are_bound_correctly()
334335
$this->assertEquals('64bf6df6-06d7-11ed-b939-0001', $queries[2]['bindings'][0]);
335336
}
336337

337-
/** @test */
338+
#[Test]
338339
public function groups_are_skipped()
339340
{
340341
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -348,7 +349,7 @@ public function groups_are_skipped()
348349
);
349350
}
350351

351-
/** @test */
352+
#[Test]
352353
public function basic_simple_test()
353354
{
354355
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -370,7 +371,7 @@ public function basic_simple_test()
370371
$this->assertEquals(1, $results->currentPage());
371372
}
372373

373-
/** @test */
374+
#[Test]
374375
public function basic_simple_test_page_two()
375376
{
376377
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -392,7 +393,7 @@ public function basic_simple_test_page_two()
392393
$this->assertEquals(2, $results->currentPage());
393394
}
394395

395-
/** @test */
396+
#[Test]
396397
public function basic_simple_test_from_relation()
397398
{
398399
$queries = $this->withQueriesLogged(function () use (&$results) {
@@ -414,15 +415,15 @@ public function basic_simple_test_from_relation()
414415
$this->assertEquals(1, $results->currentPage());
415416
}
416417

417-
/** @test */
418+
#[Test]
418419
public function custom_collection_is_preserved()
419420
{
420421
$results = UserWithCustomCollection::query()->simpleFastPaginate();
421422

422423
$this->assertInstanceOf(UserCollection::class, $results->getCollection());
423424
}
424425

425-
/** @test */
426+
#[Test]
426427
public function with_sum_has_the_correct_number_of_parameters()
427428
{
428429
$queries = $this->withQueriesLogged(function () use (&$fast, &$regular) {

0 commit comments

Comments
 (0)