Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/core/src/Base/Casts/AsAttributeData.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ public function set($model, $key, $value, $attributes)
];
}

return [$key => json_encode($data)];
// JSON_THROW_ON_ERROR, because json_encode() reports failure by
// returning false - on a value that is not valid UTF-8, or that
// exceeds max depth. Bound into the update, that false is stored
// as 0, which both destroys the attributes the row already held
// and leaves it unreadable, since get() cannot iterate an int.
return [$key => json_encode($data, JSON_THROW_ON_ERROR)];
}
};
}
Expand Down
52 changes: 52 additions & 0 deletions tests/core/Unit/Base/Casts/AsAttributeDataTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Lunar\FieldTypes\TranslatedText;
use Lunar\Models\Product;
use Lunar\Tests\Core\TestCase;

uses(TestCase::class);

uses(RefreshDatabase::class);

test('can not overwrite attribute data with an unencodable value', function () {
$product = Product::factory()->create([
'attribute_data' => collect([
'name' => new TranslatedText(['en' => 'A name worth keeping']),
]),
]);

$before = DB::table('lunar_products')->where('id', $product->id)->value('attribute_data');

// A single 0xE4 byte - "a" with an umlaut, as Latin-1 writes it. Any feed
// that is not UTF-8 produces these. The cast runs on assignment, so this
// never reaches the database at all.
expect(function () use ($product) {
$product->attribute_data = collect([
'name' => new TranslatedText(['en' => "Sh\xE4mpoo"]),
]);

$product->save();
})->toThrow(JsonException::class);

$after = DB::table('lunar_products')->where('id', $product->id)->value('attribute_data');

// The point is not that it failed, but that it failed without taking the
// existing attributes with it.
expect($after)->toEqual($before);

expect(Product::find($product->id)->translateAttribute('name'))
->toEqual('A name worth keeping');
});

test('can store attribute data containing multibyte characters', function () {
$product = Product::factory()->create([
'attribute_data' => collect([
'name' => new TranslatedText(['en' => 'Crème brûlée · 日本語 · emoji 🧴']),
]),
]);

expect(Product::find($product->id)->translateAttribute('name'))
->toEqual('Crème brûlée · 日本語 · emoji 🧴');
});
Loading