Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 5c2a8eb

Browse files
authored
Merge pull request #7 from programmatordev/YAPV-8-create-choice-rule
Create Choice rule
2 parents 0d6f113 + 7451c90 commit 5c2a8eb

14 files changed

+326
-67
lines changed

src/ChainedValidatorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ public function notBlank(array $options = []): ChainedValidatorInterface;
2020
public function greaterThan(mixed $constraint, array $options = []): ChainedValidatorInterface;
2121

2222
public function lessThan(mixed $constraint, array $options = []): ChainedValidatorInterface;
23+
24+
public function choice(array $constraints, bool $multiple = false, ?int $minConstraint = null, ?int $maxConstraint = null, array $options = []): ChainedValidatorInterface;
2325
}

src/Exception/ChoiceException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Exception;
4+
5+
class ChoiceException extends ValidationException {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Exception;
4+
5+
class UnexpectedValueException extends \UnexpectedValueException {}

src/Rule/Choice.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Rule;
4+
5+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\ChoiceException;
6+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
7+
use Symfony\Component\OptionsResolver\OptionsResolver;
8+
9+
class Choice extends AbstractRule implements RuleInterface
10+
{
11+
private array $options;
12+
13+
public function __construct(
14+
private readonly array $constraints,
15+
private readonly bool $multiple = false,
16+
private readonly ?int $minConstraint = null,
17+
private readonly ?int $maxConstraint = null,
18+
array $options = []
19+
)
20+
{
21+
$resolver = new OptionsResolver();
22+
23+
$resolver->setDefaults([
24+
'message' => 'The "{{ name }}" value is not a valid choice, "{{ value }}" given. Accepted values are: "{{ constraints }}".',
25+
'multipleMessage' => 'The "{{ name }}" value has one or more invalid choices, "{{ value }}" given. Accepted values are: "{{ constraints }}".',
26+
'maxMessage' => 'The "{{ name }}" value must have at most {{ maxConstraint }} choices, {{ numValues }} choices given.',
27+
'minMessage' => 'The "{{ name }}" value must have at least {{ minConstraint }} choices, {{ numValues }} choices given.'
28+
]);
29+
30+
$resolver->setAllowedTypes('message', 'string');
31+
$resolver->setAllowedTypes('multipleMessage', 'string');
32+
$resolver->setAllowedTypes('maxMessage', 'string');
33+
$resolver->setAllowedTypes('minMessage', 'string');
34+
35+
$this->options = $resolver->resolve($options);
36+
}
37+
38+
public function assert(mixed $value, string $name): void
39+
{
40+
if ($this->multiple && !\is_array($value)) {
41+
throw new UnexpectedValueException(
42+
\sprintf('Expected value of type "array" when multiple, "%s" given', get_debug_type($value))
43+
);
44+
}
45+
46+
if ($this->multiple) {
47+
foreach ($value as $input) {
48+
if (!\in_array($input, $this->constraints, true)) {
49+
throw new ChoiceException(
50+
message: $this->options['multipleMessage'],
51+
parameters: [
52+
'value' => $value,
53+
'name' => $name,
54+
'constraints' => $this->constraints
55+
]
56+
);
57+
}
58+
}
59+
60+
$numValues = \count($value);
61+
62+
if ($this->minConstraint !== null && $numValues < $this->minConstraint) {
63+
throw new ChoiceException(
64+
message: $this->options['minMessage'],
65+
parameters: [
66+
'value' => $value,
67+
'numValues' => $numValues,
68+
'name' => $name,
69+
'constraints' => $this->constraints,
70+
'minConstraint' => $this->minConstraint,
71+
'maxConstraint' => $this->maxConstraint
72+
]
73+
);
74+
}
75+
76+
if ($this->maxConstraint !== null && $numValues > $this->maxConstraint) {
77+
throw new ChoiceException(
78+
message: $this->options['maxMessage'],
79+
parameters: [
80+
'value' => $value,
81+
'numValues' => $numValues,
82+
'name' => $name,
83+
'constraints' => $this->constraints,
84+
'minConstraint' => $this->minConstraint,
85+
'maxConstraint' => $this->maxConstraint
86+
]
87+
);
88+
}
89+
}
90+
else if (!\in_array($value, $this->constraints, true)) {
91+
throw new ChoiceException(
92+
message: $this->options['message'],
93+
parameters: [
94+
'value' => $value,
95+
'name' => $name,
96+
'constraints' => $this->constraints
97+
]
98+
);
99+
}
100+
}
101+
}

src/Rule/GreaterThan.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
namespace ProgrammatorDev\YetAnotherPhpValidator\Rule;
44

55
use ProgrammatorDev\YetAnotherPhpValidator\Exception\GreaterThanException;
6-
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Util\AssertComparableTrait;
6+
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Util\AssertIsComparableTrait;
77
use Symfony\Component\OptionsResolver\OptionsResolver;
88

99
class GreaterThan extends AbstractRule implements RuleInterface
1010
{
11-
use AssertComparableTrait;
11+
use AssertIsComparableTrait;
1212

1313
private array $options;
1414

@@ -29,7 +29,7 @@ public function __construct(private readonly mixed $constraint, array $options =
2929
public function assert(mixed $value, string $name): void
3030
{
3131
// Assert if constraint and value can be compared
32-
$this->assertComparable($this->constraint, $value, GreaterThanException::class);
32+
$this->assertIsComparable($this->constraint, $value);
3333

3434
if (!($value > $this->constraint)) {
3535
throw new GreaterThanException(

src/Rule/LessThan.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
namespace ProgrammatorDev\YetAnotherPhpValidator\Rule;
44

55
use ProgrammatorDev\YetAnotherPhpValidator\Exception\LessThanException;
6-
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Util\AssertComparableTrait;
6+
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Util\AssertIsComparableTrait;
77
use Symfony\Component\OptionsResolver\OptionsResolver;
88

99
class LessThan extends AbstractRule implements RuleInterface
1010
{
11-
use AssertComparableTrait;
11+
use AssertIsComparableTrait;
1212

1313
private array $options;
1414

@@ -29,7 +29,7 @@ public function __construct(private readonly mixed $constraint, array $options =
2929
public function assert(mixed $value, string $name): void
3030
{
3131
// Assert if constraint and value can be compared
32-
$this->assertComparable($this->constraint, $value, LessThanException::class);
32+
$this->assertIsComparable($this->constraint, $value);
3333

3434
if (!($value < $this->constraint)) {
3535
throw new LessThanException(

src/Rule/NotBlank.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function __construct(array $options = [])
1919
]);
2020

2121
$resolver->setAllowedTypes('message', 'string');
22-
$resolver->setAllowedTypes('normalizer', ['null', 'string', 'callable']);
22+
$resolver->setAllowedTypes('normalizer', ['null', 'callable']);
2323

2424
$this->options = $resolver->resolve($options);
2525
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace ProgrammatorDev\YetAnotherPhpValidator\Rule\Util;
44

5-
trait AssertComparableTrait
5+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
6+
7+
trait AssertIsComparableTrait
68
{
7-
private function assertComparable(mixed $value1, mixed $value2, string $exception): bool
9+
private function assertIsComparable(mixed $value1, mixed $value2): bool
810
{
911
if ($value1 instanceof \DateTimeInterface && $value2 instanceof \DateTimeInterface) {
1012
return true;
@@ -18,7 +20,7 @@ private function assertComparable(mixed $value1, mixed $value2, string $exceptio
1820
return true;
1921
}
2022

21-
throw new $exception(
23+
throw new UnexpectedValueException(
2224
\sprintf(
2325
'Cannot compare a type "%s" with a type "%s"',
2426
get_debug_type($value1),

src/StaticValidatorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ public static function notBlank(array $options = []): ChainedValidatorInterface;
99
public static function greaterThan(mixed $constraint, array $options = []): ChainedValidatorInterface;
1010

1111
public static function lessThan(mixed $constraint, array $options = []): ChainedValidatorInterface;
12+
13+
public static function choice(array $constraints, bool $multiple = false, ?int $minConstraint = null, ?int $maxConstraint = null, array $options = []): ChainedValidatorInterface;
1214
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Test\Util;
4+
5+
use PHPUnit\Framework\Attributes\DataProvider;
6+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
7+
use ProgrammatorDev\YetAnotherPhpValidator\Rule\RuleInterface;
8+
9+
trait TestRuleUnexpectedValueTrait
10+
{
11+
public static abstract function provideRuleUnexpectedValueData(): \Generator;
12+
13+
#[DataProvider('provideRuleUnexpectedValueData')]
14+
public function testRuleAssertUnexpectedValue(RuleInterface $rule, mixed $value, string $expectedExceptionMessage): void
15+
{
16+
$this->expectException(UnexpectedValueException::class);
17+
$this->expectExceptionMessageMatches($expectedExceptionMessage);
18+
$rule->assert($value, 'test');
19+
}
20+
21+
#[DataProvider('provideRuleUnexpectedValueData')]
22+
public function testRuleValidateUnexpectedValue(RuleInterface $rule, mixed $value, string $expectedExceptionMessage): void
23+
{
24+
$this->expectException(UnexpectedValueException::class);
25+
$this->expectExceptionMessageMatches($expectedExceptionMessage);
26+
$rule->validate($value);
27+
}
28+
}

0 commit comments

Comments
 (0)