diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php index 72c4d2a5f..c4c73d67d 100644 --- a/src/Eloquent/Model.php +++ b/src/Eloquent/Model.php @@ -7,6 +7,7 @@ use Brick\Math\BigDecimal; use Brick\Math\Exception\MathException as BrickMathException; use Brick\Math\RoundingMode; +use Carbon\CarbonInterface; use DateTimeInterface; use Illuminate\Contracts\Queue\QueueableCollection; use Illuminate\Contracts\Queue\QueueableEntity; @@ -27,6 +28,7 @@ use function abs; use function array_key_exists; use function array_keys; +use function array_merge; use function array_unique; use function array_values; use function class_basename; @@ -41,6 +43,7 @@ use function method_exists; use function sprintf; use function str_contains; +use function str_starts_with; use function strcmp; use function uniqid; @@ -199,6 +202,36 @@ public function getAttribute($key) return parent::getAttribute($key); } + /** @inheritdoc */ + protected function transformModelValue($key, $value) + { + $value = parent::transformModelValue($key, $value); + // Casting attributes to any of date types, will convert that attribute + // to a Carbon or CarbonImmutable instance. + // @see Model::setAttribute() + if ($this->hasCast($key) && $value instanceof CarbonInterface) { + $value->settings(array_merge($value->getSettings(), ['toStringFormat' => $this->getDateFormat()])); + + $castType = $this->getCasts()[$key]; + if ($this->isCustomDateTimeCast($castType) && str_starts_with($castType, 'date:')) { + $value->startOfDay(); + } + } + + return $value; + } + + /** @inheritdoc */ + protected function getCastType($key) + { + $castType = $this->getCasts()[$key]; + if ($this->isCustomDateTimeCast($castType) || $this->isImmutableCustomDateTimeCast($castType)) { + $this->setDateFormat(Str::after($castType, ':')); + } + + return parent::getCastType($key); + } + /** @inheritdoc */ protected function getAttributeFromArray($key) { @@ -217,7 +250,7 @@ public function setAttribute($key, $value) { $key = (string) $key; - //Add casts + // Add casts if ($this->hasCast($key)) { $value = $this->castAttribute($key, $value); } @@ -270,6 +303,19 @@ public function fromJson($value, $asObject = false) return Json::decode($value ?? '', ! $asObject); } + /** @inheritdoc */ + protected function castAttribute($key, $value) + { + $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(), + default => parent::castAttribute($key, $value) + }; + } + /** @inheritdoc */ public function attributesToArray() { diff --git a/tests/Casts/DateTest.php b/tests/Casts/DateTest.php index e0c775503..bd4b76424 100644 --- a/tests/Casts/DateTest.php +++ b/tests/Casts/DateTest.php @@ -4,6 +4,7 @@ namespace MongoDB\Laravel\Tests\Casts; +use Carbon\CarbonImmutable; use DateTime; use Illuminate\Support\Carbon; use MongoDB\Laravel\Tests\Models\Casting; @@ -61,4 +62,59 @@ public function testDateAsString(): void (string) $model->dateField, ); } + + public function testDateWithCustomFormat(): void + { + $model = Casting::query()->create(['dateWithFormatField' => new DateTime()]); + + self::assertInstanceOf(Carbon::class, $model->dateWithFormatField); + self::assertEquals(now()->startOfDay()->format('j.n.Y H:i'), (string) $model->dateWithFormatField); + + $model->update(['dateWithFormatField' => now()->subDay()]); + + self::assertInstanceOf(Carbon::class, $model->dateWithFormatField); + self::assertEquals(now()->startOfDay()->subDay()->format('j.n.Y H:i'), (string) $model->dateWithFormatField); + } + + public function testImmutableDate(): void + { + $model = Casting::query()->create(['immutableDateField' => new DateTime()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateField); + self::assertEquals(now()->startOfDay()->format('Y-m-d H:i:s'), (string) $model->immutableDateField); + + $model->update(['immutableDateField' => now()->subDay()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateField); + self::assertEquals(now()->startOfDay()->subDay()->format('Y-m-d H:i:s'), (string) $model->immutableDateField); + + $model->update(['immutableDateField' => '2023-10-28']); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateField); + self::assertEquals( + Carbon::createFromTimestamp(1698577443)->subDay()->startOfDay()->format('Y-m-d H:i:s'), + (string) $model->immutableDateField, + ); + } + + public function testImmutableDateWithCustomFormat(): void + { + $model = Casting::query()->create(['immutableDateWithFormatField' => new DateTime()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateWithFormatField); + self::assertEquals(now()->startOfDay()->format('j.n.Y H:i'), (string) $model->immutableDateWithFormatField); + + $model->update(['immutableDateWithFormatField' => now()->startOfDay()->subDay()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateWithFormatField); + self::assertEquals(now()->startOfDay()->subDay()->format('j.n.Y H:i'), (string) $model->immutableDateWithFormatField); + + $model->update(['immutableDateWithFormatField' => '2023-10-28']); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateWithFormatField); + self::assertEquals( + Carbon::createFromTimestamp(1698577443)->subDay()->startOfDay()->format('j.n.Y H:i'), + (string) $model->immutableDateWithFormatField, + ); + } } diff --git a/tests/Casts/DatetimeTest.php b/tests/Casts/DatetimeTest.php index 77a9cb4b6..a90901a82 100644 --- a/tests/Casts/DatetimeTest.php +++ b/tests/Casts/DatetimeTest.php @@ -4,6 +4,8 @@ namespace MongoDB\Laravel\Tests\Casts; +use Carbon\CarbonImmutable; +use DateTime; use Illuminate\Support\Carbon; use MongoDB\Laravel\Tests\Models\Casting; use MongoDB\Laravel\Tests\TestCase; @@ -19,7 +21,7 @@ protected function setUp(): void Casting::truncate(); } - public function testDate(): void + public function testDatetime(): void { $model = Casting::query()->create(['datetimeField' => now()]); @@ -32,7 +34,7 @@ public function testDate(): void self::assertEquals(now()->subDay()->format('Y-m-d H:i:s'), (string) $model->datetimeField); } - public function testDateAsString(): void + public function testDatetimeAsString(): void { $model = Casting::query()->create(['datetimeField' => '2023-10-29']); @@ -50,4 +52,59 @@ public function testDateAsString(): void (string) $model->datetimeField, ); } + + public function testDatetimeWithCustomFormat(): void + { + $model = Casting::query()->create(['datetimeWithFormatField' => new DateTime()]); + + self::assertInstanceOf(Carbon::class, $model->datetimeWithFormatField); + self::assertEquals(now()->format('j.n.Y H:i'), (string) $model->datetimeWithFormatField); + + $model->update(['datetimeWithFormatField' => now()->subDay()]); + + self::assertInstanceOf(Carbon::class, $model->datetimeWithFormatField); + self::assertEquals(now()->subDay()->format('j.n.Y H:i'), (string) $model->datetimeWithFormatField); + } + + public function testImmutableDatetime(): void + { + $model = Casting::query()->create(['immutableDatetimeField' => new DateTime()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeField); + self::assertEquals(now()->format('Y-m-d H:i:s'), (string) $model->immutableDatetimeField); + + $model->update(['immutableDatetimeField' => now()->subDay()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeField); + self::assertEquals(now()->subDay()->format('Y-m-d H:i:s'), (string) $model->immutableDatetimeField); + + $model->update(['immutableDatetimeField' => '2023-10-28 11:04:03']); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeField); + self::assertEquals( + Carbon::createFromTimestamp(1698577443)->subDay()->format('Y-m-d H:i:s'), + (string) $model->immutableDatetimeField, + ); + } + + public function testImmutableDatetimeWithCustomFormat(): void + { + $model = Casting::query()->create(['immutableDatetimeWithFormatField' => new DateTime()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeWithFormatField); + self::assertEquals(now()->format('j.n.Y H:i'), (string) $model->immutableDatetimeWithFormatField); + + $model->update(['immutableDatetimeWithFormatField' => now()->subDay()]); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeWithFormatField); + self::assertEquals(now()->subDay()->format('j.n.Y H:i'), (string) $model->immutableDatetimeWithFormatField); + + $model->update(['immutableDatetimeWithFormatField' => '2023-10-28 11:04:03']); + + self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeWithFormatField); + self::assertEquals( + Carbon::createFromTimestamp(1698577443)->subDay()->format('j.n.Y H:i'), + (string) $model->immutableDatetimeWithFormatField, + ); + } } diff --git a/tests/Models/Casting.php b/tests/Models/Casting.php index 5f825f954..9e232cf15 100644 --- a/tests/Models/Casting.php +++ b/tests/Models/Casting.php @@ -24,7 +24,14 @@ class Casting extends Eloquent 'jsonValue', 'collectionValue', 'dateField', + 'dateWithFormatField', + 'immutableDateField', + 'immutableDateWithFormatField', 'datetimeField', + 'dateWithFormatField', + 'datetimeWithFormatField', + 'immutableDatetimeField', + 'immutableDatetimeWithFormatField', ]; protected $casts = [ @@ -38,6 +45,12 @@ class Casting extends Eloquent 'jsonValue' => 'json', 'collectionValue' => 'collection', 'dateField' => 'date', + 'dateWithFormatField' => 'date:j.n.Y H:i', + 'immutableDateField' => 'immutable_date', + 'immutableDateWithFormatField' => 'immutable_date:j.n.Y H:i', 'datetimeField' => 'datetime', + 'datetimeWithFormatField' => 'datetime:j.n.Y H:i', + 'immutableDatetimeField' => 'immutable_datetime', + 'immutableDatetimeWithFormatField' => 'immutable_datetime:j.n.Y H:i', ]; }