Passed
Push — master ( 012394...83af19 )
by Smoren
02:30 queued 19s
created

Slice   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 43
eloc 62
c 2
b 1
f 0
dl 0
loc 188
rs 8.96

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A parseSliceString() 0 6 3
B isSliceArray() 0 20 8
A squeezeInBounds() 0 3 1
A toSlice() 0 20 6
A isSliceString() 0 17 5
C normalize() 0 36 15
A toString() 0 4 1
A isSlice() 0 3 3

How to fix   Complexity   

Complex Class

Complex classes like Slice often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Slice, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Smoren\ArrayView\Structs;
4
5
use Smoren\ArrayView\Exceptions\IndexError;
6
use Smoren\ArrayView\Exceptions\ValueError;
7
use Smoren\ArrayView\Util;
8
9
/**
10
 * @property-read int|null $start
11
 * @property-read int|null $end
12
 * @property-read int|null $step
13
 */
14
class Slice
15
{
16
    /**
17
     * @var int|null
18
     */
19
    public ?int $start;
20
    /**
21
     * @var int|null
22
     */
23
    public ?int $end;
24
    /**
25
     * @var int|null
26
     */
27
    public ?int $step;
28
29
    /**
30
     * @param string|Slice|array<int> $s
31
     *
32
     * @return Slice
33
     */
34
    public static function toSlice($s): Slice
35
    {
36
        /** @var mixed $s */
37
        if ($s instanceof Slice) {
38
            return $s;
39
        }
40
41
        if (\is_array($s) && self::isSliceArray($s)) {
42
            return new Slice(...$s);
0 ignored issues
show
Bug introduced by
$s is expanded, but the parameter $start of Smoren\ArrayView\Structs\Slice::__construct() does not expect variable arguments. ( Ignorable by Annotation )

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

42
            return new Slice(/** @scrutinizer ignore-type */ ...$s);
Loading history...
43
        }
44
45
        if (!self::isSliceString($s)) {
46
            $str = \is_scalar($s) ? "{$s}" : gettype($s);
47
            throw new ValueError("Invalid slice: \"{$str}\".");
48
        }
49
50
        /** @var string $s */
51
        $slice = self::parseSliceString($s);
52
53
        return new Slice(...$slice);
54
    }
55
56
    /**
57
     * @param mixed $s
58
     *
59
     * @return bool
60
     */
61
    public static function isSlice($s): bool
62
    {
63
        return ($s instanceof Slice) || static::isSliceString($s) || static::isSliceArray($s);
64
    }
65
66
    /**
67
     * @param mixed $s
68
     *
69
     * @return bool
70
     */
71
    public static function isSliceString($s): bool
72
    {
73
        if (!\is_string($s)) {
74
            return false;
75
        }
76
77
        if (\is_numeric($s)) {
78
            return false;
79
        }
80
81
        if (!\preg_match('/^-?[0-9]*:?-?[0-9]*:?-?[0-9]*$/', $s)) {
82
            return false;
83
        }
84
85
        $slice = self::parseSliceString($s);
86
87
        return !(\count($slice) < 1 || \count($slice) > 3);
88
    }
89
90
    /**
91
     * @param mixed $s
92
     *
93
     * @return bool
94
     */
95
    public static function isSliceArray($s): bool
96
    {
97
        if (!\is_array($s)) {
98
            return false;
99
        }
100
101
        if (\count($s) > 3) {
102
            return false;
103
        }
104
105
        foreach ($s as $key => $item) {
106
            if (\is_string($key)) {
107
                return false;
108
            }
109
            if ($item !== null && (!\is_numeric($item) || \is_float($item + 0))) {
110
                return false;
111
            }
112
        }
113
114
        return true;
115
    }
116
117
    /**
118
     * @param int|null $start
119
     * @param int|null $end
120
     * @param int|null $step
121
     */
122
    public function __construct(?int $start = null, ?int $end = null, ?int $step = null)
123
    {
124
        $this->start = $start;
0 ignored issues
show
Bug introduced by
The property start is declared read-only in Smoren\ArrayView\Structs\Slice.
Loading history...
125
        $this->end = $end;
0 ignored issues
show
Bug introduced by
The property end is declared read-only in Smoren\ArrayView\Structs\Slice.
Loading history...
126
        $this->step = $step;
0 ignored issues
show
Bug introduced by
The property step is declared read-only in Smoren\ArrayView\Structs\Slice.
Loading history...
127
    }
128
129
    /**
130
     * @param int $containerSize
131
     *
132
     * @return NormalizedSlice
133
     */
134
    public function normalize(int $containerSize): NormalizedSlice
135
    {
136
        // TODO: Need refactor
137
        $step = $this->step ?? 1;
138
139
        if ($step === 0) {
140
            throw new IndexError("Step cannot be 0.");
141
        }
142
143
        $defaultEnd = ($step < 0 && $this->end === null) ? -1 : null;
144
145
        $start = $this->start ?? ($step > 0 ? 0 : $containerSize - 1);
146
        $end = $this->end ?? ($step > 0 ? $containerSize : -1);
147
148
        $start = intval(round($start));
149
        $end = intval(round($end));
150
        $step = intval(round($step));
151
152
        $start = Util::normalizeIndex($start, $containerSize, false);
153
        $end = Util::normalizeIndex($end, $containerSize, false);
154
155
        if ($step > 0 && $start >= $containerSize) {
156
            $start = $end = $containerSize - 1;
157
        } elseif ($step < 0 && $start < 0) {
158
            $start = $end = 0;
159
            $defaultEnd = 0;
160
        }
161
162
        $start = $this->squeezeInBounds($start, 0, $containerSize - 1);
163
        $end = $this->squeezeInBounds($end, $step > 0 ? 0 : -1, $containerSize);
164
165
        if (($step > 0 && $end < $start) || ($step < 0 && $end > $start)) {
166
            $end = $start;
167
        }
168
169
        return new NormalizedSlice($start, $defaultEnd ?? $end, $step);
170
    }
171
172
    /**
173
     * @return string
174
     */
175
    public function toString(): string
176
    {
177
        [$start, $end, $step] = [$this->start ?? '', $this->end ?? '', $this->step ?? ''];
178
        return "{$start}:{$end}:{$step}";
179
    }
180
181
    /**
182
     * @param string $s
183
     * @return array<int|null>
184
     */
185
    private static function parseSliceString(string $s): array
186
    {
187
        if ($s === '') {
188
            return [];
189
        }
190
        return array_map(fn($x) => trim($x) === '' ? null : \intval(trim($x)), \explode(':', $s));
191
    }
192
193
    /**
194
     * @param int $x
195
     * @param int $min
196
     * @param int $max
197
     * @return int
198
     */
199
    private function squeezeInBounds(int $x, int $min, int $max): int
200
    {
201
        return max($min, min($max, $x));
202
    }
203
}
204