diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index de94098dcfeb..3d8426c2a85d 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -448,7 +448,9 @@ protected function setForeignAttributesForCreate(Model $model) $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); foreach ($this->getQuery()->pendingAttributes as $key => $value) { - if (! $model->hasAttribute($key)) { + $attributes ??= $model->getAttributes(); + + if (! array_key_exists($key, $attributes)) { $model->setAttribute($key, $value); } } diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php index 44531957d5b7..1e879c1dcef1 100755 --- a/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php @@ -97,7 +97,9 @@ protected function setForeignAttributesForCreate(Model $model) $model->{$this->getMorphType()} = $this->morphClass; foreach ($this->getQuery()->pendingAttributes as $key => $value) { - if (! $model->hasAttribute($key)) { + $attributes ??= $model->getAttributes(); + + if (! array_key_exists($key, $attributes)) { $model->setAttribute($key, $value); } } diff --git a/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php b/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php index 80f1e677eb37..ed686e594121 100755 --- a/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php +++ b/tests/Database/DatabaseEloquentHasOneOrManyWithAttributesTest.php @@ -267,9 +267,30 @@ public function testOneKeepsAttributesFromMorphMany(): void $this->assertSame($parent::class, $relatedModel->relatable_type); $this->assertSame($value, $relatedModel->$key); } + + public function testHasManyAddsCastedAttributes(): void + { + $parentId = 123; + + $parent = new RelatedWithAttributesModel; + $parent->id = $parentId; + + $relationship = $parent + ->hasMany(RelatedWithAttributesModel::class, 'parent_id') + ->withAttributes(['is_admin' => 1]); + + $relatedModel = $relationship->make(); + + $this->assertSame($parentId, $relatedModel->parent_id); + $this->assertSame(true, $relatedModel->is_admin); + } } class RelatedWithAttributesModel extends Model { protected $guarded = []; + + protected $casts = [ + 'is_admin' => 'boolean', + ]; } diff --git a/tests/Database/DatabaseEloquentWithAttributesTest.php b/tests/Database/DatabaseEloquentWithAttributesTest.php index 85b11d7991f3..bf033678080c 100755 --- a/tests/Database/DatabaseEloquentWithAttributesTest.php +++ b/tests/Database/DatabaseEloquentWithAttributesTest.php @@ -3,7 +3,9 @@ namespace Illuminate\Tests\Database; use Illuminate\Database\Capsule\Manager as DB; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Schema\Builder; use PHPUnit\Framework\TestCase; class DatabaseEloquentWithAttributesTest extends TestCase @@ -20,6 +22,11 @@ protected function setUp(): void $db->setAsGlobal(); } + protected function tearDown(): void + { + $this->schema()->dropIfExists((new WithAttributesModel)->getTable()); + } + public function testAddsAttributes(): void { $key = 'a key'; @@ -51,9 +58,101 @@ public function testAddsWheres(): void 'boolean' => 'and', ], $wheres); } + + public function testAddsWithCasts(): void + { + $query = WithAttributesModel::query() + ->withAttributes([ + 'is_admin' => 1, + 'first_name' => 'FIRST', + 'last_name' => 'LAST', + 'type' => WithAttributesEnum::internal, + ]); + + $model = $query->make(); + + $this->assertSame(true, $model->is_admin); + $this->assertSame('First', $model->first_name); + $this->assertSame('Last', $model->last_name); + $this->assertSame(WithAttributesEnum::internal, $model->type); + + $this->assertEqualsCanonicalizing([ + 'is_admin' => 1, + 'first_name' => 'first', + 'last_name' => 'last', + 'type' => 'int', + ], $model->getAttributes()); + } + + public function testAddsWithCastsViaDb(): void + { + $this->bootTable(); + + $query = WithAttributesModel::query() + ->withAttributes([ + 'is_admin' => 1, + 'first_name' => 'FIRST', + 'last_name' => 'LAST', + 'type' => WithAttributesEnum::internal, + ]); + + $query->create(); + + $model = WithAttributesModel::first(); + + $this->assertSame(true, $model->is_admin); + $this->assertSame('First', $model->first_name); + $this->assertSame('Last', $model->last_name); + $this->assertSame(WithAttributesEnum::internal, $model->type); + } + + protected function bootTable(): void + { + $this->schema()->create((new WithAttributesModel)->getTable(), function ($table) { + $table->id(); + $table->boolean('is_admin'); + $table->string('first_name'); + $table->string('last_name'); + $table->string('type'); + $table->timestamps(); + }); + } + + protected function schema(): Builder + { + return WithAttributesModel::getConnectionResolver()->connection()->getSchemaBuilder(); + } } class WithAttributesModel extends Model { protected $guarded = []; + + protected $casts = [ + 'is_admin' => 'boolean', + 'type' => WithAttributesEnum::class, + ]; + + public function setFirstNameAttribute(string $value): void + { + $this->attributes['first_name'] = strtolower($value); + } + + public function getFirstNameAttribute(?string $value): string + { + return ucfirst($value); + } + + protected function lastName(): Attribute + { + return Attribute::make( + get: fn (string $value) => ucfirst($value), + set: fn (string $value) => strtolower($value), + ); + } +} + +enum WithAttributesEnum: string +{ + case internal = 'int'; }