Skip to content

PHPORM-148 Fix null in datetime fields and reset time in date field with custom format #2741

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.

## [4.1.3] - unreleased
## [4.1.3] - 2024-03-05

* Fix the timezone of `datetime` fields when they are read from the database by @GromNaN in [#2739](https://github.com/mongodb/laravel-mongodb/pull/2739)
* Fix the timezone of `datetime` fields when they are read from the database. By @GromNaN in [#2739](https://github.com/mongodb/laravel-mongodb/pull/2739)
* Fix support for null values in `datetime` and reset `date` fields with custom format to the start of the day. By @GromNaN in [#2741](https://github.com/mongodb/laravel-mongodb/pull/2741)

## [4.1.2] - 2024-02-22

Expand Down
18 changes: 3 additions & 15 deletions src/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,10 @@ protected function transformModelValue($key, $value)
if ($this->hasCast($key) && $value instanceof CarbonInterface) {
$value->settings(array_merge($value->getSettings(), ['toStringFormat' => $this->getDateFormat()]));

// "date" cast resets the time to 00:00:00.
$castType = $this->getCasts()[$key];
if ($this->isCustomDateTimeCast($castType) && str_starts_with($castType, 'date:')) {
$value->startOfDay();
if (str_starts_with($castType, 'date:') || str_starts_with($castType, 'immutable_date:')) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is no method to check a "date" cast, as opposed to "datetime" cast that keep the time.

$value = $value->startOfDay();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$value is reassigned because of immutable object.

}
}

Expand Down Expand Up @@ -315,19 +316,6 @@ protected function fromDecimal($value, $decimals)
return new Decimal128($this->asDecimal($value, $decimals));
}

/** @inheritdoc */
protected function castAttribute($key, $value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the removal of this method what actually fixes the null-handling issue cited in PHPORM-148?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual fix is more in transformModelValue. It seemed to me that this code was an incomplete attempt to remove the time from immutable_date with custom format; which failed with null values.

{
$castType = $this->getCastType($key);

return match ($castType) {
'immutable_custom_datetime','immutable_datetime' => str_starts_with($this->getCasts()[$key], 'immutable_date:') ?
$this->asDate($value)->toImmutable() :
$this->asDateTime($value)->toImmutable(),
Comment on lines -324 to -326
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast types immutable_custom_datetime and immutable_datetime are already supported by the parent method: HasAttribute::castAttribute()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does $this->getCastType($key) differ from $this->getCasts()[$key]? I don't understand why this would match on immutable_custom_datetime or immutable_datetime but then see a immutable_date: prefix from the latter method.

I did note that the function you linked already handles:

case 'date':
    return $this->asDate($value);
...
case 'immutable_date':
    return $this->asDate($value)->toImmutable();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • $this->getCasts()[$key] is what is defined in the $casts property (date:j.n.Y H:i).
  • $this->getCastType($key) is a classification (custom_datetime)

default => parent::castAttribute($key, $value)
};
}

/** @inheritdoc */
public function attributesToArray()
{
Expand Down
24 changes: 24 additions & 0 deletions tests/Casts/DateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ public function testDate(): void
self::assertInstanceOf(Carbon::class, $refetchedModel->dateField);
self::assertInstanceOf(UTCDateTime::class, $model->getRawOriginal('dateField'));
self::assertEquals(now()->subDay()->startOfDay()->format('Y-m-d H:i:s'), (string) $refetchedModel->dateField);

$model = Casting::query()->create();
$this->assertNull($model->dateField);

$model->update(['dateField' => null]);
$this->assertNull($model->dateField);
}

public function testDateAsString(): void
Expand Down Expand Up @@ -84,6 +90,12 @@ public function testDateWithCustomFormat(): void

self::assertInstanceOf(Carbon::class, $model->dateWithFormatField);
self::assertEquals(now()->startOfDay()->subDay()->format('j.n.Y H:i'), (string) $model->dateWithFormatField);

$model = Casting::query()->create();
$this->assertNull($model->dateWithFormatField);

$model->update(['dateWithFormatField' => null]);
$this->assertNull($model->dateWithFormatField);
}

public function testImmutableDate(): void
Expand All @@ -105,6 +117,12 @@ public function testImmutableDate(): void
Carbon::createFromTimestamp(1698577443)->subDay()->startOfDay()->format('Y-m-d H:i:s'),
(string) $model->immutableDateField,
);

$model = Casting::query()->create();
$this->assertNull($model->immutableDateField);

$model->update(['immutableDateField' => null]);
$this->assertNull($model->immutableDateField);
}

public function testImmutableDateWithCustomFormat(): void
Expand All @@ -126,5 +144,11 @@ public function testImmutableDateWithCustomFormat(): void
Carbon::createFromTimestamp(1698577443)->subDay()->startOfDay()->format('j.n.Y H:i'),
(string) $model->immutableDateWithFormatField,
);

$model = Casting::query()->create();
$this->assertNull($model->immutableDateWithFormatField);

$model->update(['immutableDateWithFormatField' => null]);
$this->assertNull($model->immutableDateWithFormatField);
}
}
24 changes: 24 additions & 0 deletions tests/Casts/DatetimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public function testDatetime(): void
self::assertInstanceOf(Carbon::class, $model->datetimeField);
self::assertInstanceOf(UTCDateTime::class, $model->getRawOriginal('datetimeField'));
self::assertEquals(now()->subDay()->format('Y-m-d H:i:s'), (string) $model->datetimeField);

$model = Casting::query()->create();
$this->assertNull($model->datetimeField);

$model->update(['datetimeField' => null]);
$this->assertNull($model->datetimeField);
}

public function testDatetimeAsString(): void
Expand Down Expand Up @@ -70,6 +76,12 @@ public function testDatetimeWithCustomFormat(): void

self::assertInstanceOf(Carbon::class, $model->datetimeWithFormatField);
self::assertEquals(now()->subDay()->format('j.n.Y H:i'), (string) $model->datetimeWithFormatField);

$model = Casting::query()->create();
$this->assertNull($model->datetimeWithFormatField);

$model->update(['datetimeWithFormatField' => null]);
$this->assertNull($model->datetimeWithFormatField);
}

public function testImmutableDatetime(): void
Expand All @@ -92,6 +104,12 @@ public function testImmutableDatetime(): void
Carbon::createFromTimestamp(1698577443)->subDay()->format('Y-m-d H:i:s'),
(string) $model->immutableDatetimeField,
);

$model = Casting::query()->create();
$this->assertNull($model->immutableDatetimeField);

$model->update(['immutableDatetimeField' => null]);
$this->assertNull($model->immutableDatetimeField);
}

public function testImmutableDatetimeWithCustomFormat(): void
Expand All @@ -113,5 +131,11 @@ public function testImmutableDatetimeWithCustomFormat(): void
Carbon::createFromTimestamp(1698577443)->subDay()->format('j.n.Y H:i'),
(string) $model->immutableDatetimeWithFormatField,
);

$model = Casting::query()->create();
$this->assertNull($model->immutableDatetimeWithFormatField);

$model->update(['immutableDatetimeWithFormatField' => null]);
$this->assertNull($model->immutableDatetimeWithFormatField);
}
}