Passed
Pull Request — master (#94)
by
unknown
14:20 queued 02:25
created

RangeParser::isAllowedType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql;
6
7
use DateInterval;
8
use DateTime;
9
use InvalidArgumentException;
10
use function preg_match;
11
12
final class RangeParser
13
{
14
    private const RANGES = [
15
        Schema::TYPE_INT_4_RANGE,
16
        Schema::TYPE_INT_8_RANGE,
17
        Schema::TYPE_NUM_RANGE,
18
        Schema::TYPE_TS_RANGE,
19
        Schema::TYPE_TS_TZ_RANGE,
20
        Schema::TYPE_DATE_RANGE,
21
    ];
22
23
    private ?string $type = null;
24
25 57
    public function __construct(?string $type = null)
26
    {
27 57
        if ($type !== null) {
28 57
            if (self::isAllowedType($type)) {
29 57
                $this->type = $type;
30
            } else {
31
                throw new InvalidArgumentException('Unsupported range type "' . $type . '"');
32
            }
33
        }
34 57
    }
35
36
    public function withType(string $type): self
37
    {
38
        if (!self::isAllowedType($type)) {
39
            throw new InvalidArgumentException('Unsupported range type "' . $type . '"');
40
        }
41
42
        $new = clone $this;
43
        $new->type = $type;
44
45
        return $new;
46
    }
47
48
    public function asInt(): self
49
    {
50
        return $this->withType(Schema::TYPE_INT_4_RANGE);
51
    }
52
53
    public function asBigInt(): self
54
    {
55
        return $this->withType(Schema::TYPE_INT_8_RANGE);
56
    }
57
58
    public function asNumeric(): self
59
    {
60
        return $this->withType(Schema::TYPE_NUM_RANGE);
61
    }
62
63
    public function asDate(): self
64
    {
65
        return $this->withType(Schema::TYPE_DATE_RANGE);
66
    }
67
68
    public function asTimestamp(): self
69
    {
70
        return $this->withType(Schema::TYPE_TS_RANGE);
71
    }
72
73
    public function asTimestampTz(): self
74
    {
75
        return $this->withType(Schema::TYPE_TS_TZ_RANGE);
76
    }
77
78 57
    public function parse(?string $value): ?array
79
    {
80 57
        if ($value === null || $value === 'empty') {
81 2
            return null;
82
        }
83
84 55
        if (!preg_match('/^(?P<open>\[|\()(?P<lower>[^,]*),(?P<upper>[^\)\]]*)(?P<close>\)|\])$/', $value, $matches)) {
85
            throw new InvalidArgumentException();
86
        }
87
88 55
        $lower = $matches['lower'] ? trim($matches['lower'], '"') : null;
89 55
        $upper = $matches['upper'] ? trim($matches['upper'], '"') : null;
90 55
        $includeLower = $matches['open'] === '[';
91 55
        $includeUpper = $matches['close'] === ']';
92
93 55
        if ($lower === null && $upper === null) {
94
            return [null, null];
95
        }
96
97 55
        $type = $this->type ?? self::parseType($lower, $upper);
98
99 55
        switch ($type) {
100
            case Schema::TYPE_INT_4_RANGE:
101 25
                return self::parseIntRange($lower, $upper, $includeLower, $includeUpper);
102
            case Schema::TYPE_INT_8_RANGE:
103 12
                return self::parseBigIntRange($lower, $upper, $includeLower, $includeUpper);
104
            case Schema::TYPE_NUM_RANGE:
105 8
                return self::parseNumRange($lower, $upper);
106
            case Schema::TYPE_DATE_RANGE:
107 9
                return self::parseDateRange($lower, $upper, $includeLower, $includeUpper);
108
            case Schema::TYPE_TS_RANGE:
109 7
                return self::parseTsRange($lower, $upper);
110
            case Schema::TYPE_TS_TZ_RANGE:
111 6
                return self::parseTsTzRange($lower, $upper);
112
            default:
113
                return null;
114
        }
115
    }
116
117 25
    private static function parseIntRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
118
    {
119 25
        $min = $lower === null ? null : (int) $lower;
120 25
        $max = $upper === null ? null : (int) $upper;
121
122 25
        if ($min !== null && $includeLower === false) {
123
            $min += 1;
124
        }
125
126 25
        if ($max !== null && $includeUpper === false) {
127 19
            $max -= 1;
128
        }
129
130 25
        return [$min, $max];
131
    }
132
133 12
    private static function parseBigIntRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
134
    {
135 12
        if (PHP_INT_SIZE === 8) {
136 12
            return self::parseIntRange($lower, $upper, $includeLower, $includeUpper);
137
        }
138
139
        [$min, $max] = self::parseNumRange($lower, $upper, $includeLower, $includeUpper);
0 ignored issues
show
Unused Code introduced by
The call to Yiisoft\Db\Pgsql\RangeParser::parseNumRange() has too many arguments starting with $includeLower. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

139
        /** @scrutinizer ignore-call */ 
140
        [$min, $max] = self::parseNumRange($lower, $upper, $includeLower, $includeUpper);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
140
141
        if ($min !== null && $includeLower === false) {
142
            $min += 1;
143
        }
144
145
        if ($max !== null && $includeUpper === false) {
146
            $max -= 1;
147
        }
148
149
        return [$min, $max];
150
    }
151
152 8
    private static function parseNumRange(?string $lower, ?string $upper): array
153
    {
154 8
        $min = $lower === null ? null : (float) $lower;
155 8
        $max = $upper === null ? null : (float) $upper;
156
157 8
        return [$min, $max];
158
    }
159
160 9
    private static function parseDateRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
161
    {
162 9
        $interval = new DateInterval('P1D');
163 9
        $min = $lower ? DateTime::createFromFormat('Y-m-d', $lower) : null;
164 9
        $max = $upper ? DateTime::createFromFormat('Y-m-d', $upper) : null;
165
166 9
        if ($min && $includeLower === false) {
167
            $min->add($interval);
168
        }
169
170 9
        if ($max && $includeUpper === false) {
171 6
            $max->sub($interval);
172
        }
173
174 9
        return [$min, $max];
175
    }
176
177 7
    private static function parseTsRange(?string $lower, ?string $upper): array
178
    {
179 7
        $min = $lower ? DateTime::createFromFormat('Y-m-d H:i:s', $lower) : null;
180 7
        $max = $upper ? DateTime::createFromFormat('Y-m-d H:i:s', $upper) : null;
181
182 7
        return [$min, $max];
183
    }
184
185 6
    private static function parseTsTzRange(?string $lower, ?string $upper): array
186
    {
187 6
        $min = $lower ? DateTime::createFromFormat('Y-m-d H:i:sP', $lower) : null;
188 6
        $max = $upper ? DateTime::createFromFormat('Y-m-d H:i:sP', $upper) : null;
189
190 6
        return [$min, $max];
191
    }
192
193 89
    public static function isAllowedType(string $type): bool
194
    {
195 89
        return in_array($type, self::RANGES, true);
196
    }
197
198
    /**
199
     * Find range type from value format
200
     *
201
     * @param string $lower
202
     * @param string $upper
203
     *
204
     * @return string|null
205
     */
206 33
    private static function parseType(?string $lower, ?string $upper): ?string
207
    {
208 33
        if ($lower !== null && $upper !== null) {
209 18
            if (filter_var($lower, FILTER_VALIDATE_INT) !== false && filter_var($upper, FILTER_VALIDATE_INT) !== false) {
210 12
                return Schema::TYPE_INT_4_RANGE;
211
            }
212
213 6
            if (filter_var($lower, FILTER_VALIDATE_FLOAT) !== false && filter_var($upper, FILTER_VALIDATE_FLOAT) !== false) {
214 6
                return Schema::TYPE_NUM_RANGE;
215
            }
216
        }
217
218 15
        $value = $lower ?? $upper;
219
220 15
        if (filter_var($value, FILTER_VALIDATE_INT) !== false) {
221 13
            return Schema::TYPE_INT_4_RANGE;
222
        }
223
224
225 2
        if (filter_var($value, FILTER_VALIDATE_FLOAT) !== false) {
226 2
            return Schema::TYPE_NUM_RANGE;
227
        }
228
229
        if (DateTime::createFromFormat('Y-m-d', $value)) {
230
            return Schema::TYPE_DATE_RANGE;
231
        }
232
233
        if (DateTime::createFromFormat('Y-m-d H:i:s', $value)) {
234
            return Schema::TYPE_TS_RANGE;
235
        }
236
237
        if (DateTime::createFromFormat('Y-m-d H:i:sP', $value)) {
238
            return Schema::TYPE_TS_TZ_RANGE;
239
        }
240
241
        return null;
242
    }
243
}
244