diff --git a/src/contract/src/Synchronized.php b/src/contract/src/Synchronized.php new file mode 100644 index 000000000..c4616b477 --- /dev/null +++ b/src/contract/src/Synchronized.php @@ -0,0 +1,20 @@ +model = $model; + $this->items = $itmes; + } + + public function __get($name) + { + return $this->items[$name]; + } + + public function __set($name, $value) + { + $this->items[$name] = $value; + $this->isSynchronized = false; + $this->model->syncAttributes(); + $this->isSynchronized = true; + } + + public function isSynchronized(): bool + { + return $this->isSynchronized; + } + + public function toArray(): array + { + return $this->items; + } +} diff --git a/src/database/src/Model/Concerns/HasAttributes.php b/src/database/src/Model/Concerns/HasAttributes.php index c04b840c6..718cd319a 100644 --- a/src/database/src/Model/Concerns/HasAttributes.php +++ b/src/database/src/Model/Concerns/HasAttributes.php @@ -17,6 +17,7 @@ use DateTimeInterface; use Hyperf\Contract\Castable; use Hyperf\Contract\CastsAttributes; use Hyperf\Contract\CastsInboundAttributes; +use Hyperf\Contract\Synchronized; use Hyperf\Database\Model\JsonEncodingException; use Hyperf\Database\Model\Relations\Relation; use Hyperf\Utils\Arr; @@ -1404,6 +1405,10 @@ trait HasAttributes protected function mergeAttributesFromClassCasts() { foreach ($this->classCastCache as $key => $value) { + if ($value instanceof Synchronized && $value->isSynchronized()) { + continue; + } + $caster = $this->resolveCasterClass($key); $this->attributes = array_merge( diff --git a/src/database/tests/DatabaseModelCustomCastingTest.php b/src/database/tests/DatabaseModelCustomCastingTest.php index 4f5e16fb0..dc05010ed 100644 --- a/src/database/tests/DatabaseModelCustomCastingTest.php +++ b/src/database/tests/DatabaseModelCustomCastingTest.php @@ -14,7 +14,9 @@ namespace HyperfTest\Database; use Hyperf\Contract\Castable; use Hyperf\Contract\CastsAttributes; use Hyperf\Contract\CastsInboundAttributes; +use Hyperf\Database\Model\CastsValue; use Hyperf\Database\Model\Model; +use Hyperf\Utils\Arr; use PHPUnit\Framework\TestCase; /** @@ -205,6 +207,24 @@ class DatabaseModelCustomCastingTest extends TestCase CastUsing::$castsAttributes = new UppercaseCaster(); $this->assertSame($method->invokeArgs($model, ['cast_using']), $method->invokeArgs($model, ['cast_using'])); } + + public function testIsSynchronized() + { + $model = new TestModelWithCustomCast(); + $model->user = $user = new UserInfo($model, ['name' => 'Hyperf', 'gender' => 1]); + $model->syncOriginal(); + + $attributes = $model->getAttributes(); + $this->assertSame(['name' => 'Hyperf', 'gender' => 1], $attributes); + + $user->name = 'Nano'; + $attributes = $model->getAttributes(); + $this->assertSame(['name' => 'Nano', 'gender' => 1], $attributes); + + $this->assertSame(['name' => 'Nano'], $model->getDirty()); + $this->assertSame(2, UserInfoCaster::$setCount); + $this->assertSame(0, UserInfoCaster::$getCount); + } } class TestModelWithCustomCast extends Model @@ -223,6 +243,7 @@ class TestModelWithCustomCast extends Model */ protected $casts = [ 'address' => AddressCaster::class, + 'user' => UserInfoCaster::class, 'password' => HashCaster::class, 'other_password' => HashCaster::class . ':md5', 'uppercase' => UppercaseCaster::class, @@ -286,6 +307,28 @@ class AddressCaster implements CastsAttributes } } +class UserInfoCaster implements CastsAttributes +{ + public static $setCount = 0; + + public static $getCount = 0; + + public function get($model, string $key, $value, array $attributes) + { + ++self::$getCount; + return new UserInfo($model, Arr::only($attributes, ['name', 'gender'])); + } + + public function set($model, string $key, $value, array $attributes) + { + ++self::$setCount; + return [ + 'name' => $value->name, + 'gender' => $value->gender, + ]; + } +} + class JsonCaster implements CastsAttributes { public function get($model, $key, $value, $attributes) @@ -369,3 +412,11 @@ class Address $this->lineTwo = $lineTwo; } } + +/** + * @property string $name + * @property int $gender + */ +class UserInfo extends CastsValue +{ +}