Passed
Pull Request — master (#521)
by Alexander
20:58 queued 17:12
created

CompareHandler::checkFloatsEqual()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
cc 1
ccs 0
cts 0
cp 0
crap 2
rs 10
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::ORIGINAL | 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
            if ($type === CompareType::STRING) {
85
                $value = (string) $value;
86
                $targetValue = (string) $targetValue;
87 76
            } elseif ($type === CompareType::NUMBER) {
88 17
                $value = (float) $value;
89 10
                $targetValue = (float) $targetValue;
90 8
            }
91 6
        }
92 8
93 8
        return match ($operator) {
94 8
            '==' => $this->checkValuesAreEqual($type, $value, $targetValue),
95 76
            '===' => $this->checkValuesAreEqual($type, $value, $targetValue, strict: true),
96
            '!=' => !$this->checkValuesAreEqual($type, $value, $targetValue),
97
            '!==' => !$this->checkValuesAreEqual($type, $value, $targetValue, strict: true),
98
            '>' => $value > $targetValue,
99
            '>=' => $value >= $targetValue,
100
            '<' => $value < $targetValue,
101
            '<=' => $value <= $targetValue,
102
        };
103
    }
104
105
    private function checkValuesAreEqual(string $type, mixed $value, mixed $targetValue, bool $strict = false): bool
106
    {
107
        if ($strict && gettype($value) !== gettype($targetValue)) {
108
            return false;
109
        }
110
111
        return match ($type) {
112
            CompareType::ORIGINAL => $value === $targetValue,
113
            CompareType::STRING => (string) $value === (string) $targetValue,
114
            CompareType::NUMBER => $this->checkFloatsEqual((float) $value, (float) $targetValue),
115
        };
116
    }
117
118
    private function checkFloatsEqual(float $value, float $targetValue): bool
119
    {
120
        return abs($value - $targetValue) < PHP_FLOAT_EPSILON;
121
    }
122
}
123