Completed
Pull Request — master (#70)
by ignace nyamagana
14:12
created

Sequence::contains()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 collection.
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 Exception 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 Exception(sprintf('%s is an invalid offset in the current sequence', $offset));
108
    }
109
110
    /**
111
     * Removes from the sequence and returns the interval at the given offset.
112
     *
113
     * The sequence is re-indexed after removal
114
     *
115
     * @throws Exception If the offset is illegal for the current sequence.
116
     */
117
    public function remove(int $offset): Period
118
    {
119
        $period = $this->get($offset);
120
        unset($this->intervals[$offset]);
121
122
        $this->intervals = array_values($this->intervals);
123
124
        return $period;
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
     * Update the interval at the specify offset.
138
     *
139
     * @throws Exception If the offset is illegal for the current sequence.
140
     */
141
    public function set(int $offset, Period $interval): void
142
    {
143
        $period = $this->get($offset);
0 ignored issues
show
Unused Code introduced by
$period 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...
144
        $this->intervals[$offset] = $interval;
145
    }
146
147
    /**
148
     * Tells whether the given interval is present in the sequence.
149
     */
150
    public function contains(Period $interval): bool
151
    {
152
        return null !== $this->find($interval);
153
    }
154
155
    /**
156
     * Attempts to find the first offset attached to the submitted interval.
157
     *
158
     * If no offset is found the method returns null.
159
     *
160
     * @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...
161
     */
162
    public function find(Period $interval): ?int
163
    {
164
        foreach ($this->intervals as $offset => $period) {
165
            if ($period->equals($interval)) {
166
                return $offset;
167
            }
168
        }
169
170
        return null;
171
    }
172
173
    /**
174
     * Returns the collection boundaries as a Period instance.
175
     *
176
     * @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...
177
     */
178
    public function getInterval(): ?Period
179
    {
180
        $period = reset($this->intervals);
181
        if (false === $period) {
182
            return null;
183
        }
184
185
        return $period->merge(...$this->intervals);
186
    }
187
188
    /**
189
     * Sort the current instance according to the given comparison callable
190
     * and maintain index association.
191
     *
192
     * Returns true on success or false on failure
193
     */
194
    public function sort(callable $compare): bool
195
    {
196
        return uasort($this->intervals, $compare);
197
    }
198
199
    /**
200
     * Returns an instance sorted according to the given comparison callable
201
     * but does not maintain index association.
202
     *
203
     * This method MUST retain the state of the current instance, and return
204
     * an instance that contains the sorted intervals. The key are re-indexed
205
     */
206
    public function sorted(callable $compare): self
207
    {
208
        $intervals = $this->intervals;
209
        usort($intervals, $compare);
210
        if ($intervals === $this->intervals) {
211
            return $this;
212
        }
213
214
        return new self(...$intervals);
215
    }
216
217
    /**
218
     * Filters the sequence according to the given predicate.
219
     *
220
     * This method MUST retain the state of the current instance, and return
221
     * an instance that contains the interval which validate the predicate.
222
     */
223
    public function filter(callable $predicate): self
224
    {
225
        return new self(...array_filter($this->intervals, $predicate));
226
    }
227
228
    /**
229
     * Tells whether some interval in the current instance satisfies the predicate.
230
     */
231
    public function some(callable $predicate): bool
232
    {
233
        foreach ($this->intervals as $interval) {
234
            if (true === $predicate($interval)) {
235
                return true;
236
            }
237
        }
238
239
        return false;
240
    }
241
242
    /**
243
     * Tells whether all interval in the current instance satisfies the predicate.
244
     */
245
    public function every(callable $predicate): bool
246
    {
247
        foreach ($this->intervals as $interval) {
248
            if (true !== $predicate($interval)) {
249
                return false;
250
            }
251
        }
252
253
        return [] !== $this->intervals;
254
    }
255
256
    /**
257
     * Returns the gaps inside the instance.
258
     */
259
    public function getGaps(): self
260
    {
261
        $sequence = new self();
262
        $interval = null;
263
        $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...
264
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
265
            $currentInterval = $period;
266
            if (null === $interval) {
267
                $interval = $currentInterval;
268
                continue;
269
            }
270
271
            if (!$interval->overlaps($currentInterval) && !$interval->abuts($currentInterval)) {
272
                $sequence->push($interval->gap($currentInterval));
273
            }
274
275
            if (!$interval->contains($currentInterval)) {
276
                $interval = $currentInterval;
277
            }
278
        }
279
280
        return $sequence;
281
    }
282
283
    /**
284
     * Sort two Interval instance using their start datepoint.
285
     */
286
    private function sortByStartDate(Period $interval1, Period $interval2): int
287
    {
288
        return $interval1->getStartDate() <=> $interval2->getStartDate();
289
    }
290
291
    /**
292
     * Returns the intersections inside the instance.
293
     */
294
    public function getIntersections(): self
295
    {
296
        $sequence = new self();
297
        $interval = null;
298
        $currentInterval = null;
299
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
300
            if (null === $interval) {
301
                $interval = $period;
302
                continue;
303
            }
304
305
            if (null !== $currentInterval && $interval->contains($period)) {
306
                continue;
307
            }
308
309
            $currentInterval = $period;
310
            if ($interval->overlaps($currentInterval)) {
311
                $sequence->push($interval->intersect($currentInterval));
312
            }
313
314
            if (!$interval->contains($currentInterval)) {
315
                $interval = $currentInterval;
316
                $currentInterval = null;
317
            }
318
        }
319
320
        return $sequence;
321
    }
322
}
323