Skip to content

Commit 8d33d3b

Browse files
committed
Add NumericRule class for numeric validation with min/max constraints and corresponding tests
1 parent 4e7d637 commit 8d33d3b

File tree

4 files changed

+215
-8
lines changed

4 files changed

+215
-8
lines changed

src/Enums/Property/Type.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,10 @@ enum Type: string
2020
self::BOOLEAN,
2121
self::STRING,
2222
];
23+
24+
/** @var list<Type> */
25+
public const array NUMERIC_TYPES = [
26+
self::FLOAT,
27+
self::INT,
28+
];
2329
}

src/Validation/Rules/Concerns/MinMaxValues.php

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,42 @@
44

55
use Nuxtifyts\PhpDto\Support\Arr;
66
use Nuxtifyts\PhpDto\Exceptions\ValidationRuleException;
7+
use Nuxtifyts\PhpDto\Enums\Property\Type;
78

89
trait MinMaxValues
910
{
1011
/**
11-
* @param ?array<string, mixed> $parameters
12-
* @return array{ 0: int|null, 1: int|null }
12+
* @param ?array<string, mixed> $parameters
13+
* @param Type::INT | Type::FLOAT $type
1314
*
14-
* @throws ValidationRuleException
15+
* @return array{ 0: int|null|float, 1: int|null|float }
16+
*
17+
* @throws ValidationRuleException
1518
*/
1619
protected static function getMinMaxValues(
1720
?array $parameters = null,
1821
string $minKey = 'min',
19-
string $maxKey = 'max'
22+
string $maxKey = 'max',
23+
Type $type = Type::INT
2024
): array {
25+
$arrFunc = match ($type) {
26+
Type::INT => 'getIntegerOrNull',
27+
Type::FLOAT => 'getFloatOrNull',
28+
};
29+
30+
$min = match ($type) {
31+
Type::INT => Arr::getIntegerOrNull($parameters ?? [], $minKey),
32+
Type::FLOAT => Arr::getFloatOrNull($parameters ?? [], $minKey),
33+
};
34+
35+
$max = match ($type) {
36+
Type::INT => Arr::getIntegerOrNull($parameters ?? [], $maxKey),
37+
Type::FLOAT => Arr::getFloatOrNull($parameters ?? [], $maxKey),
38+
};
39+
2140
if (
22-
(!is_null($min = Arr::getIntegerOrNull($parameters ?? [], $minKey))
23-
&& $min < 0)
24-
|| (!is_null($max = Arr::getIntegerOrNull($parameters ?? [], $maxKey))
25-
&& $max < $min)
41+
(!is_null($min) && $min < 0)
42+
|| (!is_null($max) && ($max <= 0 || $min > $max))
2643
) {
2744
throw ValidationRuleException::invalidParameters();
2845
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Validation\Rules;
4+
5+
use Nuxtifyts\PhpDto\Enums\Property\Type;
6+
use Nuxtifyts\PhpDto\Exceptions\ValidationRuleException;
7+
use Nuxtifyts\PhpDto\Support\Arr;
8+
use Override;
9+
10+
class NumericRule implements ValidationRule
11+
{
12+
use Concerns\MinMaxValues;
13+
14+
/** @var Type::INT | Type::FLOAT */
15+
protected Type $type;
16+
17+
protected int|float|null $min = null;
18+
19+
protected int|float|null $max = null;
20+
21+
public string $name {
22+
get {
23+
return 'numeric';
24+
}
25+
}
26+
27+
public function evaluate(mixed $value): bool
28+
{
29+
if (
30+
($this->type === Type::INT && !is_int($value))
31+
|| ($this->type === Type::FLOAT && !is_float($value))
32+
|| (!is_null($this->min) && $value < $this->min)
33+
|| (!is_null($this->max) && $value > $this->max)
34+
) {
35+
return false;
36+
}
37+
38+
return true;
39+
}
40+
41+
/**
42+
* @param ?array<string, mixed> $parameters
43+
*
44+
* @throws ValidationRuleException
45+
*/
46+
public static function make(?array $parameters = null): self
47+
{
48+
$instance = new self();
49+
50+
$numericType = Arr::getBackedEnum(
51+
$parameters ?? [],
52+
'type',
53+
Type::class,
54+
Type::INT
55+
);
56+
57+
if (!in_array(
58+
$numericType->value,
59+
array_column(Type::NUMERIC_TYPES, 'value'))
60+
) {
61+
throw ValidationRuleException::invalidParameters();
62+
}
63+
64+
/** @var Type::INT | Type::FLOAT $numericType */
65+
$instance->type = $numericType;
66+
67+
[$instance->min, $instance->max] = self::getMinMaxValues($parameters, type: $numericType);
68+
69+
return $instance;
70+
}
71+
72+
public function validationMessage(): string
73+
{
74+
return match($this->type) {
75+
Type::INT => 'The :attribute field must be a valid integer.',
76+
Type::FLOAT => 'The :attribute field must be a valid float.',
77+
};
78+
}
79+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Tests\Unit\Validation;
4+
5+
use Nuxtifyts\PhpDto\Enums\Property\Type;
6+
use PHPUnit\Framework\Attributes\Test;
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use Nuxtifyts\PhpDto\Validation\Rules\NumericRule;
9+
use Nuxtifyts\PhpDto\Exceptions\ValidationRuleException;
10+
use PHPUnit\Framework\Attributes\UsesClass;
11+
use Throwable;
12+
13+
#[CoversClass(NumericRule::class)]
14+
#[CoversClass(ValidationRuleException::class)]
15+
#[UsesClass(Type::class)]
16+
final class NumericRuleTest extends ValidationRuleTestCase
17+
{
18+
/**
19+
* @throws Throwable
20+
*/
21+
#[Test]
22+
public function validate_validation_message(): void
23+
{
24+
self::assertEquals(
25+
'The :attribute field must be a valid integer.',
26+
NumericRule::make()->validationMessage()
27+
);
28+
29+
self::assertEquals(
30+
'The :attribute field must be a valid float.',
31+
NumericRule::make(['type' => Type::FLOAT])->validationMessage()
32+
);
33+
}
34+
35+
/**
36+
* @return array<string, array{
37+
* validationRuleClassString: class-string<NumericRule>,
38+
* makeParams: ?array<string, mixed>,
39+
* expectedMakeException: ?class-string<ValidationRuleException>,
40+
* valueToBeEvaluated: mixed,
41+
* expectedResult: bool
42+
* }>
43+
*/
44+
public static function data_provider(): array
45+
{
46+
return [
47+
'Will evaluate false when value is not a number' => [
48+
'validationRuleClassString' => NumericRule::class,
49+
'makeParams' => null,
50+
'expectedMakeException' => null,
51+
'valueToBeEvaluated' => 'test',
52+
'expectedResult' => false
53+
],
54+
'Will evaluate true when an integer value is provided' => [
55+
'validationRuleClassString' => NumericRule::class,
56+
'makeParams' => ['type' => Type::INT],
57+
'expectedMakeException' => null,
58+
'valueToBeEvaluated' => 123,
59+
'expectedResult' => true
60+
],
61+
'Will evaluate false when an integer value is provided but min is greater than the value' => [
62+
'validationRuleClassString' => NumericRule::class,
63+
'makeParams' => ['type' => Type::INT, 'min' => 124],
64+
'expectedMakeException' => null,
65+
'valueToBeEvaluated' => 123,
66+
'expectedResult' => false
67+
],
68+
'Will evaluate false when an integer value is provided but max is less than the value' => [
69+
'validationRuleClassString' => NumericRule::class,
70+
'makeParams' => ['type' => Type::INT, 'max' => 122],
71+
'expectedMakeException' => null,
72+
'valueToBeEvaluated' => 123,
73+
'expectedResult' => false
74+
],
75+
'Will evaluate true when a float value is provided' => [
76+
'validationRuleClassString' => NumericRule::class,
77+
'makeParams' => ['type' => Type::FLOAT],
78+
'expectedMakeException' => null,
79+
'valueToBeEvaluated' => 123.45,
80+
'expectedResult' => true
81+
],
82+
'Will evaluate false when a float value is provided but min is greater than the value' => [
83+
'validationRuleClassString' => NumericRule::class,
84+
'makeParams' => ['type' => Type::FLOAT, 'min' => 123.46],
85+
'expectedMakeException' => null,
86+
'valueToBeEvaluated' => 123.45,
87+
'expectedResult' => false
88+
],
89+
'Will evaluate false when a float value is provided but max is less than the value' => [
90+
'validationRuleClassString' => NumericRule::class,
91+
'makeParams' => ['type' => Type::FLOAT, 'max' => 123.44],
92+
'expectedMakeException' => null,
93+
'valueToBeEvaluated' => 123.45,
94+
'expectedResult' => false
95+
],
96+
'Will throw an exception if an invalid type is provided' => [
97+
'validationRuleClassString' => NumericRule::class,
98+
'makeParams' => ['type' => Type::BOOLEAN],
99+
'expectedMakeException' => ValidationRuleException::class,
100+
'valueToBeEvaluated' => 123,
101+
'expectedResult' => false
102+
],
103+
];
104+
}
105+
}

0 commit comments

Comments
 (0)