Skip to content

Commit 1504fb7

Browse files
committed
fix: toRawArray() converts DateTime objects to strings
This squashes several fixes regarding Entity::toRawArray returning string values instead of Time objects, addressing issue #8302. It also updates DatetimeCast to handle string values to prevent DataConverter crashes, and applies phpstan, rector, and cs-fix formatting.
1 parent 5b46a44 commit 1504fb7

7 files changed

Lines changed: 198 additions & 10 deletions

File tree

rector.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
*/
1313

1414
use Rector\Caching\ValueObject\Storage\FileCacheStorage;
15-
use Rector\CodeQuality\Rector\BooleanNot\NegatedAndsToPositiveOrsRector;
1615
use Rector\CodeQuality\Rector\Empty_\SimplifyEmptyCheckOnEmptyArrayRector;
1716
use Rector\CodeQuality\Rector\FuncCall\CompactToVariablesRector;
1817
use Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector;
@@ -176,7 +175,6 @@
176175
],
177176

178177
// to be applied in separate PRs to ease review
179-
NegatedAndsToPositiveOrsRector::class,
180178
])
181179
// auto import fully qualified class names
182180
->withImportNames()

system/DataCaster/Cast/DatetimeCast.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use CodeIgniter\Database\BaseConnection;
1717
use CodeIgniter\Exceptions\InvalidArgumentException;
1818
use CodeIgniter\I18n\Time;
19+
use DateTimeInterface;
20+
use Exception;
1921

2022
/**
2123
* Class DatetimeCast
@@ -51,7 +53,15 @@ public static function set(
5153
array $params = [],
5254
?object $helper = null,
5355
): string {
54-
if (! $value instanceof Time) {
56+
if (is_string($value)) {
57+
try {
58+
$value = Time::parse($value);
59+
} catch (Exception) {
60+
self::invalidTypeValueError($value);
61+
}
62+
}
63+
64+
if (! $value instanceof DateTimeInterface) {
5565
self::invalidTypeValueError($value);
5666
}
5767

system/Entity/Entity.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recu
233233
public function toRawArray(bool $onlyChanged = false, bool $recursive = false): array
234234
{
235235
$convert = static function ($value) use (&$convert, $recursive) {
236+
// Always convert DateTime objects to string for raw output
237+
if ($value instanceof DateTimeInterface) {
238+
return method_exists($value, '__toString') ? (string) $value : $value->format('Y-m-d H:i:s');
239+
}
240+
236241
if (! $recursive) {
237242
return $value;
238243
}
@@ -261,9 +266,7 @@ public function toRawArray(bool $onlyChanged = false, bool $recursive = false):
261266

262267
// When returning everything
263268
if (! $onlyChanged) {
264-
return $recursive
265-
? array_map($convert, $this->attributes)
266-
: $this->attributes;
269+
return array_map($convert, $this->attributes);
267270
}
268271

269272
// When filtering by changed values only
@@ -335,7 +338,7 @@ public function toRawArray(bool $onlyChanged = false, bool $recursive = false):
335338
}
336339

337340
// non-recursive changed value
338-
$return[$key] = $value;
341+
$return[$key] = $convert($value);
339342
}
340343

341344
return $return;

tests/system/Entity/EntityTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,58 @@ public function testDateMutationTimeToTime(): void
367367
$this->assertCloseEnoughString($dt->format('Y-m-d H:i:s'), $time->format('Y-m-d H:i:s'));
368368
}
369369

370+
public function testToRawArrayConvertsDateTimeToString(): void
371+
{
372+
$entity = new class () extends Entity {
373+
protected $attributes = [
374+
'created_at' => null,
375+
'updated_at' => null,
376+
];
377+
protected $original = [
378+
'created_at' => null,
379+
'updated_at' => null,
380+
];
381+
};
382+
383+
$entity->created_at = '2023-12-12 12:12:12';
384+
$entity->updated_at = '2023-12-13 13:13:13';
385+
386+
$raw = $entity->toRawArray();
387+
388+
// toRawArray() should return primitive types, not objects
389+
$this->assertIsString($raw['created_at']);
390+
$this->assertSame('2023-12-12 12:12:12', $raw['created_at']);
391+
$this->assertIsString($raw['updated_at']);
392+
$this->assertSame('2023-12-13 13:13:13', $raw['updated_at']);
393+
394+
// Attributes themselves should still contain Time objects
395+
$attrs = $this->getPrivateProperty($entity, 'attributes');
396+
$this->assertInstanceOf(Time::class, $attrs['created_at']);
397+
$this->assertInstanceOf(Time::class, $attrs['updated_at']);
398+
399+
// toArray() should still return Time objects (no regression)
400+
$array = $entity->toArray();
401+
$this->assertInstanceOf(Time::class, $array['created_at']);
402+
$this->assertInstanceOf(Time::class, $array['updated_at']);
403+
}
404+
405+
public function testToRawArrayConvertsStandardDateTimeToString(): void
406+
{
407+
$entity = new class () extends Entity {
408+
protected $attributes = [
409+
'my_date' => null,
410+
];
411+
protected $dates = [];
412+
};
413+
414+
$entity->my_date = new DateTime('2023-12-12 12:12:12');
415+
416+
$raw = $entity->toRawArray();
417+
418+
$this->assertIsString($raw['my_date']);
419+
$this->assertSame('2023-12-12 12:12:12', $raw['my_date']);
420+
}
421+
370422
public function testCastInteger(): void
371423
{
372424
$entity = $this->getCastEntity();

utils/phpstan-baseline/argument.type.neon

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
# total 68 errors
1+
# total 228 errors
22

33
parameters:
44
ignoreErrors:
5+
-
6+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\RedirectResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
7+
count: 1
8+
path: ../../system/Config/Services.php
9+
10+
-
11+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
12+
count: 2
13+
path: ../../system/Config/Services.php
14+
515
-
616
message: '#^Parameter \#3 \.\.\.\$arrays of function array_map expects array, int\|string given\.$#'
717
count: 1
@@ -22,6 +32,26 @@ parameters:
2232
count: 1
2333
path: ../../system/Database/SQLite3/Builder.php
2434

35+
-
36+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
37+
count: 1
38+
path: ../../system/HTTP/CURLRequest.php
39+
40+
-
41+
message: '#^Parameter \#1 \$config of method CodeIgniter\\HTTP\\Response\:\:__construct\(\) expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
42+
count: 1
43+
path: ../../system/HTTP/DownloadResponse.php
44+
45+
-
46+
message: '#^Parameter \#1 \$config of class CodeIgniter\\Test\\Mock\\MockResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
47+
count: 2
48+
path: ../../tests/system/API/ResponseTraitTest.php
49+
50+
-
51+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
52+
count: 19
53+
path: ../../tests/system/Cache/ResponseCacheTest.php
54+
2555
-
2656
message: '#^Parameter \#2 \$to of method CodeIgniter\\Router\\RouteCollection\:\:add\(\) expects array\|\(Closure\(mixed \.\.\.\)\: \(CodeIgniter\\HTTP\\ResponseInterface\|string\|void\)\)\|string, Closure\(mixed\)\: CodeIgniter\\HTTP\\ResponseInterface given\.$#'
2757
count: 1
@@ -42,6 +72,11 @@ parameters:
4272
count: 1
4373
path: ../../tests/system/CodeIgniterTest.php
4474

75+
-
76+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\RedirectResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
77+
count: 3
78+
path: ../../tests/system/CommonFunctionsTest.php
79+
4580
-
4681
message: '#^Parameter \#1 \$expected of method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) expects class\-string\<Config\\TestRegistrar\>, string given\.$#'
4782
count: 1
@@ -52,6 +87,16 @@ parameters:
5287
count: 2
5388
path: ../../tests/system/Config/FactoriesTest.php
5489

90+
-
91+
message: '#^Parameter \#1 \$config of class CodeIgniter\\Test\\Mock\\MockResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
92+
count: 7
93+
path: ../../tests/system/Config/ServicesTest.php
94+
95+
-
96+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
97+
count: 1
98+
path: ../../tests/system/ControllerTest.php
99+
55100
-
56101
message: '#^Parameter \#1 \$from of method CodeIgniter\\Database\\BaseBuilder\:\:from\(\) expects array\|string, null given\.$#'
57102
count: 1
@@ -97,16 +142,66 @@ parameters:
97142
count: 1
98143
path: ../../tests/system/Debug/TimerTest.php
99144

145+
-
146+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\RedirectResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
147+
count: 1
148+
path: ../../tests/system/Filters/PageCacheTest.php
149+
150+
-
151+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
152+
count: 10
153+
path: ../../tests/system/Filters/PageCacheTest.php
154+
155+
-
156+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
157+
count: 1
158+
path: ../../tests/system/HTTP/CURLRequestShareOptionsTest.php
159+
160+
-
161+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
162+
count: 1
163+
path: ../../tests/system/HTTP/CURLRequestTest.php
164+
165+
-
166+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
167+
count: 6
168+
path: ../../tests/system/HTTP/ContentSecurityPolicyTest.php
169+
100170
-
101171
message: '#^Parameter \#1 \$array of method CodeIgniter\\Superglobals\:\:setServerArray\(\) expects array\<string, array\<mixed\>\|float\|int\|string\>, array\<string, string\|null\> given\.$#'
102172
count: 1
103173
path: ../../tests/system/HTTP/MessageTest.php
104174

175+
-
176+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\RedirectResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
177+
count: 15
178+
path: ../../tests/system/HTTP/RedirectResponseTest.php
179+
180+
-
181+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
182+
count: 27
183+
path: ../../tests/system/HTTP/ResponseCookieTest.php
184+
185+
-
186+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
187+
count: 5
188+
path: ../../tests/system/HTTP/ResponseSendTest.php
189+
105190
-
106191
message: '#^Parameter \#3 \$expire of method CodeIgniter\\HTTP\\Response\:\:setCookie\(\) expects int, string given\.$#'
107192
count: 1
108193
path: ../../tests/system/HTTP/ResponseSendTest.php
109194

195+
-
196+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
197+
count: 43
198+
path: ../../tests/system/HTTP/ResponseTest.php
199+
200+
-
201+
message: '#^Parameter \#1 \$config of class CodeIgniter\\Test\\Mock\\MockResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
202+
count: 2
203+
path: ../../tests/system/HTTP/ResponseTest.php
204+
110205
-
111206
message: '#^Parameter \#1 \$data of method CodeIgniter\\HTTP\\Message\:\:setBody\(\) expects string, array\<string, list\<int\>\|string\> given\.$#'
112207
count: 2
@@ -122,6 +217,11 @@ parameters:
122217
count: 1
123218
path: ../../tests/system/HTTP/SiteURITest.php
124219

220+
-
221+
message: '#^Parameter \#1 \$config of class CodeIgniter\\Test\\Mock\\MockResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
222+
count: 1
223+
path: ../../tests/system/Helpers/CookieHelperTest.php
224+
125225
-
126226
message: '#^Parameter \#2 \$value of function form_hidden expects array\|string, null given\.$#'
127227
count: 1
@@ -152,11 +252,21 @@ parameters:
152252
count: 2
153253
path: ../../tests/system/Images/GDHandlerTest.php
154254

255+
-
256+
message: '#^Parameter \#1 \$config of class CodeIgniter\\Test\\Mock\\MockResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
257+
count: 1
258+
path: ../../tests/system/Log/Handlers/ChromeLoggerHandlerTest.php
259+
155260
-
156261
message: '#^Parameter \#2 \$message of method CodeIgniter\\Log\\Handlers\\ChromeLoggerHandler\:\:handle\(\) expects string, stdClass given\.$#'
157262
count: 1
158263
path: ../../tests/system/Log/Handlers/ChromeLoggerHandlerTest.php
159264

265+
-
266+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
267+
count: 2
268+
path: ../../tests/system/RESTful/ResourceControllerTest.php
269+
160270
-
161271
message: '#^Parameter \#1 \$format of method CodeIgniter\\RESTful\\ResourceController\:\:setFormat\(\) expects ''json''\|''xml'', ''Nonsense'' given\.$#'
162272
count: 1
@@ -172,6 +282,11 @@ parameters:
172282
count: 1
173283
path: ../../tests/system/Test/FeatureTestTraitTest.php
174284

285+
-
286+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
287+
count: 2
288+
path: ../../tests/system/Test/TestCaseEmissionsTest.php
289+
175290
-
176291
message: '#^Parameter \#1 \$body of method CodeIgniter\\HTTP\\Response\:\:setJSON\(\) expects array\|object\|string, false given\.$#'
177292
count: 1
@@ -182,6 +297,16 @@ parameters:
182297
count: 1
183298
path: ../../tests/system/Test/TestResponseTest.php
184299

300+
-
301+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\RedirectResponse constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
302+
count: 5
303+
path: ../../tests/system/Test/TestResponseTest.php
304+
305+
-
306+
message: '#^Parameter \#1 \$config of class CodeIgniter\\HTTP\\Response constructor expects CodeIgniter\\HTTP\\App, Config\\App given\.$#'
307+
count: 1
308+
path: ../../tests/system/Test/TestResponseTest.php
309+
185310
-
186311
message: '#^Parameter \#3 \$errors of method CodeIgniter\\Validation\\Validation\:\:check\(\) expects list\<string\>, array\{is_numeric\: ''Nope\. Not a number\.''\} given\.$#'
187312
count: 1

utils/phpstan-baseline/empty.notAllowed.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# total 212 errors
1+
# total 205 errors
22

33
parameters:
44
ignoreErrors:

utils/phpstan-baseline/loader.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# total 1819 errors
1+
# total 1972 errors
22

33
includes:
44
- argument.type.neon

0 commit comments

Comments
 (0)