Passed
Pull Request — master (#521)
by
unknown
11:08 queued 07:08
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::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
        [$value, $targetValue] = $this->getTypeCastedValues($type, $value, $targetValue);
110
111
        if ($type === CompareType::NUMBER) {
112
            return $this->checkFloatsEqual($value, $targetValue);
113
        }
114
115
        return $value === $targetValue;
116
    }
117
118
    private function checkFloatsEqual(float $value, float $targetValue): bool
119
    {
120
        return abs($value - $targetValue) < PHP_FLOAT_EPSILON;
121
    }
122
123
    /**
124
     * @psalm-return array{0: float, 1: float}|array{0: string, 1: string}
125
     */
126
    private function getTypeCastedValues(string $type, mixed $value, mixed $targetValue): array
127
    {
128
        return match ($type) {
129
            CompareType::ORIGINAL => [$value, $targetValue],
130
            CompareType::STRING => [(string) $value, (string) $targetValue],
131
            CompareType::NUMBER => [(float) $value, (float) $targetValue],
132
        };
133
    }
134
}
135