Passed
Pull Request — master (#307)
by
unknown
03:26
created

RangeParser::asInt()   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 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
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_RANGE,
16
        Schema::TYPE_BIGINT_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;
24
25 21
    public function __construct(string $type)
26
    {
27 21
        if (self::isAllowedType($type)) {
28 21
            $this->type = $type;
29
        } else {
30
            throw new InvalidArgumentException('Unsupported range type "' . $type . '"');
31
        }
32
    }
33
34 2
    public function withType(string $type): self
35
    {
36 2
        if (!self::isAllowedType($type)) {
37
            throw new InvalidArgumentException('Unsupported range type "' . $type . '"');
38
        }
39
40 2
        $new = clone $this;
41 2
        $new->type = $type;
42
43 2
        return $new;
44
    }
45
46
    public function asInt(): self
47
    {
48
        return $this->withType(Schema::TYPE_INT_RANGE);
49
    }
50
51
    public function asBigInt(): self
52
    {
53
        return $this->withType(Schema::TYPE_BIGINT_RANGE);
54
    }
55
56
    public function asNumeric(): self
57
    {
58
        return $this->withType(Schema::TYPE_NUM_RANGE);
59
    }
60
61
    public function asDate(): self
62
    {
63
        return $this->withType(Schema::TYPE_DATE_RANGE);
64
    }
65
66
    public function asTimestamp(): self
67
    {
68
        return $this->withType(Schema::TYPE_TS_RANGE);
69
    }
70
71
    public function asTimestampTz(): self
72
    {
73
        return $this->withType(Schema::TYPE_TS_TZ_RANGE);
74
    }
75
76 21
    public function parse(?string $value): ?array
77
    {
78 21
        if ($value === null || $value === 'empty') {
79 2
            return null;
80
        }
81
82 19
        if (!preg_match('/^(?P<open>\[|\()(?P<lower>[^,]*),(?P<upper>[^\)\]]*)(?P<close>\)|\])$/', $value, $matches)) {
83
            throw new InvalidArgumentException('Unsupported range format');
84
        }
85
86 19
        $lower = $matches['lower'] ? trim($matches['lower'], '"') : null;
87 19
        $upper = $matches['upper'] ? trim($matches['upper'], '"') : null;
88 19
        $includeLower = $matches['open'] === '[';
89 19
        $includeUpper = $matches['close'] === ']';
90
91 19
        if ($lower === null && $upper === null) {
92 3
            return [null, null];
93
        }
94
95 17
        return match($this->type) {
96 17
            Schema::TYPE_INT_RANGE => self::parseIntRange($lower, $upper, $includeLower, $includeUpper),
97 17
            Schema::TYPE_BIGINT_RANGE => self::parseBigIntRange($lower, $upper, $includeLower, $includeUpper),
98 17
            Schema::TYPE_NUM_RANGE => self::parseNumRange($lower, $upper),
99 17
            Schema::TYPE_DATE_RANGE => self::parseDateRange($lower, $upper, $includeLower, $includeUpper),
100 17
            Schema::TYPE_TS_RANGE => self::parseTsRange($lower, $upper),
101 17
            Schema::TYPE_TS_TZ_RANGE => self::parseTsTzRange($lower, $upper),
102 17
        };
103
    }
104
105 7
    private static function parseIntRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
106
    {
107 7
        $min = $lower === null ? null : (int) $lower;
108 7
        $max = $upper === null ? null : (int) $upper;
109
110 7
        if ($min !== null && $includeLower === false) {
111 1
            $min += 1;
112
        }
113
114 7
        if ($max !== null && $includeUpper === false) {
115 5
            $max -= 1;
116
        }
117
118 7
        return [$min, $max];
119
    }
120
121
    private static function parseBigIntRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
122
    {
123
        if (PHP_INT_SIZE === 8) {
124
            return self::parseIntRange($lower, $upper, $includeLower, $includeUpper);
125
        }
126
127
        [$min, $max] = self::parseNumRange($lower, $upper);
128
129
        if ($min !== null && $includeLower === false) {
130
            /** @var float $min */
131
            $min += 1;
132
        }
133
134
        if ($max !== null && $includeUpper === false) {
135
            /** @var float $max */
136
            $max -= 1;
137
        }
138
139
        return [$min, $max];
140
    }
141
142 4
    private static function parseNumRange(?string $lower, ?string $upper): array
143
    {
144 4
        $min = $lower === null ? null : (float) $lower;
145 4
        $max = $upper === null ? null : (float) $upper;
146
147 4
        return [$min, $max];
148
    }
149
150 6
    private static function parseDateRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
151
    {
152 6
        $interval = new DateInterval('P1D');
153 6
        $min = $lower ? DateTime::createFromFormat('Y-m-d', $lower) : null;
154 6
        $max = $upper ? DateTime::createFromFormat('Y-m-d', $upper) : null;
155
156 6
        if ($min && $includeLower === false) {
157 2
            $min->add($interval);
158
        }
159
160 6
        if ($max && $includeUpper === false) {
161 4
            $max->sub($interval);
162
        }
163
164 6
        return [$min, $max];
165
    }
166
167
    private static function parseTsRange(?string $lower, ?string $upper): array
168
    {
169
        $min = $lower ? DateTime::createFromFormat('Y-m-d H:i:s', $lower) : null;
170
        $max = $upper ? DateTime::createFromFormat('Y-m-d H:i:s', $upper) : null;
171
172
        return [$min, $max];
173
    }
174
175
    private static function parseTsTzRange(?string $lower, ?string $upper): array
176
    {
177
        $min = $lower ? DateTime::createFromFormat('Y-m-d H:i:sP', $lower) : null;
178
        $max = $upper ? DateTime::createFromFormat('Y-m-d H:i:sP', $upper) : null;
179
180
        return [$min, $max];
181
    }
182
183 116
    public static function isAllowedType(string $type): bool
184
    {
185 116
        return in_array($type, self::RANGES, true);
186
    }
187
}
188