Skip to content

Commit

Permalink
[10.x] Add only and except methods to Enum validation rule (#50226)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
Anton5360 and taylorotwell authored Feb 25, 2024
1 parent 30324cf commit 8d47be3
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 2 deletions.
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

0 comments on commit 8d47be3

Please sign in to comment.