Skip to content

Commit 8647dcf

Browse files
PHLAKtaylorotwell
andauthored
[11.x] Prevent attributes cast to an enum from being set to another enum (#47465)
* Prevent attributes cast to an enum from being set to another enum * formatting --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent ecaf5e3 commit 8647dcf

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use ReflectionClass;
4040
use ReflectionMethod;
4141
use ReflectionNamedType;
42+
use ValueError;
4243

4344
trait HasAttributes
4445
{
@@ -309,7 +310,7 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt
309310
}
310311

311312
if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) {
312-
$attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($attributes[$key]) : null;
313+
$attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($this->getCasts()[$key], $attributes[$key]) : null;
313314
}
314315

315316
if ($attributes[$key] instanceof Arrayable) {
@@ -1155,10 +1156,10 @@ protected function setEnumCastableAttribute($key, $value)
11551156
if (! isset($value)) {
11561157
$this->attributes[$key] = null;
11571158
} elseif (is_object($value)) {
1158-
$this->attributes[$key] = $this->getStorableEnumValue($value);
1159+
$this->attributes[$key] = $this->getStorableEnumValue($enumClass, $value);
11591160
} else {
11601161
$this->attributes[$key] = $this->getStorableEnumValue(
1161-
$this->getEnumCaseFromValue($enumClass, $value)
1162+
$enumClass, $this->getEnumCaseFromValue($enumClass, $value)
11621163
);
11631164
}
11641165
}
@@ -1180,11 +1181,16 @@ protected function getEnumCaseFromValue($enumClass, $value)
11801181
/**
11811182
* Get the storable value from the given enum.
11821183
*
1184+
* @param string $expectedEnum
11831185
* @param \UnitEnum|\BackedEnum $value
11841186
* @return string|int
11851187
*/
1186-
protected function getStorableEnumValue($value)
1188+
protected function getStorableEnumValue($expectedEnum, $value)
11871189
{
1190+
if (! $value instanceof $expectedEnum) {
1191+
throw new ValueError(sprintf('Value [%s] is not of the expected enum type [%s].', var_export($value, true), $expectedEnum));
1192+
}
1193+
11881194
return $value instanceof BackedEnum
11891195
? $value->value
11901196
: $value->name;

tests/Integration/Database/EloquentModelEnumCastingTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Database\Schema\Blueprint;
99
use Illuminate\Support\Facades\DB;
1010
use Illuminate\Support\Facades\Schema;
11+
use ValueError;
1112

1213
include 'Enums.php';
1314

@@ -264,6 +265,39 @@ public function testFirstOrCreate()
264265
$this->assertEquals(StringStatus::pending, $model->string_status);
265266
$this->assertEquals(StringStatus::done, $model2->string_status);
266267
}
268+
269+
public function testAttributeCastToAnEnumCanNotBeSetToAnotherEnum(): void
270+
{
271+
$model = new EloquentModelEnumCastingTestModel;
272+
273+
$this->expectException(ValueError::class);
274+
$this->expectExceptionMessage(
275+
sprintf('Value [%s] is not of the expected enum type [%s].', var_export(ArrayableStatus::pending, true), StringStatus::class)
276+
);
277+
278+
$model->string_status = ArrayableStatus::pending;
279+
}
280+
281+
public function testAttributeCastToAnEnumCanNotBeSetToAValueNotDefinedOnTheEnum(): void
282+
{
283+
$model = new EloquentModelEnumCastingTestModel;
284+
285+
$this->expectException(ValueError::class);
286+
$this->expectExceptionMessage(
287+
sprintf('"unexpected_value" is not a valid backing value for enum %s', StringStatus::class)
288+
);
289+
290+
$model->string_status = 'unexpected_value';
291+
}
292+
293+
public function testAnAttributeWithoutACastCanBeSetToAnEnum(): void
294+
{
295+
$model = new EloquentModelEnumCastingTestModel;
296+
297+
$model->non_enum_status = StringStatus::pending;
298+
299+
$this->assertEquals(StringStatus::pending, $model->non_enum_status);
300+
}
267301
}
268302

269303
class EloquentModelEnumCastingTestModel extends Model

0 commit comments

Comments
 (0)