diff --git a/README.md b/README.md index a7789dc..e78941e 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,40 @@ $user->address->calculateDistance($otherUser->address); echo (string) $user->address; ``` + +### Using defaults for null database values + +By default, if a database value is `null`, then the model attribute will also be `null`. However, sometimes you might want to instantiate the attribute with some default values. + +To achieve this, you may provide an additional `nullable` [Cast Parameter](https://laravel.com/docs/eloquent-mutators#cast-parameters) to ensure the caster gets instantiated. + +```php +namespace App\Models; + +use App\Values\Address; +use Illuminate\Database\Eloquent\Model; + +class User extends Model +{ + protected $casts = [ + 'settings' => Settings::class . ':nullable', + ]; +} +``` + +This will ensure that the `Settings` caster is instantiated even when the `settings` column in the database is `null`. + +You may then specify some default values in the cast which will be used instead. + +```php +use JessArcher\CastableDataTransferObject\CastableDataTransferObject; + +class Settings extends CastableDataTransferObject +{ + public string $title = 'Default'; +} +``` + ### Controlling serialization You may provide the caster with flags to be used for serialization by adding the `CastUsingJsonFlags` attribute to your object: diff --git a/composer.json b/composer.json index 5c9c52a..bdd25cc 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require": { "php": "^8.0", "illuminate/contracts": "^8.0|^9.0", - "spatie/data-transfer-object": "^3.0", + "spatie/data-transfer-object": "^3.0.4", "thecodingmachine/safe": "^1.3|^2.0" }, "require-dev": { diff --git a/src/CastableDataTransferObject.php b/src/CastableDataTransferObject.php index b307335..937dcb9 100644 --- a/src/CastableDataTransferObject.php +++ b/src/CastableDataTransferObject.php @@ -14,7 +14,7 @@ abstract class CastableDataTransferObject extends DataTransferObject implements { public static function castUsing(array $arguments) { - return new DataTransferObjectCast(static::class); + return new DataTransferObjectCast(static::class, $arguments); } public function toJson($options = 0) diff --git a/src/Casts/DataTransferObject.php b/src/Casts/DataTransferObject.php index 530d1f2..ebe912b 100644 --- a/src/Casts/DataTransferObject.php +++ b/src/Casts/DataTransferObject.php @@ -12,6 +12,8 @@ class DataTransferObject implements CastsAttributes public function __construct( /** @var string The DataTransferObject class to cast to */ protected string $class, + /** @var array The cast parameters specified */ + protected array $parameters = [], ) { } @@ -20,6 +22,10 @@ public function __construct( */ public function get($model, string $key, $value, array $attributes) { + if (is_null($value) && in_array('nullable', $this->parameters)) { + $value = '{}'; + } + if (is_null($value)) { return; } diff --git a/tests/CastableDataTransferObjectTest.php b/tests/CastableDataTransferObjectTest.php index 5961134..72d5af5 100644 --- a/tests/CastableDataTransferObjectTest.php +++ b/tests/CastableDataTransferObjectTest.php @@ -127,6 +127,17 @@ public function cast_uses_specified_flags_for_data_transfer_objects() $this->assertSame('{"floatValue":52.0}', $user->with_flags); $this->assertSame('{"floatValue":20}', $user->without_flags); } + + /** @test */ + public function it_passes_null_values_to_caster_when_nullable_cast_attribute_present() + { + $user = User::factory()->create(['settings' => null]); + + $this->assertDatabaseHas('users', ['settings' => null]); + + $this->assertInstanceOf(Settings::class, $user->refresh()->settings); + $this->assertEquals('Default', $user->settings->title); + } } class Address extends CastableDataTransferObject @@ -147,6 +158,11 @@ class DataTransferObjectWithoutFlags extends CastableDataTransferObject public float $floatValue; } +class Settings extends CastableDataTransferObject +{ + public string $title = 'Default'; +} + class User extends Model { use HasFactory; @@ -155,6 +171,7 @@ class User extends Model 'address' => Address::class, 'with_flags' => DataTransferObjectWithFlags::class, 'without_flags' => DataTransferObjectWithoutFlags::class, + 'settings' => Settings::class . ':nullable', ]; protected static function newFactory() diff --git a/tests/database/migrations/2014_10_12_000000_create_users_table.php b/tests/database/migrations/2014_10_12_000000_create_users_table.php index d9b15c0..c2ee9dc 100644 --- a/tests/database/migrations/2014_10_12_000000_create_users_table.php +++ b/tests/database/migrations/2014_10_12_000000_create_users_table.php @@ -23,6 +23,7 @@ public function up() $table->jsonb('address')->nullable(); $table->jsonb('with_flags')->nullable(); $table->jsonb('without_flags')->nullable(); + $table->jsonb('settings')->nullable(); $table->timestamps(); }); }