Completed
Push — master ( f08430...0044c9 )
by Dawid
03:13
created

Collection   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 333
Duplicated Lines 0 %

Test Coverage

Coverage 76.52%

Importance

Changes 0
Metric Value
eloc 95
dl 0
loc 333
ccs 88
cts 115
cp 0.7652
rs 9.0399
c 0
b 0
f 0
wmc 42

27 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 addMany() 0 8 1
A at() 0 7 2
A last() 0 5 1
A slice() 0 8 1
A removeMany() 0 8 1
A __construct() 0 10 2
A current() 0 3 1
A first() 0 5 1
A contains() 0 3 1
A map() 0 9 2
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 rewind() 0 3 1
A where() 0 13 3
A any() 0 9 3
A every() 0 9 3
A reverse() 0 6 1
A clear() 0 5 1

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 Iterator;
7
use 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 17
    public function __construct(Iterator $cursor = null)
19
    {
20 17
        if ($cursor === null) {
21 4
            $this->items = [];
22 4
            $this->length = 0;
23 4
            return;
24
        }
25
26 17
        $this->items = iterator_to_array($cursor);
27 17
        $this->length = count($this->items);
28 17
    }
29
30
    /**
31
     * Reduces a collection to a single value by iteratively combining elements of the collection
32
     * using the provided callable.
33
     *
34
     * @param callable $f($previousValue, $current)
35
     * @param mixed $initialValue
36
     * @return mixed
37
     */
38 1
    public function reduce(callable $f, $initialValue = null)
39
    {
40 1
        foreach ($this as $element) {
41 1
            $initialValue = $f($initialValue, $element);
42
        }
43
44 1
        return $initialValue;
45
    }
46
47
    /**
48
     * Returns new collection with items sorted by the order specified by the compare function.
49
     *
50
     * @param callable $compare
51
     * @return Collection
52
     */
53 1
    public function sort(callable $compare): self
54
    {
55 1
        $collection = clone $this;
56 1
        usort($collection->items, $compare);
57
58 1
        return $collection;
59
    }
60
61
    /**
62
     * Returns new collection with elements that are created by calling f callable on each element.
63
     *
64
     * @param callable $f($element)
65
     * @return Collection
66
     */
67 1
    public function map(callable $f): self
68
    {
69 1
        $collection = new self();
70 1
        foreach ($this as $item) {
71 1
            $collection->items[] = $f($item);
72
        }
73 1
        $collection->length = $this->length;
74
75 1
        return $collection;
76
    }
77
78
    /**
79
     * Returns new collection with inserted element(s) at position index.
80
     *
81
     * @param int $index
82
     * @param mixed ...$elements
83
     * @return Collection
84
     */
85 2
    public function insert(int $index, ...$elements): self
86
    {
87 2
        if ($index < 0 || $index - 1 > $this->length) {
88
            throw CollectionException::forInvalidIndex($index);
89
        }
90
91 2
        $collection = clone $this;
92 2
        array_splice($collection->items, $index, 0, $elements);
93
94 2
        return $collection;
95
    }
96
97
    /**
98
     * Returns a new collection with elements extracted as slice.
99
     *
100
     * @param int $start
101
     * @param int $length
102
     * @return Collection
103
     */
104 1
    public function slice(int $start, int $length): self
105
    {
106 1
        $items = array_slice($this->items, $start, $length);
107 1
        $collection = new self();
108 1
        $collection->items = $items;
109 1
        $collection->length = $length;
110
111 1
        return $collection;
112
    }
113
114
    /**
115
     * Returns a new Collection with all elements that satisfy the predicate test.
116
     * @param callable $test($element)
117
     * @return Collection
118
     */
119 1
    public function where(callable $test): self
120
    {
121 1
        $collection = new self();
122 1
        $length = 0;
123 1
        foreach ($this as $item) {
124 1
            if ($test($item)) {
125 1
                $length++;
126 1
                $collection->items[] = $item;
127
            }
128
        }
129 1
        $collection->length = $length;
130
131 1
        return $collection;
132
    }
133
134
    /**
135
     * Checks whether every element of this iterable satisfies test
136
     * @param callable $test($element)
137
     * @return bool
138
     */
139 1
    public function every(callable $test): bool
140
    {
141 1
        foreach($this as $item) {
142 1
            if (!$test($item)) {
143 1
                return false;
144
            }
145
        }
146
147 1
        return true;
148
    }
149
150
    /**
151
     * Checks whether any element of this iterable satisfies test
152
     * @param callable $test($element)
153
     * @return bool
154
     */
155 1
    public function any(callable $test): bool
156
    {
157 1
        foreach($this as $item) {
158 1
            if ($test($item)) {
159 1
                return true;
160
            }
161
        }
162
163 1
        return false;
164
    }
165
166
    /**
167
     * Returns instance of the collection with reversed items.
168
     * @return Collection
169
     */
170 1
    public function reverse(): self
171
    {
172 1
        $collection = clone $this;
173 1
        $collection->items = array_reverse($collection->items);
174
175 1
        return $collection;
176
    }
177
178
    /**
179
     * Removes all objects from this collection; the length of the collection becomes zero.
180
     */
181 1
    public function clear(): void
182
    {
183 1
        $this->items = [];
184 1
        $this->length = 0;
185 1
        $this->cursor = 0;
186 1
    }
187
188
    /**
189
     * Returns new collection with value added to the end of this list, extending the length by one.
190
     *
191
     * @param mixed $element
192
     * @return Collection
193
     */
194 1
    public function add($element): self
195
    {
196 1
        $collection = clone $this;
197 1
        $collection->cursor = 0;
198 1
        $collection->items[] = $element;
199 1
        $collection->length++;
200
201 1
        return $collection;
202
    }
203
204
    public function addMany(...$elements): self
205
    {
206
        $collection = clone $this;
207
        $collection->cursor = 0;
208
        $collection->items = array_merge($collection->items, $elements);
209
        $collection->length += count($elements);
210
211
        return $collection;
212
    }
213
214
    /**
215
     * Returns new collection with removed value, decreasing the length by one.
216
     *
217
     * @param $element
218
     * @return Collection
219
     */
220
    public function remove($element): self
221
    {
222
        $index = array_search($element, $this->items);
223
        if ($index === false) {
224
            return $this;
225
        }
226
227
        $collection = clone $this;
228
        array_splice($collection->items, $index, 1);
0 ignored issues
show
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

228
        array_splice($collection->items, /** @scrutinizer ignore-type */ $index, 1);
Loading history...
229
        $collection->length--;
230
        $collection->cursor = 0;
231
232
        return $collection;
233
    }
234
235
    public function removeMany(...$elements): self
236
    {
237
        $collection = clone $this;
238
        $collection->items = array_diff($collection->items, $elements);
239
        $collection->length -= count($elements);
240
        $collection->cursor = 0;
241
242
        return $collection;
243
    }
244
245
    /**
246
     * Checks if collection contains given element.
247
     * @param $element
248
     * @return bool
249
     */
250 1
    public function contains($element): bool
251
    {
252 1
        return in_array($element, $this->items);
253
    }
254
255
    /**
256
     * Returns first element of the collection
257
     * @return mixed
258
     */
259 1
    public function first()
260
    {
261 1
        $this->cursor = 0;
262
263 1
        return $this->at($this->cursor);
264
    }
265
266
    /**
267
     * Returns last element of the collection
268
     * @return mixed
269
     */
270 1
    public function last()
271
    {
272 1
        $this->cursor = $this->length - 1;
273
274 1
        return $this->current();
275
    }
276
277
    /**
278
     * Returns element at the index
279
     * @param int $offset
280
     * @return mixed
281
     */
282 9
    public function at(int $offset)
283
    {
284 9
        if ($offset < $this->length) {
285 9
            return $this->items[$offset];
286
        }
287
288
        throw new OutOfBoundsException("Invalid offset: ${offset}");
289
    }
290
291
    /**
292
     * Returns current element
293
     * @return mixed
294
     */
295
    public function current()
296
    {
297 9
        return $this->at($this->cursor);
298
    }
299
300
    /**
301
     * Moves cursor of the collection forward by one.
302
     */
303
    public function next(): void
304
    {
305 7
        $this->cursor++;
306 7
    }
307
308
    /**
309
     * Moves cursor of the collection backward by one.
310
     */
311
    public function previous(): void
312
    {
313
        if ($this->cursor > 0) {
314
            $this->cursor--;
315
        }
316
    }
317
318
    public function key(): int
319
    {
320
        return $this->cursor;
321
    }
322
323
    public function valid(): bool
324
    {
325 6
        if ($this->cursor < $this->length) {
326 6
            return true;
327
        }
328
329 6
        return false;
330
    }
331
332
    public function rewind(): void
333
    {
334 6
        $this->cursor = 0;
335 6
    }
336
337
    public function count(): int
338
    {
339 3
        return $this->length;
340
    }
341
342
    public function toArray(): array
343
    {
344 8
        return $this->items;
345
    }
346
}
347