Passed
Push — master ( 9f6473...b567ca )
by Luke
07:10
created

Collection::keyOf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Noz\Collection;
3
4
use Countable;
5
use JsonSerializable;
6
use Iterator;
7
use ArrayAccess;
8
use RuntimeException;
9
use Traversable;
10
11
use function Noz\is_traversable;
12
13
/**
14
 * Nozavroni Collection
15
 */
16
class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
17
{
18
    /** @var array */
19
    protected $items;
20
21
    /**
22
     * Collection constructor.
23
     *
24
     * @param array $items
25
     */
26 43
    public function __construct(array $items = [])
27
    {
28 43
        $this->items = $items;
29 43
        $this->rewind();
30 43
    }
31
32
    /**
33
     * Generate a collection from an array of items.
34
     * I created this method so that it's possible to extend a collection more easily.
35
     *
36
     * @param array $items
37
     *
38
     * @return Collection
39
     */
40 10
    public static function factory(array $items = [])
41
    {
42 10
        return new Collection($items);
43
    }
44
45
    /**
46
     * Get collection as an array
47
     *
48
     * @return array
49
     */
50 16
    public function toArray()
51
    {
52 16
        return $this->items;
53
    }
54
55
    /**
56
     * Determine if collection has a given key
57
     *
58
     * @param mixed $key The key to look for
59
     *
60
     * @return bool
61
     */
62 26
    public function has($key)
63
    {
64 26
        return isset($this->items[$key]) || array_key_exists($key, $this->items);
65
    }
66
67
    /**
68
     * Does collection have item at position?
69
     *
70
     * Determine if collection has an item at a particular position (indexed from one).
71
     * Position can be positive and start from the beginning or it can be negative and
72
     * start from the end.
73
     *
74
     * @param int $position
75
     *
76
     * @return bool
77
     */
78 2
    public function hasValueAt($position)
79
    {
80
        try {
81 2
            $this->getKeyAt($position);
82 2
            return true;
83 2
        } catch (RuntimeException $e) {
84 2
            return false;
85
        }
86
    }
87
88
    /**
89
     * Get key at given position
90
     *
91
     * Returns the key at the given position, starting from one. Position can be positive (start from beginning) or
92
     * negative (start from the end).
93
     *
94
     * If the position does not exist, a RuntimeException is thrown.
95
     *
96
     * @param int $position
97
     *
98
     * @return string
99
     *
100
     * @throws RuntimeException
101
     */
102 6
    public function getKeyAt($position)
103
    {
104 6
        $collection = $this;
105 6
        if ($position < 0) {
106 3
            $collection = $this->reverse();
107 3
        }
108 6
        $i = 1;
109 6
        foreach ($collection as $key => $val) {
110 6
            if (abs($position) == $i++) {
111 5
                return $key;
112
            }
113 6
        }
114 3
        throw new RuntimeException("No key at position {$position}");
115
    }
116
117
    /**
118
     * Get value at given position
119
     *
120
     * Returns the value at the given position, starting from one. Position can be positive (start from beginning) or
121
     * negative (start from the end).
122
     *
123
     * If the position does not exist, a RuntimeException is thrown.
124
     *
125
     * @param int $position
126
     *
127
     * @return mixed
128
     *
129
     * @throws RuntimeException
130
     */
131 2
    public function getValueAt($position)
132
    {
133 2
        return $this->get($this->getKeyAt($position));
134
    }
135
136
    /**
137
     * Get the key of the first item found matching $item
138
     *
139
     * @param mixed $item
140
     *
141
     * @return mixed|null
142
     */
143 1
    public function keyOf($item)
144
    {
145 1
        foreach ($this as $key => $val) {
146 1
            if ($item === $val) {
147 1
                return $key;
148
            }
149 1
        }
150
151 1
        return null;
152
    }
153
154
    /**
155
     * Get the offset (index) of the first item found that matches $item
156
     *
157
     * @param mixed $item
158
     *
159
     * @return int|null
160
     */
161 1
    public function indexOf($item)
162
    {
163 1
        $i = 0;
164 1
        foreach ($this as $key => $val) {
165 1
            if ($item === $val) {
166 1
                return $i;
167
            }
168 1
            $i++;
169 1
        }
170
171 1
        return null;
172
    }
173
174
    /**
175
     * Get item by key, with an optional default return value
176
     *
177
     * @param mixed $key
178
     * @param mixed $default
179
     *
180
     * @return mixed
181
     */
182 8
    public function get($key, $default = null)
183
    {
184 8
        if ($this->has($key)) {
185 8
            return $this->items[$key];
186
        }
187
188 3
        return $default;
189
    }
190
191
    /**
192
     * Add an item with no regard to key
193
     *
194
     * @param mixed $value
195
     *
196
     * @return $this
197
     */
198 3
    public function add($value)
199
    {
200 3
        $this->items[] = $value;
201
202 3
        return $this;
203
    }
204
205
    /**
206
     * Set an item at a given key
207
     *
208
     * @param mixed $key
209
     * @param mixed $value
210
     * @param bool  $overwrite If false, do not overwrite existing key
211
     *
212
     * @return $this
213
     */
214 9
    public function set($key, $value, $overwrite = true)
215
    {
216 9
        if ($overwrite || !$this->has($key)) {
217 9
            $this->items[$key] = $value;
218 9
        }
219
220 9
        return $this;
221
    }
222
223
    /**
224
     * Delete an item by key
225
     *
226
     * @param mixed $key
227
     *
228
     * @return $this
229
     */
230 3
    public function delete($key)
231
    {
232 3
        unset($this->items[$key]);
233
234 3
        return $this;
235
    }
236
237
    /**
238
     * Clear the collection of all its items.
239
     *
240
     * @return $this
241
     */
242 1
    public function clear()
243
    {
244 1
        $this->items = [];
245
246 1
        return $this;
247
    }
248
249
    /**
250
     * Determine if collection contains given value
251
     *
252
     * @param mixed $val
253
     *
254
     * @return bool
255
     */
256 2
    public function contains($val)
257
    {
258 2
        return in_array($val, $this->items, true);
259
    }
260
261
    /**
262
     * Fetch item from collection by key and remove it from collection
263
     *
264
     * @param mixed $key
265
     *
266
     * @return mixed
267
     */
268 1
    public function pull($key)
269
    {
270 1
        if ($this->has($key)) {
271 1
            $value = $this->get($key);
272 1
            $this->delete($key);
273 1
            return $value;
274
        }
275 1
    }
276
277
    /**
278
     * Join collection items using a delimiter
279
     *
280
     * @param string $delim
281
     *
282
     * @return string
283
     */
284 1
    public function join($delim = '')
285
    {
286 1
        return implode($delim, $this->items);
287
    }
288
289
    /**
290
     * Determine if collection has any items
291
     *
292
     * @return bool
293
     */
294 3
    public function isEmpty()
295
    {
296 3
        return $this->count() == 0;
297
    }
298
299
    /**
300
     * Get a collection of only this collection's values (without its keys)
301
     *
302
     * @return Collection
303
     */
304 1
    public function values()
305
    {
306 1
        return static::factory(array_values($this->items));
307
    }
308
309
    /**
310
     * Get a collection of only this collection's keys
311
     *
312
     * @return Collection
313
     */
314 1
    public function keys()
315
    {
316 1
        return static::factory(array_keys($this->items));
317
    }
318
319
    /**
320
     * Get a collection with order reversed
321
     *
322
     * @return Collection
323
     */
324 3
    public function reverse()
325
    {
326 3
        return static::factory(array_reverse($this->items));
327
    }
328
329
    /**
330
     * Get a collection with keys and values flipped
331
     *
332
     * @return Collection
333
     */
334 1
    public function flip()
335
    {
336 1
        $collection = static::factory();
337 1
        foreach ($this as $key => $val) {
338 1
            $collection->set($val, $key);
339 1
        }
340 1
        return $collection;
341
    }
342
343
    /**
344
     * Shuffle the order of this collection's values
345
     *
346
     * @return Collection
347
     */
348 1
    public function shuffle()
349
    {
350 1
        shuffle($this->items);
351 1
        return $this;
352
    }
353
354
    /**
355
     * Get a random value from the collection
356
     *
357
     * @return mixed
358
     */
359 1
    public function random()
360
    {
361 1
        return $this->getValueAt(rand(1, $this->count()));
362
    }
363
364
    /**
365
     * Sort the collection
366
     *
367
     * @param mixed $algo
368
     *
369
     * @return Collection
370
     */
371
    public function sort($algo = null)
0 ignored issues
show
Unused Code introduced by
The parameter $algo is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
372
    {
373
374
    }
375
376
    /**
377
     * Get a new collection with only distinct values
378
     *
379
     * @return Collection
380
     */
381 1
    public function distinct()
382
    {
383 1
        $collection = static::factory();
384 1
        foreach ($this as $key => $val) {
385 1
            if (!$collection->contains($val)) {
386 1
                $collection->set($key, $val);
387 1
            }
388 1
        }
389 1
        return $collection;
390
    }
391
392
    /**
393
     * Remove all duplicate values from collection in-place
394
     *
395
     * @return Collection
396
     */
397 1
    public function deduplicate()
398
    {
399 1
        $this->items = array_unique($this->items);
400
401 1
        return $this;
402
    }
403
404
    /**
405
     * Return a new collection with only filtered keys/values
406
     *
407
     * The callback accepts value, key, index and should return true if the item should be added to the returned
408
     * collection
409
     *
410
     * @param callable $callback
411
     *
412
     * @return Collection
413
     */
414 1
    public function filter(callable $callback)
415
    {
416 1
        $collection = static::factory();
417 1
        $index = 0;
418 1
        foreach ($this as $key => $value) {
419 1
            if ($callback($value, $key, $index++)) {
420 1
                $collection->set($key, $value);
421 1
            }
422 1
        }
423
424 1
        return $collection;
425
    }
426
427
    /**
428
     * Fold collection into a single value
429
     *
430
     * Loop through collection calling a callback function and passing the result to the next iteration, eventually
431
     * returning a single value.
432
     *
433
     * @param callable $callback
434
     * @param mixed $initial
435
     *
436
     * @return null
437
     */
438 1
    public function fold(callable $callback, $initial = null)
439
    {
440 1
        $index = 0;
441 1
        $folded = $initial;
442 1
        foreach ($this as $key => $val) {
443 1
            $folded = $callback($folded, $val, $key, $index++);
444 1
        }
445 1
        return $folded;
446
    }
447
448
    /**
449
     * Return a merge of this collection and $items
450
     *
451
     * @param array|Traversable $items
452
     *
453
     * @return Collection
454
     */
455 3
    public function merge($items)
456
    {
457 3
        if (!is_traversable($items)) {
458 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
459
        }
460
461 2
        $collection = clone $this;
462 2
        foreach ($items as $key => $val) {
463 2
            $collection->set($key, $val);
464 2
        }
465 2
        return $collection;
466
    }
467
468
    /**
469
     * Create a new collection with a union of this collection and $items
470
     *
471
     * This method is similar to merge, except that existing items will not be overwritten.
472
     *
473
     * @param $items
474
     */
475 2
    public function union($items)
476
    {
477 2
        if (!is_traversable($items)) {
478 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
479
        }
480
481 1
        $collection = clone $this;
482 1
        foreach ($items as $key => $val) {
483 1
            $collection->set($key, $val, false);
484 1
        }
485 1
        return $collection;
486
    }
487
488
    /** ++++                  ++++ **/
489
    /** ++ Interface Compliance ++ **/
490
    /** ++++                  ++++ **/
491
492
    /**
493
     * @return array
494
     */
495 1
    public function jsonSerialize()
496
    {
497 1
        return $this->toArray();
498
    }
499
500
    /** ++++                  ++++ **/
501
    /** ++ Array Access Methods ++ **/
502
    /** ++++                  ++++ **/
503
504
    /**
505
     * {@inheritDoc}
506
     */
507 1
    public function offsetExists($offset)
508
    {
509 1
        return $this->has($offset);
510
    }
511
512
    /**
513
     * {@inheritDoc}
514
     */
515 2
    public function offsetGet($offset)
516
    {
517 2
        if (!$this->has($offset)) {
518 1
            throw new RuntimeException("Unknown offset: {$offset}");
519
        }
520
521 1
        return $this->get($offset);
522
    }
523
524
    /**
525
     * {@inheritDoc}
526
     */
527 1
    public function offsetUnset($offset)
528
    {
529 1
        $this->delete($offset);
530 1
    }
531
532
    /**
533
     * {@inheritDoc}
534
     */
535 1
    public function offsetSet($offset, $value)
536
    {
537 1
        if (!isset($offset)) {
538 1
            $this->add($value);
539 1
        }
540
541 1
        $this->set($offset, $value);
542 1
    }
543
544
    /** ++++                  ++++ **/
545
    /** ++   Iterator Methods   ++ **/
546
    /** ++++                  ++++ **/
547
548
    /**
549
     * {@inheritDoc}
550
     */
551 15
    public function current()
552
    {
553 15
        return current($this->items);
554
    }
555
556
    /**
557
     * {@inheritDoc}
558
     */
559 15
    public function key()
560
    {
561 15
        return key($this->items);
562
    }
563
564
    /**
565
     * {@inheritDoc}
566
     */
567 15
    public function next()
568
    {
569 15
        return next($this->items);
570
    }
571
572
    /**
573
     * {@inheritDoc}
574
     */
575 43
    public function rewind()
576
    {
577 43
        reset($this->items);
578 43
    }
579
580
    /**
581
     * {@inheritDoc}
582
     */
583 15
    public function valid()
584
    {
585 15
        return $this->has(key($this->items));
586
    }
587
588
    /** ++++                  ++++ **/
589
    /** ++   Countable Method   ++ **/
590
    /** ++++                  ++++ **/
591
592
    /**
593
     * {@inheritDoc}
594
     */
595 5
    public function count()
596
    {
597 5
        return count($this->items);
598
    }
599
}