|
19 | 19 | use CodeIgniter\Database\Exceptions\DatabaseException; |
20 | 20 | use CodeIgniter\Database\Exceptions\DataException; |
21 | 21 | use CodeIgniter\Database\Query; |
| 22 | +use CodeIgniter\Database\RawSql; |
22 | 23 | use CodeIgniter\DataCaster\Cast\CastInterface; |
23 | 24 | use CodeIgniter\DataConverter\DataConverter; |
24 | 25 | use CodeIgniter\Entity\Cast\CastInterface as EntityCastInterface; |
@@ -780,6 +781,67 @@ public function getInsertID() |
780 | 781 | return is_numeric($this->insertID) ? (int) $this->insertID : $this->insertID; |
781 | 782 | } |
782 | 783 |
|
| 784 | + /** |
| 785 | + * Validates that the primary key values are valid for update/delete/insert operations. |
| 786 | + * Throws exception if invalid. |
| 787 | + * |
| 788 | + * @param bool $allowArray Whether to allow array of IDs (true for update/delete, false for insert) |
| 789 | + * |
| 790 | + * @phpstan-assert non-zero-int|non-empty-list<int|string>|RawSql|non-falsy-string $id |
| 791 | + * @throws InvalidArgumentException |
| 792 | + */ |
| 793 | + protected function validateID(mixed $id, bool $allowArray = true): void |
| 794 | + { |
| 795 | + if (is_array($id)) { |
| 796 | + // Check if arrays are allowed |
| 797 | + if (! $allowArray) { |
| 798 | + throw new InvalidArgumentException( |
| 799 | + 'Invalid primary key: only a single value is allowed, not an array.', |
| 800 | + ); |
| 801 | + } |
| 802 | + |
| 803 | + // Check for empty array |
| 804 | + if ($id === []) { |
| 805 | + throw new InvalidArgumentException('Invalid primary key: cannot be an empty array.'); |
| 806 | + } |
| 807 | + |
| 808 | + // Validate each ID in the array recursively |
| 809 | + foreach ($id as $key => $valueId) { |
| 810 | + if (is_array($valueId)) { |
| 811 | + throw new InvalidArgumentException( |
| 812 | + sprintf('Invalid primary key at index %s: nested arrays are not allowed.', $key), |
| 813 | + ); |
| 814 | + } |
| 815 | + |
| 816 | + // Recursive call for each value (single values only in recursion) |
| 817 | + $this->validateID($valueId, false); |
| 818 | + } |
| 819 | + |
| 820 | + return; |
| 821 | + } |
| 822 | + |
| 823 | + // Allow RawSql objects for complex scenarios |
| 824 | + if ($id instanceof RawSql) { |
| 825 | + return; |
| 826 | + } |
| 827 | + |
| 828 | + // Check for invalid single values |
| 829 | + if (in_array($id, [null, 0, '0', '', true, false], true)) { |
| 830 | + $type = is_bool($id) ? 'boolean ' . var_export($id, true) : var_export($id, true); |
| 831 | + |
| 832 | + throw new InvalidArgumentException( |
| 833 | + sprintf('Invalid primary key: %s is not allowed.', $type), |
| 834 | + ); |
| 835 | + } |
| 836 | + |
| 837 | + // Only allow int and string at this point |
| 838 | + if (! is_int($id) && ! is_string($id)) { |
| 839 | + throw new InvalidArgumentException( |
| 840 | + sprintf('Invalid primary key: must be int or string, %s given.', get_debug_type($id)), |
| 841 | + ); |
| 842 | + } |
| 843 | + } |
| 844 | + |
783 | 845 | /** |
784 | 846 | * Inserts data into the database. If an object is provided, |
785 | 847 | * it will attempt to convert it to an array. |
@@ -962,19 +1024,19 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch |
962 | 1024 | * Updates a single record in the database. If an object is provided, |
963 | 1025 | * it will attempt to convert it into an array. |
964 | 1026 | * |
965 | | - * @param int|list<int|string>|string|null $id |
966 | | - * @param object|row_array|null $row |
| 1027 | + * @param int|list<int|string>|RawSql|string|null $id |
| 1028 | + * @param object|row_array|null $row |
967 | 1029 | * |
968 | 1030 | * @throws ReflectionException |
969 | 1031 | */ |
970 | 1032 | public function update($id = null, $row = null): bool |
971 | 1033 | { |
972 | | - if (is_bool($id)) { |
973 | | - throw new InvalidArgumentException('update(): argument #1 ($id) should not be boolean.'); |
974 | | - } |
| 1034 | + if ($id !== null) { |
| 1035 | + if (! is_array($id)) { |
| 1036 | + $id = [$id]; |
| 1037 | + } |
975 | 1038 |
|
976 | | - if (is_numeric($id) || is_string($id)) { |
977 | | - $id = [$id]; |
| 1039 | + $this->validateID($id); |
978 | 1040 | } |
979 | 1041 |
|
980 | 1042 | $row = $this->transformDataToArray($row, 'update'); |
@@ -1091,21 +1153,21 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc |
1091 | 1153 | /** |
1092 | 1154 | * Deletes a single record from the database where $id matches. |
1093 | 1155 | * |
1094 | | - * @param int|list<int|string>|string|null $id The rows primary key(s). |
1095 | | - * @param bool $purge Allows overriding the soft deletes setting. |
| 1156 | + * @param int|list<int|string>|RawSql|string|null $id The rows primary key(s). |
| 1157 | + * @param bool $purge Allows overriding the soft deletes setting. |
1096 | 1158 | * |
1097 | 1159 | * @return bool|string Returns a SQL string if in test mode. |
1098 | 1160 | * |
1099 | 1161 | * @throws DatabaseException |
1100 | 1162 | */ |
1101 | 1163 | public function delete($id = null, bool $purge = false) |
1102 | 1164 | { |
1103 | | - if (is_bool($id)) { |
1104 | | - throw new InvalidArgumentException('delete(): argument #1 ($id) should not be boolean.'); |
1105 | | - } |
| 1165 | + if ($id !== null) { |
| 1166 | + if (! is_array($id)) { |
| 1167 | + $id = [$id]; |
| 1168 | + } |
1106 | 1169 |
|
1107 | | - if (! in_array($id, [null, 0, '0'], true) && (is_numeric($id) || is_string($id))) { |
1108 | | - $id = [$id]; |
| 1170 | + $this->validateID($id); |
1109 | 1171 | } |
1110 | 1172 |
|
1111 | 1173 | $eventData = [ |
|
0 commit comments