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

rotexsoft / versatile-collections / 8286942459

14 Mar 2024 08:08PM UTC coverage: 98.453%. Remained the same
8286942459

push

github

rotimi
PHP 8.1 minimum version prep

1018 of 1034 relevant lines covered (98.45%)

29.48 hits per line

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

98.86
/src/CollectionInterfaceImplementationTrait.php
1
<?php /** @noinspection PhpFullyQualifiedNameUsageInspection */
2
namespace VersatileCollections;
3

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

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

40
    protected static array $versatile_collections_methods_for_all_instances = [];
41
    
42
    protected array $versatile_collections_methods_for_this_instance = [];
43
    
44
    protected static array $versatile_collections_static_methods = [];
45
    
46
    protected static function validateMethodName(string $name, string $method_name_was_passed_to, ?string $class_in_which_method_was_called=null): bool 
47
    {
48
        $regex_4_valid_method_name = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
72✔
49
        
50
        if( 
51
            !\preg_match( $regex_4_valid_method_name, \preg_quote($name, '/') )
72✔
52
        ) {
53
            // A valid php class' method name starts with a letter or underscore, 
54
            // followed by any number of letters, numbers, or underscores.
55

56
            // Make sure the controller name is a valid string usable as a class name
57
            // in php as defined in http://php.net/manual/en/language.oop5.basic.php
58
            $class = $class_in_which_method_was_called ?? static::class;
6✔
59
            
60
            $function = $method_name_was_passed_to;
6✔
61
            $name_var = var_to_string($name);
6✔
62
            $msg = "Error [{$class}::{$function}(...)]: Trying to add a dynamic method with an invalid name `{$name_var}` to a collection";
6✔
63
            
64
            throw new InvalidArgumentException($msg);
6✔
65
            
66
        } else if( \method_exists(static::class, $name) ) {
66✔
67
            
68
            // valid method name was supplied but conflicts with an
69
            // already defined real class method
70
            $class = $class_in_which_method_was_called ?? static::class;
12✔
71
            $function = $method_name_was_passed_to;
12✔
72
            $msg = "Error [{$class}::{$function}(...)]: Trying to add a dynamic method with the same name `{$name}` as an existing actual method to a collection";
12✔
73
            
74
            throw new Exceptions\AddConflictingMethodException($msg);
12✔
75
        }
76
        
77
        return true;
54✔
78
    }
79
    
80
    /**
81
     * @param string $name name of the method being added
82
     * @param callable $callable method being added
83
     * @param bool $has_return_val true means $callable returns a value, else false if $callable returns no value
84
     *  
85
     * @used-for: adding-methods-at-runtime
86
     *  
87
     * @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.
88
     */
89
    public static function addStaticMethod(
90
        string $name, 
91
        callable $callable, 
92
        bool $has_return_val=false
93
    ): void {
94
        if( static::validateMethodName($name, __FUNCTION__) ) {
12✔
95
            
96
            static::$versatile_collections_static_methods[ static::class.'::'. $name] = [
12✔
97
                'method' => $callable,
12✔
98
                'has_return_val' => $has_return_val
12✔
99
            ];
12✔
100
        }
101
    }
102
    
103
    /**
104
     * @param string $name name of the method being added
105
     * @param callable $callable method being added
106
     * @param bool $has_return_val true means $callable returns a value, else false if $callable returns no value
107
     * @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
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 an instance method with the specified name on any instance of the Collection class or any of its sub-classes.
112
     */
113
    public static function addMethodForAllInstances(
114
        string $name, 
115
        callable $callable, 
116
        bool $has_return_val=false,
117
        bool $bind_to_this_on_invocation=true
118
    ): void {
119
        if( static::validateMethodName($name, __FUNCTION__) ) {
18✔
120
            
121
            static::$versatile_collections_methods_for_all_instances[ static::class.'::'. $name] = [
18✔
122
                'method' => $callable,
18✔
123
                'has_return_val' => $has_return_val,
18✔
124
                'bind_to_this_on_invocation' => $bind_to_this_on_invocation
18✔
125
            ];
18✔
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 true means $callable will be bound to $this, else false if $callable should not be explicitly bound to $this
134
     * 
135
     * @used-for: adding-methods-at-runtime
136
     * 
137
     * @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.
138
     */
139
    public function addMethod(
140
        string $name, 
141
        callable $callable, 
142
        bool $has_return_val=false,
143
        bool $bind_to_this=true
144
    ): self {
145
        if( static::validateMethodName($name, __FUNCTION__, $this::class) ) {
24✔
146
            
147
            if( $bind_to_this ) {
24✔
148

149
                $callable = Utils::bindObjectAndScopeToClosure(
24✔
150
                    Utils::getClosureFromCallable($callable), 
24✔
151
                    $this
24✔
152
                );
24✔
153
            }
154
            
155
            $this->versatile_collections_methods_for_this_instance[ static::class.'::'. $name] = [
24✔
156
                'method' => $callable,
24✔
157
                'has_return_val' => $has_return_val,
24✔
158
            ];
24✔
159
        }
160
        
161
        return $this;
24✔
162
    }
163
    
164
    /**
165
     * @return string|bool a string representing the calculated key or false if calculated key does not exist in $methods_array
166
     */
167
    protected static function getKeyForDynamicMethod(string $name, array &$methods_array, bool $search_parent_class_registration=true): string|bool
168
    {
169
        if( \array_key_exists( static::class.'::'.$name , $methods_array) ) {
42✔
170
            
171
            return static::class.'::'.$name;
30✔
172
        }
173
        
174
        if( $search_parent_class_registration === true ) {
42✔
175
            
176
            $parent_class = \get_parent_class(static::class);
42✔
177

178
            while( $parent_class !== false ) {
42✔
179

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

182
                    return $parent_class.'::'.$name;
18✔
183
                }
184

185
                $parent_class = \get_parent_class($parent_class);
18✔
186
            }
187
        }
188
        
189
        return false;
42✔
190
    }
191
    
192
    /**
193
     * @return mixed
194
     * 
195
     * @used-for: other-operations
196
     * 
197
     * @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.
198
     * 
199
     * @throws BadMethodCallException
200
     *
201
     * @noinspection PhpInconsistentReturnPointsInspection
202
     */
203
    public function __call(string $method_name, array $arguments=[]) 
204
    {    
205
        $key_for_this_instance = static::getKeyForDynamicMethod($method_name, $this->versatile_collections_methods_for_this_instance, false);
36✔
206
        $key_for_all_instances = static::getKeyForDynamicMethod($method_name, static::$versatile_collections_methods_for_all_instances);
36✔
207
        
208
        if ( $key_for_this_instance !== false ) {
36✔
209
            
210
            $result = \call_user_func_array($this->versatile_collections_methods_for_this_instance[$key_for_this_instance]['method'], $arguments);
24✔
211
            
212
            if( $this->versatile_collections_methods_for_this_instance[$key_for_this_instance]['has_return_val'] ) {
18✔
213
                
214
                return $result;
18✔
215
            }
216
        
217
        } else if( $key_for_all_instances !== false ) {
24✔
218
            
219
            $new_callable = static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['method'];
12✔
220
            
221
            if( 
222
                ((bool)static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['bind_to_this_on_invocation'])   
12✔
223
            ) {
224
                
225
                $new_callable = Utils::bindObjectAndScopeToClosure(
12✔
226
                    Utils::getClosureFromCallable($new_callable), 
12✔
227
                    $this
12✔
228
                );
12✔
229
            }
230

231
            $result = \call_user_func_array($new_callable, $arguments);
12✔
232

233
            if( static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['has_return_val'] ) {
12✔
234

235
                return $result;
12✔
236
            }
237
            
238
        } else {
239
            
240
            $function = __FUNCTION__;
18✔
241
            $class = $this::class;
18✔
242
            $name_var = var_to_string($method_name);
18✔
243
            $msg = "Error [{$class}::{$function}(...)]: Trying to call a non-existent dynamic method named `{$name_var}` on a collection";
18✔
244
            throw new Exceptions\BadMethodCallException($msg);
18✔
245
        }
246
    }
247
    
248
    /**
249
     * @return mixed
250
     * 
251
     * @used-for: other-operations
252
     * 
253
     * @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.
254
     * 
255
     * @throws BadMethodCallException
256
     *
257
     * @noinspection PhpInconsistentReturnPointsInspection
258
     */
259
    public static function __callStatic(string $method_name, array $arguments=[])
260
    {
261
        $key_for_static_method = static::getKeyForDynamicMethod($method_name, static::$versatile_collections_static_methods);
6✔
262
        
263
        if( $key_for_static_method !== false ) {
6✔
264
            
265
            // never bind to this when method is called statically            
266
            $result = \call_user_func_array(
6✔
267
                static::$versatile_collections_static_methods[$key_for_static_method]['method'], $arguments
6✔
268
            );
6✔
269
            
270
            if( static::$versatile_collections_static_methods[$key_for_static_method]['has_return_val'] ) {
6✔
271
                
272
                return $result;
6✔
273
            }
274
            
275
        } else {
276
            
277
            $function = __FUNCTION__;
6✔
278
            $class = static::class;
6✔
279
            $name_var = var_to_string($method_name);
6✔
280
            $msg = "Error [{$class}::{$function}(...)]: Trying to statically call a non-existent dynamic method named `{$name_var}` on a collection";
6✔
281
            throw new BadMethodCallException($msg);
6✔
282
        }
283
    }
284
    
285
    public function __get(string $key): mixed
286
    {
287
        return $this->offsetGet($key);
78✔
288
    }
289
    
290
    public function __isset(string $key): bool
291
    {
292
        return $this->offsetExists($key);
24✔
293
    }
294
    
295
    /**
296
     * @param mixed $val The value to set it to.
297
     */
298
    public function __set(string $key, mixed $val): void 
299
    {
300
        $this->offsetSet($key, $val);
210✔
301
    }
302
    
303
    public function __unset(string $key): void 
304
    {
305
        $this->offsetUnset($key);
12✔
306
    }
307
    
308
    /**
309
     * @see \VersatileCollections\CollectionInterface::makeNew()
310
     *
311
     * @noinspection PhpIncompatibleReturnTypeInspection
312
     * @psalm-suppress UnsafeInstantiation
313
     */
314
    public static function makeNew(iterable $items=[], bool $preserve_keys=true): CollectionInterface
315
    {
316
        if ($preserve_keys === true) {
696✔
317

318
            $collection = new static();
696✔
319

320
            foreach ($items as $key => $item ) {
696✔
321

322
                $collection[$key] = $item;
480✔
323
            }
324

325
            return $collection;
690✔
326
        }
327

328
        // I use array_values to ensure that all keys 
329
        // are numeric. Argument unpacking does not
330
        // work on arrays with one or more string keys.
331
        return \is_array($items)
12✔
332
                ? new static(...\array_values($items))
12✔
333
                : new static(...\array_values(\iterator_to_array($items))); // These should be faster than loop above
12✔
334
                                                                            // since looping triggers offsetSet()
335
                                                                            // for each item
336
    }
337
    
338
    /**
339
     * @see \VersatileCollections\CollectionInterface::offsetExists()
340
     *  
341
     * @param mixed $key The requested key.
342
     */
343
    public function offsetExists(mixed $key): bool 
344
    {
345
        return (is_string($key) || is_int($key)) && \array_key_exists($key, $this->versatile_collections_items);
66✔
346
    }
347
    
348
    /**
349
     * @see \VersatileCollections\CollectionInterface::offsetGet()
350
     * 
351
     * @param mixed $key The requested key.
352
     */
353
    public function offsetGet(mixed $key):mixed
354
    {
355
        if ( \array_key_exists($key, $this->versatile_collections_items) ) {
144✔
356

357
            return $this->versatile_collections_items[$key];
120✔
358
            
359
        } else {
360

361
            throw new NonExistentItemException($this::class."::offsetGet({$key})");
36✔
362
        }
363
    }
364

365
    /**
366
     * @see \VersatileCollections\CollectionInterface::offsetSet()
367
     * 
368
     * @param string|int|null $key The requested key.
369
     * 
370
     * @param mixed $val The value to set it to.
371
     * @psalm-suppress ParamNameMismatch
372
     */
373
    public function offsetSet($key, mixed $val): void 
374
    {
375
        if($key === null) {
852✔
376
            
377
            $this->versatile_collections_items[] = $val;
180✔
378
            
379
        } else {
380
            
381
            $this->versatile_collections_items[$key] = $val;
762✔
382
        }
383
    }
384
    
385
    /**
386
     * @see \VersatileCollections\CollectionInterface::offsetUnset()
387
     * 
388
     * @param string|int $key The requested key.
389
     */
390
    public function offsetUnset($key): void 
391
    {
392
        $this->versatile_collections_items[$key] = null;
36✔
393
        unset($this->versatile_collections_items[$key]);
36✔
394
    }
395
    
396
    /**
397
     * @see \VersatileCollections\CollectionInterface::toArray()
398
     *  
399
     * @return mixed[] 
400
     */
401
    public function toArray(): array 
402
    {
403
        return $this->versatile_collections_items;
480✔
404
    }
405
    
406
    /**
407
     * @see \VersatileCollections\CollectionInterface::getIterator()
408
     */
409
    public function getIterator(): Iterator 
410
    {
411
        return new ArrayIterator($this->versatile_collections_items);
72✔
412
    }
413
    
414
    /**
415
     * @see \VersatileCollections\CollectionInterface::count()
416
     */
417
    public function count(): int 
418
    {
419
        return \count($this->versatile_collections_items);
576✔
420
    }
421
    
422
    public function __construct(mixed ...$items) 
423
    {
424
        $this->versatile_collections_items = $items;
900✔
425
    }
426
    
427
    ////////////////////////////////////////////////////////////////////////////
428
    ////////// OTHER COLLECTION METHODS ////////////////////////////////////////
429
    /**
430
     * @see \VersatileCollections\CollectionInterface::firstItem()
431
     * 
432
     * @psalm-suppress PossiblyNullArrayOffset 
433
     */
434
    public function firstItem(): mixed
435
    {
436
        return ($this->count() <= 0) ? null : $this->versatile_collections_items[\array_key_first($this->versatile_collections_items)];
258✔
437
    }
438
    
439
    /**
440
     * @see \VersatileCollections\CollectionInterface::lastItem()
441
     *
442
     * @return mixed last item or null if collection is empty
443
     * @psalm-suppress PossiblyNullArrayOffset
444
     */
445
    public function lastItem(): mixed
446
    {      
447
        return ( $this->count() <= 0 ) ? null : $this->versatile_collections_items[\array_key_last($this->versatile_collections_items)];
66✔
448
    }
449
    
450
    /**
451
     * @see \VersatileCollections\CollectionInterface::getKeys()
452
     *
453
     * @noinspection PhpIncompatibleReturnTypeInspection
454
     * @psalm-suppress MoreSpecificReturnType
455
     */
456
    public function getKeys(): GenericCollection
457
    {
458
        return GenericCollection::makeNew( \array_keys($this->versatile_collections_items) );
18✔
459
    }
460
    
461
    /**
462
     * @see \VersatileCollections\CollectionInterface::setValForEachItem()
463
     *  
464
     * @param mixed $field_val value to be set for the field whose name is the value contained in $field_name
465
     */
466
    public function setValForEachItem(string $field_name, mixed $field_val, bool $add_field_if_not_present=false): CollectionInterface
467
    {
468
        foreach ($this->versatile_collections_items as &$item) {
6✔
469
            
470
            if( 
471
                \is_object($item)
6✔
472
                && !($item instanceof ArrayAccess)
6✔
473
                && 
474
                ( 
475
                    $add_field_if_not_present 
6✔
476
                    || 
6✔
477
                    object_has_property($item, $field_name) 
6✔
478
                    || 
6✔
479
                    (
6✔
480
                        \method_exists($item, '__set')
6✔
481
                        && \method_exists($item, '__get')
6✔
482
                        && \method_exists($item, '__isset')
6✔
483
                        && isset($item->{$field_name})    
6✔
484
                    )
6✔
485
                )
486
            ) {
487
                $item->{$field_name} = $field_val;
6✔
488
                
489
            } else if(
490
                \is_array($item)
6✔
491
                && ( $add_field_if_not_present  || \array_key_exists($field_name, $item) )
6✔
492
            ) {
493
                $item[$field_name] = $field_val;
6✔
494
                
495
            } else if(
496
                $item instanceof ArrayAccess
6✔
497
                && ( $add_field_if_not_present  || $item->offsetExists($field_name) )
6✔
498
            ) {
499
                $item->offsetSet($field_name, $field_val);
6✔
500
                
501
            } else {
502
                
503
                $class = $this::class;
6✔
504
                $function = __FUNCTION__;
6✔
505
                $msg = "Error [{$class}::{$function}(...)]:Trying to set a property named `$field_name` on a collection item of type "
6✔
506
                    . "`". Utils::gettype($item)."` "
6✔
507
                    . PHP_EOL . " `\$field_val`: " . var_to_string($field_val)
6✔
508
                    . PHP_EOL . " `\$add_field_if_not_present`: " . var_to_string($add_field_if_not_present);
6✔
509
                
510
                throw new Exceptions\InvalidCollectionOperationException($msg);
6✔
511
            }
512
            
513
        } // foreach ($this->collection_items as &$item)
514
        
515
        return $this;
6✔
516
    }
517
    
518
    /**
519
     * @see \VersatileCollections\CollectionInterface::filterAll()
520
     */
521
    public function filterAll(callable $filterer, bool $copy_keys=false, bool $bind_callback_to_this=true, bool $remove_filtered_items=false): CollectionInterface
522
    {    
523
        return $this->filterFirstN($filterer, $this->count(), $copy_keys, $bind_callback_to_this, $remove_filtered_items);
6✔
524
    }
525
    
526
    /**
527
     * @see \VersatileCollections\CollectionInterface::filterFirstN()
528
     */
529
    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
530
    {
531
        if( $bind_callback_to_this === true ) {
12✔
532
            
533
            $filterer = Utils::bindObjectAndScopeToClosure(
12✔
534
                Utils::getClosureFromCallable($filterer), 
12✔
535
                $this
12✔
536
            );
12✔
537
        }
538
        
539
        $filtered_items = static::makeNew();
12✔
540
        
541
        if( 
542
            $max_number_of_filtered_items_to_return === null
12✔
543
            || ($max_number_of_filtered_items_to_return) > $this->count()
12✔
544
            || ($max_number_of_filtered_items_to_return) < 0
12✔
545
        ) {
546
            $max_number_of_filtered_items_to_return = $this->count();
6✔
547
        }
548
        
549
        $num_filtered_items = 0;
12✔
550
        
551
        foreach ( $this->versatile_collections_items as $key => $item ) {
12✔
552
            
553
            if( $num_filtered_items >= $max_number_of_filtered_items_to_return ) {
12✔
554
                
555
                break;
6✔
556
            }
557
            
558
            if( $filterer($key, $item) === true ) {
12✔
559
                
560
                $num_filtered_items++;
12✔
561
                
562
                if( $copy_keys ) {
12✔
563
                    
564
                    $filtered_items[$key] = $item;
12✔
565
                    
566
                } else {
567
                    
568
                    $filtered_items[] = $item;
12✔
569
                }
570
                
571
                if($remove_filtered_items) {
12✔
572
                    
573
                    unset($this->versatile_collections_items[$key]);
12✔
574
                }
575
            }
576
            
577
        } // foreach ( $this->collection_items as $key => $item )
578
        
579
        return $filtered_items;
12✔
580
    }
581
    
582
    /**
583
     * @see \VersatileCollections\CollectionInterface::transform()
584
     */
585
    public function transform(callable $transformer, bool $bind_callback_to_this=true): CollectionInterface
586
    {
587
        if( $bind_callback_to_this === true ) {
12✔
588
            
589
            $transformer = Utils::bindObjectAndScopeToClosure(
12✔
590
                Utils::getClosureFromCallable($transformer), 
12✔
591
                $this
12✔
592
            );
12✔
593
        }
594
        
595
        foreach ( $this->versatile_collections_items as $key => $item ) {
12✔
596
            
597
            // using $this[$key] instead of $this->collection_items[$key]
598
            // so that $this->offsetSet(...) will be invoked which will
599
            // trigger type-checking in sub-classes like StrictlyTypedCollection
600
            $this[$key] = $transformer($key, $item);
12✔
601
            
602
        } // foreach ( $this->collection_items as $key => $item )
603
        
604
        return $this;
12✔
605
    }
606
    
607
    /**
608
     * @see \VersatileCollections\CollectionInterface::reduce()
609
     * 
610
     * @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.
611
     */
612
    public function reduce(callable $reducer, mixed $initial_value=NULL) 
613
    {
614
        return \array_reduce($this->versatile_collections_items, $reducer, $initial_value);
18✔
615
    }
616
    
617
    /**
618
     * @see \VersatileCollections\CollectionInterface::reduceWithKeyAccess()
619
     * 
620
     * @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.
621
     */
622
    public function reduceWithKeyAccess(callable $reducer, mixed $initial_value=NULL) 
623
    {
624
        $reduced_result = $initial_value;
12✔
625
        
626
        foreach ($this->versatile_collections_items as $key=>$item) {
12✔
627
            
628
            $reduced_result = $reducer($reduced_result, $item, $key);
12✔
629
        }
630
        
631
        return $reduced_result;
12✔
632
    }
633
    
634
    /**
635
     * @see \VersatileCollections\CollectionInterface::reverse()
636
     */
637
    public function reverse(): CollectionInterface
638
    {
639
        return static::makeNew(
6✔
640
            \array_reverse($this->versatile_collections_items, true)
6✔
641
        );
6✔
642
    }
643
    
644
    /**
645
     * @see \VersatileCollections\CollectionInterface::reverseMe()
646
     */
647
    public function reverseMe(): CollectionInterface
648
    {
649
        $this->versatile_collections_items = 
6✔
650
            \array_reverse($this->versatile_collections_items, true);
6✔
651
        
652
        return $this;
6✔
653
    }
654
    
655
    /**
656
     * @see \VersatileCollections\CollectionInterface::slice()
657
     */
658
    public function slice(int $offset, ?int $length = null): CollectionInterface
659
    {
660
        return static::makeNew(
66✔
661
            \array_slice($this->versatile_collections_items, $offset, $length, true)
66✔
662
        );
66✔
663
    }
664
    
665
    /**
666
     * @see \VersatileCollections\CollectionInterface::isEmpty()
667
     */
668
    public function isEmpty(): bool 
669
    {
670
        return ($this->count() <= 0);
84✔
671
    }
672
    
673
    /**
674
     * @see \VersatileCollections\CollectionInterface::getIfExists()
675
     */
676
    public function getIfExists(string|int $key, mixed $default_value=null): mixed
677
    {        
678
        return \array_key_exists($key, $this->versatile_collections_items)
12✔
679
                ?  $this->versatile_collections_items[$key] : $default_value;
12✔
680
    }
681
    
682
    /**
683
     * @see \VersatileCollections\CollectionInterface::containsItem()
684
     * 
685
     * @param mixed $item item whose existence in the collection is to be checked
686
     */
687
    public function containsItem(mixed $item): bool 
688
    {
689
        return \in_array($item, $this->versatile_collections_items, true);
102✔
690
    }
691
    
692
    /**
693
     * @see \VersatileCollections\CollectionInterface::containsKey()
694
     * 
695
     * @param int|string $key key whose existence in the collection is to be checked
696
     */
697
    public function containsKey(int|string $key): bool 
698
    {
699
        return \array_key_exists($key, $this->versatile_collections_items);
102✔
700
    }
701
    
702
    /**
703
     * @see \VersatileCollections\CollectionInterface::containsItemWithKey()
704
     * 
705
     * @param int|string $key key whose existence in the collection is to be checked
706
     * @param mixed $item item whose existence in the collection is to be checked
707
     */
708
    public function containsItemWithKey(int|string $key, mixed $item): bool 
709
    {
710
        return $this->containsKey($key) 
36✔
711
                && $item === $this->versatile_collections_items[$key];
36✔
712
    }
713
    
714
    /**
715
     * @see \VersatileCollections\CollectionInterface::containsItems()
716
     */
717
    public function containsItems(array $items): bool 
718
    {
719
        $all_items_exist = \count($items) > 0;
18✔
720
        
721
        foreach ($items as $item) {
18✔
722
            
723
            $all_items_exist = $all_items_exist && $this->containsItem($item);
18✔
724
            
725
            if( $all_items_exist === false ) {
18✔
726
                
727
                break;
6✔
728
            }
729
        }
730
        
731
        return $all_items_exist;
18✔
732
    }
733
    
734
    /**
735
     * @see \VersatileCollections\CollectionInterface::containsKeys()
736
     */
737
    public function containsKeys(array $keys): bool 
738
    {
739
        $all_keys_exist = \count($keys) > 0;
24✔
740
        
741
        foreach ($keys as $key) {
24✔
742
            
743
            $all_keys_exist = $all_keys_exist && $this->containsKey($key);
24✔
744
            
745
            if( $all_keys_exist === false ) {
24✔
746
                
747
                break;
12✔
748
            }
749
        }
750
        
751
        return $all_keys_exist;
24✔
752
    }
753
    
754
    /**
755
     * @see \VersatileCollections\CollectionInterface::appendCollection()
756
     */
757
    public function appendCollection(CollectionInterface $other): CollectionInterface
758
    {
759
        if( ! $other->isEmpty() ) {
18✔
760
            
761
            foreach ($other as $item) {
18✔
762
                
763
                $this[] = $item;
18✔
764
            }
765
        }
766
        
767
        return $this;
18✔
768
    }
769
    
770
    /**
771
     * @see \VersatileCollections\CollectionInterface::appendItem()
772
     */
773
    public function appendItem(mixed $item): CollectionInterface
774
    {
775
        $this[] = $item;
42✔
776
        
777
        return $this;
36✔
778
    }
779
    
780
    /**
781
     * @see \VersatileCollections\CollectionInterface::mergeWith()
782
     */
783
    public function mergeWith(array $items): CollectionInterface
784
    {
785
        $copy = $this->versatile_collections_items;
24✔
786
        $merged_items = static::makeNew($copy);
24✔
787
        
788
        /**  */
789

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

793
            $merged_items[$key] = $item;
24✔
794
        }
795
        
796
        return $merged_items;
18✔
797
    }
798
    
799
    /**
800
     * @see \VersatileCollections\CollectionInterface::mergeMeWith()
801
     */
802
    public function mergeMeWith(array $items): CollectionInterface
803
    {
804
        // not using array_merge , want to trigger $this->offsetSet() logic
805
        foreach ( $items as $key => $item ) {
18✔
806

807
            $this[$key] = $item;
18✔
808
        }
809
        
810
        return $this;
12✔
811
    }
812
    
813
    /**
814
     * @see \VersatileCollections\CollectionInterface::prependCollection()
815
     */
816
    public function prependCollection(CollectionInterface $other): CollectionInterface
817
    {
818
        if( ! $other->isEmpty() ) {
12✔
819
            
820
            \array_unshift($this->versatile_collections_items, ...\array_values($other->toArray()));
12✔
821
        }
822
        
823
        return $this;
12✔
824
    }
825
    
826
    /**
827
     * @see \VersatileCollections\CollectionInterface::prependItem()
828
     */
829
    public function prependItem(mixed $item, string|int|null $key=null): CollectionInterface
830
    {
831
        if( $key === null ) {
6✔
832
            
833
            \array_unshift($this->versatile_collections_items, $item);
6✔
834
            
835
        } else { // \is_string($key) || \is_int($key)
836
            
837
            $this->versatile_collections_items = [$key=>$item] + $this->versatile_collections_items;
6✔
838
            
839
        }
840
        
841
        return $this;
6✔
842
    }
843
    
844
    /**
845
     * @see \VersatileCollections\CollectionInterface::getCollectionsOfSizeN()
846
     */
847
    public function getCollectionsOfSizeN(int $max_size_of_each_collection=1): CollectionInterface
848
    {
849
        if(
850
            $max_size_of_each_collection > $this->count()
6✔
851
        ) {
852
            $max_size_of_each_collection = $this->count();
6✔
853
            
854
        } else if( $max_size_of_each_collection <= 0 ) {
6✔
855
            
856
            $max_size_of_each_collection = 1;
6✔
857
        }
858
        
859
        $collections = GenericCollection::makeNew();
6✔
860
        $current_batch = static::makeNew();
6✔
861
        $counter = 0;
6✔
862
        
863
        foreach ($this->versatile_collections_items as $key=>$item) {
6✔
864
            
865
            $current_batch[$key] = $item;
6✔
866
            
867
            if( ++$counter >= $max_size_of_each_collection ) {
6✔
868

869
                $collections[] = $current_batch;
6✔
870
                $counter = 0; // reset
6✔
871
                $current_batch = static::makeNew(); // initialize next collection
6✔
872
            }
873
        }
874

875
        // yield last batch if not already yielded
876
        if( !$current_batch->isEmpty() ) {
6✔
877
            
878
            $collections[] = $current_batch;
6✔
879
        }
880
        
881
        return $collections;
6✔
882
    }
883
    
884
    /**
885
     * @see \VersatileCollections\CollectionInterface::yieldCollectionsOfSizeN()
886
     */
887
    public function yieldCollectionsOfSizeN(int $max_size_of_each_collection=1): Generator 
888
    {    
889
        if(
890
            $max_size_of_each_collection > $this->count()
12✔
891
        ) {
892
            $max_size_of_each_collection = $this->count();
6✔
893
            
894
        } else if( $max_size_of_each_collection <= 0 ) {
12✔
895
            
896
            $max_size_of_each_collection = 1;
6✔
897
        }
898
        
899
        $current_batch = static::makeNew();
12✔
900
        $counter = 0;
12✔
901
        
902
        foreach ($this->versatile_collections_items as $key=>$item) {
12✔
903
            
904
            $current_batch[$key] = $item;
12✔
905
            
906
            if( ++$counter >= $max_size_of_each_collection ) {
12✔
907

908
                yield $current_batch;
12✔
909
                $counter = 0; // reset
12✔
910
                $current_batch = static::makeNew(); // initialize next collection
12✔
911
            }
912
        }
913

914
        // yield last batch if not already yielded
915
        if( !$current_batch->isEmpty() ) {
12✔
916
            
917
            yield $current_batch;
12✔
918
        }
919
    }
920
    
921
    /**
922
     * @see \VersatileCollections\CollectionInterface::makeAllKeysNumeric()
923
     */
924
    public function makeAllKeysNumeric(int $starting_key=0): CollectionInterface
925
    {       
926
        if( $starting_key < 0 ) {
42✔
927
            
928
            $starting_key = 0;
6✔
929
        }
930
        
931
        if( $starting_key === 0 ) {
42✔
932
        
933
            $this->versatile_collections_items = 
42✔
934
                \array_values($this->versatile_collections_items);
42✔
935
            
936
        } else {
937
            
938
            $this->versatile_collections_items = 
6✔
939
                \array_combine(
6✔
940
                    \range($starting_key, ( ($starting_key + $this->count()) - 1) ),
6✔
941
                    \array_values($this->versatile_collections_items)
6✔
942
                );
6✔
943
        }
944
        
945
        return $this;
42✔
946
    }
947
    
948
    /**
949
     * @see \VersatileCollections\CollectionInterface::each()
950
     * 
951
     * @param mixed $termination_value a value that should be returned by $callback 
952
     *                                 signifying that iteration through a collection
953
     *                                 should stop.
954
     */
955
    public function each(
956
        callable $callback, mixed $termination_value=false, bool $bind_callback_to_this=true
957
    ): CollectionInterface
958
    {
959
        if( $bind_callback_to_this === true ) {
12✔
960
            
961
            $callback = Utils::bindObjectAndScopeToClosure(
12✔
962
                Utils::getClosureFromCallable($callback), 
12✔
963
                $this
12✔
964
            );
12✔
965
        }
966
        
967
        foreach ($this->versatile_collections_items as $key => $item) {
12✔
968
        
969
            if ( $callback($key, $item) === $termination_value ) {
12✔
970
                
971
                break;
6✔
972
            }
973
        }
974
        
975
        return $this;
12✔
976
    }
977
    
978
    /**
979
     * @see \VersatileCollections\CollectionInterface::map()
980
     */
981
    public function map(
982
        callable $callback, bool $preserve_keys = true, bool $bind_callback_to_this=true
983
    ): CollectionInterface
984
    {
985
        if( $bind_callback_to_this === true ) {
6✔
986
            
987
            $callback = Utils::bindObjectAndScopeToClosure(
6✔
988
                Utils::getClosureFromCallable($callback), 
6✔
989
                $this
6✔
990
            );
6✔
991
        }
992
        
993
        $new_collection = static::makeNew();
6✔
994
        
995
        foreach ( $this->versatile_collections_items as $key => $item ) {
6✔
996
            
997
            // using $new_collection[$key] or $new_collection[]
998
            // so that $new_collection->offsetSet(...) will be invoked which will
999
            // trigger type-checking in sub-classes like StrictlyTypedCollection
1000
            
1001
            if( $preserve_keys === true ) {
6✔
1002
                
1003
                $new_collection[$key] = $callback($key, $item);
6✔
1004
                
1005
            } else {
1006
                
1007
                $new_collection[] = $callback($key, $item);
6✔
1008
            }
1009
            
1010
        } // foreach ( $this->collection_items as $key => $item )
1011
        
1012
        return $new_collection;
6✔
1013
    }
1014
    
1015
    /**
1016
     * @see \VersatileCollections\CollectionInterface::everyNth()
1017
     */
1018
    public function everyNth(int $n, int $position_of_first_nth_item = 0): CollectionInterface
1019
    {
1020
        $new = static::makeNew();
6✔
1021
        $iteration_counter = 0;
6✔
1022

1023
        foreach ($this->versatile_collections_items as $item) {
6✔
1024
            
1025
            if ( ($iteration_counter % $n) === $position_of_first_nth_item ) {
6✔
1026
                
1027
                $new[] = $item;
6✔
1028
            }
1029

1030
            $iteration_counter++;
6✔
1031
        }
1032

1033
        return $new;
6✔
1034
    }
1035
    
1036
    /**
1037
     * @see \VersatileCollections\CollectionInterface::pipeAndReturnCallbackResult()
1038
     */
1039
    public function pipeAndReturnCallbackResult(callable $callback) 
1040
    {
1041
        return $callback($this);
6✔
1042
    }
1043
    
1044
    /**
1045
     * @see \VersatileCollections\CollectionInterface::pipeAndReturnSelf()
1046
     */
1047
    public function pipeAndReturnSelf(callable $callback): CollectionInterface
1048
    {
1049
        $callback($this);
6✔
1050
        
1051
        return $this;
6✔
1052
    }
1053
    
1054
    /**
1055
     * @see \VersatileCollections\CollectionInterface::tap()
1056
     */
1057
    public function tap(callable $callback): CollectionInterface
1058
    {
1059
        $callback(static::makeNew($this->versatile_collections_items));
6✔
1060

1061
        return $this;
6✔
1062
    }
1063
    
1064
    /**
1065
     * @see \VersatileCollections\CollectionInterface::getAndRemoveFirstItem()
1066
     */
1067
    public function getAndRemoveFirstItem(): mixed
1068
    {    
1069
        return \array_shift($this->versatile_collections_items);
12✔
1070
    }
1071
    
1072
    /**
1073
     * @see \VersatileCollections\CollectionInterface::getAndRemoveLastItem()
1074
     */
1075
    public function getAndRemoveLastItem(): mixed
1076
    {
1077
        return \array_pop($this->versatile_collections_items);
6✔
1078
    }
1079
    
1080
    /**
1081
     * @see \VersatileCollections\CollectionInterface::pull()
1082
     */
1083
    public function pull(int|string $key, mixed $default = null): mixed {
1084

1085
        $item = $this->getIfExists($key, $default);
6✔
1086
        unset($this[$key]);
6✔
1087
        
1088
        return $item;
6✔
1089
    }
1090
    
1091
    /**
1092
     * @see \VersatileCollections\CollectionInterface::push()
1093
     */
1094
    public function push(mixed $item): CollectionInterface
1095
    {
1096
        return $this->appendItem($item);
30✔
1097
    }
1098
    
1099
    /**
1100
     * @see \VersatileCollections\CollectionInterface::put()
1101
     * 
1102
     */
1103
    public function put(int|string $key, mixed $value): CollectionInterface
1104
    {
1105
        $this->offsetSet($key, $value);
6✔
1106
        
1107
        return $this;
6✔
1108
    }
1109
    
1110
    /**
1111
     * @see \VersatileCollections\CollectionInterface::randomKey()
1112
     * @psalm-suppress InvalidNullableReturnType
1113
     */
1114
    public function randomKey(): int|string 
1115
    { 
1116
        if( $this->count() <= 0 ) {
12✔
1117
            
1118
            $function = __FUNCTION__;
6✔
1119
            $class = $this::class;
6✔
1120
            $msg = "Error [{$class}::{$function}(...)]: You cannot request a random key from an empty collection.";
6✔
1121
            throw new LengthException($msg);
6✔
1122
        }
1123
        
1124
        return random_array_key($this->versatile_collections_items);
12✔
1125
    }
1126
    
1127
    /**
1128
     * @see \VersatileCollections\CollectionInterface::randomItem()
1129
     */
1130
    public function randomItem(): mixed
1131
    {    
1132
        if( $this->count() <= 0 ) {
6✔
1133
            
1134
            $function = __FUNCTION__;
6✔
1135
            $class = $this::class;
6✔
1136
            $msg = "Error [{$class}::{$function}(...)]: You cannot request a random item from an empty collection.";
6✔
1137
            throw new LengthException($msg);
6✔
1138
        }
1139
        
1140
        return $this[$this->randomKey()];
6✔
1141
    }
1142
    
1143
    /**
1144
     * @see \VersatileCollections\CollectionInterface::randomKeys()
1145
     * 
1146
     * @psalm-suppress LessSpecificImplementedReturnType 
1147
     */
1148
    public function randomKeys(int $number = 1): CollectionInterface
1149
    {
1150
        if( $this->count() <= 0 ) {
30✔
1151
            
1152
            $function = __FUNCTION__;
12✔
1153
            $class = $this::class;
12✔
1154
            $msg = "Error [{$class}::{$function}(...)]: You cannot request random keys from an empty collection.";
12✔
1155
            throw new LengthException($msg);
12✔
1156
        }
1157
        
1158
        if( $number > $this->count() ) {
24✔
1159
            
1160
            $function = __FUNCTION__;
6✔
1161
            $class = $this::class;
6✔
1162
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1163
            . " You requested {$number} key(s), but there are only {$this->count()} keys available.";
6✔
1164
            throw new InvalidArgumentException($msg);
6✔
1165
        }
1166
        
1167
        $keys = random_array_keys($this->versatile_collections_items, $number);
18✔
1168

1169
        // keys could be strings or ints or a mix
1170
        // GenericCollection will allow both types
1171
        return GenericCollection::makeNew($keys);
18✔
1172
    }
1173
    
1174
    /**
1175
     * @see \VersatileCollections\CollectionInterface::randomItems()
1176
     */
1177
    public function randomItems(int $number = 1, bool $preserve_keys=false): CollectionInterface
1178
    {
1179
        if( $this->count() <= 0 ) {
18✔
1180
            
1181
            $function = __FUNCTION__;
12✔
1182
            $class = $this::class;
12✔
1183
            $msg = "Error [{$class}::{$function}(...)]: You cannot request random items from an empty collection.";
12✔
1184
            throw new LengthException($msg);
12✔
1185
        }
1186
        
1187
        if( $number > $this->count() ) {
12✔
1188
            
1189
            $function = __FUNCTION__;
6✔
1190
            $class = $this::class;
6✔
1191
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1192
            . " You requested {$number} item(s), but there are only {$this->count()} items available.";
6✔
1193
            throw new InvalidArgumentException($msg);
6✔
1194
        }
1195
        
1196
        $random_items = static::makeNew();
6✔
1197
        $random_keys = $this->randomKeys($number);
6✔
1198
        
1199
        foreach ($random_keys as $random_key) {
6✔
1200
            
1201
            if($preserve_keys) {
6✔
1202
                
1203
                $random_items[$random_key] = $this[$random_key];
6✔
1204
                
1205
            } else {
1206
                
1207
                $random_items[] = $this[$random_key];
6✔
1208
            }
1209
        }
1210
        
1211
        return $random_items;
6✔
1212
    }
1213
    
1214
    /**
1215
     * @see \VersatileCollections\CollectionInterface::shuffle()
1216
     */
1217
    public function shuffle(bool $preserve_keys=true): CollectionInterface
1218
    {
1219
        if( $this->isEmpty() ) {
6✔
1220
            
1221
            return static::makeNew();
6✔
1222
        }
1223
        
1224
        $shuffled_collection = static::makeNew();
6✔
1225
        
1226
        // Decided to use $this->randomKeys() instead of php's
1227
        // native shuffle(array &$array) since $this->randomKeys() uses 
1228
        // random_array_keys(...) which uses the more cryptographically
1229
        // secure random_int(...) under the hood.
1230
        $all_keys_randomized = $this->randomKeys($this->count());
6✔
1231
        
1232
        foreach ($all_keys_randomized as $current_random_key) {
6✔
1233
            
1234
            if($preserve_keys) {
6✔
1235
                
1236
                $shuffled_collection[$current_random_key] = $this[$current_random_key];
6✔
1237
                
1238
            } else {
1239
                
1240
                $shuffled_collection[] = $this[$current_random_key];
6✔
1241
            }
1242
        }
1243
        
1244
        return $shuffled_collection;
6✔
1245
    }
1246
    
1247
    /**
1248
     * @see \VersatileCollections\CollectionInterface::searchByVal()
1249
     * 
1250
     * @param mixed $value the value to be searched for
1251
     */
1252
    public function searchByVal( mixed $value, bool $strict = false ): mixed {
1253
        
1254
        return \array_search($value, $this->versatile_collections_items, $strict);
6✔
1255
    }
1256
    
1257
    /**
1258
     * @see \VersatileCollections\CollectionInterface::searchAllByVal()
1259
     * 
1260
     * @param mixed $value the value to be searched for
1261
     */
1262
    public function searchAllByVal( mixed $value, bool $strict = false ): array|false {
1263
        
1264
        $result = \array_keys($this->versatile_collections_items, $value, $strict);
6✔
1265
        
1266
        /**  */
1267
        if( \count($result) <= 0 ) {
6✔
1268
            
1269
            $result = false;
6✔
1270
        }
1271
        
1272
        return $result;
6✔
1273
    }
1274
    
1275
    /**
1276
     * @see \VersatileCollections\CollectionInterface::searchByCallback()
1277
     * 
1278
     * @psalm-suppress MissingClosureParamType 
1279
     */
1280
    public function searchByCallback(callable $callback, bool $bind_callback_to_this=true) 
1281
    {
1282
        $results = [];
6✔
1283
        $searcher = function($key, $item) use ($callback, &$results): void {
6✔
1284
            
1285
            if( $callback($key, $item) === true ) {
6✔
1286
                
1287
                $results[] = $key;
6✔
1288
            }
1289
        };
6✔
1290
        
1291
        // Using 9999 as termination value since $callback is only ever 
1292
        // expected to return true or false, which means each will not
1293
        // terminate until iteration is fully completed.
1294
        $this->each($searcher, 9999, $bind_callback_to_this);
6✔
1295
        
1296
        return \count($results) > 0 ? $results : false;
6✔
1297
    }
1298

1299
    protected function performSort(
1300
        array &$items_to_sort,
1301
        callable $callable=null,
1302
        SortType $type=null,
1303
        string $sort_function_name_not_requiring_callback='asort',
1304
        string $sort_function_name_requiring_callback='uasort'
1305
    ): void {
1306
        if( $callable === null ) {
48✔
1307
            
1308
            $sort_type = SORT_REGULAR;
48✔
1309
            
1310
            if( $type !== null ) {
48✔
1311
                
1312
                $sort_type = $type->getSortType();
48✔
1313
            }
1314
            
1315
            $sort_function_name_not_requiring_callback($items_to_sort, $sort_type);
48✔
1316
            
1317
        } else {
1318
            
1319
            $sort_function_name_requiring_callback($items_to_sort, $callable);
48✔
1320
        }
1321
    }
1322

1323
    /**
1324
     * @psalm-suppress PossiblyInvalidIterator
1325
     * @psalm-suppress UnsupportedReferenceUsage
1326
     * 
1327
     * @return mixed[]
1328
     */
1329
    protected function performMultiSort(array $array_to_be_sorted, MultiSortParameters ...$param): array
1330
    {
1331
        if(\count($array_to_be_sorted) <= 0) {
12✔
1332
            
1333
            return $array_to_be_sorted; 
×
1334
        }
1335
        
1336
        $multi_sort_args = [];
12✔
1337
        $columns_to_sort_by = [];
12✔
1338
        
1339
        $original_key_prefix = 'http_versatile_collections_dot_com_original_key_b4_sort-';
12✔
1340
        
1341
        // add a string prefix to each key
1342
        // in the array to be sorted to force array_multisort to
1343
        // maintain key associations in the sorted array
1344
        $array_to_be_sorted_with_stringy_keys = [];
12✔
1345
        foreach ($array_to_be_sorted as $key => $val){
12✔
1346
            
1347
            $array_to_be_sorted_with_stringy_keys[$original_key_prefix.$key] = $val;
12✔
1348
        }
1349
        // update the array to be sorted
1350
        $array_to_be_sorted = $array_to_be_sorted_with_stringy_keys;
12✔
1351
        
1352
        foreach( $array_to_be_sorted as $key => $item) {
12✔
1353
            
1354
            if( \is_array($item) || $item instanceof ArrayAccess ) {
12✔
1355
                
1356
                foreach($param as $current_param) {
12✔
1357
                    
1358
                    if( !\array_key_exists($current_param->getFieldName() , $columns_to_sort_by) ) {
12✔
1359
                        
1360
                        $columns_to_sort_by[$current_param->getFieldName()] = [];
12✔
1361
                    }
1362
                    
1363
                    $columns_to_sort_by[$current_param->getFieldName()][$key] 
12✔
1364
                                        = $item[$current_param->getFieldName()];
12✔
1365
                }
1366
                
1367
            } else if ( \is_object($item) /*a non ArrayAccess object*/ ) {
12✔
1368
                
1369
                foreach($param as $current_param) {
6✔
1370
                    
1371
                    if( !\array_key_exists($current_param->getFieldName() , $columns_to_sort_by) ) {
6✔
1372
                        
1373
                        $columns_to_sort_by[$current_param->getFieldName()] = [];
6✔
1374
                    }
1375
                    
1376
                    // get the field's value even if it's private or protected
1377
                    $columns_to_sort_by[$current_param->getFieldName()][$key] 
6✔
1378
                        = get_object_property_value($item, $current_param->getFieldName(), null, true);                    
6✔
1379
                }
1380
                
1381
            } else {
1382
                
1383
                $function = __FUNCTION__;
12✔
1384
                $class = $this::class;
12✔
1385
                $msg = "Error [{$class}::{$function}(...)]:"
12✔
1386
                . " {$class}::{$function}(...) does not work with collections containing items that are"
12✔
1387
                . " not associative arrays or instances of ArrayAccess.";
12✔
1388
                throw new RuntimeException($msg);
12✔
1389
            }
1390
        }
1391
        
1392
        foreach($param as $current_param) {
12✔
1393
            
1394
            // set column
1395
            $multi_sort_args[] = $columns_to_sort_by[$current_param->getFieldName()];
12✔
1396
            
1397
            // set sort direction
1398
            $multi_sort_args[] = $current_param->getSortDirection();
12✔
1399
            
1400
            // set sort type
1401
            $multi_sort_args[] = $current_param->getSortType();
12✔
1402
        }
1403
        
1404
        // last parameter is the array to be sorted
1405
        $multi_sort_args[] = &$array_to_be_sorted;
12✔
1406
        /** @psalm-suppress ArgumentTypeCoercion */
1407
        \array_multisort(...$multi_sort_args);
12✔
1408
        $sorted_array_with_preserved_keys_with_prefix = \array_pop($multi_sort_args);
12✔
1409
        $sorted_array_with_preserved_keys = [];
12✔
1410
        
1411
        // remove the string prefix we added earlier to all keys
1412
        // in the array to be sorted to force array_multisort to
1413
        // maintain key associations in the sorted array
1414
        foreach($sorted_array_with_preserved_keys_with_prefix as $key => $val) {
12✔
1415
            
1416
            $sorted_array_with_preserved_keys[\str_replace($original_key_prefix, '', $key)] = $val;
12✔
1417
        }
1418
        
1419
        return $sorted_array_with_preserved_keys;
12✔
1420
    }
1421
    
1422
    /**
1423
     * @see \VersatileCollections\CollectionInterface::sort()
1424
     */
1425
    public function sort(callable $callable=null, SortType $type=null): CollectionInterface
1426
    {
1427
        // sort a copy
1428
        $items_to_sort = $this->versatile_collections_items;
6✔
1429
        $this->performSort(
6✔
1430
            $items_to_sort, 
6✔
1431
            $callable, 
6✔
1432
            $type, 
6✔
1433
            'asort',
6✔
1434
            'uasort'
6✔
1435
        );
6✔
1436
        
1437
        return static::makeNew($items_to_sort);
6✔
1438
    }
1439
    
1440
    /**
1441
     * @see \VersatileCollections\CollectionInterface::sortDesc()
1442
     */
1443
    public function sortDesc(callable $callable=null, SortType $type=null): CollectionInterface
1444
    {
1445
        // sort a copy
1446
        $items_to_sort = $this->versatile_collections_items;
6✔
1447
        $this->performSort(
6✔
1448
            $items_to_sort, 
6✔
1449
            $callable, 
6✔
1450
            $type, 
6✔
1451
            'arsort',
6✔
1452
            'uasort'
6✔
1453
        );
6✔
1454
        
1455
        return static::makeNew($items_to_sort);
6✔
1456
    }
1457
    
1458
    /**
1459
     * @see \VersatileCollections\CollectionInterface::sortByKey()
1460
     */
1461
    public function sortByKey(callable $callable=null, SortType $type=null): CollectionInterface
1462
    {
1463
        // sort a copy
1464
        $items_to_sort = $this->versatile_collections_items;
6✔
1465
        $this->performSort(
6✔
1466
            $items_to_sort, 
6✔
1467
            $callable, 
6✔
1468
            $type, 
6✔
1469
            'ksort',
6✔
1470
            'uksort'
6✔
1471
        );
6✔
1472
        
1473
        return static::makeNew($items_to_sort);
6✔
1474
    }
1475
    
1476
    /**
1477
     * @see \VersatileCollections\CollectionInterface::sortDescByKey()
1478
     */
1479
    public function sortDescByKey(callable $callable=null, SortType $type=null): CollectionInterface
1480
    {
1481
        // sort a copy
1482
        $items_to_sort = $this->versatile_collections_items;
6✔
1483
        $this->performSort(
6✔
1484
            $items_to_sort, 
6✔
1485
            $callable, 
6✔
1486
            $type, 
6✔
1487
            'krsort',
6✔
1488
            'uksort'
6✔
1489
        );
6✔
1490
        
1491
        return static::makeNew($items_to_sort);
6✔
1492
    }
1493

1494
    
1495
    /**
1496
     * Can also sort by private and / or protected field(s) in each object in 
1497
     * the collection.
1498
     *  
1499
     * @see \VersatileCollections\CollectionInterface::sortByMultipleFields()
1500
     */
1501
    public function sortByMultipleFields(MultiSortParameters ...$param): CollectionInterface
1502
    {
1503
        if( \count($param) <= 0 ) {
12✔
1504
            
1505
            $function = __FUNCTION__;
6✔
1506
            $class = $this::class;
6✔
1507
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1508
            . " {$class}::{$function}(...) expects at least one parameter of type `". MultiSortParameters::class ."`";
6✔
1509
            throw new InvalidArgumentException($msg);
6✔
1510
        }
1511
     
1512
        // sort a copy
1513
        $array_to_be_sorted = $this->versatile_collections_items;
6✔
1514
        
1515
        return static::makeNew($this->performMultiSort($array_to_be_sorted, ...$param));
6✔
1516
    }
1517
    
1518
    /**
1519
     * @see \VersatileCollections\CollectionInterface::sortMe()
1520
     */
1521
    public function sortMe(callable $callable=null, SortType $type=null): CollectionInterface
1522
    {
1523
        $this->performSort(
6✔
1524
            $this->versatile_collections_items, 
6✔
1525
            $callable, 
6✔
1526
            $type, 
6✔
1527
            'asort',
6✔
1528
            'uasort'
6✔
1529
        );
6✔
1530
        
1531
        return $this;
6✔
1532
    }
1533
    
1534
    /**
1535
     * @see \VersatileCollections\CollectionInterface::sortMeDesc()
1536
     */
1537
    public function sortMeDesc(callable $callable=null, SortType $type=null): CollectionInterface
1538
    {
1539
        $this->performSort(
6✔
1540
            $this->versatile_collections_items, 
6✔
1541
            $callable, 
6✔
1542
            $type, 
6✔
1543
            'arsort',
6✔
1544
            'uasort'
6✔
1545
        );
6✔
1546
        
1547
        return $this;
6✔
1548
    }
1549
    
1550
    /**
1551
     * @see \VersatileCollections\CollectionInterface::sortMeByKey()
1552
     */
1553
    public function sortMeByKey(callable $callable=null, SortType $type=null): CollectionInterface
1554
    {
1555
        $this->performSort(
6✔
1556
            $this->versatile_collections_items, 
6✔
1557
            $callable, 
6✔
1558
            $type, 
6✔
1559
            'ksort',
6✔
1560
            'uksort'
6✔
1561
        );
6✔
1562
        
1563
        return $this;
6✔
1564
    }
1565
    
1566
    /**
1567
     * @see \VersatileCollections\CollectionInterface::sortMeDescByKey()
1568
     */
1569
    public function sortMeDescByKey(callable $callable=null, SortType $type=null): CollectionInterface
1570
    {
1571
        $this->performSort(
6✔
1572
            $this->versatile_collections_items, 
6✔
1573
            $callable, 
6✔
1574
            $type, 
6✔
1575
            'krsort',
6✔
1576
            'uksort'
6✔
1577
        );
6✔
1578
        
1579
        return $this;
6✔
1580
    }
1581
    
1582
    /**
1583
     * Can also sort by private and / or protected field(s) in each object in 
1584
     * the collection.
1585
     *  
1586
     * @see \VersatileCollections\CollectionInterface::sortMeByMultipleFields()
1587
     */
1588
    public function sortMeByMultipleFields(MultiSortParameters ...$param): CollectionInterface
1589
    {
1590
        if( \count($param) <= 0 ) {
12✔
1591
            
1592
            $function = __FUNCTION__;
6✔
1593
            $class = $this::class;
6✔
1594
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1595
            . " {$class}::{$function}(...) expects at least one parameter of type `". MultiSortParameters::class ."`";
6✔
1596
            throw new InvalidArgumentException($msg);
6✔
1597
        }
1598
               
1599
        $this->versatile_collections_items = 
6✔
1600
            $this->performMultiSort(
6✔
1601
                $this->versatile_collections_items, ...$param
6✔
1602
            );
6✔
1603
        
1604
        return $this;
6✔
1605
    }
1606
    
1607
    /**
1608
     * @see \VersatileCollections\CollectionInterface::splice()
1609
     */
1610
    public function splice(int $offset, ?int $length=null, array $replacement=[]): CollectionInterface
1611
    {
1612
        if( $length === null ) {
6✔
1613
            
1614
            $length = $this->count();
6✔
1615
        }
1616

1617
        return static::makeNew(\array_splice($this->versatile_collections_items, $offset, $length, $replacement));
6✔
1618
    }
1619
    
1620
    /**
1621
     * @see \VersatileCollections\CollectionInterface::split()
1622
     */
1623
    public function split(int $numberOfGroups): CollectionInterface
1624
    {
1625
        if( $numberOfGroups > $this->count() ) {
18✔
1626
            
1627
            $function = __FUNCTION__;
6✔
1628
            $class = $this::class;
6✔
1629
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1630
            . " You requested {$numberOfGroups} group(s), but there are only {$this->count()} items available.";
6✔
1631
            throw new InvalidArgumentException($msg);
6✔
1632
        }
1633
        
1634
        if( $numberOfGroups < 0 ) {
12✔
1635
            
1636
            $function = __FUNCTION__;
6✔
1637
            $class = $this::class;
6✔
1638
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1639
            . " You requested a negative number `{$numberOfGroups}` of group(s).";
6✔
1640
            throw new InvalidArgumentException($msg);
6✔
1641
        }
1642
        
1643
        if ( $this->isEmpty() || $numberOfGroups === 0 ) {
6✔
1644
            
1645
            return static::makeNew();
6✔
1646
        }
1647

1648
        $groupSize = (int)\ceil($this->count() / $numberOfGroups);
6✔
1649
        $groups = static::makeNew();
6✔
1650

1651
        foreach ( $this->yieldCollectionsOfSizeN($groupSize) as $group ) {
6✔
1652
            
1653
            $groups[] = $group;
6✔
1654
        }
1655
        
1656
        return $groups;
6✔
1657
    }
1658
    
1659
    /**
1660
     * @see \VersatileCollections\CollectionInterface::take()
1661
     */
1662
    public function take(int $limit): CollectionInterface
1663
    {
1664
        if ($limit < 0) {
12✔
1665
            return $this->slice($limit, \abs($limit));
6✔
1666
        }
1667

1668
        return $this->slice(0, $limit);
6✔
1669
    }
1670
    
1671
    /**
1672
     * @see \VersatileCollections\CollectionInterface::unique()
1673
     *  
1674
     * @psalm-suppress MissingClosureParamType
1675
     */
1676
    public function unique(): CollectionInterface
1677
    {
1678
        return static::makeNew(
6✔
1679
            $this->reduce(
6✔
1680
                
1681
                function(array $carry, $item): array {
6✔
1682

1683
                    if( !\in_array($item, $carry, true)) {
6✔
1684

1685
                        $carry[] = $item;
6✔
1686
                    }
1687

1688
                    return $carry;
6✔
1689
                },
6✔
1690
                []
6✔
1691
            )
6✔
1692
        );
6✔
1693
    }
1694
    
1695
    /**
1696
     * @see \VersatileCollections\CollectionInterface::unionWith()
1697
     */
1698
    public function unionWith(array $items): CollectionInterface
1699
    {
1700
        return static::makeNew($this->versatile_collections_items + $items);
6✔
1701
    }
1702
    
1703
    /**
1704
     * @see \VersatileCollections\CollectionInterface::unionMeWith()
1705
     */
1706
    public function unionMeWith(array $items): CollectionInterface
1707
    {
1708
        $this->versatile_collections_items += $items;
6✔
1709
        
1710
        return $this;
6✔
1711
    }
1712
    
1713
    /**
1714
     * Can also extract values from private and  / or protected properties 
1715
     * of each object in the collection.
1716
     *  
1717
     * @see \VersatileCollections\CollectionInterface::column()
1718
     * 
1719
     * @psalm-suppress MoreSpecificReturnType
1720
     * @psalm-suppress RedundantCondition
1721
     */
1722
    public function column(int|string $column_key, int|string|null $index_key=null): GenericCollection
1723
    {
1724
        // use GenericCollection because the values 
1725
        // in the column may be of varying types
1726
        $column_2_return = GenericCollection::makeNew();
132✔
1727

1728
        foreach ( $this->versatile_collections_items as $coll_key => $item ) {
132✔
1729

1730
            if( !\is_array($item) && !\is_object($item) ) {
132✔
1731

1732
                $function = __FUNCTION__;
6✔
1733
                $class = $this::class;
6✔
1734
                $item_type = Utils::gettype($item);
6✔
1735
                $msg = "Error [{$class}::{$function}(...)]:"
6✔
1736
                . " This method only works on collections containing only arrays and / or objects."
6✔
1737
                . " A(n) invalid item of type `{$item_type}` with a value of: ". var_to_string($item)
6✔
1738
                . " was found with this key `$coll_key` in the collection". PHP_EOL
6✔
1739
                . " Collection Items: ". var_to_string($this->versatile_collections_items);
6✔
1740
                throw new RuntimeException($msg);
6✔
1741
            }
1742

1743
            if( \is_array($item) || $item instanceof ArrayAccess) {
132✔
1744

1745
                if( 
1746
                    ( \is_array($item) && !\array_key_exists($column_key, $item) )
72✔
1747
                    ||
1748
                    ( $item instanceof ArrayAccess && !isset($item[$column_key]) )
72✔
1749
                ) {
1750
                    $function = __FUNCTION__;
24✔
1751
                    $class = $this::class;
24✔
1752
                    $item_type = Utils::gettype($item);
24✔
1753

1754
                    $msg = "Error [{$class}::{$function}(...)]:"
24✔
1755
                    . " An item of type `$item_type` without the specified column key `$column_key`"
24✔
1756
                    . " was found with this key `$coll_key` in the collection." .PHP_EOL
24✔
1757
                    . " Collection Items: ". var_to_string($this->versatile_collections_items);
24✔
1758
                    throw new RuntimeException($msg);
24✔
1759

1760
                } else if (
1761
                    $index_key !== null
48✔
1762
                    &&
1763
                    (
1764
                        ( \is_array($item) && !\array_key_exists($index_key, $item) )
48✔
1765
                        ||
48✔
1766
                        ( $item instanceof ArrayAccess && !isset($item[$index_key]) )
48✔
1767
                    )
1768
                ) {
1769
                    $function = __FUNCTION__;
24✔
1770
                    $class = $this::class;
24✔
1771
                    $item_type = Utils::gettype($item);
24✔
1772

1773
                    $msg = "Error [{$class}::{$function}(...)]:"
24✔
1774
                    . " An item of type `$item_type` without the specified index key `$index_key`"
24✔
1775
                    . " was found with this key `$coll_key` in the collection." .PHP_EOL
24✔
1776
                    . " Collection Items: ". var_to_string($this->versatile_collections_items);
24✔
1777
                    throw new RuntimeException($msg);
24✔
1778

1779
                } else if( $index_key === null ) {
24✔
1780

1781
                    $column_2_return[] = $item[$column_key];
12✔
1782

1783
                } else if(
1784
                    $index_key !== null
18✔
1785
                    && 
1786
                    ( 
1787
                        ( \is_array($item) && \array_key_exists($index_key, $item) )
18✔
1788
                        ||
18✔
1789
                        ( $item instanceof ArrayAccess && isset($item[$index_key]) )
18✔
1790
                    )
1791
                ) {
1792
                    if(
1793
                        !\is_string($item[$index_key])
18✔
1794
                        && !\is_int($item[$index_key])
18✔
1795
                    ){
1796
                        $function = __FUNCTION__;
12✔
1797
                        $class = $this::class;
12✔
1798
                        $item_type = Utils::gettype($item[$index_key]);
12✔
1799

1800
                        $msg = "Error [{$class}::{$function}(...)]:"
12✔
1801
                        . " \$collection['{$coll_key}']['{$index_key}'] of type `$item_type`"
12✔
1802
                        . " has a non-string and non-int value of `". var_to_string($item[$index_key])."`"
12✔
1803
                        . " which cannot be used as a key in the collection to be returned by this method." .PHP_EOL
12✔
1804
                        . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
12✔
1805
                        throw new RuntimeException($msg);
12✔
1806
                    }
1807

1808
                    $column_2_return[$item[$index_key]] = $item[$column_key];
6✔
1809

1810
                } else {
1811

1812
                    $function = __FUNCTION__;
×
1813
                    $class = $this::class;
×
1814
                    $item_type = Utils::gettype($item);
×
1815

1816
                    $msg = "Error [{$class}::{$function}(...)]:"
×
1817
                    . " Error occured while accessing an item of type `$item_type` with the specified index key `$index_key`"
×
1818
                    . " and specified column key `$column_key` with this key `$coll_key` in the collection." . PHP_EOL
×
1819
                    . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
×
1820
                    throw new RuntimeException($msg);
4✔
1821
                }
1822

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

1825
                if( 
1826
                    $index_key !== null
66✔
1827
                    && object_has_property($item, $column_key)
66✔
1828
                    && object_has_property($item, $index_key)   
66✔
1829
                ) {
1830
                    $index_key_value = get_object_property_value($item, $index_key, null, true);
30✔
1831
                    $column_key_value = get_object_property_value($item, $column_key, null, true);
30✔
1832

1833
                    if( 
1834
                        !\is_int($index_key_value)
30✔
1835
                        && !\is_string($index_key_value)
30✔
1836
                    ) {
1837
                        $function = __FUNCTION__;
24✔
1838
                        $class = $this::class;
24✔
1839
                        $item_type = Utils::gettype($index_key_value);
24✔
1840
                        $msg = "Error [{$class}::{$function}(...)]:"
24✔
1841
                        . " \$collection['{$coll_key}']->{'{$index_key}'} of type `$item_type`"
24✔
1842
                        . " has a non-string and non-int value of `". var_to_string($index_key_value)."`"
24✔
1843
                        . " which cannot be used as a key in the collection to be returned by this method." .PHP_EOL
24✔
1844
                        . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
24✔
1845
                        throw new RuntimeException($msg);
24✔
1846
                    }            
1847

1848
                    $column_2_return[$index_key_value] = $column_key_value;
6✔
1849

1850
                } else if(
1851
                    $index_key === null
42✔
1852
                    && object_has_property($item, $column_key)
42✔
1853
                ) {
1854
                    $column_2_return[] = get_object_property_value($item, $column_key, null, true);
6✔
1855

1856
                } else {
1857

1858
                    $function = __FUNCTION__;
36✔
1859
                    $class = $this::class;
36✔
1860
                    $item_type = Utils::gettype($item);
36✔
1861
                    $msg = "Error [{$class}::{$function}(...)]:"
36✔
1862
                    . " Error occured while accessing an item of type `$item_type` with the specified index key `$index_key`"
36✔
1863
                    . " and specified column key `$column_key` with this key `$coll_key` in the collection." . PHP_EOL
36✔
1864
                    . " Either the index key `$index_key` is not an accessible property of the item"
36✔
1865
                    . " or the specified column key `$column_key` is not an accessible property of the item"
36✔
1866
                    . " or some other error occurred" .PHP_EOL
36✔
1867
                    . " Collection Items: ". var_to_string($this->versatile_collections_items).PHP_EOL .PHP_EOL;
36✔
1868
                    throw new RuntimeException($msg);
36✔
1869
                }
1870
            } // else if(is_object($item))
1871
        } // foreach ( $this->versatile_collections_items as $coll_key => $item )
1872
        
1873
        return $column_2_return;
6✔
1874
    }
1875
    
1876
    /**
1877
     * @see \VersatileCollections\CollectionInterface::getItems()
1878
     */
1879
    public function getItems(): CollectionInterface
1880
    {
1881
        return static::makeNew(\array_values($this->versatile_collections_items));
18✔
1882
    }
1883
    
1884
    /**
1885
     * @see \VersatileCollections\CollectionInterface::whenTrue()
1886
     * 
1887
     * @param bool $truthy_value
1888
     */
1889
    public function whenTrue($truthy_value, callable $callback, callable $default=null): mixed {
1890
        
1891
        if ( $truthy_value ) {
24✔
1892
            
1893
            return $callback($this);
12✔
1894
            
1895
        } elseif ( $default !== null ) {
24✔
1896
            
1897
            return $default($this);
12✔
1898
        }
1899

1900
        return $default;
12✔
1901
    }
1902
    
1903
    /**
1904
     * @see \VersatileCollections\CollectionInterface::whenFalse()
1905
     * 
1906
     * @param bool $falsy_value
1907
     */
1908
    public function whenFalse($falsy_value, callable $callback, callable $default=null): mixed {
1909
        return $this->whenTrue( (!$falsy_value) , $callback, $default);
12✔
1910
    }
1911
    
1912
    /**
1913
     * @see \VersatileCollections\CollectionInterface::getAsNewType()
1914
     */
1915
    public function getAsNewType(string|CollectionInterface $new_collection_class= GenericCollection::class): CollectionInterface 
1916
    {
1917
        if( 
1918
            !\is_subclass_of($new_collection_class, CollectionInterface::class)
84✔
1919
        ) {
1920
            $function = __FUNCTION__;
6✔
1921
            $class = $this::class;
6✔
1922
            $new_collection_class_type = Utils::gettype($new_collection_class);
6✔
1923
            $msg = "Error [{$class}::{$function}(...)]:"
6✔
1924
            . " You must specify an object or string that is a sub-class of "
6✔
1925
            . CollectionInterface::class . " as the \$new_collection_class parameter."
6✔
1926
            . " You supplied a(n) `{$new_collection_class_type}` with a value of: ". var_to_string($new_collection_class);
6✔
1927
            throw new InvalidArgumentException($msg);
6✔
1928
        }
1929
        
1930
        if( !is_string($new_collection_class) ) {
78✔
1931
            
1932
            // $new_collection_class instanceof CollectionInterface
1933
            $new_collection_class = $new_collection_class::class;
6✔
1934
        }
1935

1936
        return $new_collection_class::makeNew($this->versatile_collections_items);
78✔
1937
    }
1938
    
1939
    /**
1940
     * @see \VersatileCollections\CollectionInterface::removeAll()
1941
     */
1942
    public function removeAll(array $keys=[]): CollectionInterface
1943
    {
1944
        if( \count($keys) > 0 ) {
12✔
1945
            
1946
            foreach($keys as $key) {
6✔
1947
                
1948
                if( $this->containsKey($key) ) {
6✔
1949
                    
1950
                    $this->offsetUnset($key);
6✔
1951
                }
1952
            }
1953
            
1954
        } else {
1955
            
1956
            // shortcut
1957
            $this->versatile_collections_items = [];
12✔
1958
        }
1959
        
1960
        return $this;
12✔
1961
    }
1962
    
1963
    /**
1964
     * @see \VersatileCollections\CollectionInterface::getAllWhereKeysIn()
1965
     */
1966
    public function getAllWhereKeysIn(array $keys): CollectionInterface
1967
    {
1968
        $result = static::makeNew();
6✔
1969
        
1970
        foreach ( $this->versatile_collections_items as $key => $item ) {
6✔
1971
            
1972
            if( \in_array($key, $keys, true) ) {
6✔
1973
                
1974
                $result[$key] = $item;
6✔
1975
            }
1976
        }
1977
        
1978
        return $result;
6✔
1979
    }
1980
    
1981
    /**
1982
     * @see \VersatileCollections\CollectionInterface::getAllWhereKeysNotIn()
1983
     */
1984
    public function getAllWhereKeysNotIn(array $keys): CollectionInterface
1985
    {
1986
        $result = static::makeNew();
6✔
1987
        
1988
        foreach ( $this->versatile_collections_items as $key => $item ) {
6✔
1989
            
1990
            if( !\in_array($key, $keys, true) ) {
6✔
1991
                
1992
                $result[$key] = $item;
6✔
1993
            }
1994
        }
1995
        
1996
        return $result;
6✔
1997
    }
1998
    
1999
    /**
2000
     * @see \VersatileCollections\CollectionInterface::paginate()
2001
     */
2002
    public function paginate(int $page_number, int $num_items_per_page): CollectionInterface
2003
    {
2004
        if( $page_number < 1 ) {
6✔
2005
            
2006
            $page_number = 1;
6✔
2007
        }
2008
        
2009
        if( $num_items_per_page < 1 ) {
6✔
2010
            
2011
            $num_items_per_page = 1;
6✔
2012
        }
2013
        
2014
        if( $num_items_per_page > $this->count() ) {
6✔
2015
            
2016
            $offset = $page_number - 1;
6✔
2017
            
2018
        } else {
2019

2020
            $offset = (($page_number * $num_items_per_page) - $num_items_per_page);
6✔
2021
        }
2022

2023
        return $this->slice($offset, $num_items_per_page);
6✔
2024
    }
2025
    
2026
    /**
2027
     * @see \VersatileCollections\CollectionInterface::diff()
2028
     */
2029
    public function diff(array $items): CollectionInterface
2030
    {
2031
        return static::makeNew(\array_diff($this->versatile_collections_items, $items));
18✔
2032
    }
2033

2034
    /**
2035
     * @see \VersatileCollections\CollectionInterface::diffUsing()
2036
     */
2037
    public function diffUsing(array $items, callable $callback): CollectionInterface
2038
    {
2039
        return static::makeNew(\array_udiff($this->versatile_collections_items, $items, $callback));
12✔
2040
    }
2041

2042
    /**
2043
     * @see \VersatileCollections\CollectionInterface::diffAssoc()
2044
     */
2045
    public function diffAssoc(array $items): CollectionInterface
2046
    {
2047
        return static::makeNew(\array_diff_assoc($this->versatile_collections_items, $items));
12✔
2048
    }
2049

2050
    /**
2051
     * @see \VersatileCollections\CollectionInterface::diffAssocUsing()
2052
     */
2053
    public function diffAssocUsing(array $items, callable $key_comparator): CollectionInterface
2054
    {
2055
        return static::makeNew(\array_diff_uassoc($this->versatile_collections_items, $items, $key_comparator));
6✔
2056
    }
2057

2058
    /**
2059
     * @see \VersatileCollections\CollectionInterface::diffKeys()
2060
     */
2061
    public function diffKeys(array $items): CollectionInterface
2062
    {
2063
        return static::makeNew(\array_diff_key($this->versatile_collections_items, $items));
12✔
2064
    }
2065

2066
    /**
2067
     * @see \VersatileCollections\CollectionInterface::diffKeysUsing()
2068
     */
2069
    public function diffKeysUsing(array $items, callable $key_comparator): CollectionInterface
2070
    {
2071
        return static::makeNew(\array_diff_ukey($this->versatile_collections_items, $items, $key_comparator));
6✔
2072
    }
2073

2074
    /**
2075
     * @see \VersatileCollections\CollectionInterface::allSatisfyConditions()
2076
     * 
2077
     * @psalm-suppress MissingClosureParamType 
2078
     */
2079
    public function allSatisfyConditions(callable $callback, bool $bind_callback_to_this=true): bool 
2080
    {
2081
        if( $bind_callback_to_this === true ) {
6✔
2082
            
2083
            $callback = Utils::bindObjectAndScopeToClosure(
6✔
2084
                Utils::getClosureFromCallable($callback), 
6✔
2085
                $this
6✔
2086
            );
6✔
2087
        }
2088
        
2089
        return $this->reduceWithKeyAccess(
6✔
2090
            fn($carry, $item, $key): bool => $carry && $callback($key, $item), 
6✔
2091
            true
6✔
2092
        );
6✔
2093
    }
2094
    
2095
    /**
2096
     * @see \VersatileCollections\CollectionInterface::intersectByKeys()
2097
     */
2098
    public function intersectByKeys(array $arr): CollectionInterface
2099
    {
2100
        return static::makeNew(\array_intersect_key($this->versatile_collections_items, $arr));
6✔
2101
    }
2102
    
2103
    /**
2104
     * @see \VersatileCollections\CollectionInterface::intersectByItems()
2105
     */
2106
    public function intersectByItems(array $arr): CollectionInterface
2107
    {
2108
        return static::makeNew(\array_intersect($this->versatile_collections_items, $arr));
6✔
2109
    }
2110
    
2111
    /**
2112
     * @see \VersatileCollections\CollectionInterface::intersectByKeysAndItems()
2113
     */
2114
    public function intersectByKeysAndItems(array $arr): CollectionInterface
2115
    {
2116
        return static::makeNew(\array_intersect_assoc($this->versatile_collections_items, $arr));
6✔
2117
    }
2118
    
2119
    /**
2120
     * @see \VersatileCollections\CollectionInterface::intersectByKeysUsingCallback()
2121
     */
2122
    public function intersectByKeysUsingCallback(array $arr, callable $key_comparator): CollectionInterface
2123
    {
2124
        return static::makeNew(\array_intersect_ukey($this->versatile_collections_items, $arr, $key_comparator));
6✔
2125
    }
2126
    
2127
    /**
2128
     * @see \VersatileCollections\CollectionInterface::intersectByItemsUsingCallback()
2129
     */
2130
    public function intersectByItemsUsingCallback(array $arr, callable $item_comparator): CollectionInterface
2131
    {
2132
        return static::makeNew(\array_uintersect($this->versatile_collections_items, $arr, $item_comparator));
6✔
2133
    }
2134
    
2135
    /**
2136
     * @see \VersatileCollections\CollectionInterface::intersectByKeysAndItemsUsingCallbacks()
2137
     *
2138
     * @noinspection PhpUnusedLocalVariableInspection
2139
     * @psalm-suppress RedundantCondition
2140
     */
2141
    public function intersectByKeysAndItemsUsingCallbacks(array $arr, callable $key_comparator=null, callable $item_comparator=null): CollectionInterface
2142
    {
2143
        $result = [];
6✔
2144
        
2145
        if( $key_comparator !== null && $item_comparator === null ) {
6✔
2146
            
2147
            $result = \array_intersect_uassoc(
6✔
2148
                $this->versatile_collections_items, $arr, $key_comparator
6✔
2149
            );
6✔
2150
            
2151
        } else if( $key_comparator === null && $item_comparator !== null ) {
6✔
2152
            
2153
            $result = \array_uintersect_assoc(
6✔
2154
                $this->versatile_collections_items, $arr, $item_comparator
6✔
2155
            );
6✔
2156
            
2157
        } else if( $key_comparator !== null && $item_comparator !== null ) {
6✔
2158
            
2159
            $result = \array_uintersect_uassoc(
6✔
2160
                $this->versatile_collections_items, $arr, $item_comparator, $key_comparator
6✔
2161
            );
6✔
2162
            
2163
        } else {
2164
            
2165
            $result = \array_intersect_assoc($this->versatile_collections_items, $arr);
6✔
2166
        }
2167
        
2168
        return static::makeNew($result);
6✔
2169
    }
2170
}
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

© 2026 Coveralls, Inc