Skip to content

Commit b02716f

Browse files
authored
fix: preserve index field in updateBatch() when updateOnlyChanged is true (#9944)
* fix: preserve index field in updateBatch() when updateOnlyChanged is true * phpstan baseline * update test
1 parent 046fddf commit b02716f

File tree

4 files changed

+68
-10
lines changed

4 files changed

+68
-10
lines changed

system/BaseModel.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,15 +1099,35 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc
10991099
{
11001100
if (is_array($set)) {
11011101
foreach ($set as &$row) {
1102+
// Save the index value from the original row because
1103+
// transformDataToArray() may strip it when updateOnlyChanged
1104+
// is true.
1105+
$updateIndex = null;
1106+
1107+
if ($this->updateOnlyChanged) {
1108+
if (is_array($row)) {
1109+
$updateIndex = $row[$index] ?? null;
1110+
} elseif ($row instanceof Entity) {
1111+
$updateIndex = $row->toRawArray()[$index] ?? null;
1112+
} elseif (is_object($row)) {
1113+
$updateIndex = $row->{$index} ?? null;
1114+
}
1115+
}
1116+
11021117
$row = $this->transformDataToArray($row, 'update');
11031118

11041119
// Validate data before saving.
11051120
if (! $this->skipValidation && ! $this->validate($row)) {
11061121
return false;
11071122
}
11081123

1109-
// Save updateIndex for later
1110-
$updateIndex = $row[$index] ?? null;
1124+
// When updateOnlyChanged is true, restore the pre-extracted
1125+
// index into $row. Otherwise read it from the transformed row.
1126+
if ($updateIndex !== null) {
1127+
$row[$index] = $updateIndex;
1128+
} else {
1129+
$updateIndex = $row[$index] ?? null;
1130+
}
11111131

11121132
if ($updateIndex === null) {
11131133
throw new InvalidArgumentException(

tests/system/Models/UpdateModelTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,43 @@ public function testUpdateBatchWithEntity(): void
265265
$this->assertSame(2, $model->updateBatch([$entity1, $entity2], 'id'));
266266
}
267267

268+
/**
269+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/9943
270+
*/
271+
public function testUpdateBatchWithEntityAndUpdateOnlyChanged(): void
272+
{
273+
$entity1 = new User();
274+
$entity1->id = 1;
275+
$entity1->name = 'Derek Jones';
276+
$entity1->email = 'derek@world.com';
277+
$entity1->country = 'US';
278+
$entity1->syncOriginal();
279+
$entity1->country = 'Greece';
280+
281+
$entity2 = new User();
282+
$entity2->id = 4;
283+
$entity2->name = 'Chris Martin';
284+
$entity2->email = 'chris@world.com';
285+
$entity2->country = 'UK';
286+
$entity2->syncOriginal();
287+
$entity2->country = 'Finland';
288+
289+
// updateOnlyChanged is true by default. The index field 'id' is
290+
// unchanged but must be preserved for the batch WHERE clause.
291+
$model = $this->createModel(UserModel::class);
292+
$this->assertTrue($this->getPrivateProperty($model, 'updateOnlyChanged'));
293+
$this->assertSame(2, $model->updateBatch([$entity1, $entity2], 'id'));
294+
295+
$this->seeInDatabase('user', [
296+
'name' => 'Derek Jones',
297+
'country' => 'Greece',
298+
]);
299+
$this->seeInDatabase('user', [
300+
'name' => 'Chris Martin',
301+
'country' => 'Finland',
302+
]);
303+
}
304+
268305
public function testUpdateNoPrimaryKey(): void
269306
{
270307
$this->db->table('secondary')->insert([

user_guide_src/source/changelogs/v4.7.1.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Bugs Fixed
3333
**********
3434

3535
- **ContentSecurityPolicy:** Fixed a bug where ``generateNonces()`` produces corrupted JSON responses by replacing CSP nonce placeholders with unescaped double quotes. The method now automatically JSON-escapes nonce attributes when the response Content-Type is JSON.
36+
- **Model:** Fixed a bug where ``BaseModel::updateBatch()`` threw an exception when ``updateOnlyChanged`` was ``true`` and the index field value did not change.
3637
- **Session:** Fixed a bug in ``MemcachedHandler`` where the constructor incorrectly threw an exception when ``savePath`` was not empty.
3738

3839
See the repo's

utils/phpstan-baseline/missingType.property.neon

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -468,42 +468,42 @@ parameters:
468468
path: ../../tests/system/Models/UpdateModelTest.php
469469

470470
-
471-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$_options has no type specified\.$#'
471+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$_options has no type specified\.$#'
472472
count: 1
473473
path: ../../tests/system/Models/UpdateModelTest.php
474474

475475
-
476-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$country has no type specified\.$#'
476+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$country has no type specified\.$#'
477477
count: 1
478478
path: ../../tests/system/Models/UpdateModelTest.php
479479

480480
-
481-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$created_at has no type specified\.$#'
481+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$created_at has no type specified\.$#'
482482
count: 1
483483
path: ../../tests/system/Models/UpdateModelTest.php
484484

485485
-
486-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$deleted has no type specified\.$#'
486+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$deleted has no type specified\.$#'
487487
count: 1
488488
path: ../../tests/system/Models/UpdateModelTest.php
489489

490490
-
491-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$email has no type specified\.$#'
491+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$email has no type specified\.$#'
492492
count: 1
493493
path: ../../tests/system/Models/UpdateModelTest.php
494494

495495
-
496-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$id has no type specified\.$#'
496+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$id has no type specified\.$#'
497497
count: 1
498498
path: ../../tests/system/Models/UpdateModelTest.php
499499

500500
-
501-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$name has no type specified\.$#'
501+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$name has no type specified\.$#'
502502
count: 1
503503
path: ../../tests/system/Models/UpdateModelTest.php
504504

505505
-
506-
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:364\:\:\$updated_at has no type specified\.$#'
506+
message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:401\:\:\$updated_at has no type specified\.$#'
507507
count: 1
508508
path: ../../tests/system/Models/UpdateModelTest.php
509509

0 commit comments

Comments
 (0)