Passed
Pull Request — master (#175)
by
unknown
02:24
created

CompareTo   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Test Coverage

Coverage 91.53%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 70
dl 0
loc 154
ccs 54
cts 59
cp 0.9153
rs 10
c 1
b 0
f 0
wmc 25

5 Methods

Rating   Name   Duplication   Size   Complexity  
B getMessage() 0 23 10
B compareValues() 0 28 10
A __construct() 0 37 2
A getOptions() 0 7 1
A validateValue() 0 10 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Attribute;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use Yiisoft\Validator\FormatterInterface;
11
use Yiisoft\Validator\Result;
12
use Yiisoft\Validator\Rule;
13
use Yiisoft\Validator\ValidationContext;
14
15
/**
16
 * CompareValidator compares the specified attribute value with another value.
17
 *
18
 * The value being compared with a constant {@see CompareTo::$compareValue}, which is set
19
 * in the constructor.
20
 *
21
 * CompareValidator supports different comparison operators, specified
22
 * via the {@see CompareTo::$operator}.
23
 *
24
 * The default comparison function is based on string values, which means the values
25
 * are compared byte by byte. When comparing numbers, make sure to call the {@see CompareTo::$asNumber}
26
 * to enable numeric comparison.
27
 */
28
#[Attribute(Attribute::TARGET_PROPERTY)]
29
final class CompareTo extends Rule
30
{
31
    /**
32
     * Constant for specifying the comparison as string values.
33
     * No conversion will be done before comparison.
34
     *
35
     * @see $type
36
     */
37
    public const TYPE_STRING = 'string';
38
    /**
39
     * Constant for specifying the comparison as numeric values.
40
     * String values will be converted into numbers before comparison.
41
     *
42
     * @see $type
43
     */
44
    public const TYPE_NUMBER = 'number';
45
    private array $validOperators = [
46
        '==' => 1,
47
        '===' => 1,
48
        '!=' => 1,
49
        '!==' => 1,
50
        '>' => 1,
51
        '>=' => 1,
52
        '<' => 1,
53
        '<=' => 1,
54
    ];
55
56 3
    public function __construct(
57
        /**
58
         * @var mixed the constant value to be compared with.
59
         */
60
        private $compareValue,
61
        /**
62
         * @var string|null user-defined error message
63
         */
64
        private ?string $message = null,
65
        /**
66
         * @var string the type of the values being compared.
67
         */
68
        private string $type = self::TYPE_STRING,
69
        /**
70
         * @var string the operator for comparison. The following operators are supported:
71
         *
72
         * - `==`: check if two values are equal. The comparison is done is non-strict mode.
73
         * - `===`: check if two values are equal. The comparison is done is strict mode.
74
         * - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode.
75
         * - `!==`: check if two values are NOT equal. The comparison is done is strict mode.
76
         * - `>`: check if value being validated is greater than the value being compared with.
77
         * - `>=`: check if value being validated is greater than or equal to the value being compared with.
78
         * - `<`: check if value being validated is less than the value being compared with.
79
         * - `<=`: check if value being validated is less than or equal to the value being compared with.
80
         *
81
         * When you want to compare numbers, make sure to also call {@see $asNumber}.
82
         */
83
        private string $operator = '==',
84
        ?FormatterInterface $formatter = null,
85
        bool $skipOnEmpty = false,
86
        bool $skipOnError = false,
87
        $when = null
88
    ) {
89 3
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
90
91 3
        if (!isset($this->validOperators[$operator])) {
92
            throw new InvalidArgumentException("Operator \"$operator\" is not supported.");
93
        }
94
    }
95
96 8
    private function getMessage(): string
97
    {
98 8
        if ($this->message !== null) {
99 1
            return $this->message;
100
        }
101
102 7
        switch ($this->operator) {
103 7
            case '==':
104 3
            case '===':
105 5
                return 'Value must be equal to "{value}".';
106 3
            case '!=':
107 3
            case '!==':
108 2
                return 'Value must not be equal to "{value}".';
109 2
            case '>':
110 1
                return 'Value must be greater than "{value}".';
111 2
            case '>=':
112 2
                return 'Value must be greater than or equal to "{value}".';
113 1
            case '<':
114 1
                return 'Value must be less than "{value}".';
115 1
            case '<=':
116 1
                return 'Value must be less than or equal to "{value}".';
117
            default:
118
                throw new RuntimeException("Unknown operator: {$this->operator}");
119
        }
120
    }
121
122 2
    protected function validateValue($value, ?ValidationContext $context = null): Result
123
    {
124 2
        $result = new Result();
125
126 2
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
127 1
            $message = $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]);
128 1
            $result->addError($message);
129
        }
130
131 2
        return $result;
132
    }
133
134
    /**
135
     * Compares two values with the specified operator.
136
     *
137
     * @param string $operator the comparison operator
138
     * @param string $type the type of the values being compared
139
     * @param mixed $value the value being compared
140
     * @param mixed $compareValue another value being compared
141
     *
142
     * @return bool whether the comparison using the specified operator is true.
143
     */
144 2
    protected function compareValues(string $operator, string $type, $value, $compareValue): bool
145
    {
146 2
        if ($type === self::TYPE_NUMBER) {
147
            $value = (float) $value;
148
            $compareValue = (float)$compareValue;
149
        } else {
150 2
            $value = (string) $value;
151 2
            $compareValue = (string) $compareValue;
152
        }
153 2
        switch ($operator) {
154 2
            case '==':
155 2
                return $value == $compareValue;
156 1
            case '===':
157 1
                return $value === $compareValue;
158 1
            case '!=':
159 1
                return $value != $compareValue;
160 1
            case '!==':
161 1
                return $value !== $compareValue;
162 1
            case '>':
163 1
                return $value > $compareValue;
164 1
            case '>=':
165 1
                return $value >= $compareValue;
166 1
            case '<':
167 1
                return $value < $compareValue;
168 1
            case '<=':
169 1
                return $value <= $compareValue;
170
            default:
171
                return false;
172
        }
173
    }
174
175 7
    public function getOptions(): array
176
    {
177 7
        return array_merge(parent::getOptions(), [
178 7
            'compareValue' => $this->compareValue,
179 7
            'message' => $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]),
180 7
            'type' => $this->type,
181 7
            'operator' => $this->operator,
182
        ]);
183
    }
184
}
185