Skip to content

Commit 514bf09

Browse files
committed
feat: JsonForceEmptyObjectAsArray cast
1 parent db74f61 commit 514bf09

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,12 @@ To make those types usable, these casts can be used with your eloquent models:
12061206
| `integerArray` | `Tpetry\PostgresqlEnhanced\Eloquent\Casts\IntegerArrayCast` |
12071207
| `vector` | `Tpetry\PostgresqlEnhanced\Eloquent\Casts\VectorArray` |
12081208

1209+
Additionally, these casts exist to make using PostgreSQL more easy:
1210+
1211+
| Cast | Description |
1212+
|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
1213+
| `Tpetry\PostgresqlEnhanced\Eloquent\Casts\JsonForceEmptyObjectAsArray` | Decodes JSON value as array in Laravel but ensures that empty values are always stored as JSON object. |
1214+
12091215
### Refresh Data on Save
12101216

12111217
When you are using Laravel's `storedAs($expression)` feature in migrations to have dynamically computed columns in your database or triggers to update these columns, eloquent's behaviour is not doing exactly what you are expecting.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tpetry\PostgresqlEnhanced\Eloquent\Casts;
6+
7+
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
8+
9+
class JsonForceEmptyObjectAsArray implements CastsAttributes
10+
{
11+
/**
12+
* Transform the attribute from the underlying model values.
13+
*
14+
* @param \Illuminate\Database\Eloquent\Model $model
15+
* @param ?string $value
16+
*
17+
* @return ?array<array-key, mixed>
18+
*/
19+
public function get($model, string $key, mixed $value, array $attributes): ?array
20+
{
21+
if (null === $value) {
22+
return null;
23+
}
24+
25+
return json_decode($value, true, flags: \JSON_THROW_ON_ERROR);
26+
}
27+
28+
/**
29+
* Transform the attribute to its underlying model values.
30+
*
31+
* @param \Illuminate\Database\Eloquent\Model $model
32+
* @param array<array-key, mixed>|\Illuminate\Support\Collection<array-key, mixed>|null $value
33+
*/
34+
public function set($model, string $key, mixed $value, array $attributes): ?string
35+
{
36+
if (null === $value) {
37+
return null;
38+
}
39+
40+
return match ($casted = json_encode($value, flags: \JSON_THROW_ON_ERROR)) {
41+
'[]' => '{}',
42+
default => $casted,
43+
};
44+
}
45+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tpetry\PostgresqlEnhanced\Tests\Eloquent;
6+
7+
use Composer\Semver\Comparator;
8+
use Illuminate\Database\Eloquent\Model;
9+
use Tpetry\PostgresqlEnhanced\Eloquent\Casts\JsonForceEmptyObjectAsArray;
10+
use Tpetry\PostgresqlEnhanced\Tests\TestCase;
11+
12+
class JsonForceEmptyObjectAsArrayCastTest extends TestCase
13+
{
14+
public function testParseFromDatabaseValue(): void
15+
{
16+
if (Comparator::lessThan($this->app->version(), '8.0.0')) {
17+
$this->markTestSkipped('Cast classes have been added with Laravel 8.');
18+
}
19+
20+
$cast = new JsonForceEmptyObjectAsArray();
21+
$model = new class extends Model { };
22+
23+
$this->assertNull($cast->get($model, 'column', null, []));
24+
$this->assertEquals([], $cast->get($model, 'column', '{}', []));
25+
$this->assertEquals(['a' => 1, 'b' => 2], $cast->get($model, 'column', '{"a":1,"b":2}', []));
26+
$this->assertEquals(['c' => [1, 2, 3], 'd' => [4, 5, 6]], $cast->get($model, 'column', '{"c":[1,2,3],"d":[4,5,6]}', []));
27+
$this->assertEquals([1, 2], $cast->get($model, 'column', '[1,2]', []));
28+
$this->assertEquals([[1, 2, 3], [4, 5, 6]], $cast->get($model, 'column', '[[1,2,3],[4,5,6]]', []));
29+
}
30+
31+
public function testTransformToDatabaseValue(): void
32+
{
33+
if (Comparator::lessThan($this->app->version(), '8.0.0')) {
34+
$this->markTestSkipped('Cast classes have been added with Laravel 8.');
35+
}
36+
37+
$cast = new JsonForceEmptyObjectAsArray();
38+
$model = new class extends Model { };
39+
40+
$this->assertNull($cast->get($model, 'column', null, []));
41+
42+
$this->assertEquals('{}', $cast->set($model, 'column', [], []));
43+
$this->assertEquals('{"a":1,"b":2}', $cast->set($model, 'column', ['a' => 1, 'b' => 2], []));
44+
$this->assertEquals('{"c":[1,2,3],"d":[4,5,6]}', $cast->set($model, 'column', ['c' => [1, 2, 3], 'd' => [4, 5, 6]], []));
45+
$this->assertEquals('[1,2]', $cast->set($model, 'column', [1, 2], []));
46+
$this->assertEquals('[[1,2,3],[4,5,6]]', $cast->set($model, 'column', [[1, 2, 3], [4, 5, 6]], []));
47+
48+
$this->assertEquals('{}', $cast->set($model, 'column', collect(), []));
49+
$this->assertEquals('{"a":1,"b":2}', $cast->set($model, 'column', collect(['a' => 1, 'b' => 2]), []));
50+
$this->assertEquals('{"c":[1,2,3],"d":[4,5,6]}', $cast->set($model, 'column', collect(['c' => [1, 2, 3], 'd' => [4, 5, 6]]), []));
51+
$this->assertEquals('[1,2]', $cast->set($model, 'column', collect([1, 2]), []));
52+
$this->assertEquals('[[1,2,3],[4,5,6]]', $cast->set($model, 'column', collect([[1, 2, 3], [4, 5, 6]]), []));
53+
}
54+
}

0 commit comments

Comments
 (0)