From 3f17634fee780460e5e08636fe5b6f1b580f385f Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 5 Sep 2025 13:03:47 +0200 Subject: [PATCH 1/2] feat: make insertBatch() and updateBatch() respect model rules --- system/BaseModel.php | 55 +++------------------ tests/system/Models/UpdateModelTest.php | 4 +- user_guide_src/source/changelogs/v4.7.0.rst | 12 ++++- 3 files changed, 19 insertions(+), 52 deletions(-) diff --git a/system/BaseModel.php b/system/BaseModel.php index a1859a11b450..d3b3cd56bbdd 100644 --- a/system/BaseModel.php +++ b/system/BaseModel.php @@ -208,7 +208,8 @@ abstract class BaseModel protected bool $updateOnlyChanged = true; /** - * Rules used to validate data in insert(), update(), and save() methods. + * Rules used to validate data in insert(), update(), save(), + * insertBatch(), and updateBatch() methods. * * The array must match the format of data passed to the Validation * library. @@ -909,7 +910,7 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch if (is_array($set)) { foreach ($set as &$row) { - $row = $this->transformDataRowToArray($row); + $row = $this->transformDataToArray($row, 'insert'); // Validate every row. if (! $this->skipValidation && ! $this->validate($row)) { @@ -1036,7 +1037,7 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc { if (is_array($set)) { foreach ($set as &$row) { - $row = $this->transformDataRowToArray($row); + $row = $this->transformDataToArray($row, 'update'); // Validate data before saving. if (! $this->skipValidation && ! $this->validate($row)) { @@ -1667,52 +1668,6 @@ protected function trigger(string $event, array $eventData) return $eventData; } - /** - * If the model is using casts, this will convert the data - * in $row according to the rules defined in `$casts`. - * - * @param object|row_array|null $row Row data - * - * @return object|row_array|null Converted row data - * - * @used-by insertBatch() - * @used-by updateBatch() - * - * @throws ReflectionException - * @deprecated Since 4.6.4, temporary solution - will be removed in 4.7 - */ - protected function transformDataRowToArray(array|object|null $row): array|object|null - { - // If casts are used, convert the data first - if ($this->useCasts()) { - if (is_array($row)) { - $row = $this->converter->toDataSource($row); - } elseif ($row instanceof stdClass) { - $row = (array) $row; - $row = $this->converter->toDataSource($row); - } elseif ($row instanceof Entity) { - $row = $this->converter->extract($row); - } elseif (is_object($row)) { - $row = $this->converter->extract($row); - } - } elseif (is_object($row) && ! $row instanceof stdClass) { - // If $row is using a custom class with public or protected - // properties representing the collection elements, we need to grab - // them as an array. - $row = $this->objectToArray($row, false, true); - } - - // If it's still a stdClass, go ahead and convert to - // an array so doProtectFields and other model methods - // don't have to do special checks. - if (is_object($row)) { - $row = (array) $row; - } - - // Convert any Time instances to appropriate $dateFormat - return $this->timeToString($row); - } - /** * Sets the return type of the results to be as an associative array. * @@ -1830,7 +1785,9 @@ protected function objectToRawArray($object, bool $onlyChanged = true, bool $rec * @throws ReflectionException * * @used-by insert() + * @used-by insertBatch() * @used-by update() + * @used-by updateBatch() */ protected function transformDataToArray($row, string $type): array { diff --git a/tests/system/Models/UpdateModelTest.php b/tests/system/Models/UpdateModelTest.php index 2f01bb6a13f8..802896369aa5 100644 --- a/tests/system/Models/UpdateModelTest.php +++ b/tests/system/Models/UpdateModelTest.php @@ -249,7 +249,9 @@ public function testUpdateBatchWithEntity(): void $entity2->deleted = 0; $entity2->syncOriginal(); - $this->assertSame(2, $this->createModel(UserModel::class)->updateBatch([$entity1, $entity2], 'id')); + $model = $this->createModel(UserModel::class); + $this->setPrivateProperty($model, 'updateOnlyChanged', false); + $this->assertSame(2, $model->updateBatch([$entity1, $entity2], 'id')); } public function testUpdateNoPrimaryKey(): void diff --git a/user_guide_src/source/changelogs/v4.7.0.rst b/user_guide_src/source/changelogs/v4.7.0.rst index 7defca8f03e7..cd693fca7dbc 100644 --- a/user_guide_src/source/changelogs/v4.7.0.rst +++ b/user_guide_src/source/changelogs/v4.7.0.rst @@ -33,6 +33,13 @@ update it to use double braces: ``regex_match[/^{{placeholder}}$/]``. This change was introduced to avoid ambiguity with regular expression syntax, where single curly braces (e.g., ``{1,3}``) are used for quantifiers. +BaseModel +--------- + +The ``insertBatch()`` and ``updateBatch()`` methods now honor model settings like +``updateOnlyChanged`` and ``allowEmptyInserts``. This change ensures consistent handling +across all insert/update operations. + Interface Changes ================= @@ -45,6 +52,7 @@ Removed Deprecated Items ======================== - **Text Helper:** The deprecated types in ``random_string()`` function: ``basic``, ``md5``, and ``sha1`` has been removed. +- **BaseModel:** The deprecated method ``transformDataRowToArray()`` has been removed. ************ Enhancements @@ -55,7 +63,7 @@ Libraries - **CURLRequest:** Added ``shareConnection`` config item to change default share connection. - **CURLRequest:** Added ``dns_cache_timeout`` option to change default DNS cache timeout. -- **CURLRequest:** Added ``fresh_connect`` options to enable/disabled request fresh connection. +- **CURLRequest:** Added ``fresh_connect`` options to enable/disable request fresh connection. - **Email:** Added support for choosing the SMTP authorization method. You can change it via ``Config\Email::$SMTPAuthMethod`` option. - **Image:** The ``ImageMagickHandler`` has been rewritten to rely solely on the PHP ``imagick`` extension. - **Image:** Added ``ImageMagickHandler::clearMetadata()`` method to remove image metadata for privacy protection. @@ -70,7 +78,7 @@ Testing Database ======== -- **Exception Logging:** All DB drivers now log database exceptions uniformly. Previously, each driver has its own log format. +- **Exception Logging:** All DB drivers now log database exceptions uniformly. Previously, each driver had its own log format. Query Builder ------------- From 9918947cf366743efedc81311d99b41021e866f8 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 5 Sep 2025 14:53:21 +0200 Subject: [PATCH 2/2] update phpstan baseline --- utils/phpstan-baseline/missingType.property.neon | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/utils/phpstan-baseline/missingType.property.neon b/utils/phpstan-baseline/missingType.property.neon index c124b9b496bb..48a8f1e1f48d 100644 --- a/utils/phpstan-baseline/missingType.property.neon +++ b/utils/phpstan-baseline/missingType.property.neon @@ -473,42 +473,42 @@ parameters: path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$_options has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$_options has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$country has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$country has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$created_at has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$created_at has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$deleted has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$deleted has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$email has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$email has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$id has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$id has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$name has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$name has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php - - message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:351\:\:\$updated_at has no type specified\.$#' + message: '#^Property CodeIgniter\\Entity\\Entity@anonymous/tests/system/Models/UpdateModelTest\.php\:353\:\:\$updated_at has no type specified\.$#' count: 1 path: ../../tests/system/Models/UpdateModelTest.php