Skip to content

Commit 1a2d134

Browse files
committed
fix: ensure beforeFind model events affect both count and results in pagination
1 parent 488ace9 commit 1a2d134

File tree

4 files changed

+134
-1
lines changed

4 files changed

+134
-1
lines changed

system/BaseModel.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1280,12 +1280,49 @@ public function paginate(?int $perPage = null, string $group = 'default', ?int $
12801280
}
12811281

12821282
$page = $page >= 1 ? $page : $pager->getCurrentPage($group);
1283+
1284+
// Get the tempPager to estimate required variables, use dummy count of 1
1285+
$tempPager = $pager->store($group, $page, $perPage, 1, $segment);
1286+
$perPage = $tempPager->getPerPage($group);
1287+
$offset = ($page - 1) * $perPage;
1288+
1289+
if ($this->tempAllowCallbacks) {
1290+
// Call the before event and check for a return
1291+
$eventData = $this->trigger('beforeFind', [
1292+
'method' => 'findAll',
1293+
'limit' => $perPage,
1294+
'offset' => $offset,
1295+
'singleton' => false,
1296+
]);
1297+
1298+
if (isset($eventData['returnData']) && $eventData['returnData'] === true) {
1299+
return $eventData['data'];
1300+
}
1301+
}
1302+
12831303
// Store it in the Pager library, so it can be paginated in the views.
12841304
$this->pager = $pager->store($group, $page, $perPage, $this->countAllResults(false), $segment);
12851305
$perPage = $this->pager->getPerPage($group);
12861306
$offset = ($pager->getCurrentPage($group) - 1) * $perPage;
12871307

1288-
return $this->findAll($perPage, $offset);
1308+
// Backup since it will be reset in the findAll method
1309+
$tempAllowCallbacks = $this->tempAllowCallbacks;
1310+
1311+
$data = $this->allowCallbacks(false)->findAll($perPage, $offset);
1312+
1313+
if ($tempAllowCallbacks) {
1314+
$eventData = $this->trigger('afterFind', [
1315+
'data' => $data,
1316+
'limit' => $perPage,
1317+
'offset' => $offset,
1318+
'method' => 'findAll',
1319+
'singleton' => false,
1320+
]);
1321+
1322+
return $eventData['data'];
1323+
}
1324+
1325+
return $data;
12891326
}
12901327

12911328
/**
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) CodeIgniter Foundation <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Tests\Support\Models;
15+
16+
use CodeIgniter\Model;
17+
18+
/**
19+
* @method int affectedRows()
20+
*/
21+
class UserWithEventsModel extends Model
22+
{
23+
protected $table = 'user';
24+
protected $allowedFields = [
25+
'name',
26+
'email',
27+
'country',
28+
'deleted_at',
29+
];
30+
protected $returnType = 'object';
31+
protected $useSoftDeletes = true;
32+
protected $dateFormat = 'datetime';
33+
public $name = '';
34+
public $email = '';
35+
public $country = '';
36+
protected $beforeFind = ['onlyUS'];
37+
38+
protected function onlyUS(array $data): array
39+
{
40+
$this->where('country', 'US');
41+
42+
return $data;
43+
}
44+
}

tests/system/Models/PaginateModelTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use PHPUnit\Framework\Attributes\Group;
1717
use Tests\Support\Models\UserModel;
18+
use Tests\Support\Models\UserWithEventsModel;
1819
use Tests\Support\Models\ValidModel;
1920

2021
/**
@@ -109,4 +110,54 @@ public function testMultiplePager(): void
109110
$this->assertStringContainsString('?page_user=3"', $pager->links('user'));
110111
$this->assertStringContainsString('?page_user=4"', $pager->links('user'));
111112
}
113+
114+
public function testPaginateWithBeforeFindEvents(): void
115+
{
116+
$this->createModel(UserWithEventsModel::class);
117+
118+
$this->seedPaginateEventModel();
119+
120+
// Test pagination - beforeFind event should filter to only US users
121+
$data = $this->model->paginate(2);
122+
123+
// Should only get US users in results
124+
$this->assertCount(2, $data);
125+
$this->assertSame(3, $this->model->pager->getDetails()['total']);
126+
$this->assertSame(2, $this->model->pager->getPageCount());
127+
128+
// Verify all returned users are from US
129+
foreach ($data as $user) {
130+
$this->assertSame('US', $user->country);
131+
}
132+
}
133+
134+
public function testPaginateWithBeforeFindEventsAndDisabledCallbacks(): void
135+
{
136+
$this->createModel(UserWithEventsModel::class);
137+
138+
$this->seedPaginateEventModel();
139+
140+
$data = $this->model->allowCallbacks(false)->paginate(2);
141+
142+
// Should get all users
143+
$this->assertCount(2, $data);
144+
$this->assertSame(9, $this->model->pager->getDetails()['total']);
145+
146+
// Should have users from different countries
147+
$countries = array_unique(array_column($data, 'country'));
148+
$this->assertGreaterThan(1, count($countries));
149+
}
150+
151+
private function seedPaginateEventModel(): void
152+
{
153+
$testData = [
154+
['name' => 'Jean', 'email' => '[email protected]', 'country' => 'France'],
155+
['name' => 'Marie', 'email' => '[email protected]', 'country' => 'France'],
156+
['name' => 'John', 'email' => '[email protected]', 'country' => 'US'],
157+
['name' => 'Hans', 'email' => '[email protected]', 'country' => 'Germany'],
158+
['name' => 'Luigi', 'email' => '[email protected]', 'country' => 'Italy'],
159+
];
160+
161+
$this->model->insertBatch($testData);
162+
}
112163
}

user_guide_src/source/changelogs/v4.6.2.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Bugs Fixed
3939
- **Database:** Fixed a bug where ``when()`` and ``whenNot()`` in ``ConditionalTrait`` incorrectly evaluated certain falsy values (such as ``[]``, ``0``, ``0.0``, and ``'0'``) as truthy, causing callbacks to be executed unexpectedly. These methods now cast the condition to a boolean using ``(bool)`` to ensure consistent behavior with PHP's native truthiness.
4040
- **Database:** Fixed encapsulation violation in ``BasePreparedQuery`` when accessing ``BaseConnection::transStatus`` protected property.
4141
- **Email:** Fixed a bug where ``Email::getHostname()`` failed to use ``$_SERVER['SERVER_ADDR']`` when ``$_SERVER['SERVER_NAME']`` was not set.
42+
- **Model:** Fixed a bug in ``BaseModel::paginate()`` where ``beforeFind`` events were not applied to the count query, causing inconsistent pagination metadata. The total count now correctly reflects the same filters applied to the paginated results.
4243
- **Security:** Fixed a bug where the ``sanitize_filename()`` function from the Security helper would throw an error when used in CLI requests.
4344
- **Session:** Fixed a bug where using the ``DatabaseHandler`` with an unsupported database driver (such as ``SQLSRV``, ``OCI8``, or ``SQLite3``) did not throw an appropriate error.
4445
- **URI:** Fixed a bug in ``URI::getAuthority()`` where schemes without defined default ports (like ``rtsp://``) would cause issues due to missing array key handling.

0 commit comments

Comments
 (0)