Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Add only and except methods to Enum validation rule #50226

62 changes: 60 additions & 2 deletions src/Illuminate/Validation/Rules/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Support\Arr;
use TypeError;

class Enum implements Rule, ValidatorAwareRule
Expand All @@ -22,6 +23,20 @@ class Enum implements Rule, ValidatorAwareRule
*/
protected $validator;

/**
* The cases that should be considered valid.
*
* @var array
*/
protected $only = [];

/**
* The cases that should be considered invalid.
*
* @var array
*/
protected $except = [];

/**
* Create a new rule instance.
*
Expand All @@ -43,20 +58,63 @@ public function __construct($type)
public function passes($attribute, $value)
{
if ($value instanceof $this->type) {
return true;
return $this->isDesirable($value);
}

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

try {
return ! is_null($this->type::tryFrom($value));
$value = $this->type::tryFrom($value);

return ! is_null($value) && $this->isDesirable($value);
} catch (TypeError) {
return false;
}
}

/**
* Specify the cases that should be considered valid.
*
* @param \UnitEnum[]|\UnitEnum $values
* @return $this
*/
public function only($values)
{
$this->only = Arr::wrap($values);

return $this;
}

/**
* Specify the cases that should be considered invalid.
*
* @param \UnitEnum[]|\UnitEnum $values
* @return $this
*/
public function except($values)
{
$this->except = Arr::wrap($values);

return $this;
}

/**
* Determine if the given case is a valid case based on the only / except values.
*
* @param mixed $value
* @return bool
*/
protected function isDesirable($value)
{
return match (true) {
! empty($this->only) => in_array(needle: $value, haystack: $this->only, strict: true),
! empty($this->except) => ! in_array(needle: $value, haystack: $this->except, strict: true),
default => true,
};
}

/**
* Get the validation error message.
*
Expand Down
88 changes: 88 additions & 0 deletions tests/Validation/ValidationEnumRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,84 @@ public function testValidationFailsWhenProvidingNoExistingCases()
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
}

public function testValidationPassesForAllCasesUntilEitherOnlyOrExceptIsPassed()
{
$v = new Validator(
resolve('translator'),
[
'status_1' => PureEnum::one,
'status_2' => PureEnum::two,
'status_3' => IntegerStatus::done->value,
],
[
'status_1' => new Enum(PureEnum::class),
'status_2' => (new Enum(PureEnum::class))->only([])->except([]),
'status_3' => new Enum(IntegerStatus::class),
],
);

$this->assertTrue($v->passes());
}

/**
* @dataProvider conditionalCasesDataProvider
*/
public function testValidationPassesWhenOnlyCasesProvided(
IntegerStatus|int $enum,
array|IntegerStatus $only,
bool $expected
) {
$v = new Validator(
resolve('translator'),
[
'status' => $enum,
],
[
'status' => (new Enum(IntegerStatus::class))->only($only),
],
);

$this->assertSame($expected, $v->passes());
}

/**
* @dataProvider conditionalCasesDataProvider
*/
public function testValidationPassesWhenExceptCasesProvided(
int|IntegerStatus $enum,
array|IntegerStatus $except,
bool $expected
) {
$v = new Validator(
resolve('translator'),
[
'status' => $enum,
],
[
'status' => (new Enum(IntegerStatus::class))->except($except),
],
);

$this->assertSame($expected, $v->fails());
}

public function testOnlyHasHigherOrderThanExcept()
{
$v = new Validator(
resolve('translator'),
[
'status' => PureEnum::one,
],
[
'status' => (new Enum(PureEnum::class))
->only(PureEnum::one)
->except(PureEnum::one),
],
);

$this->assertTrue($v->passes());
}

public function testValidationFailsWhenProvidingDifferentType()
{
$v = new Validator(
Expand Down Expand Up @@ -171,6 +249,16 @@ public function testValidationFailsWhenProvidingStringToIntegerType()
$this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status'));
}

public static function conditionalCasesDataProvider(): array
{
return [
[IntegerStatus::done, IntegerStatus::done, true],
[IntegerStatus::done, [IntegerStatus::done, IntegerStatus::pending], true],
[IntegerStatus::pending->value, [IntegerStatus::done, IntegerStatus::pending], true],
[IntegerStatus::done->value, IntegerStatus::pending, false],
];
}

protected function setUp(): void
{
$container = Container::getInstance();
Expand Down
Loading