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

rotexsoft / versatile-collections / 6764770557

07 Sep 2023 08:54AM UTC coverage: 98.356% (-0.2%) from 98.536%
6764770557

push

github

rotimi
Upgraded to rector 0.18 & did some refactoring for PHP 8.1

49 of 53 new or added lines in 9 files covered. (92.45%)

1 existing line in 1 file now uncovered.

1017 of 1034 relevant lines covered (98.36%)

9.82 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]*$/';
48✔
49
        
50
        if( 
51
            !\preg_match( $regex_4_valid_method_name, \preg_quote($name, '/') )
48✔
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;
4✔
59
            
60
            $function = $method_name_was_passed_to;
4✔
61
            $name_var = var_to_string($name);
4✔
62
            $msg = "Error [{$class}::{$function}(...)]: Trying to add a dynamic method with an invalid name `{$name_var}` to a collection";
4✔
63
            
64
            throw new InvalidArgumentException($msg);
4✔
65
            
66
        } else if( \method_exists(static::class, $name) ) {
44✔
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;
8✔
71
            $function = $method_name_was_passed_to;
8✔
72
            $msg = "Error [{$class}::{$function}(...)]: Trying to add a dynamic method with the same name `{$name}` as an existing actual method to a collection";
8✔
73
            
74
            throw new Exceptions\AddConflictingMethodException($msg);
8✔
75
        }
76
        
77
        return true;
36✔
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__) ) {
8✔
95
            
96
            static::$versatile_collections_static_methods[ static::class.'::'. $name] = [
8✔
97
                'method' => $callable,
8✔
98
                'has_return_val' => $has_return_val
8✔
99
            ];
8✔
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__) ) {
12✔
120
            
121
            static::$versatile_collections_methods_for_all_instances[ static::class.'::'. $name] = [
12✔
122
                'method' => $callable,
12✔
123
                'has_return_val' => $has_return_val,
12✔
124
                'bind_to_this_on_invocation' => $bind_to_this_on_invocation
12✔
125
            ];
12✔
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) ) {
16✔
146
            
147
            if( $bind_to_this ) {
16✔
148

149
                $callable = Utils::bindObjectAndScopeToClosure(
16✔
150
                    Utils::getClosureFromCallable($callable), 
16✔
151
                    $this
16✔
152
                );
16✔
153
            }
154
            
155
            $this->versatile_collections_methods_for_this_instance[ static::class.'::'. $name] = [
16✔
156
                'method' => $callable,
16✔
157
                'has_return_val' => $has_return_val,
16✔
158
            ];
16✔
159
        }
160
        
161
        return $this;
16✔
162
    }
163
    
164
    /**
165
     * @return mixed 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) ) {
28✔
170
            
171
            return static::class.'::'.$name;
20✔
172
        }
173
        
174
        if( $search_parent_class_registration === true ) {
28✔
175
            
176
            $parent_class = \get_parent_class(static::class);
28✔
177

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

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

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

185
                $parent_class = \get_parent_class($parent_class);
12✔
186
            }
187
        }
188
        
189
        return false;
28✔
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);
24✔
206
        $key_for_all_instances = static::getKeyForDynamicMethod($method_name, static::$versatile_collections_methods_for_all_instances);
24✔
207
        
208
        if ( $key_for_this_instance !== false ) {
24✔
209
            
210
            $result = \call_user_func_array($this->versatile_collections_methods_for_this_instance[$key_for_this_instance]['method'], $arguments);
16✔
211
            
212
            if( $this->versatile_collections_methods_for_this_instance[$key_for_this_instance]['has_return_val'] ) {
12✔
213
                
214
                return $result;
12✔
215
            }
216
        
217
        } else if( $key_for_all_instances !== false ) {
16✔
218
            
219
            $new_callable = static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['method'];
8✔
220
            
221
            if( 
222
                ((bool)static::$versatile_collections_methods_for_all_instances[$key_for_all_instances]['bind_to_this_on_invocation'])   
8✔
223
            ) {
224
                
225
                $new_callable = Utils::bindObjectAndScopeToClosure(
8✔
226
                    Utils::getClosureFromCallable($new_callable), 
8✔
227
                    $this
8✔
228
                );
8✔
229
            }
230

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

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

235
                return $result;
8✔
236
            }
237
            
238
        } else {
239
            
240
            $function = __FUNCTION__;
12✔
241
            $class = $this::class;
12✔
242
            $name_var = var_to_string($method_name);
12✔
243
            $msg = "Error [{$class}::{$function}(...)]: Trying to call a non-existent dynamic method named `{$name_var}` on a collection";
12✔
244
            throw new Exceptions\BadMethodCallException($msg);
12✔
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);
4✔
262
        
263
        if( $key_for_static_method !== false ) {
4✔
264
            
265
            // never bind to this when method is called statically            
266
            $result = \call_user_func_array(
4✔
267
                static::$versatile_collections_static_methods[$key_for_static_method]['method'], $arguments
4✔
268
            );
4✔
269
            
270
            if( static::$versatile_collections_static_methods[$key_for_static_method]['has_return_val'] ) {
4✔
271
                
272
                return $result;
4✔
273
            }
274
            
275
        } else {
276
            
277
            $function = __FUNCTION__;
4✔
278
            $class = static::class;
4✔
279
            $name_var = var_to_string($method_name);
4✔
280
            $msg = "Error [{$class}::{$function}(...)]: Trying to statically call a non-existent dynamic method named `{$name_var}` on a collection";
4✔
281
            throw new BadMethodCallException($msg);
4✔
282
        }
283
    }
284
    
285
    public function __get(string $key): mixed
286
    {
287
        return $this->offsetGet($key);
52✔
288
    }
289
    
290
    public function __isset(string $key): bool
291
    {
292
        return $this->offsetExists($key);
16✔
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);
140✔
301
    }
302
    
303
    public function __unset(string $key): void 
304
    {
305
        $this->offsetUnset($key);
8✔
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) {
464✔
317

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

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

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

325
            return $collection;
460✔
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)
8✔
332
                ? new static(...\array_values($items))
8✔
333
                : new static(...\array_values(\iterator_to_array($items))); // These should be faster than loop above
8✔
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);
44✔
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) ) {
96✔
356

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

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

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

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

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

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

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

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

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

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

1029
            $iteration_counter++;
4✔
1030
        }
1031

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

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

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

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

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

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

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

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

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

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

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

1682
                    if( !\in_array($item, $carry, true)) {
4✔
1683

1684
                        $carry[] = $item;
4✔
1685
                    }
1686

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

1727
        foreach ( $this->versatile_collections_items as $coll_key => $item ) {
88✔
1728

1729
            if( !\is_array($item) && !\is_object($item) ) {
88✔
1730

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

1742
            if( \is_array($item) || $item instanceof ArrayAccess) {
88✔
1743

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

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

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

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

1778
                } else if( \is_null($index_key) ) {
16✔
1779

1780
                    $column_2_return[] = $item[$column_key];
8✔
1781

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

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

1807
                    $column_2_return[$item[$index_key]] = $item[$column_key];
4✔
1808

1809
                } else {
1810

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

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

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

1824
                if( 
1825
                    !\is_null($index_key)
44✔
1826
                    && object_has_property($item, $column_key)
44✔
1827
                    && object_has_property($item, $index_key)   
44✔
1828
                ) {
1829
                    $index_key_value = get_object_property_value($item, $index_key, null, true);
20✔
1830
                    $column_key_value = get_object_property_value($item, $column_key, null, true);
20✔
1831

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

1847
                    $column_2_return[$index_key_value] = $column_key_value;
4✔
1848

1849
                } else if(
1850
                    \is_null($index_key)
28✔
1851
                    && object_has_property($item, $column_key)
28✔
1852
                ) {
1853
                    $column_2_return[] = get_object_property_value($item, $column_key, null, true);
4✔
1854

1855
                } else {
1856

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

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

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

2022
            $offset = (($page_number * $num_items_per_page) - $num_items_per_page);
4✔
2023
        }
2024

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

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

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

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

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

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

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