From 9f453627d0d1d19655d8715079e01d5146a1b008 Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Fri, 5 Mar 2021 13:19:29 +0000 Subject: [PATCH 1/8] Tiny correction of example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 711fc50..057ddf9 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ $model = new TestModel(); $model->carbon_interval = now()->subHours(3)->diffAsCarbonInterval(); -$model->save(); // Saved as `P3H` +$model->save(); // Saved as `PT3H` $model->fresh(); $model->carbon_interval; // Instance of `CarbonInterval` From ff4108850f99a333c91fc7cc34571fb07f29b8ac Mon Sep 17 00:00:00 2001 From: Leonel Elimpe Date: Mon, 21 Nov 2022 19:12:40 +0100 Subject: [PATCH 2/8] Support PHP 8, Laravel 9 --- .gitignore | 2 ++ composer.json | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8b7ef35..a2dfdd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /vendor +/.idea composer.lock +.phpunit.result.cache diff --git a/composer.json b/composer.json index 8a1238c..53cde3d 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,8 @@ ], "homepage": "https://github.com/atymic/laravel-dateinterval-cast", "require": { - "php": "^7.3", - "laravel/framework": "^7.0 || ^8.0" + "php": "^7.3|^8.0.2", + "laravel/framework": "^7.0 || ^8.0 || ^9.0" }, "require-dev": { "phpunit/phpunit": "^9.0", From 7254aa79491d062381d4c69eb626365c67ae5e24 Mon Sep 17 00:00:00 2001 From: Leonel Elimpe Date: Thu, 24 Nov 2022 19:02:50 +0100 Subject: [PATCH 3/8] feat: handle nullable columns --- src/Cast/CarbonIntervalCast.php | 3 +++ src/Cast/DateIntervalCast.php | 7 ++++++- tests/CastsIntervalsTest.php | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Cast/CarbonIntervalCast.php b/src/Cast/CarbonIntervalCast.php index 0755436..4ce97d9 100644 --- a/src/Cast/CarbonIntervalCast.php +++ b/src/Cast/CarbonIntervalCast.php @@ -20,6 +20,9 @@ class CarbonIntervalCast extends DateIntervalCast */ public function get($model, string $key, $value, array $attributes) { + if (is_null($value)) { + return; + } try { return CarbonInterval::create($value); } catch (\Exception $e) { diff --git a/src/Cast/DateIntervalCast.php b/src/Cast/DateIntervalCast.php index d6a2e3c..d91e9c2 100644 --- a/src/Cast/DateIntervalCast.php +++ b/src/Cast/DateIntervalCast.php @@ -21,6 +21,9 @@ class DateIntervalCast implements CastsAttributes */ public function get($model, string $key, $value, array $attributes) { + if (is_null($value)) { + return; + } try { return new \DateInterval($value); } catch (\Exception $e) { @@ -39,9 +42,11 @@ public function get($model, string $key, $value, array $attributes) */ public function set($model, string $key, $value, array $attributes) { + if (is_null($value)) { + return; + } try { $value = is_string($value) ? CarbonInterval::create($value) : $value; - return [$key => CarbonInterval::getDateIntervalSpec($value)]; } catch (\Exception $e) { throw InvalidIsoDuration::make($value, $e); diff --git a/tests/CastsIntervalsTest.php b/tests/CastsIntervalsTest.php index 3473eef..5ccda31 100644 --- a/tests/CastsIntervalsTest.php +++ b/tests/CastsIntervalsTest.php @@ -28,6 +28,17 @@ public function testDateIntervalCast() $this->assertInstanceOf(\DateInterval::class, $model->date_interval); $this->assertDatabaseHas('test', ['id' => $model->id, 'date_interval' => 'P1D']); } + public function testForNullableColumnsDateIntervalCastIsSkippedIfColumnValueIsNull() + { + $model = new TestEloquentModelWithCustomCasts(); + $model->date_interval = null; + $this->assertNotInstanceOf(\DateInterval::class, $model->date_interval); + $this->assertNull($model->getAttributes()['date_interval']); + $model->save(); + $model->fresh(); + $this->assertNotInstanceOf(\DateInterval::class, $model->date_interval); + $this->assertDatabaseHas('test', ['id' => $model->id, 'date_interval' => null]); + } public function testCarbonIntervalCast() { @@ -43,6 +54,17 @@ public function testCarbonIntervalCast() $this->assertInstanceOf(CarbonInterval::class, $model->carbon_interval); $this->assertDatabaseHas('test', ['id' => $model->id, 'carbon_interval' => 'P4D']); } + public function testForNullableColumnsCarbonIntervalCastIsSkippedIfColumnValueIsNull() + { + $model = new TestEloquentModelWithCustomCasts(); + $model->carbon_interval = null; + $this->assertNotInstanceOf(CarbonInterval::class, $model->carbon_interval); + $this->assertSame(null, $model->getAttributes()['carbon_interval']); + $model->save(); + $model->fresh(); + $this->assertNotInstanceOf(CarbonInterval::class, $model->carbon_interval); + $this->assertDatabaseHas('test', ['id' => $model->id, 'carbon_interval' => null]); + } public function testThrowsExceptionOnInvalidDateInterval() { From 888833c89333bb8e9e63548c80fa4bb7e172154c Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Tue, 14 Nov 2023 17:01:42 +0000 Subject: [PATCH 4/8] L10 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 53cde3d..be724aa 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "homepage": "https://github.com/atymic/laravel-dateinterval-cast", "require": { "php": "^7.3|^8.0.2", - "laravel/framework": "^7.0 || ^8.0 || ^9.0" + "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0" }, "require-dev": { "phpunit/phpunit": "^9.0", From 4298556a6adb5089086bef3f52a35da260deea04 Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Mon, 4 Dec 2023 17:48:13 +0000 Subject: [PATCH 5/8] Add livewire synth --- src/Synth/CarbonIntervalSynth.php | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/Synth/CarbonIntervalSynth.php diff --git a/src/Synth/CarbonIntervalSynth.php b/src/Synth/CarbonIntervalSynth.php new file mode 100644 index 0000000..c00a1b0 --- /dev/null +++ b/src/Synth/CarbonIntervalSynth.php @@ -0,0 +1,37 @@ + DateTimeInterval::class, + 'carbon' => CarbonInterval::class, + ]; + + public static $key = 'cbn'; + + static function match($target) { + foreach (static::$types as $type => $class) { + if ($target instanceof $class) return true; + } + + return false; + } + + function dehydrate($target) { + return [ + $target->spec(), + ['type' => array_search(get_class($target), static::$types)], + ]; + } + + function hydrate($value, $meta) { + return new static::$types[$meta['type']]($value); + } +} From 73cfcde05e04128be4d032d4e86051559f3d5d99 Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Wed, 20 Mar 2024 08:39:05 +0000 Subject: [PATCH 6/8] L11 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index be724aa..2c1d8a8 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "homepage": "https://github.com/atymic/laravel-dateinterval-cast", "require": { "php": "^7.3|^8.0.2", - "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0" + "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0" }, "require-dev": { "phpunit/phpunit": "^9.0", From 6bdd295433ccc1c2c717fad2fbb3f4fbf72754d4 Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Thu, 28 Nov 2024 11:04:54 +0000 Subject: [PATCH 7/8] Fix up Synth; it used the same identifier as the built in carbon synth; which causes collisions / is just wrong. Also added tests + fixed up deps --- composer.json | 7 +-- src/Synth/CarbonIntervalSynth.php | 37 +++++++++++-- tests/Synth/CarbonIntervalSynthTest.php | 69 +++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 tests/Synth/CarbonIntervalSynthTest.php diff --git a/composer.json b/composer.json index 2c1d8a8..66c10b8 100644 --- a/composer.json +++ b/composer.json @@ -11,13 +11,14 @@ ], "homepage": "https://github.com/atymic/laravel-dateinterval-cast", "require": { - "php": "^7.3|^8.0.2", + "php": "^7.3|^8.0.2|^8.1|^8.2|^8.3", "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0" }, "require-dev": { "phpunit/phpunit": "^9.0", - "orchestra/testbench": "^5.0 || ^6.0", - "phpstan/phpstan": "^0.12.9" + "orchestra/testbench": "^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "phpstan/phpstan": "^0.12.9", + "livewire/livewire": "^3" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/src/Synth/CarbonIntervalSynth.php b/src/Synth/CarbonIntervalSynth.php index c00a1b0..812a181 100644 --- a/src/Synth/CarbonIntervalSynth.php +++ b/src/Synth/CarbonIntervalSynth.php @@ -5,16 +5,15 @@ use Carbon\Carbon; use Carbon\CarbonImmutable; use Carbon\CarbonInterval; -use Livewire\Mechanisms\HandleComponents\Synthesizers\CarbonSynth; use Livewire\Mechanisms\HandleComponents\Synthesizers\Synth; class CarbonIntervalSynth extends Synth { public static $types = [ - 'native' => DateTimeInterval::class, + 'native' => \DateInterval::class, 'carbon' => CarbonInterval::class, ]; - public static $key = 'cbn'; + public static $key = 'cbnint'; static function match($target) { foreach (static::$types as $type => $class) { @@ -26,7 +25,7 @@ static function match($target) { function dehydrate($target) { return [ - $target->spec(), + $target instanceof CarbinInterval ? $target->spec() : $this->getDateIntervalSpec($target), ['type' => array_search(get_class($target), static::$types)], ]; } @@ -34,4 +33,34 @@ function dehydrate($target) { function hydrate($value, $meta) { return new static::$types[$meta['type']]($value); } + + protected function getDateIntervalSpec(\DateInterval $interval): string + { + $spec = 'P'; + + if ($interval->y) { + $spec .= $interval->y . 'Y'; + } + if ($interval->m) { + $spec .= $interval->m . 'M'; + } + if ($interval->d) { + $spec .= $interval->d . 'D'; + } + + if ($interval->h || $interval->i || $interval->s) { + $spec .= 'T'; + if ($interval->h) { + $spec .= $interval->h . 'H'; + } + if ($interval->i) { + $spec .= $interval->i . 'M'; + } + if ($interval->s) { + $spec .= $interval->s . 'S'; + } + } + + return $spec; + } } diff --git a/tests/Synth/CarbonIntervalSynthTest.php b/tests/Synth/CarbonIntervalSynthTest.php new file mode 100644 index 0000000..f532208 --- /dev/null +++ b/tests/Synth/CarbonIntervalSynthTest.php @@ -0,0 +1,69 @@ +createPartialMock(CarbonIntervalSynth::class, ['__construct']); + + $carbonInterval = CarbonInterval::days(2); + $dateInterval = new \DateInterval('P2D'); + + $this->assertTrue($synth::match($carbonInterval), "Should match CarbonInterval"); + $this->assertTrue($synth::match($dateInterval), "Should match DateInterval"); + $this->assertFalse($synth::match('Not an interval'), "Should not match non-interval values"); + } + + public function testDehydrate() + { + $synth = $this->createPartialMock(CarbonIntervalSynth::class, ['__construct']); + + $carbonInterval = CarbonInterval::days(2); + $expectedCarbonDehydration = [ + 'P2D', + ['type' => 'carbon'], + ]; + + $this->assertEquals( + $expectedCarbonDehydration, + $synth->dehydrate($carbonInterval), + "Dehydration of CarbonInterval should produce expected output" + ); + + $dateInterval = new \DateInterval('P2D'); + $expectedDateDehydration = [ + 'P2D', + ['type' => 'native'], + ]; + + $this->assertEquals( + $expectedDateDehydration, + $synth->dehydrate($dateInterval), + "Dehydration of DateInterval should produce expected output" + ); + } + + public function testHydrate() + { + $synth = $this->createPartialMock(CarbonIntervalSynth::class, ['__construct']); + + $carbonMeta = ['type' => 'carbon']; + $carbonValue = 'P2D'; + $hydratedCarbon = $synth->hydrate($carbonValue, $carbonMeta); + + $this->assertInstanceOf(CarbonInterval::class, $hydratedCarbon, "Hydrated object should be an instance of CarbonInterval"); + $this->assertEquals(CarbonInterval::days(2), $hydratedCarbon, "Hydrated CarbonInterval should match the expected interval"); + + $dateMeta = ['type' => 'native']; + $dateValue = 'P2D'; + $hydratedDate = $synth->hydrate($dateValue, $dateMeta); + + $this->assertInstanceOf(\DateInterval::class, $hydratedDate, "Hydrated object should be an instance of DateInterval"); + $this->assertEquals(new \DateInterval('P2D'), $hydratedDate, "Hydrated DateInterval should match the expected interval"); + } +} From 345acf70853800321542df6c77eada74e118e032 Mon Sep 17 00:00:00 2001 From: Ben Lumley Date: Tue, 18 Mar 2025 09:48:47 +0000 Subject: [PATCH 8/8] add l12 to composer deps --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 66c10b8..9797007 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,8 @@ ], "homepage": "https://github.com/atymic/laravel-dateinterval-cast", "require": { - "php": "^7.3|^8.0.2|^8.1|^8.2|^8.3", - "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0" + "php": "^7.3|^8.0.2|^8.1|^8.2|^8.3|^8.4", + "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0" }, "require-dev": { "phpunit/phpunit": "^9.0",