Passed
Pull Request — master (#521)
by
unknown
06:43 queued 04:08
created

CompareHandler   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
dl 0
loc 102
rs 10
c 5
b 1
f 0
eloc 42
ccs 36
cts 36
cp 1
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
B validate() 0 37 8
A compareValues() 0 15 2
A checkValuesAreEqual() 0 15 4
A getTypeCastedValues() 0 5 2
A checkFloatsEqual() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Yiisoft\Validator\Exception\UnexpectedRuleException;
8
use Yiisoft\Validator\Result;
9
use Yiisoft\Validator\RuleHandlerInterface;
10
use Yiisoft\Validator\ValidationContext;
11
12
use function gettype;
13
use function in_array;
14
15
/**
16
 * Compares the specified value with another value.
17
 *
18
 * @see AbstractCompare
19
 * @see Equal
20
 * @see GreaterThan
21
 * @see GreaterThanOrEqual
22
 * @see LessThan
23
 * @see LessThanOrEqual
24
 * @see Compare
25
 * @see NotEqual
26
 */
27 84
final class CompareHandler implements RuleHandlerInterface
28
{
29 84
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
30 1
    {
31
        if (!$rule instanceof AbstractCompare) {
32
            throw new UnexpectedRuleException(AbstractCompare::class, $rule);
33 83
        }
34 83
35 4
        $result = new Result();
36 4
        if ($value !== null && !is_scalar($value)) {
37 4
            return $result->addError($rule->getIncorrectInputMessage(), [
38
                'attribute' => $context->getTranslatedAttribute(),
39
                'type' => get_debug_type($value),
40
            ]);
41 79
        }
42 79
43
        $targetAttribute = $rule->getTargetAttribute();
44 79
        $targetValue = $rule->getTargetValue();
45
46 8
        if ($targetValue === null && $targetAttribute !== null) {
47 8
            /** @var mixed $targetValue */
48 3
            $targetValue = $context->getDataSet()->getAttributeValue($targetAttribute);
49 3
            if (!is_scalar($targetValue)) {
50
                return $result->addError($rule->getIncorrectDataSetTypeMessage(), [
51
                    'type' => get_debug_type($targetValue),
52
                ]);
53
            }
54 76
        }
55 34
56
        if ($this->compareValues($rule->getOperator(), $rule->getType(), $value, $targetValue)) {
57
            return $result;
58 42
        }
59 42
60 42
        return $result->addError($rule->getMessage(), [
61 42
            'attribute' => $context->getTranslatedAttribute(),
62
            'targetValue' => $rule->getTargetValue(),
63
            'targetAttribute' => $rule->getTargetAttribute(),
64
            'targetValueOrAttribute' => $targetValue ?? $targetAttribute,
65
            'value' => $value,
66
        ]);
67
    }
68
69
    /**
70
     * Compares two values with the specified operator.
71
     *
72
     * @param string $operator The comparison operator. One of `==`, `===`, `!=`, `!==`, `>`, `>=`, `<`, `<=`.
73
     * @param string $type The type of the values being compared.
74
     * @psalm-param CompareType::STRING | CompareType::NUMBER $type
75
     *
76
     * @param mixed $value The value being compared.
77 76
     * @param mixed $targetValue Another value being compared.
78
     *
79 76
     * @return bool Whether the result of comparison using the specified operator is true.
80 2
     */
81 2
    private function compareValues(string $operator, string $type, mixed $value, mixed $targetValue): bool
82
    {
83 74
        if (!in_array($operator, ['==', '===', '!=', '!=='])) {
84 74
            [$value, $targetValue] = $this->getTypeCastedValues($type, $value, $targetValue);
85
        }
86
87 76
        return match ($operator) {
88 17
            '==' => $this->checkValuesAreEqual($type, $value, $targetValue),
89 10
            '===' => $this->checkValuesAreEqual($type, $value, $targetValue, strict: true),
90 8
            '!=' => !$this->checkValuesAreEqual($type, $value, $targetValue),
91 6
            '!==' => !$this->checkValuesAreEqual($type, $value, $targetValue, strict: true),
92 8
            '>' => $value > $targetValue,
93 8
            '>=' => $value >= $targetValue,
94 8
            '<' => $value < $targetValue,
95 76
            '<=' => $value <= $targetValue,
96
        };
97
    }
98
99
    private function checkValuesAreEqual(
100
        string $type,
101
        mixed $value,
102
        mixed $targetValue,
103
        bool $strict = false,
104
    ): bool {
105
        if ($strict && gettype($value) !== gettype($targetValue)) {
106
            return false;
107
        }
108
109
        if ($type === CompareType::NUMBER) {
110
            return $this->checkFloatsEqual((float) $value, (float) $targetValue);
111
        }
112
113
        return (string) $value === (string) $targetValue;
114
    }
115
116
    private function checkFloatsEqual(float $value, float $targetValue): bool
117
    {
118
        return abs($value - $targetValue) < PHP_FLOAT_EPSILON;
119
    }
120
121
    /**
122
     * @psalm-return array{0: float, 1: float}|array{0: string, 1: string}
123
     */
124
    private function getTypeCastedValues(string $type, mixed $value, mixed $targetValue): array
125
    {
126
        return $type === CompareType::NUMBER
127
            ? [(float) $value, (float) $targetValue]
128
            : [(string) $value, (string) $targetValue];
129
    }
130
}
131