Completed
Push — master ( a53672...655b9a )
by ignace nyamagana
32:28 queued 17:30
created

Sequence::every()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.9332
cc 3
nc 3
nop 1
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 function array_filter;
20
use function array_merge;
21
use function count;
22
use function reset;
23
use function sort;
24
use function sprintf;
25
use function uasort;
26
27
/**
28
 * A class to manipulate interval collection.
29
 *
30
 * @package League.period
31
 * @author  Ignace Nyamagana Butera <[email protected]>
32
 * @since   4.1.0
33
 */
34
final class Sequence implements Countable, IteratorAggregate
35
{
36
    /**
37
     * @var Period[]
38
     */
39
    private $intervals = [];
40
41
    /**
42
     * new instance.
43
     *
44
     * @param Period... $intervals
0 ignored issues
show
Documentation introduced by
The doc-type Period... could not be parsed: Unknown type name "Period..." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
45
     */
46
    public function __construct(Period ...$intervals)
47
    {
48
        $this->intervals = $intervals;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function count(): int
55
    {
56
        return count($this->intervals);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function getIterator(): Iterator
63
    {
64
        foreach ($this->intervals as $offset => $interval) {
65
            yield $offset => $interval;
66
        }
67
    }
68
69
    /**
70
     * Tells whether the sequence is empty.
71
     */
72
    public function isEmpty(): bool
73
    {
74
        return [] === $this->intervals;
75
    }
76
77
    /**
78
     * Removes all intervals from the sequence.
79
     */
80
    public function clear(): void
81
    {
82
        $this->intervals = [];
83
    }
84
85
    /**
86
     * Returns the array representation of the sequence.
87
     *
88
     * @return Period[]
89
     */
90
    public function toArray(): array
91
    {
92
        return $this->intervals;
93
    }
94
95
    /**
96
     * Returns the interval specified at a given offset.
97
     *
98
     * @throws InvalidIndex If the offset is illegal for the current sequence
99
     */
100
    public function get(int $offset): Period
101
    {
102
        $period = $this->intervals[$offset] ?? null;
103
        if (null !== $period) {
104
            return $period;
105
        }
106
107
        throw new InvalidIndex(sprintf('%s is an invalid offset in the current sequence', $offset));
108
    }
109
110
    /**
111
     * Removes an interval from the sequence at the given offset and returns it.
112
     *
113
     * The sequence is re-indexed after removal
114
     *
115
     * @throws InvalidIndex If the offset is illegal for the current sequence.
116
     */
117
    public function remove(int $offset): Period
118
    {
119
        $interval = $this->get($offset);
120
        unset($this->intervals[$offset]);
121
122
        $this->intervals = array_values($this->intervals);
123
124
        return $interval;
125
    }
126
127
    /**
128
     * Adds new interval at the end of the sequence.
129
     * @param Period... $intervals
0 ignored issues
show
Documentation introduced by
The doc-type Period... could not be parsed: Unknown type name "Period..." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
130
     */
131
    public function push(Period $interval, Period ...$intervals): void
132
    {
133
        $this->intervals = array_merge($this->intervals, [$interval], $intervals);
134
    }
135
136
    /**
137
     * Updates the interval at the specify offset.
138
     *
139
     * @throws InvalidIndex If the offset is illegal for the current sequence.
140
     */
141
    public function set(int $offset, Period $interval): void
142
    {
143
        $this->get($offset);
144
        $this->intervals[$offset] = $interval;
145
    }
146
147
    /**
148
     * Tells whether the given interval is present in the sequence.
149
     * @param Period... $intervals
0 ignored issues
show
Documentation introduced by
The doc-type Period... could not be parsed: Unknown type name "Period..." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
150
     */
151
    public function contains(Period $interval, Period ... $intervals): bool
152
    {
153
        $intervals[] = $interval;
154
        foreach ($intervals as $period) {
155
            if (null === $this->indexOf($period)) {
156
                return false;
157
            }
158
        }
159
160
        return true;
161
    }
162
163
    /**
164
     * Attempts to find the first offset attached to the submitted interval.
165
     *
166
     * If no offset is found the method returns null.
167
     *
168
     * @return ?int
0 ignored issues
show
Documentation introduced by
The doc-type ?int could not be parsed: Unknown type name "?int" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
169
     */
170
    public function indexOf(Period $interval): ?int
171
    {
172
        foreach ($this->intervals as $offset => $period) {
173
            if ($period->equals($interval)) {
174
                return $offset;
175
            }
176
        }
177
178
        return null;
179
    }
180
181
    /**
182
     * Returns the sequence boundaries as a Period instance.
183
     *
184
     * If the sequence contains no interval null is returned.
185
     *
186
     * @return ?Period
0 ignored issues
show
Documentation introduced by
The doc-type ?Period could not be parsed: Unknown type name "?Period" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
187
     */
188
    public function getBoundaries(): ?Period
189
    {
190
        $period = reset($this->intervals);
191
        if (false === $period) {
192
            return null;
193
        }
194
195
        return $period->merge(...$this->intervals);
196
    }
197
198
    /**
199
     * Sort the current instance according to the given comparison callable
200
     * and maintain index association.
201
     *
202
     * Returns true on success or false on failure
203
     */
204
    public function sort(callable $compare): bool
205
    {
206
        return uasort($this->intervals, $compare);
207
    }
208
209
    /**
210
     * Returns an instance sorted according to the given comparison callable
211
     * but does not maintain index association.
212
     *
213
     * This method MUST retain the state of the current instance, and return
214
     * an instance that contains the sorted intervals. The key are re-indexed
215
     */
216
    public function sorted(callable $compare): self
217
    {
218
        $intervals = $this->intervals;
219
        usort($intervals, $compare);
220
        if ($intervals === $this->intervals) {
221
            return $this;
222
        }
223
224
        return new self(...$intervals);
225
    }
226
227
    /**
228
     * Filters the sequence according to the given predicate.
229
     *
230
     * This method MUST retain the state of the current instance, and return
231
     * an instance that contains the interval which validate the predicate.
232
     */
233
    public function filter(callable $predicate): self
234
    {
235
        $intervals = array_filter($this->intervals, $predicate);
236
        if ($intervals === $this->intervals) {
237
            return $this;
238
        }
239
240
        return new self(...$intervals);
241
    }
242
243
    /**
244
     * Tells whether some intervals in the current instance satisfies the predicate.
245
     */
246
    public function some(callable $predicate): bool
247
    {
248
        foreach ($this->intervals as $interval) {
249
            if (true === $predicate($interval)) {
250
                return true;
251
            }
252
        }
253
254
        return false;
255
    }
256
257
    /**
258
     * Tells whether all intervals in the current instance satisfies the predicate.
259
     */
260
    public function every(callable $predicate): bool
261
    {
262
        foreach ($this->intervals as $interval) {
263
            if (true !== $predicate($interval)) {
264
                return false;
265
            }
266
        }
267
268
        return [] !== $this->intervals;
269
    }
270
271
    /**
272
     * Returns the gaps inside the instance.
273
     */
274
    public function getGaps(): self
275
    {
276
        $sequence = new self();
277
        $interval = null;
278
        $currentInterval = null;
0 ignored issues
show
Unused Code introduced by
$currentInterval is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
279
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
280
            $currentInterval = $period;
281
            if (null === $interval) {
282
                $interval = $currentInterval;
283
                continue;
284
            }
285
286
            if (!$interval->overlaps($currentInterval) && !$interval->abuts($currentInterval)) {
287
                $sequence->push($interval->gap($currentInterval));
288
            }
289
290
            if (!$interval->contains($currentInterval)) {
291
                $interval = $currentInterval;
292
            }
293
        }
294
295
        return $sequence;
296
    }
297
298
    /**
299
     * Sort two Interval instance using their start datepoint.
300
     */
301
    private function sortByStartDate(Period $interval1, Period $interval2): int
302
    {
303
        return $interval1->getStartDate() <=> $interval2->getStartDate();
304
    }
305
306
    /**
307
     * Returns the intersections inside the instance.
308
     */
309
    public function getIntersections(): self
310
    {
311
        $sequence = new self();
312
        $interval = null;
313
        $currentInterval = null;
314
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
315
            if (null === $interval) {
316
                $interval = $period;
317
                continue;
318
            }
319
320
            if (null !== $currentInterval && $interval->contains($period)) {
321
                continue;
322
            }
323
324
            $currentInterval = $period;
325
            if ($interval->overlaps($currentInterval)) {
326
                $sequence->push($interval->intersect($currentInterval));
327
            }
328
329
            if (!$interval->contains($currentInterval)) {
330
                $interval = $currentInterval;
331
                $currentInterval = null;
332
            }
333
        }
334
335
        return $sequence;
336
    }
337
}
338