Skip to content

Commit 44f9749

Browse files
authored
[8.x] Add an Enum validation rule (#39437)
* add an Enum validation rule * fix style * fix test requirements * fix style
1 parent d0353b8 commit 44f9749

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Illuminate\Validation\Rules;
4+
5+
use Illuminate\Contracts\Validation\Rule;
6+
7+
class Enum implements Rule
8+
{
9+
/**
10+
* The type of the enum.
11+
*
12+
* @var string
13+
*/
14+
protected $type;
15+
16+
/**
17+
* Create a new rule instance.
18+
*
19+
* @param string $type
20+
* @return void
21+
*/
22+
public function __construct($type)
23+
{
24+
$this->type = $type;
25+
}
26+
27+
/**
28+
* Determine if the validation rule passes.
29+
*
30+
* @param string $attribute
31+
* @param mixed $value
32+
* @return bool
33+
*/
34+
public function passes($attribute, $value)
35+
{
36+
if (is_null($value) || ! function_exists('enum_exists') || ! enum_exists($this->type)) {
37+
return false;
38+
}
39+
40+
return ! is_null($this->type::tryFrom($value));
41+
}
42+
43+
/**
44+
* Get the validation error message.
45+
*
46+
* @return array
47+
*/
48+
public function message()
49+
{
50+
return [
51+
'The selected :attribute is invalid.',
52+
];
53+
}
54+
}

tests/Validation/Enums.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Validation;
4+
5+
enum StringStatus: string
6+
{
7+
case pending = 'pending';
8+
case done = 'done';
9+
}
10+
11+
enum IntegerStatus: int
12+
{
13+
case pending = 1;
14+
case done = 2;
15+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Validation;
4+
5+
use Illuminate\Container\Container;
6+
use Illuminate\Support\Facades\Facade;
7+
use Illuminate\Translation\ArrayLoader;
8+
use Illuminate\Translation\Translator;
9+
use Illuminate\Validation\Rules\Enum;
10+
use Illuminate\Validation\Rules\Password;
11+
use Illuminate\Validation\ValidationServiceProvider;
12+
use Illuminate\Validation\Validator;
13+
use PHPUnit\Framework\TestCase;
14+
15+
if (PHP_VERSION_ID >= 80100) {
16+
include 'Enums.php';
17+
}
18+
19+
/**
20+
* @requires PHP >= 8.1
21+
*/
22+
class ValidationEnumRuleTest extends TestCase
23+
{
24+
public function testvalidationPassesWhenPassingCorrectEnum()
25+
{
26+
$v = new Validator(
27+
resolve('translator'),
28+
[
29+
'status' => 'pending',
30+
'int_status' => 1,
31+
],
32+
[
33+
'status' => new Enum(StringStatus::class),
34+
'int_status' => new Enum(IntegerStatus::class),
35+
]
36+
);
37+
38+
$this->assertFalse($v->fails());
39+
}
40+
41+
public function testValidationFailsWhenProvidingNoExistingCases()
42+
{
43+
$v = new Validator(
44+
resolve('translator'),
45+
[
46+
'status' => 'finished',
47+
],
48+
[
49+
'status' => new Enum(StringStatus::class),
50+
]
51+
);
52+
53+
$this->assertTrue($v->fails());
54+
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
55+
}
56+
57+
public function testValidationFailsWhenProvidingDifferentType()
58+
{
59+
$v = new Validator(
60+
resolve('translator'),
61+
[
62+
'status' => 10,
63+
],
64+
[
65+
'status' => new Enum(StringStatus::class),
66+
]
67+
);
68+
69+
$this->assertTrue($v->fails());
70+
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
71+
}
72+
73+
public function testValidationPassesWhenProvidingDifferentTypeThatIsCastableToTheEnumType()
74+
{
75+
$v = new Validator(
76+
resolve('translator'),
77+
[
78+
'status' => '1',
79+
],
80+
[
81+
'status' => new Enum(IntegerStatus::class),
82+
]
83+
);
84+
85+
$this->assertFalse($v->fails());
86+
}
87+
88+
public function testValidationFailsWhenProvidingNull()
89+
{
90+
$v = new Validator(
91+
resolve('translator'),
92+
[
93+
'status' => null,
94+
],
95+
[
96+
'status' => new Enum(StringStatus::class),
97+
]
98+
);
99+
100+
$this->assertTrue($v->fails());
101+
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
102+
}
103+
104+
public function testValidationPassesWhenProvidingNullButTheFieldIsNullable()
105+
{
106+
$v = new Validator(
107+
resolve('translator'),
108+
[
109+
'status' => null,
110+
],
111+
[
112+
'status' => ['nullable', new Enum(StringStatus::class)],
113+
]
114+
);
115+
116+
$this->assertFalse($v->fails());
117+
}
118+
119+
protected function setUp(): void
120+
{
121+
$container = Container::getInstance();
122+
123+
$container->bind('translator', function () {
124+
return new Translator(
125+
new ArrayLoader, 'en'
126+
);
127+
});
128+
129+
Facade::setFacadeApplication($container);
130+
131+
(new ValidationServiceProvider($container))->register();
132+
}
133+
134+
protected function tearDown(): void
135+
{
136+
Container::setInstance(null);
137+
138+
Facade::clearResolvedInstances();
139+
140+
Facade::setFacadeApplication(null);
141+
142+
Password::$defaultCallback = null;
143+
}
144+
}

0 commit comments

Comments
 (0)