fix: convert DateTimeInterface objects to strings in Entity::toRawArray#10402
fix: convert DateTimeInterface objects to strings in Entity::toRawArray#10402gr8man wants to merge 1 commit into
Conversation
2346fdc to
d87f3a6
Compare
…n-recursive calls
4c3f524 to
3eceb0c
Compare
michalsn
left a comment
There was a problem hiding this comment.
This change affects all date-time attributes, not only Entity date fields, and will likely break model casting.
|
I agree, I do not know how to describe it. |
|
Thank you for the review @michalsn. I've traced through all code paths where
Model casting is not affected because the conversion layer ( |
michalsn
left a comment
There was a problem hiding this comment.
As I said, it breaks model casting.
public function testExtractEntityWithTimestampCast(): void
{
$converter = $this->createDataConverter([
'updated_at' => 'timestamp',
]);
$entity = new class extends Entity {
protected $dates = [];
};
$entity->injectRawData([
'updated_at' => Time::createFromTimestamp(1_784_092_299),
]);
$data = $converter->extract($entity);
$this->assertSame(1_784_092_299, $data['updated_at']);
}
public function testExtractEntityPreservesMicrosecondsWithDatetimeCast(): void
{
$converter = $this->createDataConverter(
['updated_at' => 'datetime[us]'],
[],
db_connect(),
);
$entity = new class extends Entity {
protected $dates = [];
};
$entity->injectRawData([
'updated_at' => Time::createFromFormat('Y-m-d H:i:s.u', '2026-07-15 00:00:01.123456'),
]);
$data = $converter->extract($entity);
$this->assertSame('2026-07-15 00:00:01.123456', $data['updated_at']);
}I don't believe it can be fixed safely in v4 without introducing BC breaks. It may need to be addressed as part of the Entity redesign in v5.
| if (! $value instanceof Time) { | ||
| if (is_string($value)) { | ||
| try { | ||
| $value = Time::parse($value); | ||
| } catch (Exception) { | ||
| self::invalidTypeValueError($value); | ||
| } | ||
| } | ||
|
|
||
| if (! $value instanceof DateTimeInterface) { |
There was a problem hiding this comment.
Now relative values such as "tomorrow" are accepted, which broadens the caster's behavior beyond this fix.
| // Convert DateTime objects to string for user-facing calls | ||
| if (! $recursive) { | ||
| $result = array_map(static function ($value) { | ||
| if ($value instanceof DateTimeInterface) { | ||
| return method_exists($value, '__toString') ? (string) $value : $value->format('Y-m-d H:i:s'); | ||
| } |
There was a problem hiding this comment.
Now every top-level DateTimeInterface value is converted to a string, regardless of whether the field is declared in Entity $dates or $casts.
| Bugs Fixed | ||
| ********** | ||
|
|
||
| - **Entity:** Fixed a bug where ``toRawArray()`` returned ``Time`` objects instead of ISO-8601 strings for date fields. |
There was a problem hiding this comment.
Even now, we don't return ISO-8601 strings.
Description
Supersedes #10207. Fixes #8302.
Entity::toRawArray()was returningTimeobjects instead of primitive strings for date fields. This PR fixes it by converting allDateTimeInterfaceobjects to strings in non-recursive mode.Unlike the original PR (#10207), this version safely handles native PHP
DateTimeobjects to prevent Fatal Errors (Object of class DateTime could not be converted to string), by falling back to$value->format('Y-m-d H:i:s')if__toString()is not implemented.The conversion only applies to non-recursive calls (
$recursive = false). The recursive mode preservesTimeobjects, which are then handled byBaseModel::timeToString()in the non-cast path and byDataConverter::toDataSource()in the cast path — so model saving/casting is unaffected.Changes:
DateTimeInterfaceobjects inEntity::toRawArray()(non-recursive only)DatetimeCast::set()to accept strings and anyDateTimeInterfacefor round-trip compatibilityChecklist: