fix: convert DateTimeInterface objects to strings in Entity::toRawArray#10402
fix: convert DateTimeInterface objects to strings in Entity::toRawArray#10402gr8man wants to merge 2 commits 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.
|
I still don't think this approach is safe to merge. Timestamps can lose timezone information and therefore represent a different instant, while changes to the built-in casters do not protect custom Model casters expecting |
|
Thank you for the review and the detailed explanation. I completely agree with your assessment — attempting to fix this BC break by adding more special cases and relying on lossy string conversions during persistence is indeed fragile and too risky. Since there is no clean way to make this BC-safe in v4 without compromising the integrity of the data layer, I am closing this PR. We can accept the current behavior as the standard going forward and look forward to a more robust, non-lossy persistence architecture in v5. Thanks again for the feedback! |
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: