Completed
Pull Request — master (#15)
by James
05:52 queued 02:57
created

Collection::clear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
namespace Intraxia\Jaxion\Axolotl;
3
4
use Intraxia\Jaxion\Contract\Axolotl\Collection as CollectionContract;
5
use Intraxia\Jaxion\Contract\Axolotl\Serializes;
6
use InvalidArgumentException;
7
use OutOfBoundsException;
8
use OutOfRangeException;
9
10
/**
11
 * Class Collection
12
 *
13
 * @package Intraxia\Jaxion
14
 * @subpackage Axolotl
15
 */
16
class Collection implements CollectionContract {
17
18
	/**
19
	 * Collection elements.
20
	 *
21
	 * @var array
22
	 */
23
	protected $elements = array();
24
25
	/**
26
	 * Collection type to enforce.
27
	 *
28
	 * @var Type
29
	 */
30
	private $type;
31
32
	/**
33
	 * Where Collection is in loop.
34
	 *
35
	 * @var int
36
	 */
37
	protected $position = 0;
38
39
	/**
40
	 * Collection constructor.
41
	 *
42
	 * @param string $type
43
	 * @param array  $elements
44
	 */
45
	public function __construct( $type, array $elements = array() ) {
46
		$this->type = new Type( $type );
47
48
		if ( $elements ) {
49
			$this->type->validate_elements( $elements );
50
		}
51 60
52 60
		$this->elements = $elements;
53
	}
54 57
55 24
	/**
56 57
	 * {@inheritdoc}
57 57
	 *
58
	 * @return string
59
	 */
60
	public function get_type() {
61
		return $this->type->get_type();
62
	}
63
64
	/**
65
	 * {@inheritdoc}
66
	 *
67
	 * @param mixed $element
68 27
	 *
69 27
	 * @return Collection
70 9
	 *
71 9
	 * @throws InvalidArgumentException
72
	 */
73 27
	public function add( $element ) {
74 3
		if ( $this->type->is_model() && is_array( $element ) ) {
75
			$element = $this->type->create_model( $element );
76
		}
77 24
78
		$this->type->validate_element( $element );
79 24
80
		$elements   = $this->elements;
81
		$elements[] = $element;
82
83
		$collection = new static( $this->get_type() );
84
		$collection->set_from_trusted( $elements );
85
86
		return $collection;
87
	}
88
89
	/**
90
	 * {@inheritdoc}
91
	 *
92
	 * @return Collection
93
	 */
94
	public function clear() {
95
		return new static( $this->get_type() );
96
	}
97
98
	/**
99
	 * {@inheritdoc}
100
	 *
101
	 * @param  callable $condition Condition to satisfy.
102
	 *
103
	 * @return bool
104
	 */
105
	public function contains( callable $condition ) {
106
		return (bool) $this->find( $condition );
107
	}
108
109
	/**
110
	 * {@inheritdoc}
111
	 *
112
	 * @param  callable $condition Condition to satisfy.
113
	 *
114 12
	 * @return mixed
115 12
	 */
116
	public function find( callable $condition ) {
117
		$index = $this->find_index( $condition );
118
119
		return -1 === $index ? false : $this->elements[ $index ];
120
	}
121
122
	/**
123
	 * {@inheritdoc}
124
	 *
125
	 * @param  callable $condition Condition to satisfy.
126
	 *
127
	 * @return int
128
	 */
129
	public function find_index( callable $condition ) {
130
		$index = -1;
131
132
		for ( $i = 0, $count = count( $this->elements ); $i < $count; $i++ ) {
133
			if ( call_user_func( $condition, ($this->at( $i ) ) ) ) {
134
				$index = $i;
135
				break;
136
			}
137
		}
138
139
		return $index;
140
	}
141
142
	/**
143
	 * Fetches the element at the provided index.
144
	 *
145
	 * @param int $index Index to get element from.
146
	 *
147
	 * @return mixed
148
	 *
149
	 * @throws OutOfRangeException
150
	 */
151
	public function at( $index ) {
152
		$this->validate_index( $index );
153
154
		return $this->elements[ $index ];
155
	}
156
157
	/**
158
	 * {@inheritdoc}
159
	 *
160
	 * @param  int $index Index to check for existence.
161
	 *
162
	 * @return bool
163 9
	 *
164 6
	 * @throws InvalidArgumentException
165 3
	 */
166
	public function index_exists( $index ) {
167
		if ( ! is_int( $index ) ) {
168 3
			throw new InvalidArgumentException( 'Index must be an integer' );
169 9
		}
170
171
		if ( $index < 0 ) {
172
			throw new InvalidArgumentException( 'Index must be a non-negative integer' );
173
		}
174
175
		return $index < $this->count();
176
	}
177 3
178 3
	/**
179
	 * {@inheritdoc}
180
	 *
181
	 * @param  callable $condition Condition to satisfy.
182
	 *
183
	 * @return mixed
184 3
	 */
185 3
	public function filter( callable $condition ) {
186 3
		$elements = array();
187
188
		foreach ( $this->elements as $element ) {
189
			if ( call_user_func( $condition, $element ) ) {
190
				$elements[] = $element;
191
			}
192
		}
193 3
194 3
		return $this->new_from_trusted( $elements );
195
	}
196
	/**
197
	 * {@inheritdoc}
198
	 *
199
	 * @param  callable $condition Condition to satisfy.
200
	 *
201
	 * @return mixed
202 3
	 */
203 3
	public function find_last( callable $condition ) {
204
		$index = $this->find_last_index( $condition );
205
206
		return -1 === $index ? null : $this->elements[ $index ];
207
	}
208
209 3
	/**
210 3
	 * {@inheritdoc}
211 3
	 *
212
	 * @param  callable $condition
213
	 * @return int
214
	 */
215
	public function find_last_index( callable $condition ) {
216
		$index = -1;
217
218 6
		for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) {
219 6
			if ( call_user_func( $condition, $this->elements[ $i ] ) ) {
220
				$index = $i;
221
				break;
222
			}
223
		}
224
225
		return $index;
226
	}
227
228
	/**
229 60
	 * {@inheritdoc}
230 60
	 *
231 15
	 * @param  int $start Begining index to slice from.
232
	 * @param  int $end   End index to slice to.
233 15
	 *
234 3
	 * @return Collection
235
	 *
236
	 * @throws InvalidArgumentException
237 12
	 */
238 12
	public function slice( $start, $end ) {
239 57
		if ( $start < 0 || ! is_int( $start ) ) {
240
			throw new InvalidArgumentException( 'Start must be a non-negative integer' );
241
		}
242
243
		if ( $end < 0 || ! is_int( $end ) ) {
244
			throw new InvalidArgumentException( 'End must be a positive integer' );
245
		}
246
247
		if ( $start > $end ) {
248
			throw new InvalidArgumentException( 'End must be greater than start' );
249
		}
250
251
		if ( $end > $this->count() + 1 ) {
252
			throw new InvalidArgumentException( 'End must be less than the count of the items in the Collection' );
253
		}
254
255
		$length = $end - $start + 1;
256
		$subset = array_slice( $this->elements, $start, $length );
257
258
		$collection = new static($this->type);
0 ignored issues
show
Documentation introduced by
$this->type is of type object<Intraxia\Jaxion\Axolotl\Type>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
259
		$collection->set_from_trusted( $subset );
260
261
		return $collection;
262
	}
263
264
	/**
265
	 * {@inheritdoc}
266
	 *
267
	 * @param int   $index     Index to start at.
268
	 * @param mixed $element Element to insert.
269
	 *
270
	 * @return Collection
271
	 *
272
	 * @throws InvalidArgumentException
273
	 * @throws OutOfRangeException
274
	 */
275
	public function insert( $index, $element ) {
276
		$this->validate_index( $index );
277
		$this->type->validate_element( $element );
278
279
		$a = array_slice( $this->elements, 0, $index );
280
		$b = array_slice( $this->elements, $index, count( $this->elements ) );
281
282
		$a[] = $element;
283
284
		return $this->new_from_trusted( array_merge( $a, $b ) );
285
	}
286
287
	/**
288
	 * {@inheritdoc}
289
	 *
290
	 * @param int   $index    Index to start insertion at.
291
	 * @param array $elements Elements in insert.
292
	 *
293
	 * @return Collection
294
	 *
295
	 * @throws OutOfRangeException
296
	 */
297
	public function insert_range( $index, array $elements ) {
298
		$this->validate_index( $index );
299
		$this->type->validate_elements( $elements );
300
301
		// To work with negative index, get the positive relation to 0 index
302
		$index < 0 && $index = $this->count() + $index + 1;
303
304
		$partA = array_slice( $this->elements, 0, $index );
305
		$partB = array_slice( $this->elements, $index, count( $this->elements ) );
306
307
		$elements1 = array_merge( $partA, $elements );
308
		$elements1 = array_merge( $elements1, $partB );
309
310
		$col = new static( $this->type );
0 ignored issues
show
Documentation introduced by
$this->type is of type object<Intraxia\Jaxion\Axolotl\Type>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
311
		$col->set_from_trusted( $elements1 );
312
313
		return $col;
314
	}
315
316
	/**
317
	 * {@inheritdoc}
318
	 *
319
	 * @param  callable $condition Condition to satisfy.
320
	 *
321
	 * @return Collection
322
	 */
323
	public function without( callable $condition ) {
324
		$inverse = function ( $element ) use ( $condition ) {
325
			return ! call_user_func( $condition, $element );
326
		};
327
328
		return $this->filter( $inverse );
329
	}
330
331
	/**
332
	 * {@inheritdoc}
333
	 *
334
	 * @param  int $index Index to remove.
335
	 *
336
	 * @return Collection
337
	 *
338
	 * @throws OutOfRangeException
339
	 */
340
	public function remove_at( $index ) {
341
		$this->validate_index( $index );
342
343
		$elements = $this->elements;
344
345
		return $this->new_from_trusted(
346
			array_merge(
347
				array_slice( $elements, 0, $index ),
348
				array_slice( $elements, $index + 1, count( $elements ) )
349
			)
350
		);
351
	}
352
	/**
353
	 * {@inheritdoc}
354
	 *
355
	 * @return Collection
356
	 */
357
	public function reverse() {
358
		return $this->new_from_trusted(
359
			array_reverse( $this->elements )
360
		);
361
	}
362
363
	/**
364
	 * {@inheritdoc}
365
	 *
366
	 * @param callable $callback Sort callback.
367
	 *
368
	 * @return Collection
369
	 */
370
	public function sort( callable $callback ) {
371
		$elements = $this->elements;
372
		usort( $elements, $callback );
373
		return $this->new_from_trusted( $elements );
374
	}
375
376
	/**
377
	 * {@inheritdoc}
378
	 *
379
	 * @return array
380
	 */
381
	public function to_array() {
382
		return $this->elements;
383
	}
384
385
	/**
386
	 * {@inheritdoc}
387
	 *
388
	 * @param callable $callable Reducer function.
389
	 *
390
	 * @param null     $initial  Initial reducer value.
391
	 *
392
	 * @return mixed
393
	 */
394
	public function reduce( callable $callable, $initial = null ) {
395
		return array_reduce( $this->elements, $callable, $initial );
396
	}
397
398
	/**
399
	 * {@inheritdoc}
400
	 *
401
	 * @param callable $condition Condition callback.
402
	 *
403
	 * @return bool
404
	 */
405
	public function every( callable $condition ) {
406
		$response = true;
407
408
		foreach ( $this->elements as $element ) {
409
			$result = call_user_func( $condition, $element );
410
411
			if ( false === $result ) {
412
				$response = false;
413
				break;
414
			}
415
		}
416
417
		return $response;
418
	}
419
420
	/**
421
	 * {@inheritdoc}
422
	 *
423
	 * @param  int $num Number of elements to drop.
424
	 *
425
	 * @return Collection
426
	 *
427
	 * @throws InvalidArgumentException
428
	 */
429
	public function drop( $num ) {
430
		return $this->slice( $num, $this->count() );
431
	}
432
433
	/**
434
	 * {@inheritdoc}
435
	 *
436
	 * @param int $num Number of elements to drop.
437
	 *
438
	 * @return Collection
439
	 *
440
	 * @throws InvalidArgumentException
441
	 */
442
	public function drop_right( $num ) {
443
		return $num !== $this->count()
444
			? $this->slice( 0, $this->count() - $num - 1 )
445
			: $this->clear();
446
	}
447
448
	/**
449
	 * {@inheritdoc}
450
	 *
451
	 * @param callable $condition Condition callback.
452
	 *
453
	 * @return Collection
454
	 */
455
	public function drop_while( callable $condition ) {
456
		$count = $this->count_while_true( $condition );
457
		return $count ? $this->drop( $count ) : $this;
458
	}
459
	/**
460
	 * {@inheritdoc}
461
	 *
462
	 * @return Collection
463
	 *
464
	 * @throws InvalidArgumentException
465
	 */
466
	public function tail() {
467
		return $this->slice( 1, $this->count() );
468
	}
469
470
	/**
471
	 * {@inheritdoc}
472
	 *
473
	 * @param  int $num Number of elements to take.
474
	 *
475
	 * @return Collection
476
	 *
477
	 * @throws InvalidArgumentException
478
	 */
479
	public function take( $num ) {
480
		return $this->slice( 0, $num - 1 );
481
	}
482
483
	/**
484
	 * {@inheritdoc}
485
	 *
486
	 * @param int $num Number of elements to take.
487
	 *
488
	 * @return Collection
489
	 *
490
	 * @throws InvalidArgumentException
491
	 */
492
	public function take_right( $num ) {
493
		return $this->slice( $this->count() - $num, $this->count() );
494
	}
495
496
	/**
497
	 * {@inheritdoc}
498
	 *
499
	 * @param callable $condition Callback function.
500
	 *
501
	 * @return Collection
502
	 */
503
	public function take_while( callable $condition ) {
504
		$count = $this->count_while_true( $condition );
505
506
		return $count ? $this->take( $count ) : $this->clear();
507
	}
508
509
	/**
510
	 * {@inheritdoc}
511
	 *
512
	 * @param callable $callable Callback function.
513
	 */
514
	public function each( callable $callable ) {
515
		foreach ( $this->elements as $element ) {
516
			call_user_func( $callable, $element );
517
		}
518
	}
519
520
	/**
521
	 * {@inheritdoc}
522
	 *
523
	 * @param callable $callable Callback function.
524
	 *
525
	 * @return Collection
526
	 */
527
	public function map( callable $callable ) {
528
		$elements = array();
529
		$type = null;
530
		foreach ( $this->elements as $element ) {
531
			$result = call_user_func( $callable, $element );
532
533
			if ( null === $type ) {
534
				$type = gettype( $result );
535
536
				if ( 'object' === $type ) {
537
					$type = get_class( $result );
538
				}
539
			}
540
541
			$elements[] = $result;
542
		}
543
544
		return $this->new_from_trusted( $elements, $type ? : $this->get_type() );
545
	}
546
547
	/**
548
	 * {@inheritdoc}
549
	 *
550
	 * @param callable $callable Reducer function.
551
	 * @param null     $initial  Initial value.
552
	 *
553
	 * @return mixed
554
	 */
555
	public function reduce_right( callable $callable, $initial = null ) {
556
		return array_reduce(
557
			array_reverse( $this->elements ),
558
			$callable,
559
			$initial
560
		);
561
	}
562
563
	/**
564
	 * {@inheritdoc}
565
	 *
566
	 * @return Collection
567
	 */
568
	public function shuffle() {
569
		$elements = $this->elements;
570
		shuffle( $elements );
571
572
		return $this->new_from_trusted( $elements );
573
	}
574
575
	/**
576
	 * {@inheritdoc}
577
	 *
578
	 * @param array|Collection $elements Array of elements to merge.
579
	 *
580
	 * @return Collection
581
	 *
582
	 * @throws InvalidArgumentException
583
	 */
584
	public function merge( $elements ) {
585
		if ( $elements instanceof static ) {
586
			$elements = $elements->to_array();
587
		}
588
589
		if ( ! is_array( $elements ) ) {
590
			throw new InvalidArgumentException( 'Merge must be given array or Collection' );
591
		}
592
593
		$this->type->validate_elements( $elements );
594
595
		return $this->new_from_trusted(
596
			array_merge( $this->elements, $elements )
597
		);
598
	}
599
600
	/**
601
	 * {@inheritdoc}
602
	 *
603
	 * @return mixed
604
	 *
605
	 * @throws OutOfBoundsException
606
	 */
607
	public function first() {
608
		if ( empty( $this->elements ) ) {
609
			throw new OutOfBoundsException( 'Cannot get first element of empty Collection' );
610
		}
611
612
		return reset( $this->elements );
613
	}
614
615
	/**
616
	 * {@inheritdoc}
617
	 *
618
	 * @return mixed
619
	 *
620
	 * @throws OutOfBoundsException
621
	 */
622
	public function last() {
623
		if ( empty( $this->elements ) ) {
624
			throw new OutOfBoundsException( 'Cannot get last element of empty Collection' );
625
		}
626
627
		return end( $this->elements );
628
	}
629
630
	/**
631
	 * {@inheritdoc}
632
	 *
633
	 * @return int
634
	 */
635
	public function count() {
636
		return count( $this->elements );
637
	}
638
639
	/**
640
	 * {@inheritDoc}
641
	 *
642
	 * @return array
643
	 */
644
	public function serialize() {
645
		return $this->map(function( $element ) {
646
			if ( $element instanceof Serializes ) {
647
				return $element->serialize();
648
			}
649
650
			return $element;
651
		} )->to_array();
652
	}
653
654
	/**
655
	 * Return the current element.
656
	 *
657
	 * @return mixed
658
	 */
659
	public function current() {
660
		return $this->at( $this->position );
661
	}
662
663
	/**
664
	 * Move forward to next element.
665
	 */
666
	public function next() {
667
		$this->position ++;
668
	}
669
670
	/**
671
	 * Return the key of the current element.
672
	 *
673
	 * @return mixed
674
	 */
675
	public function key() {
676
		return $this->position;
677
	}
678
679
	/**
680
	 * Checks if current position is valid.
681
	 *
682
	 * @return bool
683
	 */
684
	public function valid() {
685
		return isset( $this->elements[ $this->position ] );
686
	}
687
688
	/**
689
	 * Rewind the Iterator to the first element.
690
	 */
691
	public function rewind() {
692
		$this->position = 0;
693
	}
694
695
	/**
696
	 * Creates a new instance of the Collection
697
	 * from a trusted set of elements.
698
	 *
699
	 * @param array      $elements Array of elements to pass into new collection.
700
	 * @param null|mixed $type
701
	 *
702
	 * @return static
703
	 */
704
	protected function new_from_trusted( array $elements, $type = null ) {
705
		$collection = new static( null !== $type ? $type : $this->get_type() );
706
		$collection->set_from_trusted( $elements );
707
708
		return $collection;
709
	}
710
711
	/**
712
	 * Sets the elements without validating them.
713
	 *
714
	 * @param array $elements Pre-validated elements to set.
715
	 */
716
	protected function set_from_trusted( array $elements ) {
717
		$this->elements = $elements;
718
	}
719
720
	/**
721
	 * Number of elements true for the condition.
722
	 *
723
	 * @param callable $condition Condition to check.
724
	 * @return int
725
	 */
726
	protected function count_while_true( callable $condition ) {
727
		$count = 0;
728
729
		foreach ( $this->elements as $element ) {
730
			if ( ! $condition($element) ) {
731
				break;
732
			}
733
			$count++;
734
		}
735
736
		return $count;
737
	}
738
739
	/**
740
	 * Validates a number to be used as an index.
741
	 *
742
	 * @param  integer $index The number to be validated as an index.
743
	 *
744
	 * @throws OutOfRangeException
745
	 */
746
	protected function validate_index( $index ) {
747
		$exists = $this->index_exists( $index );
748
749
		if ( ! $exists ) {
750
			throw new OutOfRangeException( 'Index out of bounds of collection' );
751
		}
752
	}
753
}
754