• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rotexsoft / versatile-collections / 8278679284

14 Mar 2024 09:52AM UTC coverage: 98.445%. Remained the same
8278679284

push

github

rotimi
Tweak

1076 of 1093 relevant lines covered (98.44%)

28.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.93
/src/CollectionInterfaceImplementationTrait.php
1
<?php /** @noinspection PhpFullyQualifiedNameUsageInspection */
2

3
namespace VersatileCollections;
4

5
use ArrayAccess;
6
use ArrayIterator;
7
use BadMethodCallException;
8
use Generator;
9
use InvalidArgumentException;
10
use Iterator;
11
use LengthException;
12
use RuntimeException;
13
use VersatileCollections\Exceptions\NonExistentItemException;
14

15
/**
16
 *
17
 * Below is a list of acceptable value(s), that could be comma separated, 
18
 * for the @used-for tag in phpdoc blocks for public methods in this trait:
19
 *  
20
 *      - accessing-or-extracting-keys-or-items
21
 *      - adding-items
22
 *      - adding-methods-at-runtime
23
 *      - checking-keys-presence
24
 *      - checking-items-presence
25
 *      - creating-new-collections
26
 *      - deleting-items
27
 *      - finding-or-searching-for-items
28
 *      - getting-collection-meta-data
29
 *      - iteration
30
 *      - mathematical-operations
31
 *      - modifying-keys
32
 *      - modifying-items
33
 *      - ordering-or-sorting-items
34
 *      - other-operations
35
 *
36
 * @author Rotimi Ade
37
 */
38
trait CollectionInterfaceImplementationTrait {
39

40
    /**
41
     *
42
     * @var array
43
     *  
44
     */
45
    protected $versatile_collections_items = [];
46
    
47
    /**
48
     *
49
     * @var array
50
     *  
51
     */
52
    protected static $versatile_collections_methods_for_all_instances = [];
53

54
    /**
55
     *
56
     * @var array
57
     *  
58
     */
59
    protected $versatile_collections_methods_for_this_instance = [];
60

61
    /**
62
     *
63
     * @var array
64
     *  
65
     */
66
    protected static $versatile_collections_static_methods = [];
67
    
68
    protected static function validateMethodName(string $name, string $method_name_was_passed_to, ?string $class_in_which_method_was_called=null): bool {
69
        
70
        $regex_4_valid_method_name = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
72✔
71
        
72
        if( 
73
            !\preg_match( $regex_4_valid_method_name, \preg_quote($name, '/') )
72✔
74
        ) {
75
            // A valid php class' method name starts with a letter or underscore, 
76
            // followed by any number of letters, numbers, or underscores.
77

78
            // Make sure the controller name is a valid string usable as a class name
79
            // in php as defined in http://php.net/manual/en/language.oop5.basic.php
80
            $class = $class_in_which_method_was_called ?? static::class;
6✔
81
            
82
            $function = $method_name_was_passed_to;
6✔
83
            $name_var = var_to_string($name);
6✔
84
            $msg = "Error [{$class}::{$function}(...)]: Trying to add a dynamic method with an invalid name `{$name_var}` to a collection";
6✔
85
            
86
            throw new InvalidArgumentException($msg);
6✔
87
            
88
        } else if( \method_exists(static::class, $name) ) {
66✔
89
            
90
            // valid method name was supplied but conflicts with an
91
            // already defined real class method
92
            $class = $class_in_which_method_was_called ?? static::class;
12✔
93
            
94
            $function = $method_name_was_passed_to;
12✔
95
            $msg = "Error [{$class}::{$function}(...)]: Trying to add a dynamic method with the same name `{$name}` as an existing actual method to a collection";
12✔
96
            
97
            throw new Exceptions\AddConflictingMethodException($msg);
12✔
98
        }
99
        
100
        return true;
54✔
101
    }
102
    
103
    /**
104
     *  
105
     * @param string $name name of the method being added
106
     * @param callable $callable method being added
107
     * @param bool $has_return_val true means $callable returns a value, else false if $callable returns no value
108
     *  
109
     * @used-for: adding-methods-at-runtime
110
     *  
111
     * @title: Registers a specified `callable` with a specified name to a Collection class, so that the registered callable can be later called as a static method with the specified name on the Collection class or any of its sub-classes.
112
     *  
113
     */
114
    public static function addStaticMethod(
115
        string $name, 
116
        callable $callable, 
117
        bool $has_return_val=false
118
    ): void {
119
        if( static::validateMethodName($name, __FUNCTION__) ) {
12✔
120
            
121
            static::$versatile_collections_static_methods[ static::class.'::'. $name] = [
12✔
122
                'method' => $callable,
12✔
123
                'has_return_val' => $has_return_val
12✔
124
            ];
12✔
125
        }
126
    }
127
    
128
    /**
129
     *  
130
     * @param string $name name of the method being added
131
     * @param callable $callable method being added
132
     * @param bool $has_return_val true means $callable returns a value, else false if $callable returns no value
133
     * @param bool $bind_to_this_on_invocation true means $callable will be bound to $this before invocation, else false if $callable should not be explicitly bound to $this before invocation
134
     *  
135
     * @used-for: adding-methods-at-runtime
136
     *  
137
     * @title: Registers a specified `callable` with a specified name to a Collection class, so that the registered callable can be later called as an instance method with the specified name on any instance of the Collection class or any of its sub-classes.
138
     *  
139
     */
140
    public static function addMethodForAllInstances(
141
        string $name, 
142
        callable $callable, 
143
        bool $has_return_val=false,
144
        bool $bind_to_this_on_invocation=true
145
    ): void {
146
        if( static::validateMethodName($name, __FUNCTION__) ) {
18✔
147
            
148
            static::$versatile_collections_methods_for_all_instances[ static::class.'::'. $name] = [
18✔
149
                'method' => $callable,
18✔
150
                'has_return_val' => $has_return_val,
18✔
151
                'bind_to_this_on_invocation' => $bind_to_this_on_invocation
18✔
152
            ];
18✔
153
        }
154
    }
155
    
156
    /**
157
     * 
158
     * @param string $name name of the method being added
159
     * @param callable $callable method being added
160
     * @param bool $has_return_val true means $callable returns a value, else false if $callable returns no value
161
     * @param bool $bind_to_this true means $callable will be bound to $this, else false if $callable should not be explicitly bound to $this
162
     * 
163
     * @used-for: adding-methods-at-runtime
164
     * 
165
     * @title: Registers a specified `callable` with a specified name to a single instance of a Collection class, so that the registered callable can be later called as an instance method with the specified name on the instance of the Collection class the callable was registered to.
166
     * 
167
     * 
168
     */
169
    public function addMethod(
170
        string $name, 
171
        callable $callable, 
172
        bool $has_return_val=false,
173
        bool $bind_to_this=true
174
    ): self {
175
        if( static::validateMethodName($name, __FUNCTION__, \get_class($this)) ) {
24✔
176
            
177
            if( $bind_to_this ) {
24✔
178

179
                $callable = Utils::bindObjectAndScopeToClosure(
24✔
180
                    Utils::getClosureFromCallable($callable), 
24✔
181
                    $this
24✔
182
                );
24✔
183
            }
184
            
185
            $this->versatile_collections_methods_for_this_instance[ static::class.'::'. $name] = [
24✔
186
                'method' => $callable,
24✔
187
                'has_return_val' => $has_return_val,
24✔
188
            ];
24✔
189
        }
190
        
191
        return $this;
24✔
192
    }
193
    
194
    /**
195
     *
196
     * @return mixed a string representing the calculated key or false if calculated key does not exist in $methods_array
197
     */
198
    protected static function getKeyForDynamicMethod(string $name, array &$methods_array, bool $search_parent_class_registration=true) {
199
        
200
        if( \array_key_exists( static::class.'::'.$name , $methods_array) ) {
42✔
201
            
202
            return static::class.'::'.$name;
30✔
203
        }
204
        
205
        if( $search_parent_class_registration === true ) {
42✔
206
            
207
            $parent_class = \get_parent_class(static::class);
42✔
208

209
            while( $parent_class !== false ) {
42✔
210

211
                if( \array_key_exists( $parent_class.'::'.$name , $methods_array) ) {
24✔
212

213
                    return $parent_class.'::'.$name;
18✔
214
                }
215

216
                $parent_class = \get_parent_class($parent_class);
18✔
217
            }
218
        }
219
        
220
        return false;
42✔
221
    }
222
    
223
    /**
224
     * 
225
     * 
226
     * @return mixed
227
     * 
228
     * @used-for: other-operations
229
     * 
230
     * @title: Tries to call the specified method with the specified arguments and return its return value if it was registered via either `addMethod` or `addMethodForAllInstances` . An exception of type **\BadMethodCallException** is thrown if the method could not be called.
231
     * 
232
     * @throws BadMethodCallException
233
     *
234
     * @noinspection PhpInconsistentReturnPointsInspection
235
     */
236
    public function __call(string $method_name, array $arguments=[]) {
237
        
238
        $key_for_this_instance = static::getKeyForDynamicMethod($method_name, $this->versatile_collections_methods_for_this_instance, false);
36✔
239
        $key_for_all_instances = static::getKeyForDynamicMethod($method_name, static::$versatile_collections_methods_for_all_instances);
36✔
240
        
241
        if ( $key_for_this_instance !== false ) {
36✔
242
            
243
            $result = \call_user_func_array($this->versatile_collections_methods_for_this_instance[$key_for_this_instance]['method'], $arguments);
24✔
244
            
245
            if( $this->versatile_collections_methods_for_this_instance[$key_for_this_instance]['has_return_val'] ) {
18✔
246
                
247
                return $result;
18✔
248
            }
249
        
250
        } else if( $key_for_all_instances !== false ) {
24✔
251
            
252
            $new_callable = static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['method'];
12✔
253
            
254
            if( 
255
                ((bool)static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['bind_to_this_on_invocation'])   
12✔
256
            ) {
257
                
258
                $new_callable = Utils::bindObjectAndScopeToClosure(
12✔
259
                    Utils::getClosureFromCallable($new_callable), 
12✔
260
                    $this
12✔
261
                );
12✔
262
            }
263

264
            $result = \call_user_func_array($new_callable, $arguments);
12✔
265

266
            if( static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['has_return_val'] ) {
12✔
267

268
                return $result;
12✔
269
            }
270
            
271
        } else {
272
            
273
            $function = __FUNCTION__;
18✔
274
            $class = \get_class($this);
18✔
275
            $name_var = var_to_string($method_name);
18✔
276
            $msg = "Error [{$class}::{$function}(...)]: Trying to call a non-existent dynamic method named `{$name_var}` on a collection";
18✔
277
            throw new Exceptions\BadMethodCallException($msg);
18✔
278
        }
279
    }
280
    
281
    /**
282
     * 
283
     * 
284
     * @return mixed
285
     * 
286
     * @used-for: other-operations
287
     * 
288
     * @title: Tries to call the specified method with the specified arguments and return its return value if it was registered via `addStaticMethod`. An exception of type **\BadMethodCallException** is thrown if the method could not be called.
289
     * 
290
     * @throws BadMethodCallException
291
     *
292
     * @noinspection PhpInconsistentReturnPointsInspection
293
     */
294
    public static function __callStatic(string $method_name, array $arguments=[]) {
295
        
296
        $key_for_static_method = static::getKeyForDynamicMethod($method_name, static::$versatile_collections_static_methods);
6✔
297
        
298
        if( $key_for_static_method !== false ) {
6✔
299
            
300
            // never bind to this when method is called statically            
301
            $result = \call_user_func_array(
6✔
302
                static::$versatile_collections_static_methods[$key_for_static_method]['method'], $arguments
6✔
303
            );
6✔
304
            
305
            if( static::$versatile_collections_static_methods[$key_for_static_method]['has_return_val'] ) {
6✔
306
                
307
                return $result;
6✔
308
            }
309
            
310
        } else {
311
            
312
            $function = __FUNCTION__;
6✔
313
            $class = static::class;
6✔
314
            $name_var = var_to_string($method_name);
6✔
315
            $msg = "Error [{$class}::{$function}(...)]: Trying to statically call a non-existent dynamic method named `{$name_var}` on a collection";
6✔
316
            throw new BadMethodCallException($msg);
6✔
317
        }
318
    }
319
    
320
    public function __get(string $key) {
321
        
322
        return $this->offsetGet($key);
78✔
323
    }
324
    
325
    public function __isset(string $key): bool {
326
        
327
        return $this->offsetExists($key);
24✔
328
    }
329
    
330
    /**
331
     * 
332
     * @param mixed $val The value to set it to.
333
     */
334
    public function __set(string $key, $val): void {
335
        
336
        $this->offsetSet($key, $val);
210✔
337
    }
338
    
339
    public function __unset(string $key): void {
340
        
341
        $this->offsetUnset($key);
12✔
342
    }
343
    
344
    /**
345
     *  
346
     * @see \VersatileCollections\CollectionInterface::makeNew()
347
     *
348
     * @noinspection PhpIncompatibleReturnTypeInspection
349
     * @psalm-suppress UnsafeInstantiation
350
     */
351
    public static function makeNew(iterable $items=[], bool $preserve_keys=true): CollectionInterface {
352

353
        if ($preserve_keys === true) {
720✔
354

355
            $collection = new static();
720✔
356

357
            foreach ($items as $key => $item ) {
720✔
358

359
                $collection[$key] = $item;
492✔
360
            }
361

362
            return $collection;
714✔
363
        }
364

365
        // I use array_values to ensure that all keys 
366
        // are numeric. Argument unpacking does not
367
        // work on arrays with one or more string keys.
368
        return \is_array($items)
12✔
369
                ? new static(...\array_values($items))
12✔
370
                : new static(...\array_values(\iterator_to_array($items))); // These should be faster than loop above
12✔
371
                                                                            // since looping triggers offsetSet()
372
                                                                            // for each item
373
    }
374
    
375
    /**
376
     *  
377
     * @see \VersatileCollections\CollectionInterface::offsetExists()
378
     *  
379
     * @param mixed $key The requested key.
380
     * 
381
     */
382
    public function offsetExists($key): bool {
383
        
384
        return \array_key_exists($key, $this->versatile_collections_items);
66✔
385
    }
386
    
387
    /**
388
     * 
389
     * @see \VersatileCollections\CollectionInterface::offsetGet()
390
     * 
391
     * @param mixed $key The requested key.
392
     *
393
     * @return mixed|void 
394
     */
395
    public function offsetGet($key) {
396
        
397
        if ( \array_key_exists($key, $this->versatile_collections_items) ) {
144✔
398

399
            return $this->versatile_collections_items[$key];
120✔
400
            
401
        } else {
402

403
            throw new NonExistentItemException(\get_class($this)."::offsetGet({$key})");
36✔
404
        }
405
    }
406

407
    /**
408
     *  
409
     * @see \VersatileCollections\CollectionInterface::offsetSet()
410
     * 
411
     * @param string|int|null $key The requested key.
412
     * 
413
     * @param mixed $val The value to set it to.
414
     * 
415
     * @psalm-suppress ParamNameMismatch
416
     */
417
    public function offsetSet($key, $val): void {
418
        
419
        if(\is_null($key) ) {
864✔
420
            
421
            $this->versatile_collections_items[] = $val;
180✔
422
            
423
        } else {
424
            
425
            $this->versatile_collections_items[$key] = $val;
774✔
426
        }
427
    }
428
    
429
    /**
430
     *  
431
     * @see \VersatileCollections\CollectionInterface::offsetUnset()
432
     * 
433
     * @param string|int $key The requested key.
434
     *  
435
     */
436
    public function offsetUnset($key): void {
437
        
438
        $this->versatile_collections_items[$key] = null;
36✔
439
        unset($this->versatile_collections_items[$key]);
36✔
440
    }
441
    
442
    /**
443
     *    
444
     * @see \VersatileCollections\CollectionInterface::toArray()
445
     *  
446
     * @return mixed[] 
447
     */
448
    public function toArray(): array {
449

450
        return $this->versatile_collections_items;
480✔
451
    }
452
    
453
    /**
454
     * @see \VersatileCollections\CollectionInterface::getIterator()
455
     * 
456
     * @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
457
     */
458
    public function getIterator(): Iterator {
459

460
        return new ArrayIterator($this->versatile_collections_items);
72✔
461
    }
462
    
463
    /**
464
     *  
465
     * @see \VersatileCollections\CollectionInterface::count()
466
     *  
467
     */
468
    public function count(): int {
469
        
470
        return \count($this->versatile_collections_items);
576✔
471
    }
472

473
    /**
474
     * 
475
     * @param mixed $items
476
     */
477
    public function __construct(...$items) {
478

479
        $this->versatile_collections_items = $items;
912✔
480
    }
481
    
482
    ////////////////////////////////////////////////////////////////////////////
483
    ////////////////////////////////////////////////////////////////////////////
484
    ////////////////////////////////////////////////////////////////////////////
485
    ////////// OTHER COLLECTION METHODS ////////////////////////////////////////
486
    /**
487
     * 
488
     * @see \VersatileCollections\CollectionInterface::firstItem()
489
     *
490
     * @return null|mixed
491
     * @psalm-suppress PossiblyNullArrayOffset 
492
     */
493
    public function firstItem() {
494
        
495
        return ($this->count() <= 0) ? null : $this->versatile_collections_items[\array_key_first($this->versatile_collections_items)];
258✔
496
    }
497
    
498
    /**
499
     * 
500
     * @see \VersatileCollections\CollectionInterface::lastItem()
501
     *
502
     * @return null|mixed 
503
     * @psalm-suppress PossiblyNullArrayOffset
504
     */
505
    public function lastItem() {
506
                
507
        return ( $this->count() <= 0 ) ? null : $this->versatile_collections_items[\array_key_last($this->versatile_collections_items)];
66✔
508
    }
509
    
510
    /**
511
     *  
512
     * @see \VersatileCollections\CollectionInterface::getKeys()
513
     *
514
     * @noinspection PhpIncompatibleReturnTypeInspection
515
     * @psalm-suppress MoreSpecificReturnType
516
     */
517
    public function getKeys(): GenericCollection
518
    {
519
        
520
        return GenericCollection::makeNew( \array_keys($this->versatile_collections_items) );
18✔
521
    }
522
    
523
    /**
524
     *  
525
     * @see \VersatileCollections\CollectionInterface::setValForEachItem()
526
     *  
527
     * @param mixed $field_val value to be set for the field whose name is the value contained in $field_name 
528
     * 
529
     * @psalm-suppress LessSpecificImplementedReturnType
530
     */
531
    public function setValForEachItem(string $field_name, $field_val, bool $add_field_if_not_present=false): CollectionInterface
532
    {
533
        
534
        foreach ($this->versatile_collections_items as &$item) {
6✔
535
            
536
            if( 
537
                \is_object($item)
6✔
538
                && !($item instanceof ArrayAccess)
6✔
539
                && 
540
                ( 
541
                    $add_field_if_not_present 
6✔
542
                    || 
6✔
543
                    object_has_property($item, $field_name) 
6✔
544
                    || 
6✔
545
                    (
6✔
546
                        \method_exists($item, '__set')
6✔
547
                        && \method_exists($item, '__get')
6✔
548
                        && \method_exists($item, '__isset')
6✔
549
                        && isset($item->{$field_name})    
6✔
550
                    )
6✔
551
                )
552
            ) {
553
                $item->{$field_name} = $field_val;
6✔
554
                
555
            } else if(
556
                \is_array($item)
6✔
557
                && ( $add_field_if_not_present  || \array_key_exists($field_name, $item) )
6✔
558
            ) {
559
                $item[$field_name] = $field_val;
6✔
560
                
561
            } else if(
562
                $item instanceof ArrayAccess
6✔
563
                && ( $add_field_if_not_present  || $item->offsetExists($field_name) )
6✔
564
            ) {
565
                $item->offsetSet($field_name, $field_val);
6✔
566
                
567
            } else {
568
                
569
                $class = \get_class($this);
6✔
570
                $function = __FUNCTION__;
6✔
571
                $msg = "Error [{$class}::{$function}(...)]:Trying to set a property named `$field_name` on a collection item of type "
6✔
572
                    . "`". Utils::gettype($item)."` "
6✔
573
                    . PHP_EOL . " `\$field_val`: " . var_to_string($field_val)
6✔
574
                    . PHP_EOL . " `\$add_field_if_not_present`: " . var_to_string($add_field_if_not_present);
6✔
575
                
576
                throw new Exceptions\InvalidCollectionOperationException($msg);
6✔
577
            }
578
            
579
        } // foreach ($this->collection_items as &$item)
580
        
581
        return $this;
6✔
582
    }
583
    
584
    /**
585
     *  
586
     * @see \VersatileCollections\CollectionInterface::filterAll()
587
     *  
588
     */
589
    public function filterAll(callable $filterer, bool $copy_keys=false, bool $bind_callback_to_this=true, bool $remove_filtered_items=false): CollectionInterface
590
    {
591
                
592
        return $this->filterFirstN($filterer, $this->count(), $copy_keys, $bind_callback_to_this, $remove_filtered_items);
6✔
593
    }
594
    
595
    /**
596
     *  
597
     * @see \VersatileCollections\CollectionInterface::filterFirstN()
598
     *  
599
     */
600
    public function filterFirstN(callable $filterer, ?int $max_number_of_filtered_items_to_return =null, bool $copy_keys=false, bool $bind_callback_to_this=true, bool $remove_filtered_items=false): CollectionInterface
601
    {
602
        
603
        if( $bind_callback_to_this === true ) {
12✔
604
            
605
            $filterer = Utils::bindObjectAndScopeToClosure(
12✔
606
                Utils::getClosureFromCallable($filterer), 
12✔
607
                $this
12✔
608
            );
12✔
609
        }
610
        
611
        $filtered_items = static::makeNew();
12✔
612
        
613
        if( 
614
            \is_null($max_number_of_filtered_items_to_return)
12✔
615
            || ($max_number_of_filtered_items_to_return) > $this->count()
12✔
616
            || ($max_number_of_filtered_items_to_return) < 0
12✔
617
        ) {
618
            $max_number_of_filtered_items_to_return = $this->count();
6✔
619
        }
620
        
621
        $num_filtered_items = 0;
12✔
622
        
623
        foreach ( $this->versatile_collections_items as $key => $item ) {
12✔
624
            
625
            if( $num_filtered_items >= $max_number_of_filtered_items_to_return ) {
12✔
626
                
627
                break;
6✔
628
            }
629
            
630
            if( $filterer($key, $item) === true ) {
12✔
631
                
632
                $num_filtered_items++;
12✔
633
                
634
                if( $copy_keys ) {
12✔
635
                    
636
                    $filtered_items[$key] = $item;
12✔
637
                    
638
                } else {
639
                    
640
                    $filtered_items[] = $item;
12✔
641
                }
642
                
643
                if($remove_filtered_items) {
12✔
644
                    
645
                    unset($this->versatile_collections_items[$key]);
12✔
646
                }
647
            }
648
            
649
        } // foreach ( $this->collection_items as $key => $item )
650
        
651
        return $filtered_items;
12✔
652
    }
653
    
654
    /**
655
     *  
656
     * @see \VersatileCollections\CollectionInterface::transform()
657
     * 
658
     * @psalm-suppress LessSpecificImplementedReturnType 
659
     */
660
    public function transform(callable $transformer, bool $bind_callback_to_this=true): CollectionInterface
661
    {
662
        
663
        if( $bind_callback_to_this === true ) {
12✔
664
            
665
            $transformer = Utils::bindObjectAndScopeToClosure(
12✔
666
                Utils::getClosureFromCallable($transformer), 
12✔
667
                $this
12✔
668
            );
12✔
669
        }
670
        
671
        foreach ( $this->versatile_collections_items as $key => $item ) {
12✔
672
            
673
            // using $this[$key] instead of $this->collection_items[$key]
674
            // so that $this->offsetSet(...) will be invoked which will
675
            // trigger type-checking in sub-classes like StrictlyTypedCollection
676
            $this[$key] = $transformer($key, $item);
12✔
677
            
678
        } // foreach ( $this->collection_items as $key => $item )
679
        
680
        return $this;
12✔
681
    }
682
    
683
    /**
684
     *  
685
     * @see \VersatileCollections\CollectionInterface::reduce()
686
     * 
687
     * @param mixed $initial_value If the optional initial is available, it will be used at the beginning of the process, or as a final result in case the collection is empty.
688
     *  
689
     */
690
    public function reduce(callable $reducer, $initial_value=NULL) {
691
        
692
        return \array_reduce($this->versatile_collections_items, $reducer, $initial_value);
18✔
693
    }
694
    
695
    /**
696
     *  
697
     * @see \VersatileCollections\CollectionInterface::reduceWithKeyAccess()
698
     * 
699
     * @param mixed $initial_value If the optional initial is available, it will be used at the beginning of the process, or as a final result in case the collection is empty.
700
     *  
701
     */
702
    public function reduceWithKeyAccess(callable $reducer, $initial_value=NULL) {
703
        
704
        $reduced_result = $initial_value;
12✔
705
        
706
        foreach ($this->versatile_collections_items as $key=>$item) {
12✔
707
            
708
            $reduced_result = $reducer($reduced_result, $item, $key);
12✔
709
        }
710
        
711
        return $reduced_result;
12✔
712
    }
713
    
714
    /**
715
     *  
716
     * @see \VersatileCollections\CollectionInterface::reverse()
717
     *  
718
     */
719
    public function reverse(): CollectionInterface
720
    {
721
        
722
        return static::makeNew(
6✔
723
            \array_reverse($this->versatile_collections_items, true)
6✔
724
        );
6✔
725
    }
726
    
727
    /**
728
     *  
729
     * @see \VersatileCollections\CollectionInterface::reverseMe()
730
     * 
731
     * @psalm-suppress LessSpecificImplementedReturnType
732
     */
733
    public function reverseMe(): CollectionInterface
734
    {
735
        
736
        $this->versatile_collections_items = 
6✔
737
            \array_reverse($this->versatile_collections_items, true);
6✔
738
        
739
        return $this;
6✔
740
    }
741
    
742
    /**
743
     *  
744
     * @see \VersatileCollections\CollectionInterface::slice()
745
     *  
746
     */
747
    public function slice(int $offset, ?int $length = null): CollectionInterface
748
    {
749
        
750
        return static::makeNew(
66✔
751
            \array_slice($this->versatile_collections_items, $offset, $length, true)
66✔
752
        );
66✔
753
    }
754
    
755
    /**
756
     *  
757
     * @see \VersatileCollections\CollectionInterface::isEmpty()
758
     *  
759
     */
760
    public function isEmpty(): bool {
761
        
762
        return ($this->count() <= 0);
84✔
763
    }
764
    
765
    /**
766
     *  
767
     * @see \VersatileCollections\CollectionInterface::getIfExists()
768
     * 
769
     * @param string|int $key
770
     * @param mixed $default_value
771
     * 
772
     * @psalm-suppress DocblockTypeContradiction
773
     */
774
    public function getIfExists($key, $default_value=null) {
775
        
776
        if( !\is_int($key) && !\is_string($key) ) {
12✔
777
            
778
            $function = __FUNCTION__;
6✔
779
            $class = \get_class($this);
6✔
780
            $key_type = Utils::gettype($key);
6✔
781
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
782
            . " You must specify an integer or string as the \$key parameter."
6✔
783
            . " You supplied a(n) `{$key_type}` with a value of: ". var_to_string($key);
6✔
784
            throw new InvalidArgumentException($msg);
6✔
785
        }
786
        
787
        return \array_key_exists($key, $this->versatile_collections_items)
12✔
788
                ?  $this->versatile_collections_items[$key] : $default_value;
12✔
789
    }
790
    
791
    /**
792
     *  
793
     * @see \VersatileCollections\CollectionInterface::containsItem()
794
     * 
795
     * @param mixed $item item whose existence in the collection is to be checked
796
     *  
797
     */
798
    public function containsItem($item): bool {
799
        
800
        return \in_array($item, $this->versatile_collections_items, true);
102✔
801
    }
802
    
803
    /**
804
     *  
805
     * @see \VersatileCollections\CollectionInterface::containsKey()
806
     * 
807
     * @param int|string $key key whose existence in the collection is to be checked
808
     * @psalm-suppress DocblockTypeContradiction
809
     */
810
    public function containsKey($key): bool {
811
        
812
        if( !\is_int($key) && !\is_string($key) ) {
102✔
813
            
814
            return false; 
6✔
815
        }
816
        
817
        return \array_key_exists($key, $this->versatile_collections_items);
102✔
818
    }
819
    
820
    /**
821
     *  
822
     * @see \VersatileCollections\CollectionInterface::containsItemWithKey()
823
     * 
824
     * @param int|string $key key whose existence in the collection is to be checked
825
     * @param mixed $item item whose existence in the collection is to be checked
826
     * 
827
     * @psalm-suppress DocblockTypeContradiction
828
     */
829
    public function containsItemWithKey($key, $item): bool {
830
        
831
        if( !\is_int($key) && !\is_string($key) ) {
36✔
832
            
833
            return false; 
6✔
834
        }
835
        
836
        return $this->containsKey($key) 
36✔
837
                && $item === $this->versatile_collections_items[$key];
36✔
838
    }
839
    
840
    /**
841
     *  
842
     * @see \VersatileCollections\CollectionInterface::containsItems()
843
     *  
844
     */
845
    public function containsItems(array $items): bool {
846
        
847
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
848
        $all_items_exist = \count($items) > 0;
18✔
849
        
850
        foreach ($items as $item) {
18✔
851
            
852
            $all_items_exist = $all_items_exist && $this->containsItem($item);
18✔
853
            
854
            if( $all_items_exist === false ) {
18✔
855
                
856
                break;
6✔
857
            }
858
        }
859
        
860
        return $all_items_exist;
18✔
861
    }
862
    
863
    /**
864
     *  
865
     * @see \VersatileCollections\CollectionInterface::containsKeys()
866
     *  
867
     */
868
    public function containsKeys(array $keys): bool {
869
        
870
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
871
        $all_keys_exist = \count($keys) > 0;
24✔
872
        
873
        foreach ($keys as $key) {
24✔
874
            
875
            $all_keys_exist = $all_keys_exist && $this->containsKey($key);
24✔
876
            
877
            if( $all_keys_exist === false ) {
24✔
878
                
879
                break;
12✔
880
            }
881
        }
882
        
883
        return $all_keys_exist;
24✔
884
    }
885
    
886
    /**
887
     *
888
     * @see \VersatileCollections\CollectionInterface::appendCollection()
889
     *
890
     * @psalm-suppress LessSpecificImplementedReturnType
891
     */
892
    public function appendCollection(CollectionInterface $other): CollectionInterface
893
    {
894
        
895
        if( ! $other->isEmpty() ) {
18✔
896
            
897
            foreach ($other as $item) {
18✔
898
                
899
                $this[] = $item;
18✔
900
            }
901
        }
902
        
903
        return $this;
18✔
904
    }
905
    
906
    /**
907
     *  
908
     * @see \VersatileCollections\CollectionInterface::appendItem()
909
     * 
910
     * @param mixed $item
911
     *  
912
     * @psalm-suppress LessSpecificImplementedReturnType
913
     */
914
    public function appendItem($item): CollectionInterface
915
    {
916
        
917
        $this[] = $item;
42✔
918
        
919
        return $this;
36✔
920
    }
921
    
922
    /**
923
     *  
924
     * @see \VersatileCollections\CollectionInterface::mergeWith()
925
     *  
926
     */
927
    public function mergeWith(array $items): CollectionInterface
928
    {
929
        
930
        $copy = $this->versatile_collections_items;
24✔
931
        $merged_items = static::makeNew($copy);
24✔
932
        
933
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
934

935
        // not using array_merge , want to trigger $merged_items->offsetSet() logic
936
        foreach ( $items as $key => $item ) {
24✔
937

938
            $merged_items[$key] = $item;
24✔
939
        }
940
        
941
        return $merged_items;
18✔
942
    }
943
    
944
    /**
945
     *  
946
     * @see \VersatileCollections\CollectionInterface::mergeMeWith()
947
     * 
948
     * @psalm-suppress LessSpecificImplementedReturnType 
949
     */
950
    public function mergeMeWith(array $items): CollectionInterface
951
    {
952

953
        // not using array_merge , want to trigger $this->offsetSet() logic
954
        foreach ( $items as $key => $item ) {
18✔
955

956
            $this[$key] = $item;
18✔
957
        }
958
        
959
        return $this;
12✔
960
    }
961
    
962
    /**
963
     *
964
     * @see \VersatileCollections\CollectionInterface::prependCollection()
965
     *
966
     * @psalm-suppress LessSpecificImplementedReturnType 
967
     */
968
    public function prependCollection(CollectionInterface $other): CollectionInterface
969
    {
970
        
971
        if( ! $other->isEmpty() ) {
12✔
972
            
973
            \array_unshift($this->versatile_collections_items, ...\array_values($other->toArray()));
12✔
974
        }
975
        
976
        return $this;
12✔
977
    }
978
    
979
    /**
980
     *  
981
     * @see \VersatileCollections\CollectionInterface::prependItem()
982
     * 
983
     * @param mixed $item
984
     * @param string|int|null $key
985
     * 
986
     * @psalm-suppress LessSpecificImplementedReturnType 
987
     * @psalm-suppress RedundantConditionGivenDocblockType 
988
     */
989
    public function prependItem($item, $key=null): CollectionInterface
990
    {
991
        
992
        if( \is_null($key) ) {
12✔
993
            
994
            \array_unshift($this->versatile_collections_items, $item);
6✔
995
            
996
        } else if( \is_string($key) || \is_int($key) ) {
12✔
997
            
998
            $this->versatile_collections_items = [$key=>$item] + $this->versatile_collections_items;
6✔
999
            
1000
        } else {
1001
            
1002
            $class = \get_class($this);
6✔
1003
            $function = __FUNCTION__;
6✔
1004
            $msg = "Error [{$class}::{$function}(...)]:Trying prepend an item with a non-integer and non-string key on a collection. "
6✔
1005
                . PHP_EOL . " `\$key`: " . var_to_string($key)
6✔
1006
                . PHP_EOL . " `\$item`: " . var_to_string($item);
6✔
1007
            
1008
            throw new Exceptions\InvalidKeyException($msg);
6✔
1009
        }
1010
        
1011
        return $this;
6✔
1012
    }
1013
    
1014
    /**
1015
     *  
1016
     * @see \VersatileCollections\CollectionInterface::getCollectionsOfSizeN()
1017
     *  
1018
     */
1019
    public function getCollectionsOfSizeN(int $max_size_of_each_collection=1): CollectionInterface
1020
    {
1021
        
1022
        if(
1023
            $max_size_of_each_collection > $this->count()
6✔
1024
        ) {
1025
            $max_size_of_each_collection = $this->count();
6✔
1026
            
1027
        } else if( $max_size_of_each_collection <= 0 ) {
6✔
1028
            
1029
            $max_size_of_each_collection = 1;
6✔
1030
        }
1031
        
1032
        $collections = GenericCollection::makeNew();
6✔
1033
        $current_batch = static::makeNew();
6✔
1034
        $counter = 0;
6✔
1035
        
1036
        foreach ($this->versatile_collections_items as $key=>$item) {
6✔
1037
            
1038
            $current_batch[$key] = $item;
6✔
1039
            
1040
            if( ++$counter >= $max_size_of_each_collection ) {
6✔
1041

1042
                $collections[] = $current_batch;
6✔
1043
                $counter = 0; // reset
6✔
1044
                $current_batch = static::makeNew(); // initialize next collection
6✔
1045
            }
1046
        }
1047

1048
        // yield last batch if not already yielded
1049
        if( !$current_batch->isEmpty() ) {
6✔
1050
            
1051
            $collections[] = $current_batch;
6✔
1052
        }
1053
        
1054
        return $collections;
6✔
1055
    }
1056
    
1057
    /**
1058
     *  
1059
     * @see \VersatileCollections\CollectionInterface::yieldCollectionsOfSizeN()
1060
     * 
1061
     * @noRector \Rector\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector
1062
     *  
1063
     */
1064
    public function yieldCollectionsOfSizeN(int $max_size_of_each_collection=1): Generator {
1065
        
1066
        if(
1067
            $max_size_of_each_collection > $this->count()
12✔
1068
        ) {
1069
            $max_size_of_each_collection = $this->count();
6✔
1070
            
1071
        } else if( $max_size_of_each_collection <= 0 ) {
12✔
1072
            
1073
            $max_size_of_each_collection = 1;
6✔
1074
        }
1075
        
1076
        $current_batch = static::makeNew();
12✔
1077
        $counter = 0;
12✔
1078
        
1079
        foreach ($this->versatile_collections_items as $key=>$item) {
12✔
1080
            
1081
            $current_batch[$key] = $item;
12✔
1082
            
1083
            if( ++$counter >= $max_size_of_each_collection ) {
12✔
1084

1085
                yield $current_batch;
12✔
1086
                $counter = 0; // reset
12✔
1087
                $current_batch = static::makeNew(); // initialize next collection
12✔
1088
            }
1089
        }
1090

1091
        // yield last batch if not already yielded
1092
        if( !$current_batch->isEmpty() ) {
12✔
1093
            
1094
            yield $current_batch;
12✔
1095
        }
1096
    }
1097
    
1098
    /**
1099
     *  
1100
     * @see \VersatileCollections\CollectionInterface::makeAllKeysNumeric()
1101
     * 
1102
     * @psalm-suppress LessSpecificImplementedReturnType 
1103
     */
1104
    public function makeAllKeysNumeric(int $starting_key=0): CollectionInterface
1105
    {
1106
               
1107
        if( $starting_key < 0 ) {
42✔
1108
            
1109
            $starting_key = 0;
6✔
1110
        }
1111
        
1112
        if( $starting_key === 0 ) {
42✔
1113
        
1114
            $this->versatile_collections_items = 
42✔
1115
                \array_values($this->versatile_collections_items);
42✔
1116
            
1117
        } else {
1118
            
1119
            $this->versatile_collections_items = 
6✔
1120
                \array_combine(
6✔
1121
                    \range($starting_key, ( ($starting_key + $this->count()) - 1) ),
6✔
1122
                    \array_values($this->versatile_collections_items)
6✔
1123
                );
6✔
1124
        }
1125
        
1126
        return $this;
42✔
1127
    }
1128
    
1129
    /**
1130
     *  
1131
     * @see \VersatileCollections\CollectionInterface::each()
1132
     * 
1133
     * @param mixed $termination_value a value that should be returned by $callback 
1134
     *                                 signifying that iteration through a collection
1135
     *                                 should stop.
1136
     * 
1137
     * @psalm-suppress LessSpecificImplementedReturnType 
1138
     */
1139
    public function each(
1140
        callable $callback, $termination_value=false, bool $bind_callback_to_this=true
1141
    ): CollectionInterface
1142
    {
1143
        
1144
        if( $bind_callback_to_this === true ) {
12✔
1145
            
1146
            $callback = Utils::bindObjectAndScopeToClosure(
12✔
1147
                Utils::getClosureFromCallable($callback), 
12✔
1148
                $this
12✔
1149
            );
12✔
1150
        }
1151
        
1152
        foreach ($this->versatile_collections_items as $key => $item) {
12✔
1153
        
1154
            if ( $callback($key, $item) === $termination_value ) {
12✔
1155
                
1156
                break;
6✔
1157
            }
1158
        }
1159
        
1160
        return $this;
12✔
1161
    }
1162
    
1163
    /**
1164
     *  
1165
     * @see \VersatileCollections\CollectionInterface::map()
1166
     *  
1167
     */
1168
    public function map(
1169
        callable $callback, bool $preserve_keys = true, bool $bind_callback_to_this=true
1170
    ): CollectionInterface
1171
    {
1172
        if( $bind_callback_to_this === true ) {
6✔
1173
            
1174
            $callback = Utils::bindObjectAndScopeToClosure(
6✔
1175
                Utils::getClosureFromCallable($callback), 
6✔
1176
                $this
6✔
1177
            );
6✔
1178
        }
1179
        
1180
        $new_collection = static::makeNew();
6✔
1181
        
1182
        foreach ( $this->versatile_collections_items as $key => $item ) {
6✔
1183
            
1184
            // using $new_collection[$key] or $new_collection[]
1185
            // so that $new_collection->offsetSet(...) will be invoked which will
1186
            // trigger type-checking in sub-classes like StrictlyTypedCollection
1187
            
1188
            if( $preserve_keys === true ) {
6✔
1189
                
1190
                $new_collection[$key] = $callback($key, $item);
6✔
1191
                
1192
            } else {
1193
                
1194
                $new_collection[] = $callback($key, $item);
6✔
1195
            }
1196
            
1197
        } // foreach ( $this->collection_items as $key => $item )
1198
        
1199
        return $new_collection;
6✔
1200
    }
1201
    
1202
    /**
1203
     *  
1204
     * @see \VersatileCollections\CollectionInterface::everyNth()
1205
     *  
1206
     */
1207
    public function everyNth(int $n, int $position_of_first_nth_item = 0): CollectionInterface
1208
    {
1209
        
1210
        $new = static::makeNew();
6✔
1211
        $iteration_counter = 0;
6✔
1212

1213
        foreach ($this->versatile_collections_items as $item) {
6✔
1214
            
1215
            if ( ($iteration_counter % $n) === $position_of_first_nth_item ) {
6✔
1216
                
1217
                $new[] = $item;
6✔
1218
            }
1219

1220
            $iteration_counter++;
6✔
1221
        }
1222

1223
        return $new;
6✔
1224
    }
1225
    
1226
    /**
1227
     *  
1228
     * @see \VersatileCollections\CollectionInterface::pipeAndReturnCallbackResult()
1229
     *  
1230
     */
1231
    public function pipeAndReturnCallbackResult(callable $callback) {
1232
        
1233
        return $callback($this);
6✔
1234
    }
1235
    
1236
    /**
1237
     *  
1238
     * @see \VersatileCollections\CollectionInterface::pipeAndReturnSelf()
1239
     * 
1240
     * @psalm-suppress LessSpecificImplementedReturnType 
1241
     */
1242
    public function pipeAndReturnSelf(callable $callback): CollectionInterface
1243
    {
1244
        
1245
        $callback($this);
6✔
1246
        
1247
        return $this;
6✔
1248
    }
1249
    
1250
    /**
1251
     *  
1252
     * @see \VersatileCollections\CollectionInterface::tap()
1253
     * 
1254
     * @psalm-suppress LessSpecificImplementedReturnType 
1255
     */
1256
    public function tap(callable $callback): CollectionInterface
1257
    {
1258
        
1259
        $callback(static::makeNew($this->versatile_collections_items));
6✔
1260

1261
        return $this;
6✔
1262
    }
1263
    
1264
    /**
1265
     *  
1266
     * @see \VersatileCollections\CollectionInterface::getAndRemoveFirstItem()
1267
     *  
1268
     */
1269
    public function getAndRemoveFirstItem() {
1270
        
1271
        return \array_shift($this->versatile_collections_items);
12✔
1272
    }
1273
    
1274
    /**
1275
     *  
1276
     * @see \VersatileCollections\CollectionInterface::getAndRemoveLastItem()
1277
     *  
1278
     */
1279
    public function getAndRemoveLastItem()
1280
    {
1281
        return \array_pop($this->versatile_collections_items);
6✔
1282
    }
1283
    
1284
    /**
1285
     *  
1286
     * @see \VersatileCollections\CollectionInterface::pull()
1287
     * 
1288
     * @param int|string  $key
1289
     * @param mixed       $default
1290
     * 
1291
     */
1292
    public function pull($key, $default = null) {
1293

1294
        $item = $this->getIfExists($key, $default);
6✔
1295
        
1296
        unset($this[$key]);
6✔
1297
        
1298
        return $item;
6✔
1299
    }
1300
    
1301
    /**
1302
     *  
1303
     * @see \VersatileCollections\CollectionInterface::push()
1304
     * 
1305
     * @param mixed  $item
1306
     * 
1307
     * @psalm-suppress LessSpecificImplementedReturnType 
1308
     */
1309
    public function push($item): CollectionInterface
1310
    {
1311
        
1312
        return $this->appendItem($item);
30✔
1313
    }
1314
    
1315
    /**
1316
     *  
1317
     * @see \VersatileCollections\CollectionInterface::put()
1318
     * 
1319
     * @param int|string  $key
1320
     * @param mixed       $value
1321
     * 
1322
     * @psalm-suppress LessSpecificImplementedReturnType 
1323
     */
1324
    public function put($key, $value): CollectionInterface
1325
    {
1326
        
1327
        $this->offsetSet($key, $value);
6✔
1328
        
1329
        return $this;
6✔
1330
    }
1331
    
1332
    /**
1333
     *  
1334
     * @see \VersatileCollections\CollectionInterface::randomKey()
1335
     *  
1336
     */
1337
    public function randomKey() {
1338
        
1339
        if( $this->count() <= 0 ) {
12✔
1340
            
1341
            $function = __FUNCTION__;
6✔
1342
            $class = \get_class($this);
6✔
1343
            $msg = "Error [{$class}::{$function}(...)]: You cannot request a random key from an empty collection.";
6✔
1344
            throw new LengthException($msg);
6✔
1345
        }
1346
        
1347
        return random_array_key($this->versatile_collections_items);
12✔
1348
    }
1349
    
1350
    /**
1351
     *  
1352
     * @see \VersatileCollections\CollectionInterface::randomItem()
1353
     *  
1354
     */
1355
    public function randomItem() {
1356
        
1357
        if( $this->count() <= 0 ) {
6✔
1358
            
1359
            $function = __FUNCTION__;
6✔
1360
            $class = \get_class($this);
6✔
1361
            $msg = "Error [{$class}::{$function}(...)]: You cannot request a random item from an empty collection.";
6✔
1362
            throw new LengthException($msg);
6✔
1363
        }
1364
        
1365
        return $this[$this->randomKey()];
6✔
1366
    }
1367
    
1368
    /**
1369
     *  
1370
     * @see \VersatileCollections\CollectionInterface::randomKeys()
1371
     * 
1372
     * @psalm-suppress LessSpecificImplementedReturnType 
1373
     */
1374
    public function randomKeys(int $number = 1): CollectionInterface
1375
    {
1376
        
1377
        if( $this->count() <= 0 ) {
30✔
1378
            
1379
            $function = __FUNCTION__;
12✔
1380
            $class = \get_class($this);
12✔
1381
            $msg = "Error [{$class}::{$function}(...)]: You cannot request random keys from an empty collection.";
12✔
1382
            throw new LengthException($msg);
12✔
1383
        }
1384
        
1385
        if( $number > $this->count() ) {
24✔
1386
            
1387
            $function = __FUNCTION__;
6✔
1388
            $class = \get_class($this);
6✔
1389
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1390
            . " You requested {$number} key(s), but there are only {$this->count()} keys available.";
6✔
1391
            throw new InvalidArgumentException($msg);
6✔
1392
        }
1393
        
1394
        $keys = random_array_keys($this->versatile_collections_items, $number);
18✔
1395

1396
        // keys could be strings or ints or a mix
1397
        // GenericCollection will allow both types
1398
        return GenericCollection::makeNew($keys);
18✔
1399
    }
1400
    
1401
    /**
1402
     *  
1403
     * @see \VersatileCollections\CollectionInterface::randomItems()
1404
     *  
1405
     */
1406
    public function randomItems(int $number = 1, bool $preserve_keys=false): CollectionInterface
1407
    {
1408
        
1409
        if( $this->count() <= 0 ) {
18✔
1410
            
1411
            $function = __FUNCTION__;
12✔
1412
            $class = \get_class($this);
12✔
1413
            $msg = "Error [{$class}::{$function}(...)]: You cannot request random items from an empty collection.";
12✔
1414
            throw new LengthException($msg);
12✔
1415
        }
1416
        
1417
        if( $number > $this->count() ) {
12✔
1418
            
1419
            $function = __FUNCTION__;
6✔
1420
            $class = \get_class($this);
6✔
1421
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1422
            . " You requested {$number} item(s), but there are only {$this->count()} items available.";
6✔
1423
            throw new InvalidArgumentException($msg);
6✔
1424
        }
1425
        
1426
        $random_items = static::makeNew();
6✔
1427
        $random_keys = $this->randomKeys($number);
6✔
1428
        
1429
        foreach ($random_keys as $random_key) {
6✔
1430
            
1431
            if($preserve_keys) {
6✔
1432
                
1433
                $random_items[$random_key] = $this[$random_key];
6✔
1434
                
1435
            } else {
1436
                
1437
                $random_items[] = $this[$random_key];
6✔
1438
            }
1439
        }
1440
        
1441
        return $random_items;
6✔
1442
    }
1443
    
1444
    /**
1445
     *  
1446
     * @see \VersatileCollections\CollectionInterface::shuffle()
1447
     *  
1448
     */
1449
    public function shuffle(bool $preserve_keys=true): CollectionInterface
1450
    {
1451
                
1452
        if( $this->isEmpty() ) {
6✔
1453
            
1454
            return static::makeNew();
6✔
1455
        }
1456
        
1457
        $shuffled_collection = static::makeNew();
6✔
1458
        
1459
        // Decided to use $this->randomKeys() instead of php's
1460
        // native shuffle(array &$array) since $this->randomKeys() uses 
1461
        // random_array_keys(...) which uses the more cryptographically
1462
        // secure random_int(...) under the hood.
1463
        $all_keys_randomized = $this->randomKeys($this->count());
6✔
1464
        
1465
        foreach ($all_keys_randomized as $current_random_key) {
6✔
1466
            
1467
            if($preserve_keys) {
6✔
1468
                
1469
                $shuffled_collection[$current_random_key] = $this[$current_random_key];
6✔
1470
                
1471
            } else {
1472
                
1473
                $shuffled_collection[] = $this[$current_random_key];
6✔
1474
            }
1475
        }
1476
        
1477
        return $shuffled_collection;
6✔
1478
    }
1479
    
1480
    /**
1481
     *  
1482
     * @see \VersatileCollections\CollectionInterface::searchByVal()
1483
     * 
1484
     * @param mixed $value the value to be searched for
1485
     *  
1486
     */
1487
    public function searchByVal( $value, bool $strict = false ) {
1488
        
1489
        return \array_search($value, $this->versatile_collections_items, $strict);
6✔
1490
    }
1491
    
1492
    /**
1493
     *  
1494
     * @see \VersatileCollections\CollectionInterface::searchAllByVal()
1495
     * 
1496
     * @param mixed $value the value to be searched for
1497
     *  
1498
     */
1499
    public function searchAllByVal( $value, bool $strict = false ){
1500
        
1501
        $result = \array_keys($this->versatile_collections_items, $value, $strict);
6✔
1502
        
1503
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
1504
        if( \count($result) <= 0 ) {
6✔
1505
            
1506
            $result = false;
6✔
1507
        }
1508
        
1509
        return $result;
6✔
1510
    }
1511
    
1512
    /**
1513
     *  
1514
     * @see \VersatileCollections\CollectionInterface::searchByCallback()
1515
     * 
1516
     * @psalm-suppress MissingClosureParamType 
1517
     */
1518
    public function searchByCallback(callable $callback, bool $bind_callback_to_this=true) {
1519
        
1520
        $results = [];
6✔
1521
        
1522
        
1523
        $searcher = function($key, $item) use ($callback, &$results): void {
6✔
1524
            
1525
            if( $callback($key, $item) === true ) {
6✔
1526
                
1527
                $results[] = $key;
6✔
1528
            }
1529
        };
6✔
1530
        
1531
        // using 9999 as termination value since $callback is only ever 
1532
        // expected to return true or false, which means each will not
1533
        // terminate until iteration is fully completed.
1534
        $this->each($searcher, 9999, $bind_callback_to_this);
6✔
1535
        
1536
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
1537
        return \count($results) > 0 ? $results : false;
6✔
1538
    }
1539

1540
    protected function performSort(
1541
        array &$items_to_sort,
1542
        callable $callable=null,
1543
        SortType $type=null,
1544
        string $sort_function_name_not_requiring_callback='asort',
1545
        string $sort_function_name_requiring_callback='uasort'
1546
    ): void {
1547
        if( \is_null($callable) ) {
48✔
1548
            
1549
            $sort_type = SORT_REGULAR;
48✔
1550
            
1551
            if( !\is_null($type) ) {
48✔
1552
                
1553
                $sort_type = $type->getSortType();
48✔
1554
            }
1555
            
1556
            $sort_function_name_not_requiring_callback($items_to_sort, $sort_type);
48✔
1557
            
1558
        } else {
1559
            
1560
            $sort_function_name_requiring_callback($items_to_sort, $callable);
48✔
1561
        }
1562
    }
1563

1564
    /**
1565
     * @psalm-suppress PossiblyInvalidArgument
1566
     * @psalm-suppress PossiblyInvalidIterator
1567
     * 
1568
     * @return mixed[]
1569
     */
1570
    protected function performMultiSort(array $array_to_be_sorted, MultiSortParameters ...$param): array {
1571
        
1572
        if(\count($array_to_be_sorted) <= 0) {
12✔
1573
            
1574
            return $array_to_be_sorted; 
×
1575
        }
1576
        
1577
        $multi_sort_args = [];
12✔
1578
        $columns_to_sort_by = [];
12✔
1579
        
1580
        $original_key_prefix = 'http_versatile_collections_dot_com_original_key_b4_sort-';
12✔
1581
        
1582
        // add a string prefix to each key
1583
        // in the array to be sorted to force array_multisort to
1584
        // maintain key associations in the sorted array
1585
        $array_to_be_sorted_with_stringy_keys = [];
12✔
1586
        foreach ($array_to_be_sorted as $key => $val){
12✔
1587
            
1588
            $array_to_be_sorted_with_stringy_keys[$original_key_prefix.$key] = $val;
12✔
1589
        }
1590
        // update the array to be sorted
1591
        $array_to_be_sorted = $array_to_be_sorted_with_stringy_keys;
12✔
1592
        
1593
        foreach( $array_to_be_sorted as $key => $item) {
12✔
1594
            
1595
            if( \is_array($item) || $item instanceof ArrayAccess ) {
12✔
1596
                
1597
                foreach($param as $current_param) {
12✔
1598
                    
1599
                    if( !\array_key_exists($current_param->getFieldName() , $columns_to_sort_by) ) {
12✔
1600
                        
1601
                        $columns_to_sort_by[$current_param->getFieldName()] = [];
12✔
1602
                    }
1603
                    
1604
                    $columns_to_sort_by[$current_param->getFieldName()][$key] 
12✔
1605
                                        = $item[$current_param->getFieldName()];
12✔
1606
                }
1607
                
1608
            } else if ( \is_object($item) /*a non ArrayAccess object*/ ) {
12✔
1609
                
1610
                foreach($param as $current_param) {
6✔
1611
                    
1612
                    if( !\array_key_exists($current_param->getFieldName() , $columns_to_sort_by) ) {
6✔
1613
                        
1614
                        $columns_to_sort_by[$current_param->getFieldName()] = [];
6✔
1615
                    }
1616
                    
1617
                    // get the field's value even if it's private or protected
1618
                    $columns_to_sort_by[$current_param->getFieldName()][$key] 
6✔
1619
                        = get_object_property_value($item, $current_param->getFieldName(), null, true);                    
6✔
1620
                }
1621
                
1622
            } else {
1623
                
1624
                $function = __FUNCTION__;
12✔
1625
                $class = \get_class($this);
12✔
1626
                $msg = "Error [{$class}::{$function}(...)]:"
12✔
1627
                . " {$class}::{$function}(...) does not work with collections containing items that are"
12✔
1628
                . " not associative arrays or instances of ArrayAccess.";
12✔
1629
                throw new RuntimeException($msg);
12✔
1630
            }
1631
        }
1632
        
1633
        foreach($param as $current_param) {
12✔
1634
            
1635
            // set column
1636
            $multi_sort_args[] = $columns_to_sort_by[$current_param->getFieldName()];
12✔
1637
            
1638
            // set sort direction
1639
            $multi_sort_args[] = $current_param->getSortDirection();
12✔
1640
            
1641
            // set sort type
1642
            $multi_sort_args[] = $current_param->getSortType();
12✔
1643
        }
1644
        
1645
        // last parameter is the array to be sorted
1646
        /** @psalm-suppress UnsupportedReferenceUsage */
1647
        $multi_sort_args[] = &$array_to_be_sorted;
12✔
1648
        /** @psalm-suppress ArgumentTypeCoercion */
1649
        \array_multisort(...$multi_sort_args);
12✔
1650
        $sorted_array_with_preserved_keys_with_prefix = \array_pop($multi_sort_args);
12✔
1651
        $sorted_array_with_preserved_keys = [];
12✔
1652
        
1653
        // remove the string prefix we added earlier to all keys
1654
        // in the array to be sorted to force array_multisort to
1655
        // maintain key associations in the sorted array
1656
        foreach($sorted_array_with_preserved_keys_with_prefix as $key => $val) {
12✔
1657
            
1658
            $sorted_array_with_preserved_keys[\str_replace($original_key_prefix, '', $key)] = $val;
12✔
1659
        }
1660
        
1661
        return $sorted_array_with_preserved_keys;
12✔
1662
    }
1663
    
1664
    /**
1665
     *  
1666
     * @see \VersatileCollections\CollectionInterface::sort()
1667
     *  
1668
     */
1669
    public function sort(callable $callable=null, SortType $type=null): CollectionInterface
1670
    {
1671
        
1672
        // sort a copy
1673
        $items_to_sort = $this->versatile_collections_items;
6✔
1674

1675
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1676
        $this->performSort(
6✔
1677
            $items_to_sort, 
6✔
1678
            $callable, 
6✔
1679
            $type, 
6✔
1680
            'asort',
6✔
1681
            'uasort'
6✔
1682
        );
6✔
1683
        
1684
        return static::makeNew($items_to_sort);
6✔
1685
    }
1686
    
1687
    /**
1688
     *  
1689
     * @see \VersatileCollections\CollectionInterface::sortDesc()
1690
     *  
1691
     */
1692
    public function sortDesc(callable $callable=null, SortType $type=null): CollectionInterface
1693
    {
1694
        
1695
        // sort a copy
1696
        $items_to_sort = $this->versatile_collections_items;
6✔
1697

1698
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1699
        $this->performSort(
6✔
1700
            $items_to_sort, 
6✔
1701
            $callable, 
6✔
1702
            $type, 
6✔
1703
            'arsort',
6✔
1704
            'uasort'
6✔
1705
        );
6✔
1706
        
1707
        return static::makeNew($items_to_sort);
6✔
1708
    }
1709
    
1710
    /**
1711
     *  
1712
     * @see \VersatileCollections\CollectionInterface::sortByKey()
1713
     *  
1714
     */
1715
    public function sortByKey(callable $callable=null, SortType $type=null): CollectionInterface
1716
    {
1717
        
1718
        // sort a copy
1719
        $items_to_sort = $this->versatile_collections_items;
6✔
1720

1721
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1722
        $this->performSort(
6✔
1723
            $items_to_sort, 
6✔
1724
            $callable, 
6✔
1725
            $type, 
6✔
1726
            'ksort',
6✔
1727
            'uksort'
6✔
1728
        );
6✔
1729
        
1730
        return static::makeNew($items_to_sort);
6✔
1731
    }
1732
    
1733
    /**
1734
     *  
1735
     * @see \VersatileCollections\CollectionInterface::sortDescByKey()
1736
     *  
1737
     */
1738
    public function sortDescByKey(callable $callable=null, SortType $type=null): CollectionInterface
1739
    {
1740
        
1741
        // sort a copy
1742
        $items_to_sort = $this->versatile_collections_items;
6✔
1743

1744
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1745
        $this->performSort(
6✔
1746
            $items_to_sort, 
6✔
1747
            $callable, 
6✔
1748
            $type, 
6✔
1749
            'krsort',
6✔
1750
            'uksort'
6✔
1751
        );
6✔
1752
        
1753
        return static::makeNew($items_to_sort);
6✔
1754
    }
1755

1756
    
1757
    /**
1758
     *  
1759
     * Can also sort by private and / or protected field(s) in each object in 
1760
     * the collection.
1761
     *  
1762
     * @see \VersatileCollections\CollectionInterface::sortByMultipleFields()
1763
     *  
1764
     */
1765
    public function sortByMultipleFields(MultiSortParameters ...$param): CollectionInterface
1766
    {
1767
        
1768
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
1769
        if( \count($param) <= 0 ) {
12✔
1770
            
1771
            $function = __FUNCTION__;
6✔
1772
            $class = \get_class($this);
6✔
1773
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1774
            . " {$class}::{$function}(...) expects at least one parameter of type `". MultiSortParameters::class ."`";
6✔
1775
            throw new InvalidArgumentException($msg);
6✔
1776
        }
1777
     
1778
        // sort a copy
1779
        $array_to_be_sorted = $this->versatile_collections_items;
6✔
1780
        
1781
        return static::makeNew($this->performMultiSort($array_to_be_sorted, ...$param));
6✔
1782
    }
1783
    
1784
    /**
1785
     *  
1786
     * @see \VersatileCollections\CollectionInterface::sortMe()
1787
     * 
1788
     * @psalm-suppress LessSpecificImplementedReturnType 
1789
     */
1790
    public function sortMe(callable $callable=null, SortType $type=null): CollectionInterface
1791
    {
1792

1793
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1794
        $this->performSort(
6✔
1795
            $this->versatile_collections_items, 
6✔
1796
            $callable, 
6✔
1797
            $type, 
6✔
1798
            'asort',
6✔
1799
            'uasort'
6✔
1800
        );
6✔
1801
        
1802
        return $this;
6✔
1803
    }
1804
    
1805
    /**
1806
     *  
1807
     * @see \VersatileCollections\CollectionInterface::sortMeDesc()
1808
     * 
1809
     * @psalm-suppress LessSpecificImplementedReturnType 
1810
     */
1811
    public function sortMeDesc(callable $callable=null, SortType $type=null): CollectionInterface
1812
    {
1813

1814
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1815
        $this->performSort(
6✔
1816
            $this->versatile_collections_items, 
6✔
1817
            $callable, 
6✔
1818
            $type, 
6✔
1819
            'arsort',
6✔
1820
            'uasort'
6✔
1821
        );
6✔
1822
        
1823
        return $this;
6✔
1824
    }
1825
    
1826
    /**
1827
     *  
1828
     * @see \VersatileCollections\CollectionInterface::sortMeByKey()
1829
     * 
1830
     * @psalm-suppress LessSpecificImplementedReturnType 
1831
     */
1832
    public function sortMeByKey(callable $callable=null, SortType $type=null): CollectionInterface
1833
    {
1834

1835
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1836
        $this->performSort(
6✔
1837
            $this->versatile_collections_items, 
6✔
1838
            $callable, 
6✔
1839
            $type, 
6✔
1840
            'ksort',
6✔
1841
            'uksort'
6✔
1842
        );
6✔
1843
        
1844
        return $this;
6✔
1845
    }
1846
    
1847
    /**
1848
     *  
1849
     * @see \VersatileCollections\CollectionInterface::sortMeDescByKey()
1850
     * 
1851
     * @psalm-suppress LessSpecificImplementedReturnType 
1852
     */
1853
    public function sortMeDescByKey(callable $callable=null, SortType $type=null): CollectionInterface
1854
    {
1855

1856
        /** @noRector \Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector */
1857
        $this->performSort(
6✔
1858
            $this->versatile_collections_items, 
6✔
1859
            $callable, 
6✔
1860
            $type, 
6✔
1861
            'krsort',
6✔
1862
            'uksort'
6✔
1863
        );
6✔
1864
        
1865
        return $this;
6✔
1866
    }
1867
    
1868
    /**
1869
     *  
1870
     * Can also sort by private and / or protected field(s) in each object in 
1871
     * the collection.
1872
     *  
1873
     * @see \VersatileCollections\CollectionInterface::sortMeByMultipleFields()
1874
     * 
1875
     * @psalm-suppress LessSpecificImplementedReturnType 
1876
     */
1877
    public function sortMeByMultipleFields(MultiSortParameters ...$param): CollectionInterface
1878
    {
1879
        
1880
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
1881
        if( \count($param) <= 0 ) {
12✔
1882
            
1883
            $function = __FUNCTION__;
6✔
1884
            $class = \get_class($this);
6✔
1885
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1886
            . " {$class}::{$function}(...) expects at least one parameter of type `". MultiSortParameters::class ."`";
6✔
1887
            throw new InvalidArgumentException($msg);
6✔
1888
        }
1889
               
1890
        $this->versatile_collections_items = 
6✔
1891
            $this->performMultiSort(
6✔
1892
                $this->versatile_collections_items, ...$param
6✔
1893
            );
6✔
1894
        
1895
        return $this;
6✔
1896
    }
1897
    
1898
    /**
1899
     *  
1900
     * @see \VersatileCollections\CollectionInterface::splice()
1901
     *  
1902
     */
1903
    public function splice(int $offset, ?int $length=null, array $replacement=[]): CollectionInterface
1904
    {
1905
        
1906
        if( \is_null($length) ) {
6✔
1907
            
1908
            $length = $this->count();
6✔
1909
        }
1910

1911
        return static::makeNew(\array_splice($this->versatile_collections_items, $offset, $length, $replacement));
6✔
1912
    }
1913
    
1914
    /**
1915
     *  
1916
     * @see \VersatileCollections\CollectionInterface::split()
1917
     *  
1918
     */
1919
    public function split(int $numberOfGroups): CollectionInterface
1920
    {
1921
        
1922
        if( $numberOfGroups > $this->count() ) {
18✔
1923
            
1924
            $function = __FUNCTION__;
6✔
1925
            $class = \get_class($this);
6✔
1926
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1927
            . " You requested {$numberOfGroups} group(s), but there are only {$this->count()} items available.";
6✔
1928
            throw new InvalidArgumentException($msg);
6✔
1929
        }
1930
        
1931
        if( $numberOfGroups < 0 ) {
12✔
1932
            
1933
            $function = __FUNCTION__;
6✔
1934
            $class = \get_class($this);
6✔
1935
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1936
            . " You requested a negative number `{$numberOfGroups}` of group(s).";
6✔
1937
            throw new InvalidArgumentException($msg);
6✔
1938
        }
1939
        
1940
        if ( $this->isEmpty() || $numberOfGroups === 0 ) {
6✔
1941
            
1942
            return static::makeNew();
6✔
1943
        }
1944

1945
        $groupSize = (int)\ceil($this->count() / $numberOfGroups);
6✔
1946
        
1947
        $groups = static::makeNew();
6✔
1948

1949
        foreach ( $this->yieldCollectionsOfSizeN($groupSize) as $group ) {
6✔
1950
            
1951
            $groups[] = $group;
6✔
1952
        }
1953
        
1954
        return $groups;
6✔
1955
    }
1956
    
1957
    /**
1958
     *  
1959
     * @see \VersatileCollections\CollectionInterface::take()
1960
     *  
1961
     */
1962
    public function take(int $limit): CollectionInterface
1963
    {
1964
        
1965
        if ($limit < 0) {
12✔
1966
            return $this->slice($limit, \abs($limit));
6✔
1967
        }
1968

1969
        return $this->slice(0, $limit);
6✔
1970
    }
1971
    
1972
    /**
1973
     *  
1974
     * @see \VersatileCollections\CollectionInterface::unique()
1975
     *  
1976
     * @psalm-suppress MissingClosureParamType
1977
     */
1978
    public function unique(): CollectionInterface
1979
    {
1980
        
1981
        return static::makeNew(
6✔
1982
            $this->reduce(
6✔
1983
                
1984
                function(array $carry, $item): array {
6✔
1985

1986
                    if( !\in_array($item, $carry, true)) {
6✔
1987

1988
                        $carry[] = $item;
6✔
1989
                    }
1990

1991
                    return $carry;
6✔
1992
                },
6✔
1993
                []
6✔
1994
            )
6✔
1995
        );
6✔
1996
    }
1997
    
1998
    /**
1999
     *  
2000
     * @see \VersatileCollections\CollectionInterface::unionWith()
2001
     *  
2002
     */
2003
    public function unionWith(array $items): CollectionInterface
2004
    {
2005
        
2006
        return static::makeNew($this->versatile_collections_items + $items);
6✔
2007
    }
2008
    
2009
    /**
2010
     *  
2011
     * @see \VersatileCollections\CollectionInterface::unionMeWith()
2012
     * 
2013
     * @psalm-suppress LessSpecificImplementedReturnType 
2014
     */
2015
    public function unionMeWith(array $items): CollectionInterface
2016
    {
2017
        
2018
        $this->versatile_collections_items += $items;
6✔
2019
        
2020
        return $this;
6✔
2021
    }
2022
    
2023
    /**
2024
     *  
2025
     * Can also extract values from private and  / or protected properties 
2026
     * of each object in the collection.
2027
     *  
2028
     * @see \VersatileCollections\CollectionInterface::column()
2029
     * 
2030
     * @param int|string $column_key name of field in each item to be used as values / items in the collection to be returned
2031
     * @param int|string|null $index_key
2032
     *  
2033
     * @psalm-suppress DocblockTypeContradiction
2034
     * @psalm-suppress MoreSpecificReturnType
2035
     * @psalm-suppress RedundantCondition
2036
     */
2037
    public function column($column_key, $index_key=null): GenericCollection
2038
    {
2039
        // use GenericCollection because the values 
2040
        // in the column may be of varying types
2041
        $column_2_return = GenericCollection::makeNew();
144✔
2042
        
2043
        if( !\is_int($column_key) && !\is_string($column_key) ) {
144✔
2044
            
2045
            $function = __FUNCTION__;
6✔
2046
            $class = \get_class($this);
6✔
2047
            $column_key_type = Utils::gettype($column_key);
6✔
2048
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
2049
            . " You must specify an integer or string as the \$column_key parameter."
6✔
2050
            . " You supplied a(n) `{$column_key_type}` with a value of: ". var_to_string($column_key);
6✔
2051
            throw new InvalidArgumentException($msg);
6✔
2052
        }
2053
        
2054
        if( !\is_null($index_key) && !\is_int($index_key) && !\is_string($index_key) ) {
138✔
2055
            
2056
            $function = __FUNCTION__;
6✔
2057
            $class = \get_class($this);
6✔
2058
            $index_key_type = Utils::gettype($index_key);
6✔
2059
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
2060
            . " You must specify an integer or string as the \$index_key parameter."
6✔
2061
            . " You supplied a(n) `{$index_key_type}` with a value of: ". var_to_string($index_key);
6✔
2062
            throw new InvalidArgumentException($msg);
6✔
2063
        }
2064

2065
        foreach ( $this->versatile_collections_items as $coll_key => $item ) {
132✔
2066

2067
            if( !\is_array($item) && !\is_object($item) ) {
132✔
2068

2069
                $function = __FUNCTION__;
6✔
2070
                $class = \get_class($this);
6✔
2071
                $item_type = Utils::gettype($item);
6✔
2072
                $msg = "Error [{$class}::{$function}(...)]:"
6✔
2073
                . " This method only works on collections containing only arrays and / or objects."
6✔
2074
                . " A(n) invalid item of type `{$item_type}` with a value of: ". var_to_string($item)
6✔
2075
                . " was found with this key `$coll_key` in the collection". PHP_EOL
6✔
2076
                . " Collection Items: ". var_to_string($this->versatile_collections_items);
6✔
2077
                throw new RuntimeException($msg);
6✔
2078
            }
2079

2080
            if( \is_array($item) || $item instanceof ArrayAccess) {
132✔
2081

2082
                if( 
2083
                    ( \is_array($item) && !\array_key_exists($column_key, $item) )
72✔
2084
                    ||
2085
                    ( $item instanceof ArrayAccess && !isset($item[$column_key]) )
72✔
2086
                ) {
2087
                    $function = __FUNCTION__;
24✔
2088
                    $class = \get_class($this);
24✔
2089
                    $item_type = Utils::gettype($item);
24✔
2090

2091
                    $msg = "Error [{$class}::{$function}(...)]:"
24✔
2092
                    . " An item of type `$item_type` without the specified column key `$column_key`"
24✔
2093
                    . " was found with this key `$coll_key` in the collection." .PHP_EOL
24✔
2094
                    . " Collection Items: ". var_to_string($this->versatile_collections_items);
24✔
2095
                    throw new RuntimeException($msg);
24✔
2096

2097
                } else if (
2098
                    !\is_null($index_key)
48✔
2099
                    &&
2100
                    (
2101
                        ( \is_array($item) && !\array_key_exists($index_key, $item) )
48✔
2102
                        ||
48✔
2103
                        ( $item instanceof ArrayAccess && !isset($item[$index_key]) )
48✔
2104
                    )
2105
                ) {
2106
                    $function = __FUNCTION__;
24✔
2107
                    $class = \get_class($this);
24✔
2108
                    $item_type = Utils::gettype($item);
24✔
2109

2110
                    $msg = "Error [{$class}::{$function}(...)]:"
24✔
2111
                    . " An item of type `$item_type` without the specified index key `$index_key`"
24✔
2112
                    . " was found with this key `$coll_key` in the collection." .PHP_EOL
24✔
2113
                    . " Collection Items: ". var_to_string($this->versatile_collections_items);
24✔
2114
                    throw new RuntimeException($msg);
24✔
2115

2116
                } else if( \is_null($index_key) ) {
24✔
2117

2118
                    $column_2_return[] = $item[$column_key];
12✔
2119

2120
                } else if(
2121
                    !\is_null($index_key)
18✔
2122
                    && 
2123
                    ( 
2124
                        ( \is_array($item) && \array_key_exists($index_key, $item) )
18✔
2125
                        ||
18✔
2126
                        ( $item instanceof ArrayAccess && isset($item[$index_key]) )
18✔
2127
                    )
2128
                ) {
2129
                    if(
2130
                        !\is_string($item[$index_key])
18✔
2131
                        && !\is_int($item[$index_key])
18✔
2132
                    ){
2133
                        $function = __FUNCTION__;
12✔
2134
                        $class = \get_class($this);
12✔
2135
                        $item_type = Utils::gettype($item[$index_key]);
12✔
2136

2137
                        $msg = "Error [{$class}::{$function}(...)]:"
12✔
2138
                        . " \$collection['{$coll_key}']['{$index_key}'] of type `$item_type`"
12✔
2139
                        . " has a non-string and non-int value of `". var_to_string($item[$index_key])."`"
12✔
2140
                        . " which cannot be used as a key in the collection to be returned by this method." .PHP_EOL
12✔
2141
                        . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
12✔
2142
                        throw new RuntimeException($msg);
12✔
2143
                    }
2144

2145
                    $column_2_return[$item[$index_key]] = $item[$column_key];
6✔
2146

2147
                } else {
2148

2149
                    $function = __FUNCTION__;
×
2150
                    $class = \get_class($this);
×
2151
                    $item_type = Utils::gettype($item);
×
2152

2153
                    $msg = "Error [{$class}::{$function}(...)]:"
×
2154
                    . " Error occured while accessing an item of type `$item_type` with the specified index key `$index_key`"
×
2155
                    . " and specified column key `$column_key` with this key `$coll_key` in the collection." . PHP_EOL
×
2156
                    . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
×
2157
                    throw new RuntimeException($msg);
8✔
2158
                }
2159

2160
            } else { // \is_object($item) === true && !($item instanceof ArrayAccess)
2161

2162
                if( 
2163
                    !\is_null($index_key)
66✔
2164
                    && object_has_property($item, $column_key)
66✔
2165
                    && object_has_property($item, $index_key)   
66✔
2166
                ) {
2167
                    $index_key_value = get_object_property_value($item, $index_key, null, true);
30✔
2168
                    $column_key_value = get_object_property_value($item, $column_key, null, true);
30✔
2169

2170
                    if( 
2171
                        !\is_int($index_key_value)
30✔
2172
                        && !\is_string($index_key_value)
30✔
2173
                    ) {
2174
                        $function = __FUNCTION__;
24✔
2175
                        $class = \get_class($this);
24✔
2176
                        $item_type = Utils::gettype($index_key_value);
24✔
2177
                        $msg = "Error [{$class}::{$function}(...)]:"
24✔
2178
                        . " \$collection['{$coll_key}']->{'{$index_key}'} of type `$item_type`"
24✔
2179
                        . " has a non-string and non-int value of `". var_to_string($index_key_value)."`"
24✔
2180
                        . " which cannot be used as a key in the collection to be returned by this method." .PHP_EOL
24✔
2181
                        . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
24✔
2182
                        throw new RuntimeException($msg);
24✔
2183
                    }            
2184

2185
                    $column_2_return[$index_key_value] = $column_key_value;
6✔
2186

2187
                } else if(
2188
                    \is_null($index_key)
42✔
2189
                    && object_has_property($item, $column_key)
42✔
2190
                ) {
2191
                    $column_2_return[] = get_object_property_value($item, $column_key, null, true);
6✔
2192

2193
                } else {
2194

2195
                    $function = __FUNCTION__;
36✔
2196
                    $class = \get_class($this);
36✔
2197
                    $item_type = Utils::gettype($item);
36✔
2198
                    $msg = "Error [{$class}::{$function}(...)]:"
36✔
2199
                    . " Error occured while accessing an item of type `$item_type` with the specified index key `$index_key`"
36✔
2200
                    . " and specified column key `$column_key` with this key `$coll_key` in the collection." . PHP_EOL
36✔
2201
                    . " Either the index key `$index_key` is not an accessible property of the item"
36✔
2202
                    . " or the specified column key `$column_key` is not an accessible property of the item"
36✔
2203
                    . " or some other error occurred" .PHP_EOL
36✔
2204
                    . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
36✔
2205
                    throw new RuntimeException($msg);
36✔
2206
                }
2207
            } // else if(is_object($item))
2208
        } // foreach ( $this->versatile_collections_items as $coll_key => $item )
2209
        
2210
        return $column_2_return;
6✔
2211
    }
2212
    
2213
    /**
2214
     *  
2215
     * @see \VersatileCollections\CollectionInterface::getItems()
2216
     *  
2217
     */
2218
    public function getItems(): CollectionInterface
2219
    {
2220
        
2221
        return static::makeNew(\array_values($this->versatile_collections_items));
18✔
2222
    }
2223
    
2224
    /**
2225
     *  
2226
     * @see \VersatileCollections\CollectionInterface::whenTrue()
2227
     * 
2228
     * @param bool $truthy_value
2229
     * @noRector \Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector
2230
     */
2231
    public function whenTrue( 
2232
        $truthy_value, callable $callback, callable $default=null
2233
    ) {
2234
        if ( $truthy_value ) {
24✔
2235
            
2236
            return $callback($this);
12✔
2237
            
2238
        } elseif ( !\is_null($default) ) {
24✔
2239
            
2240
            return $default($this);
12✔
2241
        }
2242

2243
        return $default;
12✔
2244
    }
2245
    
2246
    /**
2247
     *  
2248
     * @see \VersatileCollections\CollectionInterface::whenFalse()
2249
     * 
2250
     * @param bool $falsy_value
2251
     * @noRector \Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector
2252
     */
2253
    public function whenFalse( 
2254
        $falsy_value, callable $callback, callable $default=null
2255
    ) {
2256
        return $this->whenTrue( (!$falsy_value) , $callback, $default);
12✔
2257
    }
2258
    
2259
    /**
2260
     *  
2261
     * @see \VersatileCollections\CollectionInterface::getAsNewType()
2262
     * 
2263
     * @param string|CollectionInterface $new_collection_class
2264
     * 
2265
     * @psalm-suppress DocblockTypeContradiction
2266
     * @psalm-suppress RedundantConditionGivenDocblockType
2267
     */
2268
    public function getAsNewType($new_collection_class= GenericCollection::class): CollectionInterface {
2269
        
2270
        if( 
2271
            !\is_string($new_collection_class)
96✔
2272
            && !\is_object($new_collection_class)
96✔
2273
        ) {
2274
            $function = __FUNCTION__;
6✔
2275
            $class = \get_class($this);
6✔
2276
            $new_collection_class_type = Utils::gettype($new_collection_class);
6✔
2277
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
2278
            . " You must specify an object or string as the \$new_collection_class parameter."
6✔
2279
            . " You supplied a(n) `{$new_collection_class_type}` with a value of: ". var_to_string($new_collection_class);
6✔
2280
            throw new InvalidArgumentException($msg);
6✔
2281
        }
2282
        
2283
        if( 
2284
            !\is_subclass_of($new_collection_class, CollectionInterface::class)
90✔
2285
        ) {
2286
            $function = __FUNCTION__;
12✔
2287
            $class = \get_class($this);
12✔
2288
            $new_collection_class_type = Utils::gettype($new_collection_class);
12✔
2289
            $msg = "Error [{$class}::{$function}(...)]:"
12✔
2290
            . " You must specify an object or string that is a sub-class of "
12✔
2291
            . CollectionInterface::class . " as the \$new_collection_class parameter."
12✔
2292
            . " You supplied a(n) `{$new_collection_class_type}` with a value of: ". var_to_string($new_collection_class);
12✔
2293
            throw new InvalidArgumentException($msg);
12✔
2294
        }
2295
        
2296
        if( 
2297
            \is_object($new_collection_class)
78✔
2298
            && $new_collection_class instanceof CollectionInterface
78✔
2299
        ) {
2300
            $new_collection_class = \get_class($new_collection_class);
6✔
2301
        }
2302
        
2303
        /** @psalm-suppress InvalidStringClass */
2304
        return $new_collection_class::makeNew($this->versatile_collections_items);
78✔
2305
    }
2306
    
2307
    /**
2308
     *  
2309
     * @see \VersatileCollections\CollectionInterface::removeAll()
2310
     * 
2311
     * @psalm-suppress LessSpecificImplementedReturnType 
2312
     */
2313
    public function removeAll(array $keys=[]): CollectionInterface
2314
    {
2315
        
2316
        /** @noRector \Rector\Php71\Rector\FuncCall\CountOnNullRector */
2317
        if( \count($keys) > 0 ) {
12✔
2318
            
2319
            foreach($keys as $key) {
6✔
2320
                
2321
                if( $this->containsKey($key) ) {
6✔
2322
                    
2323
                    $this->offsetUnset($key);
6✔
2324
                }
2325
            }
2326
            
2327
        } else {
2328
            
2329
            // shortcut
2330
            $this->versatile_collections_items = [];
12✔
2331
        }
2332
        
2333
        return $this;
12✔
2334
    }
2335
    
2336
    /**
2337
     *  
2338
     * @see \VersatileCollections\CollectionInterface::getAllWhereKeysIn()
2339
     *  
2340
     */
2341
    public function getAllWhereKeysIn(array $keys): CollectionInterface
2342
    {
2343
        
2344
        $result = static::makeNew();
6✔
2345
        
2346
        foreach ( $this->versatile_collections_items as $key => $item ) {
6✔
2347
            
2348
            if( \in_array($key, $keys, true) ) {
6✔
2349
                
2350
                $result[$key] = $item;
6✔
2351
            }
2352
        }
2353
        
2354
        return $result;
6✔
2355
    }
2356
    
2357
    /**
2358
     *  
2359
     * @see \VersatileCollections\CollectionInterface::getAllWhereKeysNotIn()
2360
     *  
2361
     */
2362
    public function getAllWhereKeysNotIn(array $keys): CollectionInterface
2363
    {
2364
        
2365
        $result = static::makeNew();
6✔
2366
        
2367
        foreach ( $this->versatile_collections_items as $key => $item ) {
6✔
2368
            
2369
            if( !\in_array($key, $keys, true) ) {
6✔
2370
                
2371
                $result[$key] = $item;
6✔
2372
            }
2373
        }
2374
        
2375
        return $result;
6✔
2376
    }
2377
    
2378
    /**
2379
     *  
2380
     * @see \VersatileCollections\CollectionInterface::paginate()
2381
     *  
2382
     */
2383
    public function paginate(int $page_number, int $num_items_per_page): CollectionInterface
2384
    {
2385
        
2386
        if( $page_number < 1 ) {
6✔
2387
            
2388
            $page_number = 1;
6✔
2389
        }
2390
        
2391
        if( $num_items_per_page < 1 ) {
6✔
2392
            
2393
            $num_items_per_page = 1;
6✔
2394
        }
2395
        
2396
        if( $num_items_per_page > $this->count() ) {
6✔
2397
            
2398
            $offset = $page_number - 1;
6✔
2399
            
2400
        } else {
2401

2402
            $offset = (($page_number * $num_items_per_page) - $num_items_per_page);
6✔
2403
        }
2404

2405
        return $this->slice($offset, $num_items_per_page);
6✔
2406
    }
2407
    
2408
    /**
2409
     *  
2410
     * @see \VersatileCollections\CollectionInterface::diff()
2411
     *  
2412
     */
2413
    public function diff(array $items): CollectionInterface
2414
    {
2415
        
2416
        return static::makeNew(\array_diff($this->versatile_collections_items, $items));
18✔
2417
    }
2418

2419
    /**
2420
     *  
2421
     * @see \VersatileCollections\CollectionInterface::diffUsing()
2422
     *  
2423
     */
2424
    public function diffUsing(array $items, callable $callback): CollectionInterface
2425
    {
2426
        
2427
        return static::makeNew(\array_udiff($this->versatile_collections_items, $items, $callback));
12✔
2428
    }
2429

2430
    /**
2431
     *  
2432
     * @see \VersatileCollections\CollectionInterface::diffAssoc()
2433
     *  
2434
     */
2435
    public function diffAssoc(array $items): CollectionInterface
2436
    {
2437
        
2438
        return static::makeNew(\array_diff_assoc($this->versatile_collections_items, $items));
12✔
2439
    }
2440

2441
    /**
2442
     *  
2443
     * @see \VersatileCollections\CollectionInterface::diffAssocUsing()
2444
     *  
2445
     */
2446
    public function diffAssocUsing(array $items, callable $key_comparator): CollectionInterface
2447
    {
2448
        
2449
        return static::makeNew(\array_diff_uassoc($this->versatile_collections_items, $items, $key_comparator));
6✔
2450
    }
2451

2452
    /**
2453
     *  
2454
     * @see \VersatileCollections\CollectionInterface::diffKeys()
2455
     *  
2456
     */
2457
    public function diffKeys(array $items): CollectionInterface
2458
    {
2459
        
2460
        return static::makeNew(\array_diff_key($this->versatile_collections_items, $items));
12✔
2461
    }
2462

2463
    /**
2464
     *  
2465
     * @see \VersatileCollections\CollectionInterface::diffKeysUsing()
2466
     *  
2467
     */
2468
    public function diffKeysUsing(array $items, callable $key_comparator): CollectionInterface
2469
    {
2470
        
2471
        return static::makeNew(\array_diff_ukey($this->versatile_collections_items, $items, $key_comparator));
6✔
2472
    }
2473

2474
    /**
2475
     *  
2476
     * @see \VersatileCollections\CollectionInterface::allSatisfyConditions()
2477
     * 
2478
     * @psalm-suppress MissingClosureParamType 
2479
     */
2480
    public function allSatisfyConditions(callable $callback, bool $bind_callback_to_this=true): bool {
2481
        
2482
        if( $bind_callback_to_this === true ) {
6✔
2483
            
2484
            $callback = Utils::bindObjectAndScopeToClosure(
6✔
2485
                Utils::getClosureFromCallable($callback), 
6✔
2486
                $this
6✔
2487
            );
6✔
2488
        }
2489
        
2490
        return $this->reduceWithKeyAccess(
6✔
2491
            fn($carry, $item, $key): bool => $carry && $callback($key, $item), 
6✔
2492
            true
6✔
2493
        );
6✔
2494
    }
2495
    
2496
    /**
2497
     *  
2498
     * @see \VersatileCollections\CollectionInterface::intersectByKeys()
2499
     *  
2500
     */
2501
    public function intersectByKeys(array $arr): CollectionInterface
2502
    {
2503
        
2504
        return static::makeNew(\array_intersect_key($this->versatile_collections_items, $arr));
6✔
2505
    }
2506
    
2507
    /**
2508
     *  
2509
     * @see \VersatileCollections\CollectionInterface::intersectByItems()
2510
     *  
2511
     */
2512
    public function intersectByItems(array $arr): CollectionInterface
2513
    {
2514
        
2515
        return static::makeNew(\array_intersect($this->versatile_collections_items, $arr));
6✔
2516
    }
2517
    
2518
    /**
2519
     *  
2520
     * @see \VersatileCollections\CollectionInterface::intersectByKeysAndItems()
2521
     *  
2522
     */
2523
    public function intersectByKeysAndItems(array $arr): CollectionInterface
2524
    {
2525
        
2526
        return static::makeNew(\array_intersect_assoc($this->versatile_collections_items, $arr));
6✔
2527
    }
2528
    
2529
    /**
2530
     *  
2531
     * @see \VersatileCollections\CollectionInterface::intersectByKeysUsingCallback()
2532
     *  
2533
     */
2534
    public function intersectByKeysUsingCallback(array $arr, callable $key_comparator): CollectionInterface
2535
    {
2536
        
2537
        return static::makeNew(\array_intersect_ukey($this->versatile_collections_items, $arr, $key_comparator));
6✔
2538
    }
2539
    
2540
    /**
2541
     *  
2542
     * @see \VersatileCollections\CollectionInterface::intersectByItemsUsingCallback()
2543
     *  
2544
     */
2545
    public function intersectByItemsUsingCallback(array $arr, callable $item_comparator): CollectionInterface
2546
    {
2547
        
2548
        return static::makeNew(\array_uintersect($this->versatile_collections_items, $arr, $item_comparator));
6✔
2549
    }
2550
    
2551
    /**
2552
     *  
2553
     * @see \VersatileCollections\CollectionInterface::intersectByKeysAndItemsUsingCallbacks()
2554
     *
2555
     * @noinspection PhpUnusedLocalVariableInspection
2556
     */
2557
    public function intersectByKeysAndItemsUsingCallbacks(array $arr, callable $key_comparator=null, callable $item_comparator=null): CollectionInterface
2558
    {
2559
        
2560
        $result = [];
6✔
2561
        
2562
        if( !\is_null($key_comparator) && \is_null($item_comparator) ) {
6✔
2563
            
2564
            $result = \array_intersect_uassoc(
6✔
2565
                $this->versatile_collections_items, $arr, $key_comparator
6✔
2566
            );
6✔
2567
            
2568
        } else if( \is_null($key_comparator) && !\is_null($item_comparator) ) {
6✔
2569
            
2570
            $result = \array_uintersect_assoc(
6✔
2571
                $this->versatile_collections_items, $arr, $item_comparator
6✔
2572
            );
6✔
2573
            
2574
        } else if( !\is_null($key_comparator) && !\is_null($item_comparator) ) {
6✔
2575
            
2576
            $result = \array_uintersect_uassoc(
6✔
2577
                $this->versatile_collections_items, $arr, $item_comparator, $key_comparator
6✔
2578
            );
6✔
2579
            
2580
        } else {
2581
            
2582
            $result = \array_intersect_assoc($this->versatile_collections_items, $arr);
6✔
2583
        }
2584
        
2585
        return static::makeNew($result);
6✔
2586
    }
2587
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc