diff --git a/src/VirtualColumn.php b/src/VirtualColumn.php index a829973..4dfaa2b 100644 --- a/src/VirtualColumn.php +++ b/src/VirtualColumn.php @@ -31,9 +31,11 @@ trait VirtualColumn */ public bool $dataEncoded = false; + public bool $dataNotLoaded = false; + protected function decodeVirtualColumn(): void { - if (! $this->dataEncoded) { + if (! $this->dataEncoded || ! $this->dataNotLoaded) { return; } @@ -65,6 +67,10 @@ trait VirtualColumn return; } + if ($this->dataNotLoaded) { + throw new \Exception('Data column was not loaded from the database. Make sure the data column is selected in the query.'); + } + $dataColumn = static::getDataColumn(); $customColumns = static::getCustomColumns(); $attributes = array_filter($this->getAttributes(), fn ($key) => ! in_array($key, $customColumns), ARRAY_FILTER_USE_KEY); @@ -107,11 +113,14 @@ trait VirtualColumn return [ 'retrieved' => [ function () { + // If the data column wasn't retrieved, don't decode it if (($this->attributes[static::getDataColumn()] ?? null) === null) { + $this->dataNotLoaded = true; + return; } - // Always decode after model retrieval + // Mark the data as encoded so that it doesn't get encoded again $this->dataEncoded = true; $this->decodeVirtualColumn(); diff --git a/tests/VirtualColumnTest.php b/tests/VirtualColumnTest.php index 9290b34..9089a4b 100644 --- a/tests/VirtualColumnTest.php +++ b/tests/VirtualColumnTest.php @@ -128,6 +128,20 @@ class VirtualColumnTest extends TestCase $this->assertSame($encodedBar->bar, 'bar'); } + /** @test */ + public function model_doesnt_overwrite_when_selectively_fetching() { + $this->expectExceptionMessage('Data column was not loaded from the database. Make sure the data column is selected in the query.'); + + FooModel::create([ + 'id' => 1, + 'foo' => 'bar' + ]); + + $foo = FooModel::query()->first(['id']); + $foo->bar = 'baz'; + $foo->save(); + } + /** @test */ public function decoding_works_with_strict_mode_enabled() { FooModel::shouldBeStrict();