Skip to content

Commit ea9612f

Browse files
authored
fix: Casting in insertBatch and updateBatch methods. (#9698)
* fix: Casting in insertBatch and updateBatch methods. * Fixed coding styles and done requested changes in BaseModel.php * Changed assertion to seeInDatabase function * Changed assertion to seeInDatabase function in testUpdateBatchWithCasts * Added comment to ignore argument.type by phpstan. * Attempt to fix CPD by introducing a common casting function. * Ran composer phpstan:baseline * Replaced "casting only" function to more generic one.
1 parent 63e9ebd commit ea9612f

File tree

5 files changed

+160
-71
lines changed

5 files changed

+160
-71
lines changed

system/BaseModel.php

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -909,22 +909,7 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch
909909

910910
if (is_array($set)) {
911911
foreach ($set as &$row) {
912-
// If $row is using a custom class with public or protected
913-
// properties representing the collection elements, we need to grab
914-
// them as an array.
915-
if (is_object($row) && ! $row instanceof stdClass) {
916-
$row = $this->objectToArray($row, false, true);
917-
}
918-
919-
// If it's still a stdClass, go ahead and convert to
920-
// an array so doProtectFields and other model methods
921-
// don't have to do special checks.
922-
if (is_object($row)) {
923-
$row = (array) $row;
924-
}
925-
926-
// Convert any Time instances to appropriate $dateFormat
927-
$row = $this->timeToString($row);
912+
$row = $this->transformDataRowToArray($row);
928913

929914
// Validate every row.
930915
if (! $this->skipValidation && ! $this->validate($row)) {
@@ -1051,21 +1036,7 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc
10511036
{
10521037
if (is_array($set)) {
10531038
foreach ($set as &$row) {
1054-
// If $row is using a custom class with public or protected
1055-
// properties representing the collection elements, we need to grab
1056-
// them as an array.
1057-
if (is_object($row) && ! $row instanceof stdClass) {
1058-
// For updates the index field is needed even if it is not changed.
1059-
// So set $onlyChanged to false.
1060-
$row = $this->objectToArray($row, false, true);
1061-
}
1062-
1063-
// If it's still a stdClass, go ahead and convert to
1064-
// an array so doProtectFields and other model methods
1065-
// don't have to do special checks.
1066-
if (is_object($row)) {
1067-
$row = (array) $row;
1068-
}
1039+
$row = $this->transformDataRowToArray($row);
10691040

10701041
// Validate data before saving.
10711042
if (! $this->skipValidation && ! $this->validate($row)) {
@@ -1696,6 +1667,52 @@ protected function trigger(string $event, array $eventData)
16961667
return $eventData;
16971668
}
16981669

1670+
/**
1671+
* If the model is using casts, this will convert the data
1672+
* in $row according to the rules defined in `$casts`.
1673+
*
1674+
* @param object|row_array|null $row Row data
1675+
*
1676+
* @return object|row_array|null Converted row data
1677+
*
1678+
* @used-by insertBatch()
1679+
* @used-by updateBatch()
1680+
*
1681+
* @throws ReflectionException
1682+
* @deprecated Since 4.6.4, temporary solution - will be removed in 4.7
1683+
*/
1684+
protected function transformDataRowToArray(array|object|null $row): array|object|null
1685+
{
1686+
// If casts are used, convert the data first
1687+
if ($this->useCasts()) {
1688+
if (is_array($row)) {
1689+
$row = $this->converter->toDataSource($row);
1690+
} elseif ($row instanceof stdClass) {
1691+
$row = (array) $row;
1692+
$row = $this->converter->toDataSource($row);
1693+
} elseif ($row instanceof Entity) {
1694+
$row = $this->converter->extract($row);
1695+
} elseif (is_object($row)) {
1696+
$row = $this->converter->extract($row);
1697+
}
1698+
} elseif (is_object($row) && ! $row instanceof stdClass) {
1699+
// If $row is using a custom class with public or protected
1700+
// properties representing the collection elements, we need to grab
1701+
// them as an array.
1702+
$row = $this->objectToArray($row, false, true);
1703+
}
1704+
1705+
// If it's still a stdClass, go ahead and convert to
1706+
// an array so doProtectFields and other model methods
1707+
// don't have to do special checks.
1708+
if (is_object($row)) {
1709+
$row = (array) $row;
1710+
}
1711+
1712+
// Convert any Time instances to appropriate $dateFormat
1713+
return $this->timeToString($row);
1714+
}
1715+
16991716
/**
17001717
* Sets the return type of the results to be as an associative array.
17011718
*

tests/system/Models/InsertModelTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use stdClass;
2323
use Tests\Support\Entity\User;
2424
use Tests\Support\Models\JobModel;
25+
use Tests\Support\Models\UserCastsTimestampModel;
2526
use Tests\Support\Models\UserModel;
2627
use Tests\Support\Models\UserObjModel;
2728
use Tests\Support\Models\WithoutAutoIncrementModel;
@@ -376,4 +377,34 @@ public function testInsertWithDefaultValue(): void
376377
$id = $this->model->getInsertID();
377378
$this->assertSame($entity->country, $this->model->find($id)->country);
378379
}
380+
381+
public function testInsertBatchWithCasts(): void
382+
{
383+
$userData = [
384+
[
385+
'name' => 'Smriti',
386+
'email' => [
387+
'personal' => '[email protected]',
388+
'work' => '[email protected]',
389+
],
390+
'country' => 'India',
391+
],
392+
[
393+
'name' => 'Rahul',
394+
'email' => [
395+
'personal' => '[email protected]',
396+
'work' => '[email protected]',
397+
],
398+
'country' => 'India',
399+
],
400+
];
401+
$this->createModel(UserCastsTimestampModel::class);
402+
403+
$numRows = $this->model->insertBatch($userData); // @phpstan-ignore argument.type
404+
405+
$this->assertSame(2, $numRows);
406+
407+
$this->seeInDatabase('user', ['email' => json_encode($userData[0]['email'])]);
408+
$this->seeInDatabase('user', ['email' => json_encode($userData[1]['email'])]);
409+
}
379410
}

tests/system/Models/UpdateModelTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Tests\Support\Models\EventModel;
2727
use Tests\Support\Models\JobModel;
2828
use Tests\Support\Models\SecondaryModel;
29+
use Tests\Support\Models\UserCastsTimestampModel;
2930
use Tests\Support\Models\UserModel;
3031
use Tests\Support\Models\UserTimestampModel;
3132
use Tests\Support\Models\UUIDPkeyModel;
@@ -602,4 +603,43 @@ public function testUpdateEntityUpdateOnlyChangedFalse(): void
602603
$this->assertTrue($result);
603604
$this->assertNotSame($updateAtBefore, $updateAtAfter);
604605
}
606+
607+
public function testUpdateBatchWithCasts(): void
608+
{
609+
$this->createModel(UserCastsTimestampModel::class);
610+
611+
$rows = $this->db->table('user')->limit(2)->orderBy('id', 'asc')->get()->getResultArray();
612+
613+
$this->assertNotEmpty($rows);
614+
$this->assertCount(2, $rows);
615+
616+
$row1 = $rows[0];
617+
$row2 = $rows[1];
618+
619+
$this->assertNotNull($row1);
620+
$this->assertNotNull($row2);
621+
622+
$updateData = [
623+
[
624+
'id' => $row1['id'],
625+
'email' => [
626+
'personal' => '[email protected]',
627+
'work' => '[email protected]',
628+
],
629+
],
630+
[
631+
'id' => $row2['id'],
632+
'email' => [
633+
'personal' => '[email protected]',
634+
'work' => '[email protected]',
635+
],
636+
],
637+
];
638+
639+
$numRows = $this->model->updateBatch($updateData, 'id'); // @phpstan-ignore argument.type
640+
$this->assertSame(2, $numRows);
641+
642+
$this->seeInDatabase('user', ['email' => json_encode($updateData[0]['email'])]);
643+
$this->seeInDatabase('user', ['email' => json_encode($updateData[1]['email'])]);
644+
}
605645
}

user_guide_src/source/changelogs/v4.6.4.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Bugs Fixed
3434
- **Database:** Fixed a bug in ``Connection::getFieldData()`` for ``SQLSRV`` and ``OCI8`` where extra characters were returned in column default values (specific to those handlers), instead of following the convention used by other drivers.
3535
- **Forge:** Fixed a bug in ``Postgre`` and ``SQLSRV`` where changing a column's default value using ``Forge::modifyColumn()`` method produced incorrect SQL syntax.
3636
- **Model:** Fixed a bug in ``Model::replace()`` where ``created_at`` field (when available) wasn't set correctly.
37+
- **Model:** Fixed a bug in ``Model::insertBatch()`` and ``Model::updateBatch()`` where casts were not applied to inserted or updated values.
3738

3839
See the repo's
3940
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_

0 commit comments

Comments
 (0)