Test Setup Failed
Push — master ( e94fe5...b7ec94 )
by
unknown
02:01
created

CompareTo   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Test Coverage

Coverage 77.78%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 70
c 1
b 0
f 0
dl 0
loc 155
ccs 42
cts 54
cp 0.7778
rs 10
wmc 25

5 Methods

Rating   Name   Duplication   Size   Complexity  
B getMessage() 0 23 10
B compareValues() 0 28 10
A __construct() 0 38 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
 * Compares the specified 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
 * It 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 change {@see CompareTo::$type} to
26
 * {@see CompareTo::TYPE_NUMBER} 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
    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 chabge @see CompareTo::$type} to
82
         * {@see CompareTo::TYPE_NUMBER}.
83
         */
84
        private string $operator = '==',
85 8
        ?FormatterInterface $formatter = null,
86
        bool $skipOnEmpty = false,
87 8
        bool $skipOnError = false,
88 1
        $when = null
89
    ) {
90
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
91 7
92 7
        if (!isset($this->validOperators[$operator])) {
93 3
            throw new InvalidArgumentException("Operator \"$operator\" is not supported.");
94 5
        }
95 3
    }
96 3
97 2
    private function getMessage(): string
98 2
    {
99 1
        if ($this->message !== null) {
100 2
            return $this->message;
101 2
        }
102 1
103 1
        switch ($this->operator) {
104 1
            case '==':
105 1
            case '===':
106
                return 'Value must be equal to "{value}".';
107
            case '!=':
108
            case '!==':
109
                return 'Value must not be equal to "{value}".';
110
            case '>':
111 3
                return 'Value must be greater than "{value}".';
112
            case '>=':
113 3
                return 'Value must be greater than or equal to "{value}".';
114 3
            case '<':
115 3
                return 'Value must be less than "{value}".';
116
            case '<=':
117
                return 'Value must be less than or equal to "{value}".';
118 1
            default:
119
                throw new RuntimeException("Unknown operator: {$this->operator}");
120 1
        }
121
    }
122
123
    protected function validateValue($value, ?ValidationContext $context = null): Result
124 1
    {
125 1
        $result = new Result();
126 1
127
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
128
            $message = $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]);
129
            $result->addError($message);
130
        }
131
132
        return $result;
133
    }
134
135
    /**
136
     * Compares two values with the specified operator.
137
     *
138
     * @param string $operator the comparison operator
139
     * @param string $type the type of the values being compared
140
     * @param mixed $value the value being compared
141
     * @param mixed $compareValue another value being compared
142
     *
143 2
     * @return bool whether the comparison using the specified operator is true.
144
     */
145 2
    protected function compareValues(string $operator, string $type, $value, $compareValue): bool
146
    {
147 2
        if ($type === self::TYPE_NUMBER) {
148 1
            $value = (float) $value;
149 1
            $compareValue = (float)$compareValue;
150 1
        } else {
151
            $value = (string) $value;
152 1
            $compareValue = (string) $compareValue;
153
        }
154
        switch ($operator) {
155
            case '==':
156
                return $value == $compareValue;
157
            case '===':
158 2
                return $value === $compareValue;
159
            case '!=':
160
                return $value != $compareValue;
161
            case '!==':
162
                return $value !== $compareValue;
163
            case '>':
164
                return $value > $compareValue;
165
            case '>=':
166
                return $value >= $compareValue;
167
            case '<':
168
                return $value < $compareValue;
169
            case '<=':
170
                return $value <= $compareValue;
171 2
            default:
172
                return false;
173 2
        }
174
    }
175
176
    public function getOptions(): array
177 2
    {
178 2
        return array_merge(parent::getOptions(), [
179
            'compareValue' => $this->compareValue,
180 2
            'message' => $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]),
181 2
            'type' => $this->type,
182 2
            'operator' => $this->operator,
183 1
        ]);
184 1
    }
185
}
186