Skip to content

Commit

Permalink
fix: ensure nested bags are cast correctly with Eloquent
Browse files Browse the repository at this point in the history
fixes #70
  • Loading branch information
dshafik committed Jan 24, 2025
1 parent efd21cd commit 057344c
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/Bag/Eloquent/Casts/AsBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public function set(Model $model, string $key, mixed $value, array $attributes):
return null;
}

if (is_array($value)) {
return [$key => json_encode($value)];
}

return [$key => json_encode($value->getRaw())];
}
}
138 changes: 138 additions & 0 deletions tests/Feature/Concerns/WithEloquentCastingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
use Tests\Fixtures\Models\CastedModel;
use Tests\Fixtures\Models\CastedModelLegacy;
use Tests\Fixtures\Values\BagWithCollection;
use Tests\Fixtures\Values\CastedModelValues;
use Tests\Fixtures\Values\HiddenParametersBag;
use Tests\Fixtures\Values\OptionalPropertiesBag;
use Tests\Fixtures\Values\TestBag;

covers(WithEloquentCasting::class, AsBag::class, AsBagCollection::class);
Expand Down Expand Up @@ -158,6 +160,74 @@
->and($model->hidden_bag->age)->toBe(40)
->and($model->hidden_bag->email)->toBe('[email protected]');
});

test('it stores bags with toArray on Laravel 11+', function () {
$model = CastedModel::create(CastedModelValues::from(
[
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]
)->toArray());

expect($model->bag)->toBeInstanceOf(TestBag::class)
->and($model->bag->name)->toBe('Davey Shafik')
->and($model->bag->age)->toBe(40)
->and($model->bag->email)->toBe('[email protected]');

assertDatabaseHas('testing', ['bag' => '{"name":"Davey Shafik","age":40,"email":"[email protected]"}']);
});

test('it stores nested bags with toArray on Laravel 11+', function () {
$model = CastedModel::create(CastedModelValues::from(
[
'optionalsBag' => OptionalPropertiesBag::from([
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]),
]
)->toArray());

expect($model->optionals_bag)
->toBeInstanceOf(OptionalPropertiesBag::class)
->and($model->optionals_bag->bag)->toBeInstanceOf(TestBag::class)
->and($model->optionals_bag->bag->name)->toBe('Davey Shafik')
->and($model->optionals_bag->bag->age)->toBe(40)
->and($model->optionals_bag->bag->email)->toBe('[email protected]');

assertDatabaseHas('testing', ['optionals_bag' => '{"name":null,"age":null,"email":null,"bag":{"name":"Davey Shafik","age":40,"email":"[email protected]"}}']);
});

test('it retrieves bags stored with toArray on Laravel 11+', function () {
CastedModel::create(CastedModelValues::from(
[
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]
)->toArray());

$model = CastedModel::first();

expect($model->bag)->toBeInstanceOf(TestBag::class)
->and($model->bag->name)->toBe('Davey Shafik')
->and($model->bag->age)->toBe(40)
->and($model->bag->email)->toBe('[email protected]');
});

test('it retrieves nested bags stored with toArray on Laravel 11+', function () {
CastedModel::create(CastedModelValues::from(
[
'optionalsBag' => OptionalPropertiesBag::from([
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]),
]
)->toArray());

$model = CastedModel::first();

expect($model->optionals_bag)
->toBeInstanceOf(OptionalPropertiesBag::class)
->and($model->optionals_bag->bag)->toBeInstanceOf(TestBag::class)
->and($model->optionals_bag->bag->name)->toBe('Davey Shafik')
->and($model->optionals_bag->bag->age)->toBe(40)
->and($model->optionals_bag->bag->email)->toBe('[email protected]');
});
})->skip(fn () => !version_compare(Application::VERSION, '11.0.0', '>='), 'Requires Laravel 11+');

describe('Laravel 10+', function () {
Expand Down Expand Up @@ -302,4 +372,72 @@
->and($model->hidden_bag->age)->toBe(40)
->and($model->hidden_bag->email)->toBe('[email protected]');
});

test('it stores bags with toArray on Laravel 10+', function () {
$model = CastedModelLegacy::create(CastedModelValues::from(
[
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]
)->toArray());

expect($model->bag)->toBeInstanceOf(TestBag::class)
->and($model->bag->name)->toBe('Davey Shafik')
->and($model->bag->age)->toBe(40)
->and($model->bag->email)->toBe('[email protected]');

assertDatabaseHas('testing', ['bag' => '{"name":"Davey Shafik","age":40,"email":"[email protected]"}']);
});

test('it stores nested bags with toArray on Laravel 10+', function () {
$model = CastedModelLegacy::create(CastedModelValues::from(
[
'optionalsBag' => OptionalPropertiesBag::from([
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]),
]
)->toArray());

expect($model->optionals_bag)
->toBeInstanceOf(OptionalPropertiesBag::class)
->and($model->optionals_bag->bag)->toBeInstanceOf(TestBag::class)
->and($model->optionals_bag->bag->name)->toBe('Davey Shafik')
->and($model->optionals_bag->bag->age)->toBe(40)
->and($model->optionals_bag->bag->email)->toBe('[email protected]');

assertDatabaseHas('testing', ['optionals_bag' => '{"name":null,"age":null,"email":null,"bag":{"name":"Davey Shafik","age":40,"email":"[email protected]"}}']);
});

test('it retrieves bags stored with toArray on Laravel 10+', function () {
CastedModelLegacy::create(CastedModelValues::from(
[
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]
)->toArray());

$model = CastedModelLegacy::first();

expect($model->bag)->toBeInstanceOf(TestBag::class)
->and($model->bag->name)->toBe('Davey Shafik')
->and($model->bag->age)->toBe(40)
->and($model->bag->email)->toBe('[email protected]');
});

test('it retrieves nested bags stored with toArray on Laravel 10+', function () {
CastedModelLegacy::create(CastedModelValues::from(
[
'optionalsBag' => OptionalPropertiesBag::from([
'bag' => TestBag::from(['name' => 'Davey Shafik', 'age' => 40, 'email' => '[email protected]'])
]),
]
)->toArray());

$model = CastedModelLegacy::first();

expect($model->optionals_bag)
->toBeInstanceOf(OptionalPropertiesBag::class)
->and($model->optionals_bag->bag)->toBeInstanceOf(TestBag::class)
->and($model->optionals_bag->bag->name)->toBe('Davey Shafik')
->and($model->optionals_bag->bag->age)->toBe(40)
->and($model->optionals_bag->bag->email)->toBe('[email protected]');
});
});
7 changes: 5 additions & 2 deletions tests/Fixtures/Models/CastedModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,28 @@
use Tests\Fixtures\Collections\BagWithCollectionCollection;
use Tests\Fixtures\Values\BagWithCollection;
use Tests\Fixtures\Values\HiddenParametersBag;
use Tests\Fixtures\Values\OptionalPropertiesBag;
use Tests\Fixtures\Values\TestBag;

/**
* @property int $id
* @property TestBag $bag
* @property OptionalPropertiesBag $optionals_bag
* @property HiddenParametersBag $hidden_bag
* @property Collection $collection
* @property Collection<TestBag> $collection
* @property BagWithCollectionCollection $custom_collection
*/
class CastedModel extends Model
{
protected $table = 'testing';

protected $fillable = ['bag', 'hidden_bag', 'collection', 'custom_collection'];
protected $fillable = ['bag', 'optionals_bag', 'hidden_bag', 'collection', 'custom_collection'];

protected function casts()
{
return [
'bag' => TestBag::class,
'optionals_bag' => OptionalPropertiesBag::class,
'hidden_bag' => HiddenParametersBag::class,
'collection' => TestBag::castAsCollection(),
'custom_collection' => BagWithCollection::castAsCollection(),
Expand Down
5 changes: 4 additions & 1 deletion tests/Fixtures/Models/CastedModelLegacy.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
use Tests\Fixtures\Collections\BagWithCollectionCollection;
use Tests\Fixtures\Values\BagWithCollection;
use Tests\Fixtures\Values\HiddenParametersBag;
use Tests\Fixtures\Values\OptionalPropertiesBag;
use Tests\Fixtures\Values\TestBag;

/**
* @property int $id
* @property TestBag $bag
* @property OptionalPropertiesBag $optionals_bag
* @property HiddenParametersBag $hidden_bag
* @property Collection $collection
* @property BagWithCollectionCollection $custom_collection
Expand All @@ -23,10 +25,11 @@ class CastedModelLegacy extends Model
{
protected $table = 'testing';

protected $fillable = ['bag', 'hidden_bag', 'collection', 'custom_collection'];
protected $fillable = ['bag', 'optionals_bag', 'hidden_bag', 'collection', 'custom_collection'];

protected $casts = [
'bag' => TestBag::class,
'optionals_bag' => OptionalPropertiesBag::class,
'hidden_bag' => HiddenParametersBag::class,
'collection' => AsBagCollection::class . ':' . TestBag::class,
'custom_collection' => AsBagCollection::class . ':' . BagWithCollection::class,
Expand Down
22 changes: 22 additions & 0 deletions tests/Fixtures/Values/CastedModelValues.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Tests\Fixtures\Values;

use Bag\Attributes\MapName;
use Bag\Bag;
use Bag\Mappers\SnakeCase;

#[MapName(SnakeCase::class, SnakeCase::class)]
readonly class CastedModelValues extends Bag
{
public function __construct(
public ?string $name = null,
public ?int $age = null,
public ?string $email = null,
public ?TestBag $bag = null,
public ?OptionalPropertiesBag $optionalsBag = null,
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function up(): void
$table->json('collection')->nullable();
$table->json('custom_collection')->nullable();
$table->json('hidden_bag')->nullable();
$table->json('optionals_bag')->nullable();
$table->timestamps();
});
}
Expand Down

0 comments on commit 057344c

Please sign in to comment.