From 15370af5bfb3067f63b9cd394a5f2aee9045d255 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 23 Oct 2020 13:20:06 +0200 Subject: [PATCH 1/3] Custom casts may provide increment and decrement functionality for attribute values --- .../Eloquent/DeviatesCastableAttributes.php | 28 +++++++++ .../Eloquent/Concerns/HasAttributes.php | 30 ++++++++++ src/Illuminate/Database/Eloquent/Model.php | 4 +- ...DatabaseEloquentModelCustomCastingTest.php | 57 ++++++++++++++++++- 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/Illuminate/Contracts/Database/Eloquent/DeviatesCastableAttributes.php diff --git a/src/Illuminate/Contracts/Database/Eloquent/DeviatesCastableAttributes.php b/src/Illuminate/Contracts/Database/Eloquent/DeviatesCastableAttributes.php new file mode 100644 index 00000000000..48ba73abc36 --- /dev/null +++ b/src/Illuminate/Contracts/Database/Eloquent/DeviatesCastableAttributes.php @@ -0,0 +1,28 @@ +getCasts()[$key])); } + /** + * Increment or decrement the given attribute using the custom cast class. + * + * @param string $method + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function deviateClassCastableAttribute($method, $key, $value) + { + return $this->resolveCasterClass($key)->{$method}( + $this, $key, $value, $this->attributes + ); + } + /** * Serialize the given attribute using the custom cast class. * @@ -1108,6 +1123,21 @@ protected function isClassCastable($key) throw new InvalidCastException($this->getModel(), $key, $castType); } + /** + * Determine if the key is deviable using a custom class. + * + * @param string $key + * @return bool + * + * @throws \Illuminate\Database\Eloquent\InvalidCastException + */ + protected function isClassDeviable($key) + { + return $this->isClassCastable($key) && + method_exists($castType = $this->parseCasterClass($this->getCasts()[$key]), 'increment') && + method_exists($castType, 'decrement'); + } + /** * Determine if the key is serializable using a custom class. * diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 1d7362deb05..13e00c91246 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -612,7 +612,9 @@ protected function incrementOrDecrement($column, $amount, $extra, $method) return $query->{$method}($column, $amount, $extra); } - $this->{$column} = $this->{$column} + ($method === 'increment' ? $amount : $amount * -1); + $this->{$column} = $this->isClassDeviable($column) + ? $this->deviateClassCastableAttribute($method, $column, $amount) + : $this->{$column} = $this->{$column} + ($method === 'increment' ? $amount : $amount * -1); $this->forceFill($extra); diff --git a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php index ef2036ffee8..718f53e9b0b 100644 --- a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php @@ -5,16 +5,30 @@ use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; +use Illuminate\Contracts\Database\Eloquent\DeviatesCastableAttributes; use Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes; use Illuminate\Database\Eloquent\InvalidCastException; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Schema; /** * @group integration */ class DatabaseEloquentModelCustomCastingTest extends DatabaseTestCase { + protected function setUp(): void + { + parent::setUp(); + + Schema::create('test_eloquent_model_with_custom_casts', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->decimal('price'); + }); + } + public function testBasicCustomCasting() { $model = new TestEloquentModelWithCustomCast; @@ -135,6 +149,21 @@ public function testGetOriginalWithCastValueObjects() $this->assertNull($model->address); } + public function testDeviableCasts() + { + $model = new TestEloquentModelWithCustomCast; + $model->price = '123.456'; + $model->save(); + + $model->increasePrice('530.865'); + + $this->assertSame((new Decimal('654.321'))->getValue(), $model->price->getValue()); + + $model->decreasePrice('333.333'); + + $this->assertSame((new Decimal('320.988'))->getValue(), $model->price->getValue()); + } + public function testSerializableCasts() { $model = new TestEloquentModelWithCustomCast; @@ -261,6 +290,22 @@ class TestEloquentModelWithCustomCast extends Model 'undefined_cast_column' => UndefinedCast::class, 'birthday_at' => DateObjectCaster::class, ]; + + /** + * @param float|int $amount + */ + public function increasePrice($amount) + { + $this->increment('price', $amount); + } + + /** + * @param float|int $amount + */ + public function decreasePrice($amount) + { + $this->decrement('price', $amount); + } } class HashCaster implements CastsInboundAttributes @@ -326,7 +371,7 @@ public function set($model, $key, $value, $attributes) } } -class DecimalCaster implements CastsAttributes, SerializesCastableAttributes +class DecimalCaster implements CastsAttributes, DeviatesCastableAttributes, SerializesCastableAttributes { public function get($model, $key, $value, $attributes) { @@ -338,6 +383,16 @@ public function set($model, $key, $value, $attributes) return (string) $value; } + public function increment($model, $key, $value, $attributes) + { + return new Decimal($attributes[$key] + $value); + } + + public function decrement($model, $key, $value, $attributes) + { + return new Decimal($attributes[$key] - $value); + } + public function serialize($model, $key, $value, $attributes) { return (string) $value; From 3b8455cb71e0c10847f5a178a75bd4779e55deb2 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 23 Oct 2020 15:32:29 +0200 Subject: [PATCH 2/3] Wrapper methods increasePrice/decreasePrice were unnecessary --- ...DatabaseEloquentModelCustomCastingTest.php | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php index 718f53e9b0b..bbbb1bc3e50 100644 --- a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php @@ -155,11 +155,11 @@ public function testDeviableCasts() $model->price = '123.456'; $model->save(); - $model->increasePrice('530.865'); + $model->increment('price', '530.865'); $this->assertSame((new Decimal('654.321'))->getValue(), $model->price->getValue()); - $model->decreasePrice('333.333'); + $model->decrement('price', '333.333'); $this->assertSame((new Decimal('320.988'))->getValue(), $model->price->getValue()); } @@ -290,22 +290,6 @@ class TestEloquentModelWithCustomCast extends Model 'undefined_cast_column' => UndefinedCast::class, 'birthday_at' => DateObjectCaster::class, ]; - - /** - * @param float|int $amount - */ - public function increasePrice($amount) - { - $this->increment('price', $amount); - } - - /** - * @param float|int $amount - */ - public function decreasePrice($amount) - { - $this->decrement('price', $amount); - } } class HashCaster implements CastsInboundAttributes From 8f35c06adc790e90b4e8fe2365eacf37d6ff3a36 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 23 Oct 2020 22:56:14 +0200 Subject: [PATCH 3/3] Junk code eliminated --- src/Illuminate/Database/Eloquent/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 13e00c91246..2808a051203 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -614,7 +614,7 @@ protected function incrementOrDecrement($column, $amount, $extra, $method) $this->{$column} = $this->isClassDeviable($column) ? $this->deviateClassCastableAttribute($method, $column, $amount) - : $this->{$column} = $this->{$column} + ($method === 'increment' ? $amount : $amount * -1); + : $this->{$column} + ($method === 'increment' ? $amount : $amount * -1); $this->forceFill($extra);