diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 5225cc01207f..3206de9a74a1 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -265,6 +265,37 @@ protected function addMutatedAttributesToArray(array $attributes, array $mutated return $attributes; } + /** + * Check if model has a given attribute. + * + * @param string $key + * @return bool + */ + public function hasAttribute($key) + { + if (array_key_exists($key, $this->attributes)) { + return true; + } + + if (array_key_exists($key, $this->casts)) { + return true; + } + + if ($this->hasGetMutator($key)) { + return true; + } + + if ($this->hasAttributeMutator($key)) { + return true; + } + + if ($this->isClassCastable($key)) { + return true; + } + + return false; + } + /** * Add the casted attributes to the attributes array. * @@ -432,13 +463,9 @@ public function getAttribute($key) } // If the attribute exists in the attribute array or has a "get" mutator we will - // get the attribute's value. Otherwise, we will proceed as if the developers + // return the attribute's value. Otherwise, we will proceed as if the developers // are asking for a relationship's value. This covers both types of values. - if (array_key_exists($key, $this->attributes) || - array_key_exists($key, $this->casts) || - $this->hasGetMutator($key) || - $this->hasAttributeMutator($key) || - $this->isClassCastable($key)) { + if ($this->hasAttribute($key)) { return $this->getAttributeValue($key); } diff --git a/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php b/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php index 4432a5bc6846..9917d537371f 100644 --- a/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php +++ b/tests/Database/DatabaseEloquentBelongsToManyWithCastedAttributesTest.php @@ -20,6 +20,7 @@ public function testModelsAreProperlyMatchedToParents() { $relation = $this->getRelation(); $model1 = m::mock(Model::class); + $model1->shouldReceive('hasAttribute')->passthru(); $model1->shouldReceive('getAttribute')->with('parent_key')->andReturn(1); $model1->shouldReceive('getAttribute')->with('foo')->passthru(); $model1->shouldReceive('hasGetMutator')->andReturn(false); @@ -28,6 +29,7 @@ public function testModelsAreProperlyMatchedToParents() $model1->shouldReceive('getRelationValue', 'relationLoaded', 'setRelation', 'isRelation')->passthru(); $model2 = m::mock(Model::class); + $model2->shouldReceive('hasAttribute')->passthru(); $model2->shouldReceive('getAttribute')->with('parent_key')->andReturn(2); $model2->shouldReceive('getAttribute')->with('foo')->passthru(); $model2->shouldReceive('hasGetMutator')->andReturn(false); diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 5c8cdea6f5af..3ddc482c2595 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject; use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection; use Illuminate\Database\Eloquent\Casts\AsStringable; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Database\Eloquent\MassAssignmentException; @@ -373,6 +374,49 @@ public function testOnly() $this->assertEquals(['first_name' => 'taylor', 'last_name' => 'otwell'], $model->only(['first_name', 'last_name'])); } + public function testHasAttributeWithAttributes() + { + $model = new EloquentModelStub; + $model->first_name = 'Taylor'; + $model->last_name = 'Otwell'; + + $this->assertTrue($model->hasAttribute('first_name')); + $this->assertTrue($model->hasAttribute('last_name')); + $this->assertFalse($model->hasAttribute('project')); + } + + public function testHasAttributeWithCasts() + { + $model = new EloquentModelStub; + + $this->assertTrue($model->hasAttribute('castedFloat')); + $this->assertFalse($model->hasAttribute('project')); + } + + public function testHasAttributeWithGetMutators() + { + $model = new EloquentModelGetMutatorsStub(); + + $this->assertTrue($model->hasAttribute('first_name')); + $this->assertFalse($model->hasAttribute('project')); + } + + public function testHasAttributeWithAttributeMutators() + { + $model = new EloquentModelWithAttributeMutator(); + + $this->assertTrue($model->hasAttribute('first_name')); + $this->assertFalse($model->hasAttribute('project')); + } + + public function testHasAttributeWithCastableCast() + { + $model = new EloquentModelCastingStub(); + + $this->assertTrue($model->hasAttribute('asarrayobjectAttribute')); + $this->assertFalse($model->hasAttribute('project')); + } + public function testNewInstanceReturnsNewInstanceWithAttributesSet() { $model = new EloquentModelStub; @@ -3047,6 +3091,20 @@ class EloquentModelWithUpdatedAtNull extends Model const UPDATED_AT = null; } +class EloquentModelWithAttributeMutator extends Model +{ + protected $table = 'stub'; + + public function firstName(): Attribute + { + return Attribute::make(function () { + return 'Taylor'; + }, function ($value) { + return $value; + }); + } +} + class UnsavedModel extends Model { protected $casts = ['name' => Uppercase::class];