Passed
Push — master ( 583647...efdb33 )
by Dawid
02:55
created

Collection   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 344
Duplicated Lines 0 %

Test Coverage

Coverage 99.15%

Importance

Changes 0
Metric Value
eloc 99
dl 0
loc 344
ccs 117
cts 118
cp 0.9915
rs 8.8798
c 0
b 0
f 0
wmc 44

28 Methods

Rating   Name   Duplication   Size   Complexity  
A key() 0 3 1
A previous() 0 4 2
A count() 0 3 1
A toArray() 0 3 1
A sort() 0 6 1
A next() 0 3 1
A any() 0 9 3
A addMany() 0 8 1
A fromList() 0 3 1
A at() 0 7 2
A last() 0 5 1
A slice() 0 8 1
A removeMany() 0 8 1
A __construct() 0 16 3
A current() 0 3 1
A first() 0 5 1
A contains() 0 3 1
A map() 0 9 2
A every() 0 9 3
A add() 0 8 1
A remove() 0 13 2
A insert() 0 10 3
A valid() 0 7 2
A reduce() 0 7 2
A reverse() 0 6 1
A clear() 0 5 1
A rewind() 0 3 1
A where() 0 13 3

How to fix   Complexity   

Complex Class

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

1
<?php declare(strict_types=1);
2
3
namespace Igni\Storage\Mapping\Collection;
4
5
use Igni\Storage\Exception\CollectionException;
6
use Traversable;
7
use Igni\Exception\OutOfBoundsException;
8
9
/**
10
 * Immutable collection representation.
11
 */
12
class Collection implements \Igni\Storage\Mapping\Collection
13
{
14
    protected $length = 0;
15
    protected $cursor = 0;
16
    protected $items = [];
17
18 21
    public function __construct(iterable $cursor = null)
19
    {
20 21
        if ($cursor === null) {
21 4
            $this->items = [];
22 4
            $this->length = 0;
23 4
            return;
24
        }
25
26
        /** @TODO: Maybe keep cursor as iterator */
27 21
        if ($cursor instanceof Traversable) {
28 7
            $this->items = iterator_to_array($cursor);
29
        } else {
30 14
            $this->items = $cursor;
31
        }
32
33 21
        $this->length = count($this->items);
34 21
    }
35
36
    /**
37
     * Reduces a collection to a single value by iteratively combining elements of the collection
38
     * using the provided callable.
39
     *
40
     * @param callable $f($previousValue, $current)
41
     * @param mixed $initialValue
42
     * @return mixed
43
     */
44 1
    public function reduce(callable $f, $initialValue = null)
45
    {
46 1
        foreach ($this as $element) {
47 1
            $initialValue = $f($initialValue, $element);
48
        }
49
50 1
        return $initialValue;
51
    }
52
53
    /**
54
     * Returns new collection with items sorted by the order specified by the compare function.
55
     *
56
     * @param callable $compare
57
     * @return Collection
58
     */
59 1
    public function sort(callable $compare): self
60
    {
61 1
        $collection = clone $this;
62 1
        usort($collection->items, $compare);
0 ignored issues
show
Bug introduced by
It seems like $collection->items can also be of type iterable; however, parameter $array of usort() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

62
        usort(/** @scrutinizer ignore-type */ $collection->items, $compare);
Loading history...
63
64 1
        return $collection;
65
    }
66
67
    /**
68
     * Returns new collection with elements that are created by calling f callable on each element.
69
     *
70
     * @param callable $f($element)
71
     * @return Collection
72
     */
73 1
    public function map(callable $f): self
74
    {
75 1
        $collection = new self();
76 1
        foreach ($this as $item) {
77 1
            $collection->items[] = $f($item);
78
        }
79 1
        $collection->length = $this->length;
80
81 1
        return $collection;
82
    }
83
84
    /**
85
     * Returns new collection with inserted element(s) at position index.
86
     *
87
     * @param int $index
88
     * @param mixed ...$elements
89
     * @return Collection
90
     */
91 3
    public function insert(int $index, ...$elements): self
92
    {
93 3
        if ($index < 0 || $index - 1 > $this->length) {
94 1
            throw CollectionException::forInvalidIndex($index);
95
        }
96
97 2
        $collection = clone $this;
98 2
        array_splice($collection->items, $index, 0, $elements);
0 ignored issues
show
Bug introduced by
It seems like $collection->items can also be of type iterable; however, parameter $input of array_splice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

98
        array_splice(/** @scrutinizer ignore-type */ $collection->items, $index, 0, $elements);
Loading history...
99
100 2
        return $collection;
101
    }
102
103
    /**
104
     * Returns a new collection with elements extracted as slice.
105
     *
106
     * @param int $start
107
     * @param int $length
108
     * @return Collection
109
     */
110 1
    public function slice(int $start, int $length): self
111
    {
112 1
        $items = array_slice($this->items, $start, $length);
0 ignored issues
show
Bug introduced by
It seems like $this->items can also be of type iterable; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

112
        $items = array_slice(/** @scrutinizer ignore-type */ $this->items, $start, $length);
Loading history...
113 1
        $collection = new self();
114 1
        $collection->items = $items;
115 1
        $collection->length = $length;
116
117 1
        return $collection;
118
    }
119
120
    /**
121
     * Returns a new Collection with all elements that satisfy the predicate test.
122
     * @param callable $test($element)
123
     * @return Collection
124
     */
125 1
    public function where(callable $test): self
126
    {
127 1
        $collection = new self();
128 1
        $length = 0;
129 1
        foreach ($this as $item) {
130 1
            if ($test($item)) {
131 1
                $length++;
132 1
                $collection->items[] = $item;
133
            }
134
        }
135 1
        $collection->length = $length;
136
137 1
        return $collection;
138
    }
139
140
    /**
141
     * Checks whether every element of this iterable satisfies test
142
     * @param callable $test($element)
143
     * @return bool
144
     */
145 1
    public function every(callable $test): bool
146
    {
147 1
        foreach($this as $item) {
148 1
            if (!$test($item)) {
149 1
                return false;
150
            }
151
        }
152
153 1
        return true;
154
    }
155
156
    /**
157
     * Checks whether any element of this iterable satisfies test
158
     * @param callable $test($element)
159
     * @return bool
160
     */
161 1
    public function any(callable $test): bool
162
    {
163 1
        foreach($this as $item) {
164 1
            if ($test($item)) {
165 1
                return true;
166
            }
167
        }
168
169 1
        return false;
170
    }
171
172
    /**
173
     * Returns instance of the collection with reversed items.
174
     * @return Collection
175
     */
176 1
    public function reverse(): self
177
    {
178 1
        $collection = clone $this;
179 1
        $collection->items = array_reverse($collection->items);
0 ignored issues
show
Bug introduced by
It seems like $collection->items can also be of type iterable; however, parameter $array of array_reverse() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

179
        $collection->items = array_reverse(/** @scrutinizer ignore-type */ $collection->items);
Loading history...
180
181 1
        return $collection;
182
    }
183
184
    /**
185
     * Removes all objects from this collection; the length of the collection becomes zero.
186
     */
187 1
    public function clear(): void
188
    {
189 1
        $this->items = [];
190 1
        $this->length = 0;
191 1
        $this->cursor = 0;
192 1
    }
193
194
    /**
195
     * Returns new collection with value added to the end of this list, extending the length by one.
196
     *
197
     * @param mixed $element
198
     * @return Collection
199
     */
200 1
    public function add($element): self
201
    {
202 1
        $collection = clone $this;
203 1
        $collection->cursor = 0;
204 1
        $collection->items[] = $element;
205 1
        $collection->length++;
206
207 1
        return $collection;
208
    }
209
210 1
    public function addMany(...$elements): self
211
    {
212 1
        $collection = clone $this;
213 1
        $collection->cursor = 0;
214 1
        $collection->items = array_merge($collection->items, $elements);
0 ignored issues
show
Bug introduced by
It seems like $collection->items can also be of type iterable; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

214
        $collection->items = array_merge(/** @scrutinizer ignore-type */ $collection->items, $elements);
Loading history...
215 1
        $collection->length += count($elements);
216
217 1
        return $collection;
218
    }
219
220
    /**
221
     * Returns new collection with removed value, decreasing the length by one.
222
     *
223
     * @param $element
224
     * @return Collection
225
     */
226 1
    public function remove($element): self
227
    {
228 1
        $index = array_search($element, $this->items);
0 ignored issues
show
Bug introduced by
It seems like $this->items can also be of type iterable; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

228
        $index = array_search($element, /** @scrutinizer ignore-type */ $this->items);
Loading history...
229 1
        if ($index === false) {
230
            return $this;
231
        }
232
233 1
        $collection = clone $this;
234 1
        array_splice($collection->items, $index, 1);
0 ignored issues
show
Bug introduced by
It seems like $collection->items can also be of type iterable; however, parameter $input of array_splice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

234
        array_splice(/** @scrutinizer ignore-type */ $collection->items, $index, 1);
Loading history...
Bug introduced by
It seems like $index can also be of type string; however, parameter $offset of array_splice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

234
        array_splice($collection->items, /** @scrutinizer ignore-type */ $index, 1);
Loading history...
235 1
        $collection->length--;
236 1
        $collection->cursor = 0;
237
238 1
        return $collection;
239
    }
240
241 1
    public function removeMany(...$elements): self
242
    {
243 1
        $collection = clone $this;
244 1
        $collection->items = array_values(array_diff($collection->items, $elements));
0 ignored issues
show
Bug introduced by
It seems like $collection->items can also be of type iterable; however, parameter $array1 of array_diff() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

244
        $collection->items = array_values(array_diff(/** @scrutinizer ignore-type */ $collection->items, $elements));
Loading history...
245 1
        $collection->length -= count($elements);
246 1
        $collection->cursor = 0;
247
248 1
        return $collection;
249
    }
250
251
    /**
252
     * Checks if collection contains given element.
253
     * @param $element
254
     * @return bool
255
     */
256 1
    public function contains($element): bool
257
    {
258 1
        return in_array($element, $this->items);
0 ignored issues
show
Bug introduced by
It seems like $this->items can also be of type iterable; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

258
        return in_array($element, /** @scrutinizer ignore-type */ $this->items);
Loading history...
259
    }
260
261
    /**
262
     * Returns first element of the collection
263
     * @return mixed
264
     */
265 1
    public function first()
266
    {
267 1
        $this->cursor = 0;
268
269 1
        return $this->at($this->cursor);
270
    }
271
272
    /**
273
     * Returns last element of the collection
274
     * @return mixed
275
     */
276 1
    public function last()
277
    {
278 1
        $this->cursor = $this->length - 1;
279
280 1
        return $this->current();
281
    }
282
283
    /**
284
     * Returns element at the index
285
     * @param int $offset
286
     * @return mixed
287
     */
288 9
    public function at(int $offset)
289
    {
290 9
        if ($offset < $this->length) {
291 9
            return $this->items[$offset];
292
        }
293
294 1
        throw new OutOfBoundsException("Invalid offset: ${offset}");
295
    }
296
297
    /**
298
     * Returns current element
299
     * @return mixed
300
     */
301
    public function current()
302
    {
303 9
        return $this->at($this->cursor);
304
    }
305
306
    /**
307
     * Moves cursor of the collection forward by one.
308
     */
309
    public function next(): void
310
    {
311 7
        $this->cursor++;
312 7
    }
313
314
    /**
315
     * Moves cursor of the collection backward by one.
316
     */
317
    public function previous(): void
318
    {
319 1
        if ($this->cursor > 0) {
320 1
            $this->cursor--;
321
        }
322 1
    }
323
324
    public function key(): int
325
    {
326 1
        return $this->cursor;
327
    }
328
329
    public function valid(): bool
330
    {
331 6
        if ($this->cursor < $this->length) {
332 6
            return true;
333
        }
334
335 6
        return false;
336
    }
337
338
    public function rewind(): void
339
    {
340 6
        $this->cursor = 0;
341 6
    }
342
343
    public function count(): int
344
    {
345 3
        return $this->length;
346
    }
347
348
    public function toArray(): array
349
    {
350 11
        return $this->items;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->items could return the type iterable which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
351
    }
352
353
    public static function fromList(...$elements): Collection
354
    {
355 1
        return new self($elements);
356
    }
357
}
358