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

rotexsoft / versatile-collections / 6042672854

31 Aug 2023 09:32PM UTC coverage: 98.445% (-0.09%) from 98.532%
6042672854

push

github

rotimi
Upgraded rector to 0.18

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
     */
416
    public function offsetSet($key, $val): void {
417
        
418
        if(\is_null($key) ) {
864✔
419
            
420
            $this->versatile_collections_items[] = $val;
180✔
421
            
422
        } else {
423
            
424
            $this->versatile_collections_items[$key] = $val;
774✔
425
        }
426
    }
427
    
428
    /**
429
     *  
430
     * @see \VersatileCollections\CollectionInterface::offsetUnset()
431
     * 
432
     * @param string|int $key The requested key.
433
     *  
434
     */
435
    public function offsetUnset($key): void {
436
        
437
        $this->versatile_collections_items[$key] = null;
36✔
438
        unset($this->versatile_collections_items[$key]);
36✔
439
    }
440
    
441
    /**
442
     *    
443
     * @see \VersatileCollections\CollectionInterface::toArray()
444
     *  
445
     * @return mixed[] 
446
     */
447
    public function toArray(): array {
448

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

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

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

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

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

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

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

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

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

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

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

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

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

1219
            $iteration_counter++;
6✔
1220
        }
1221

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1984
                    if( !\in_array($item, $carry, true)) {
6✔
1985

1986
                        $carry[] = $item;
6✔
1987
                    }
1988

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

2063
        foreach ( $this->versatile_collections_items as $coll_key => $item ) {
132✔
2064

2065
            if( !\is_array($item) && !\is_object($item) ) {
132✔
2066

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

2078
            if( \is_array($item) || $item instanceof ArrayAccess) {
132✔
2079

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

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

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

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

2114
                } else if( \is_null($index_key) ) {
24✔
2115

2116
                    $column_2_return[] = $item[$column_key];
12✔
2117

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

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

2143
                    $column_2_return[$item[$index_key]] = $item[$column_key];
6✔
2144

2145
                } else {
2146

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

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

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

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

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

2183
                    $column_2_return[$index_key_value] = $column_key_value;
6✔
2184

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

2191
                } else {
2192

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

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

2400
            $offset = (($page_number * $num_items_per_page) - $num_items_per_page);
6✔
2401
        }
2402

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

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

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

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

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

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

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