Skip to content

Commit

Permalink
feat: JsonForceEmptyObjectAsArray cast
Browse files Browse the repository at this point in the history
  • Loading branch information
tpetry committed Dec 16, 2024
1 parent db74f61 commit 514bf09
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,12 @@ To make those types usable, these casts can be used with your eloquent models:
| `integerArray` | `Tpetry\PostgresqlEnhanced\Eloquent\Casts\IntegerArrayCast` |
| `vector` | `Tpetry\PostgresqlEnhanced\Eloquent\Casts\VectorArray` |

Additionally, these casts exist to make using PostgreSQL more easy:

| Cast | Description |
|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| `Tpetry\PostgresqlEnhanced\Eloquent\Casts\JsonForceEmptyObjectAsArray` | Decodes JSON value as array in Laravel but ensures that empty values are always stored as JSON object. |

### Refresh Data on Save

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.
Expand Down
45 changes: 45 additions & 0 deletions src/Eloquent/Casts/JsonForceEmptyObjectAsArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Tpetry\PostgresqlEnhanced\Eloquent\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class JsonForceEmptyObjectAsArray implements CastsAttributes
{
/**
* Transform the attribute from the underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param ?string $value
*
* @return ?array<array-key, mixed>
*/
public function get($model, string $key, mixed $value, array $attributes): ?array
{
if (null === $value) {
return null;
}

return json_decode($value, true, flags: \JSON_THROW_ON_ERROR);
}

/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param array<array-key, mixed>|\Illuminate\Support\Collection<array-key, mixed>|null $value
*/
public function set($model, string $key, mixed $value, array $attributes): ?string
{
if (null === $value) {
return null;
}

return match ($casted = json_encode($value, flags: \JSON_THROW_ON_ERROR)) {
'[]' => '{}',
default => $casted,
};
}
}
54 changes: 54 additions & 0 deletions tests/Eloquent/JsonForceEmptyObjectAsArrayCastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Tpetry\PostgresqlEnhanced\Tests\Eloquent;

use Composer\Semver\Comparator;
use Illuminate\Database\Eloquent\Model;
use Tpetry\PostgresqlEnhanced\Eloquent\Casts\JsonForceEmptyObjectAsArray;
use Tpetry\PostgresqlEnhanced\Tests\TestCase;

class JsonForceEmptyObjectAsArrayCastTest extends TestCase
{
public function testParseFromDatabaseValue(): void
{
if (Comparator::lessThan($this->app->version(), '8.0.0')) {
$this->markTestSkipped('Cast classes have been added with Laravel 8.');
}

$cast = new JsonForceEmptyObjectAsArray();
$model = new class extends Model { };

$this->assertNull($cast->get($model, 'column', null, []));
$this->assertEquals([], $cast->get($model, 'column', '{}', []));
$this->assertEquals(['a' => 1, 'b' => 2], $cast->get($model, 'column', '{"a":1,"b":2}', []));
$this->assertEquals(['c' => [1, 2, 3], 'd' => [4, 5, 6]], $cast->get($model, 'column', '{"c":[1,2,3],"d":[4,5,6]}', []));
$this->assertEquals([1, 2], $cast->get($model, 'column', '[1,2]', []));
$this->assertEquals([[1, 2, 3], [4, 5, 6]], $cast->get($model, 'column', '[[1,2,3],[4,5,6]]', []));
}

public function testTransformToDatabaseValue(): void
{
if (Comparator::lessThan($this->app->version(), '8.0.0')) {
$this->markTestSkipped('Cast classes have been added with Laravel 8.');
}

$cast = new JsonForceEmptyObjectAsArray();
$model = new class extends Model { };

$this->assertNull($cast->get($model, 'column', null, []));

$this->assertEquals('{}', $cast->set($model, 'column', [], []));
$this->assertEquals('{"a":1,"b":2}', $cast->set($model, 'column', ['a' => 1, 'b' => 2], []));
$this->assertEquals('{"c":[1,2,3],"d":[4,5,6]}', $cast->set($model, 'column', ['c' => [1, 2, 3], 'd' => [4, 5, 6]], []));
$this->assertEquals('[1,2]', $cast->set($model, 'column', [1, 2], []));
$this->assertEquals('[[1,2,3],[4,5,6]]', $cast->set($model, 'column', [[1, 2, 3], [4, 5, 6]], []));

$this->assertEquals('{}', $cast->set($model, 'column', collect(), []));
$this->assertEquals('{"a":1,"b":2}', $cast->set($model, 'column', collect(['a' => 1, 'b' => 2]), []));
$this->assertEquals('{"c":[1,2,3],"d":[4,5,6]}', $cast->set($model, 'column', collect(['c' => [1, 2, 3], 'd' => [4, 5, 6]]), []));
$this->assertEquals('[1,2]', $cast->set($model, 'column', collect([1, 2]), []));
$this->assertEquals('[[1,2,3],[4,5,6]]', $cast->set($model, 'column', collect([[1, 2, 3], [4, 5, 6]]), []));
}
}

0 comments on commit 514bf09

Please sign in to comment.