Skip to content

Commit 8d47be3

Browse files
[10.x] Add only and except methods to Enum validation rule (#50226)
* Implement only and except logic * Cover only and except logic with tests * Fix code styling * Fix code styling * Improve php doc * Fix code styling * Fix code styling * formatting * fix visibility * fix type hints * remove type hint * fix type hints --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 30324cf commit 8d47be3

File tree

2 files changed

+148
-2
lines changed

2 files changed

+148
-2
lines changed

src/Illuminate/Validation/Rules/Enum.php

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Contracts\Validation\Rule;
66
use Illuminate\Contracts\Validation\ValidatorAwareRule;
7+
use Illuminate\Support\Arr;
78
use TypeError;
89

910
class Enum implements Rule, ValidatorAwareRule
@@ -22,6 +23,20 @@ class Enum implements Rule, ValidatorAwareRule
2223
*/
2324
protected $validator;
2425

26+
/**
27+
* The cases that should be considered valid.
28+
*
29+
* @var array
30+
*/
31+
protected $only = [];
32+
33+
/**
34+
* The cases that should be considered invalid.
35+
*
36+
* @var array
37+
*/
38+
protected $except = [];
39+
2540
/**
2641
* Create a new rule instance.
2742
*
@@ -43,20 +58,63 @@ public function __construct($type)
4358
public function passes($attribute, $value)
4459
{
4560
if ($value instanceof $this->type) {
46-
return true;
61+
return $this->isDesirable($value);
4762
}
4863

4964
if (is_null($value) || ! enum_exists($this->type) || ! method_exists($this->type, 'tryFrom')) {
5065
return false;
5166
}
5267

5368
try {
54-
return ! is_null($this->type::tryFrom($value));
69+
$value = $this->type::tryFrom($value);
70+
71+
return ! is_null($value) && $this->isDesirable($value);
5572
} catch (TypeError) {
5673
return false;
5774
}
5875
}
5976

77+
/**
78+
* Specify the cases that should be considered valid.
79+
*
80+
* @param \UnitEnum[]|\UnitEnum $values
81+
* @return $this
82+
*/
83+
public function only($values)
84+
{
85+
$this->only = Arr::wrap($values);
86+
87+
return $this;
88+
}
89+
90+
/**
91+
* Specify the cases that should be considered invalid.
92+
*
93+
* @param \UnitEnum[]|\UnitEnum $values
94+
* @return $this
95+
*/
96+
public function except($values)
97+
{
98+
$this->except = Arr::wrap($values);
99+
100+
return $this;
101+
}
102+
103+
/**
104+
* Determine if the given case is a valid case based on the only / except values.
105+
*
106+
* @param mixed $value
107+
* @return bool
108+
*/
109+
protected function isDesirable($value)
110+
{
111+
return match (true) {
112+
! empty($this->only) => in_array(needle: $value, haystack: $this->only, strict: true),
113+
! empty($this->except) => ! in_array(needle: $value, haystack: $this->except, strict: true),
114+
default => true,
115+
};
116+
}
117+
60118
/**
61119
* Get the validation error message.
62120
*

tests/Validation/ValidationEnumRuleTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,84 @@ public function testValidationFailsWhenProvidingNoExistingCases()
7878
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
7979
}
8080

81+
public function testValidationPassesForAllCasesUntilEitherOnlyOrExceptIsPassed()
82+
{
83+
$v = new Validator(
84+
resolve('translator'),
85+
[
86+
'status_1' => PureEnum::one,
87+
'status_2' => PureEnum::two,
88+
'status_3' => IntegerStatus::done->value,
89+
],
90+
[
91+
'status_1' => new Enum(PureEnum::class),
92+
'status_2' => (new Enum(PureEnum::class))->only([])->except([]),
93+
'status_3' => new Enum(IntegerStatus::class),
94+
],
95+
);
96+
97+
$this->assertTrue($v->passes());
98+
}
99+
100+
/**
101+
* @dataProvider conditionalCasesDataProvider
102+
*/
103+
public function testValidationPassesWhenOnlyCasesProvided(
104+
IntegerStatus|int $enum,
105+
array|IntegerStatus $only,
106+
bool $expected
107+
) {
108+
$v = new Validator(
109+
resolve('translator'),
110+
[
111+
'status' => $enum,
112+
],
113+
[
114+
'status' => (new Enum(IntegerStatus::class))->only($only),
115+
],
116+
);
117+
118+
$this->assertSame($expected, $v->passes());
119+
}
120+
121+
/**
122+
* @dataProvider conditionalCasesDataProvider
123+
*/
124+
public function testValidationPassesWhenExceptCasesProvided(
125+
int|IntegerStatus $enum,
126+
array|IntegerStatus $except,
127+
bool $expected
128+
) {
129+
$v = new Validator(
130+
resolve('translator'),
131+
[
132+
'status' => $enum,
133+
],
134+
[
135+
'status' => (new Enum(IntegerStatus::class))->except($except),
136+
],
137+
);
138+
139+
$this->assertSame($expected, $v->fails());
140+
}
141+
142+
public function testOnlyHasHigherOrderThanExcept()
143+
{
144+
$v = new Validator(
145+
resolve('translator'),
146+
[
147+
'status' => PureEnum::one,
148+
],
149+
[
150+
'status' => (new Enum(PureEnum::class))
151+
->only(PureEnum::one)
152+
->except(PureEnum::one),
153+
],
154+
);
155+
156+
$this->assertTrue($v->passes());
157+
}
158+
81159
public function testValidationFailsWhenProvidingDifferentType()
82160
{
83161
$v = new Validator(
@@ -171,6 +249,16 @@ public function testValidationFailsWhenProvidingStringToIntegerType()
171249
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
172250
}
173251

252+
public static function conditionalCasesDataProvider(): array
253+
{
254+
return [
255+
[IntegerStatus::done, IntegerStatus::done, true],
256+
[IntegerStatus::done, [IntegerStatus::done, IntegerStatus::pending], true],
257+
[IntegerStatus::pending->value, [IntegerStatus::done, IntegerStatus::pending], true],
258+
[IntegerStatus::done->value, IntegerStatus::pending, false],
259+
];
260+
}
261+
174262
protected function setUp(): void
175263
{
176264
$container = Container::getInstance();

0 commit comments

Comments
 (0)