Passed
Push — master ( 4bb680...ad2987 )
by ignace nyamagana
02:05
created

Sequence   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 341
rs 8.4
c 0
b 0
f 0
wmc 50

24 Methods

Rating   Name   Duplication   Size   Complexity  
A clear() 0 3 1
B getIntersections() 0 27 7
A getIterator() 0 4 2
A unshift() 0 3 1
A remove() 0 8 1
A set() 0 4 1
A sortByStartDate() 0 3 1
A filter() 0 8 2
A indexOf() 0 9 3
A getGaps() 0 20 6
A insert() 0 8 3
A contains() 0 10 3
A jsonSerialize() 0 3 1
A getBoundaries() 0 8 2
A get() 0 8 2
A count() 0 3 1
A sort() 0 3 1
A some() 0 9 3
A isEmpty() 0 3 1
A toArray() 0 3 1
A sorted() 0 9 2
A every() 0 9 3
A __construct() 0 3 1
A push() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Sequence 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 Sequence, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * League.Period (https://period.thephpleague.com).
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Period;
15
16
use Countable;
17
use Iterator;
18
use IteratorAggregate;
19
use JsonSerializable;
20
use function array_filter;
21
use function array_merge;
22
use function count;
23
use function reset;
24
use function sort;
25
use function sprintf;
26
use function uasort;
27
use const ARRAY_FILTER_USE_BOTH;
28
29
/**
30
 * A class to manipulate interval collection.
31
 *
32
 * @package League.period
33
 * @author  Ignace Nyamagana Butera <[email protected]>
34
 * @since   4.1.0
35
 */
36
final class Sequence implements Countable, IteratorAggregate, JsonSerializable
37
{
38
    /**
39
     * @var Period[]
40
     */
41
    private $intervals = [];
42
43
    /**
44
     * new instance.
45
     *
46
     * @param Period ...$intervals
47
     */
48
    public function __construct(Period ...$intervals)
49
    {
50
        $this->intervals = $intervals;
51
    }
52
53
    /**
54
     * Returns the sequence boundaries as a Period instance.
55
     *
56
     * If the sequence contains no interval null is returned.
57
     *
58
     * @return ?Period
59
     */
60
    public function getBoundaries(): ?Period
61
    {
62
        $period = reset($this->intervals);
63
        if (false === $period) {
64
            return null;
65
        }
66
67
        return $period->merge(...$this->intervals);
68
    }
69
70
    /**
71
     * Returns the gaps inside the instance.
72
     */
73
    public function getGaps(): self
74
    {
75
        $sequence = new self();
76
        $interval = null;
77
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
78
            if (null === $interval) {
79
                $interval = $period;
80
                continue;
81
            }
82
83
            if (!$interval->overlaps($period) && !$interval->abuts($period)) {
84
                $sequence->push($interval->gap($period));
85
            }
86
87
            if (!$interval->contains($period)) {
88
                $interval = $period;
89
            }
90
        }
91
92
        return $sequence;
93
    }
94
95
    /**
96
     * Sorts two Interval instance using their start datepoint.
97
     */
98
    private function sortByStartDate(Period $interval1, Period $interval2): int
99
    {
100
        return $interval1->getStartDate() <=> $interval2->getStartDate();
101
    }
102
103
    /**
104
     * Returns the intersections inside the instance.
105
     */
106
    public function getIntersections(): self
107
    {
108
        $sequence = new self();
109
        $current = null;
110
        $isPreviouslyContained = false;
111
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
112
            if (null === $current) {
113
                $current = $period;
114
                continue;
115
            }
116
117
            $isContained = $current->contains($period);
118
            if ($isContained && $isPreviouslyContained) {
119
                continue;
120
            }
121
122
            if ($current->overlaps($period)) {
123
                $sequence->push($current->intersect($period));
124
            }
125
126
            $isPreviouslyContained = $isContained;
127
            if (!$isContained) {
128
                $current = $period;
129
            }
130
        }
131
132
        return $sequence;
133
    }
134
135
    /**
136
     * Tells whether some intervals in the current instance satisfies the predicate.
137
     */
138
    public function some(callable $predicate): bool
139
    {
140
        foreach ($this->intervals as $offset => $interval) {
141
            if (true === $predicate($interval, $offset)) {
142
                return true;
143
            }
144
        }
145
146
        return false;
147
    }
148
149
    /**
150
     * Tells whether all intervals in the current instance satisfies the predicate.
151
     */
152
    public function every(callable $predicate): bool
153
    {
154
        foreach ($this->intervals as $offset => $interval) {
155
            if (true !== $predicate($interval, $offset)) {
156
                return false;
157
            }
158
        }
159
160
        return [] !== $this->intervals;
161
    }
162
163
    /**
164
     * Returns the array representation of the sequence.
165
     *
166
     * @return Period[]
167
     */
168
    public function toArray(): array
169
    {
170
        return $this->intervals;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function jsonSerialize(): array
177
    {
178
        return $this->intervals;
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function getIterator(): Iterator
185
    {
186
        foreach ($this->intervals as $offset => $interval) {
187
            yield $offset => $interval;
188
        }
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function count(): int
195
    {
196
        return count($this->intervals);
197
    }
198
199
    /**
200
     * Tells whether the sequence is empty.
201
     */
202
    public function isEmpty(): bool
203
    {
204
        return [] === $this->intervals;
205
    }
206
207
    /**
208
     * Tells whether the given interval is present in the sequence.
209
     *
210
     * @param Period ...$intervals
211
     */
212
    public function contains(Period $interval, Period ... $intervals): bool
213
    {
214
        $intervals[] = $interval;
215
        foreach ($intervals as $period) {
216
            if (null === $this->indexOf($period)) {
217
                return false;
218
            }
219
        }
220
221
        return true;
222
    }
223
224
    /**
225
     * Attempts to find the first offset attached to the submitted interval.
226
     *
227
     * If no offset is found the method returns null.
228
     *
229
     * @return ?int
230
     */
231
    public function indexOf(Period $interval): ?int
232
    {
233
        foreach ($this->intervals as $offset => $period) {
234
            if ($period->equals($interval)) {
235
                return $offset;
236
            }
237
        }
238
239
        return null;
240
    }
241
242
    /**
243
     * Returns the interval specified at a given offset.
244
     *
245
     * @throws InvalidIndex If the offset is illegal for the current sequence
246
     */
247
    public function get(int $offset): Period
248
    {
249
        $period = $this->intervals[$offset] ?? null;
250
        if (null !== $period) {
251
            return $period;
252
        }
253
254
        throw new InvalidIndex(sprintf('%s is an invalid offset in the current sequence', $offset));
255
    }
256
257
    /**
258
     * Sort the current instance according to the given comparison callable
259
     * and maintain index association.
260
     *
261
     * Returns true on success or false on failure
262
     */
263
    public function sort(callable $compare): bool
264
    {
265
        return uasort($this->intervals, $compare);
266
    }
267
268
    /**
269
     * Adds new intervals at the front of the sequence.
270
     *
271
     * The sequence is re-indexed after addition
272
     *
273
     * @param Period ...$intervals
274
     */
275
    public function unshift(Period $interval, Period ...$intervals): void
276
    {
277
        $this->intervals = array_merge([$interval], $intervals, $this->intervals);
278
    }
279
280
    /**
281
     * Adds new intervals at the end of the sequence.
282
     *
283
     * @param Period ...$intervals
284
     */
285
    public function push(Period $interval, Period ...$intervals): void
286
    {
287
        $this->intervals = array_merge($this->intervals, [$interval], $intervals);
288
    }
289
290
    /**
291
     * Inserts new intervals at the specified offset of the sequence.
292
     *
293
     * The sequence is re-indexed after addition
294
     *
295
     * @param Period ...$intervals
296
     *
297
     * @throws InvalidIndex If the offset is illegal for the current sequence.
298
     */
299
    public function insert(int $offset, Period $interval, Period ...$intervals): void
300
    {
301
        if ($offset < 0 || $offset > count($this->intervals)) {
302
            throw new InvalidIndex(sprintf('%s is an invalid offset in the current sequence', $offset));
303
        }
304
305
        array_unshift($intervals, $interval);
306
        array_splice($this->intervals, $offset, 0, $intervals);
307
    }
308
309
    /**
310
     * Updates the interval at the specify offset.
311
     *
312
     * @throws InvalidIndex If the offset is illegal for the current sequence.
313
     */
314
    public function set(int $offset, Period $interval): void
315
    {
316
        $this->get($offset);
317
        $this->intervals[$offset] = $interval;
318
    }
319
320
    /**
321
     * Removes an interval from the sequence at the given offset and returns it.
322
     *
323
     * The sequence is re-indexed after removal
324
     *
325
     * @throws InvalidIndex If the offset is illegal for the current sequence.
326
     */
327
    public function remove(int $offset): Period
328
    {
329
        $interval = $this->get($offset);
330
        unset($this->intervals[$offset]);
331
332
        $this->intervals = array_values($this->intervals);
333
334
        return $interval;
335
    }
336
337
    /**
338
     * Filters the sequence according to the given predicate.
339
     *
340
     * This method MUST retain the state of the current instance, and return
341
     * an instance that contains the interval which validate the predicate.
342
     */
343
    public function filter(callable $predicate): self
344
    {
345
        $intervals = array_filter($this->intervals, $predicate, ARRAY_FILTER_USE_BOTH);
346
        if ($intervals === $this->intervals) {
347
            return $this;
348
        }
349
350
        return new self(...$intervals);
351
    }
352
353
    /**
354
     * Removes all intervals from the sequence.
355
     */
356
    public function clear(): void
357
    {
358
        $this->intervals = [];
359
    }
360
361
    /**
362
     * Returns an instance sorted according to the given comparison callable
363
     * but does not maintain index association.
364
     *
365
     * This method MUST retain the state of the current instance, and return
366
     * an instance that contains the sorted intervals. The key are re-indexed
367
     */
368
    public function sorted(callable $compare): self
369
    {
370
        $intervals = $this->intervals;
371
        usort($intervals, $compare);
372
        if ($intervals === $this->intervals) {
373
            return $this;
374
        }
375
376
        return new self(...$intervals);
377
    }
378
}
379